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.

Note there is not really any error checking currently and there is no security other than not knowing the CAP URI for the prim and the server URI and port, which is not really security. You could pass encrypted messages or use https and have a secret inside the message protected by the https.

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=80;

# 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();