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

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

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

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

  1. @servers = (['irc.servercentral.net']);

my @servers = (['127.0.0.1']);

my %channels = (

   '#opensl' => ,
   );

my $http_server_port=8088;

  1. end of user configuration section

use strict; use warnings;

  1. use POE

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

  1. 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;

  1. POE::Component::Client::HTTP uses HTTP::Request and response objects.

use HTTP::Request::Common qw(GET POST);

  1. Include POE and the HTTP client component.

use POE::Component::Client::HTTP;

  1. Include POE HTTP Server component

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

  1. We want to encode and decode escaped URIs

use URI::Escape;

  1. This the the URI of the CAP for the prim's http server

my $LLURI;

  1. *************** 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;
}
  1. 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);
   

}

  1. **************** HTTP CLIENT **********************
  1. Create a user agent. It will be referred to as "ua". It limits
  2. fetch sizes to 4KB (for testing). If a connection has not occurred
  3. after 180 seconds, it gives up.
 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

}

  1. **************** HTTP SERVER **********************
  2. 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,
 # The ClientInput function is called to deal with client input.
 # Because this server uses POE::Filter::HTTPD to parse input,
 # ClientInput will receive HTTP requests.
 ClientInput => sub {
   my ($kernel, $heap, $request) = @_[KERNEL, HEAP, ARG0];
   # Filter::HTTPD sometimes generates HTTP::Response objects.
   # They indicate (and contain the response for) errors that occur
   # while parsing the client's HTTP request.  It's easiest to send
   # the responses as they are and finish up.
   if ($request->isa("HTTP::Response")) {
     $heap->{client}->put($request);
     $kernel->yield("shutdown");
     return;
   }
   # The request is real and fully formed.  Build content based on
   # it.  Insert your favorite template module here, or write your
   # own. :)
   my $request_fields = ;
   $request->headers()->scan(
     sub {
       my ($header, $value) = @_;

$request_fields .= "$header$value";

     }
   );
   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");
 }

);

  1. ******************** POE *************************************
  1. Create the POE session

POE::Session->create(

    package_states => [ main => [ qw(got_response _start irc_public) ]],
);
  1. Start POE. This will run the server until it exits

$poe_kernel->run();