Difference between revisions of "User:Robin Cornelius/IRC LSL Bridge"

From Second Life Wiki
Jump to navigation Jump to search
 
(5 intermediate revisions by the same user not shown)
Line 1: Line 1:
A simple LSL/IRC bridge using a perl implementation of an IRC client
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 code, insert in to a prim in world
Line 97: Line 103:
Perl code, run on an appropriate server
Perl code, run on an appropriate server


<code>
#!/usr/bin/perl
 
#!/usr/bin/perl
#
 
# Copyright (C) 2010 Robin Cornelius <robin.cornelius@gmail.com>
#
#
# 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
#  This program is free software: you can redistribute it and/or modify
#    the Free Software Foundation, either version 3 of the License, or
#    it under the terms of the GNU General Public License as published by
#    (at your option) any later version.
#    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
#    This program is distributed in the hope that it will be useful,
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    GNU General Public License for more details.
#    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/>.
#  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
 
# User configuration section
my $nickname = 'irc-bridge';
 
my $ircname = 'The bridge';
my $nickname = 'irc-bridge';
my $ircname = 'The bridge';
#@servers = (['irc.servercentral.net']);
 
my @servers = (['127.0.0.1']);
#@servers = (['irc.servercentral.net']);
my @servers = (['127.0.0.1']);
my %channels = (
 
    '#opensl' => '',
my %channels = (
    );
    '#opensl' => '',
    );
my $http_server_port=80;
 
my $http_server_port=8088;
# end of user configuration section
 
# end of user configuration section
use strict;
 
use warnings;
use strict;
use warnings;
#use POE
 
use POE;
#use POE
use POE::Component::Child;
use POE;
use POE::Component::Child;
# Pull in various IRC components
 
use POE::Component::IRC;
# Pull in various IRC components
use POE::Component::IRC::State;
use POE::Component::IRC;
use POE::Component::IRC::Plugin::NickReclaim;
use POE::Component::IRC::State;
use POE::Component::IRC::Plugin::AutoJoin;
use POE::Component::IRC::Plugin::NickReclaim;
use POE::Component::IRC::Plugin::Connector;
use POE::Component::IRC::Plugin::AutoJoin;
use POE::Component::IRC::Plugin::BotCommand;
use POE::Component::IRC::Plugin::Connector;
use POE::Component::IRC::Plugin::CycleEmpty;
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);
# 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 and the HTTP client component.
use POE::Component::Client::HTTP;
# Include POE HTTP Server component
 
use POE qw(Component::Server::TCP Filter::HTTPD);
# Include POE HTTP Server component
use HTTP::Response;
use POE qw(Component::Server::TCP Filter::HTTPD);
use HTTP::Response;
# We want to encode and decode escaped URIs
 
use URI::Escape;
# 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;
# This the the URI of the CAP for the prim's http server
my $LLURI;
# *************** IRC ****************************
 
# *************** IRC ****************************
my $irc = POE::Component::IRC::State->spawn(
 
    nick => $nickname,
my $irc = POE::Component::IRC::State->spawn(
    ircname => $ircname,
    nick => $nickname,
    Server => $servers[0]->[0],
    ircname => $ircname,
) or die "Could not use name $!";
    Server => $servers[0]->[0],
) or die "Could not use name $!";
sub _start {
 
sub _start {
    my ($kernel, $heap) = @_[KERNEL, HEAP];     
 
    my ($kernel, $heap) = @_[KERNEL, HEAP];     
      $irc->yield( register => 'all' );
 
    $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 ) );
    # 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);
   
   
     my $chans=\%channels;
     # Send the recieved message from IRC component to the http client component
     my $svrs=\@servers;
     $kernel->post("ua" => "request", "got_response", GET $url);
      
      
    $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
# **************** HTTP CLIENT **********************
sub irc_public {
 
  POE::Component::Client::HTTP->spawn(
    my ($kernel,$sender)=@_[KERNEL,SENDER];
  Alias  => 'ua',
 
  MaxSize => 4096,    # Remove for unlimited page sizes.
    my $hostmask=$_[ARG0];
  Timeout => 180,
    my $target=@$_[ARG1];
);
    my $msg=$_[ARG2];
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/\///;
      
      
    $hostmask =~ s/^(.*)!(.*)@(.*)$//;;
    if($msg =~ m/\?URI=/)
    my $nick = $1;
    {
    my $hostuser = $2;
$msg =~ s/\?URI=//;
    my $hostname = $3;
$LLURI=$msg;
 
    }
    my $url=$LLURI."/?".uri_escape("<$nick> ".$msg);
   
 
    if($msg =~ m/\?MSG=/)
    # Send the recieved message from IRC component to the http client component
    {
    $kernel->post("ua" => "request", "got_response", GET $url);
$msg =~ s/\?MSG=//;
   
# Send the recieved message from http server to the IRC component
}
$irc->call(privmsg => ("#opensl",$msg));
 
# **************** HTTP CLIENT **********************
 
# Create a user agent. It will be referred to as "ua".  It limits
# fetch sizes to 4KB (for testing).  If a connection has not occurred
# 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
}
 
# **************** 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,
 
  # 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
    my $response = HTTP::Response->new(200);
    # it.  Insert your favorite template module here, or write your
    $response->push_header('Content-type', 'text');
    # own. :)
    $response->content("OK");
 
    my $request_fields = '';
    # Once the content has been built, send it back to the client
    $request->headers()->scan(
    # and schedule a shutdown.
      sub {
        my ($header, $value) = @_;
    $heap->{client}->put($response);
        $request_fields .= "<tr><td>$header</td><td>$value</td></tr>";
    $kernel->yield("shutdown");
      }
    );
 
    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 *************************************
 
$poe_kernel->run();
# Create the POE session
 
 
POE::Session->create(
</code>
      package_states => [ main => [ qw(got_response _start irc_public) ]],
  );
# Start POE.  This will run the server until it exits
$poe_kernel->run();

Latest revision as of 10:23, 10 April 2010

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