User:Robin Cornelius/IRC LSL Bridge

From Second Life Wiki
Jump to navigation Jump to search

A simple LSL/IRC bridge using a perl implementation of an IRC client

A prim listens in world on channel 0 and relays this via http to a perl irc client that prints it in the IRC channel, any messages received in the IRC channel are also relayed to the prim and spoken on channel 0, forming a SL to IRC bridge.

Limitations, http in SL is throttled to a max of 1 request per second, so for really busy areas or IRC channels this will miss messages.

LSL code, insert in to a prim in world

<lsl> // // Copyright (C) 2010 Robin Cornelius <robin.cornelius@gmail.com> // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see <http://www.gnu.org/licenses/>. //

// CHANGE THE URL AND PORT BELOW string server_url="MY URL"; string server_port="MY PORT";


string url; key http_request_id;


default {

   touch_start(integer num_detected)
   {
       if(llDetectedKey(0)==llGetOwner())
       {
           // Rsend the URI to the server if the owner touches us
           http_request_id = llHTTPRequest(server_url+":"+server_port+"/?URI="+llEscapeURL(url), [], "");
       }
   }
   
   state_entry() 
   {
       // Start up request a URL and listen on channel 0
       llRequestURL();
       llListen(0,"","","");
   }
   
   changed(integer What) 
   {
       // On a region restart request a new URL
       if (What & CHANGED_REGION_START) 
       {
           llRequestURL();
       }
   }
   
   http_request(key ID, string Method, string Body) 
   {
       if (Method == URL_REQUEST_GRANTED) 
       {
           //If the URL was granted save the URL and send it to the server
           url = Body;
           http_request_id = llHTTPRequest(server_url+":"+server_port+"/?URI="+llEscapeURL(Body), [], "");
       } 
       else if (Method == URL_REQUEST_DENIED) 
       {
          // We are boned
          llOwnerSay("No URLs");
       } 
       else if (Method == "GET") 
       {
           // Get the data sent to us, say it on channel 0 and respond with a 200
           llSay(0,llUnescapeURL(llGetHTTPHeader(ID, "x-query-string")));
           llHTTPResponse(ID, 200, "Hello there !");        
       }
   }
   
   listen(integer channel,string name, key id,string msg)
   {
       // Send all channel 0 data to the server
       llHTTPRequest(server_url+":"+server_port+"/?MSG="+llEscapeURL("<"+name+"> "+msg), [], "");
   }
   
   http_response(key request_id, integer status, list metadata, string body)
   {
       //Check http response after sending data from listen or a URI update
       if (request_id == http_request_id)
       {
          //Do some error checking for status !=200
       }
   }

} </lsl>

Perl code, run on an appropriate server

#!/usr/bin/perl

#
# Copyright (C) 2010 Robin Cornelius <robin.cornelius@gmail.com>
#
#   This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation, either version 3 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#   You should have received a copy of the GNU General Public License
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
#

# User configuration section

my $nickname = 'irc-bridge';
my $ircname = 'The bridge';

#@servers = (['irc.servercentral.net']);
my @servers = (['127.0.0.1']);

my %channels = (
    '#opensl' => ,
    );

my $http_server_port=8088;

# end of user configuration section

use strict;
use warnings;

#use POE
use POE;
use POE::Component::Child;

# Pull in various IRC components
use POE::Component::IRC;
use POE::Component::IRC::State;
use POE::Component::IRC::Plugin::NickReclaim;
use POE::Component::IRC::Plugin::AutoJoin;
use POE::Component::IRC::Plugin::Connector;
use POE::Component::IRC::Plugin::BotCommand;
use POE::Component::IRC::Plugin::CycleEmpty;

# POE::Component::Client::HTTP uses HTTP::Request and response objects.
use HTTP::Request::Common qw(GET POST);

# Include POE and the HTTP client component.
use POE::Component::Client::HTTP;

# Include POE HTTP Server component
use POE qw(Component::Server::TCP Filter::HTTPD);
use HTTP::Response;

# We want to encode and decode escaped URIs
use URI::Escape;

# This the the URI of the CAP for the prim's http server
my $LLURI;

# *************** IRC ****************************

my $irc = POE::Component::IRC::State->spawn(
    nick => $nickname,
    ircname => $ircname,
    Server => $servers[0]->[0],
) or die "Could not use name $!";

sub _start {

    my ($kernel, $heap) = @_[KERNEL, HEAP];    

     $irc->yield( register => 'all' );

     # Create and load our NickReclaim plugin, before we connect 
     $irc->plugin_add( 'NickReclaim' => POE::Component::IRC::Plugin::NickReclaim->new( poll => 60 ) );
 
     my $chans=\%channels;
     my $svrs=\@servers;
     
     $irc->plugin_add('AutoJoin', POE::Component::IRC::Plugin::AutoJoin->new( Channels => $chans));
     $irc->plugin_add('Connector',POE::Component::IRC::Plugin::Connector->new( servers => $svrs));
     $irc->plugin_add('CycleEmpty', POE::Component::IRC::Plugin::CycleEmpty->new());

     $irc->yield( connect => { } );
     return;
 }

# channel message
sub irc_public {

    my ($kernel,$sender)=@_[KERNEL,SENDER];

    my $hostmask=$_[ARG0];
    my $target=@$_[ARG1];
    my $msg=$_[ARG2];
    
    $hostmask =~ s/^(.*)!(.*)@(.*)$//;;
    my $nick = $1;
    my $hostuser = $2;
    my $hostname = $3;
   
    my $url=$LLURI."/?".uri_escape("<$nick> ".$msg);

    # Send the recieved message from IRC component to the http client component
    $kernel->post("ua" => "request", "got_response", GET $url);
    
}

# **************** HTTP CLIENT **********************

  POE::Component::Client::HTTP->spawn(
  Alias   => 'ua',
  MaxSize => 4096,    # Remove for unlimited page sizes.
  Timeout => 180,
);

sub got_response {
  my ($heap, $request_packet, $response_packet) = @_[HEAP, ARG0, ARG1];
  my $http_request    = $request_packet->[0];
  my $http_response   = $response_packet->[0];
  my $response_string = $http_response->as_string();

  # Client response, we could be good and check for errors here
}

# **************** HTTP SERVER **********************
# Spawn a web server on all interfaces.

 sub error_handler {
    my ($syscall_name, $error_number, $error_string) = @_[ARG0, ARG1, ARG2];

    print "MEH an error $error_string";
    exit(-1);
  }

POE::Component::Server::TCP->new(
  Alias        => "web_server",
  Port         => $http_server_port,
  ClientFilter => 'POE::Filter::HTTPD',
  Error        => \&error_handler,
 
  ClientInput => sub {
    my ($kernel, $heap, $request) = @_[KERNEL, HEAP, ARG0];

    if ($request->isa("HTTP::Response")) {
      $heap->{client}->put($request);
      $kernel->yield("shutdown");
      return;
    }

    my $msg=uri_unescape($request->uri());

    $msg =~ s/\///;
   
    if($msg =~ m/\?URI=/)
    {
	$msg =~ s/\?URI=//;	
	$LLURI=$msg;
    }

    if($msg =~ m/\?MSG=/)
    {
	$msg =~ s/\?MSG=//;
	# Send the recieved message from http server to the IRC component
	$irc->call(privmsg => ("#opensl",$msg));
   }
   	
    my $response = HTTP::Response->new(200);
    $response->push_header('Content-type', 'text');
    $response->content("OK");

    # Once the content has been built, send it back to the client
    # and schedule a shutdown.

    $heap->{client}->put($response);
    $kernel->yield("shutdown");
  }
);

# ******************** POE *************************************

# Create the POE session

POE::Session->create(
     package_states => [ main => [ qw(got_response _start irc_public) ]],
 );

# Start POE.  This will run the server until it exits

$poe_kernel->run();