# Author: Chris "BinGOs" Williams
# Derived from code by Dennis Taylor
#
# This module may be used, modified, and distributed under the same
# terms as Perl itself. Please see the license that came with your Perl
# distribution for details.
#
package POE::Component::IRC::Service::Hybrid;
use strict;
use POE qw( Wheel::SocketFactory Wheel::ReadWrite Driver::SysRW
Filter::Line Filter::Stream );
use POE::Filter::IRC::Hybrid;
use POE::Filter::CTCP::Hybrid;
use Carp;
use Socket;
use Sys::Hostname;
use vars qw($VERSION);
$VERSION = '0.996';
use constant PCI_REFCOUNT_TAG => "P::C::I registered";
my %irc_commands =
('quit' => \&oneoptarg_client,
'nick' => \&onlyonearg_client,
'invite' => \&onlytwoargs_client,
'kill' => \&onlytwoargs,
'gline' => \&spacesep,
'kline' => \&spacesep,
'jupe' => \&spacesep,
'privmsg' => \&privandnotice,
'notice' => \&privandnotice,
'join' => \&sjoin,
'stats' => \&spacesep_client,
'links' => \&spacesep_client,
'mode' => \&spacesep_client,
'part' => \&commasep_client,
'ctcp' => \&ctcp,
'ctcpreply' => \&ctcp,
);
# Create a new IRC Service
sub new {
my ($package,$alias,$hash) = splice @_, 0, 3;
my ($package_events);
unless ($alias and $hash) {
croak "Not enough parameters to POE::Component::IRC::Service::Hybrid->new()";
}
unless (ref $hash eq 'HASH') {
croak "Second argument to POE::Component::IRC::Service::P10::new() must be a hash reference";
}
warn "This module has now been deprecated by POE::Component::Server::IRC\n";
$hash->{EventMode} = 1 unless ( defined ( $hash->{EventMode} ) and $hash->{EventMode} == 0 );
$hash->{Reconnect} = 0 unless ( defined ( $hash->{Reconnect} ) and $hash->{Reconnect} == 1 );
$hash->{Debug} = 0 unless ( defined ( $hash->{Debug} ) and $hash->{Debug} == 1 );
if ( $hash->{EventMode} == 1 ) {
$package_events = [qw( _start
_stop
_parseline
_sock_up
_sock_down
_sock_failed
autoping
addnick
connect
topic
irc_hyb_stats
irc_hyb_version
irc_hyb_server_link
irc_hyb_server
irc_hyb_squit
irc_hyb_eob
irc_hyb_ping
irc_hyb_quit
irc_hyb_kill
irc_hyb_nick
irc_hyb_whois
irc_hyb_sjoin
irc_hyb_part
irc_hyb_kick
irc_hyb_mode
kick
join
register
sl_server
sl_client
shutdown
squit
unregister)];
} else {
$package_events = [qw( _start
_stop
_parseline
_sock_up
_sock_down
_sock_failed
autoping
addnick
connect
topic
irc_hyb_stats
irc_hyb_version
irc_hyb_server_link
irc_hyb_server
irc_hyb_squit
irc_hyb_eob
irc_hyb_ping
irc_hyb_quit
irc_hyb_kill
irc_hyb_nick
irc_hyb_whois
irc_hyb_mode
kick
join
register
sl_server
sl_client
shutdown
squit
unregister)];
}
# Create our object
my ($self) = { };
bless ($self);
# Parse the passed hash reference
unless ($hash->{'ServerName'} and $hash->{'RemoteServer'} and $hash->{'Password'} and $hash->{'ServerPort'}) {
croak "You must specify ServerName, RemoteServer, Password and ServerPort in your hash reference.";
}
$hash->{ServerDesc} = "*** POE::Component::IRC::Service ***" unless defined ($hash->{ServerDesc});
$hash->{Version} = "POE-Component-IRC-Service-P10-$VERSION" unless defined ($hash->{Version});
$hash->{'PingFreq'} = 90 unless ( defined ( $hash->{'PingFreq'} ) );
my @event_map = map {($_, $irc_commands{$_})} keys %irc_commands;
POE::Session->create( inline_states => { @event_map },
package_states => [
$package => $package_events, ],
args => [ $alias, @_ ],
heap => { State => $self,
servername => $hash->{'ServerName'},
serverdesc => $hash->{'ServerDesc'},
remoteserver => $hash->{'RemoteServer'},
serverport => $hash->{'ServerPort'},
password => $hash->{'Password'},
localaddr => $hash->{'LocalAddr'},
pingfreq => $hash->{'PingFreq'},
eventmode => $hash->{'EventMode'},
reconnect => $hash->{'Reconnect'},
debug => $hash->{'Debug'},
version => $hash->{'Version'}, },
);
return $self;
}
# Register and unregister to receive events
sub register {
my ($kernel, $heap, $session, $sender, @events) =
@_[KERNEL, HEAP, SESSION, SENDER, ARG0 .. $#_];
die "Not enough arguments" unless @events;
# FIXME: What "special" event names go here? (ie, "errors")
# basic, dcc (implies ctcp), ctcp, oper ...what other categories?
foreach (@events) {
$_ = "irc_hyb_" . $_ unless /^_/;
$heap->{events}->{$_}->{$sender} = $sender;
$heap->{sessions}->{$sender}->{'ref'} = $sender;
unless ($heap->{sessions}->{$sender}->{refcnt}++ or $session == $sender) {
$kernel->refcount_increment($sender->ID(), PCI_REFCOUNT_TAG);
}
}
}
sub unregister {
my ($kernel, $heap, $session, $sender, @events) =
@_[KERNEL, HEAP, SESSION, SENDER, ARG0 .. $#_];
die "Not enough arguments" unless @events;
foreach (@events) {
delete $heap->{events}->{$_}->{$sender};
if (--$heap->{sessions}->{$sender}->{refcnt} <= 0) {
delete $heap->{sessions}->{$sender};
unless ($session == $sender) {
$kernel->refcount_decrement($sender->ID(), PCI_REFCOUNT_TAG);
}
}
}
}
# Session starts or stops
sub _start {
my ($kernel, $session, $heap, $alias) = @_[KERNEL, SESSION, HEAP, ARG0];
my @options = @_[ARG1 .. $#_];
$session->option( @options ) if @options;
$kernel->alias_set($alias);
$kernel->yield( 'register', qw(stats version server_link server squit eob quit kill nick whois sjoin part kick mode) );
$heap->{irc_filter} = POE::Filter::IRC::Hybrid->new();
$heap->{ctcp_filter} = POE::Filter::CTCP::Hybrid->new();
$heap->{irc_filter}->debug(1) if ( $heap->{debug} );
$heap->{connected} = 0;
$heap->{serverlink} = "";
$heap->{starttime} = time();
}
sub _stop {
my ($kernel, $heap, $quitmsg) = @_[KERNEL, HEAP, ARG0];
if ($heap->{connected}) {
$kernel->call( $_[SESSION], 'shutdown', $quitmsg );
}
}
# Connect to IRC Network
sub connect {
my ($kernel, $heap, $session, $args) = @_[KERNEL, HEAP, SESSION, ARG0];
if ($heap->{'sock'}) {
$kernel->call ($session, 'squit');
}
$heap->{socketfactory} = POE::Wheel::SocketFactory->new(
SocketDomain => AF_INET,
SocketType => SOCK_STREAM,
SocketProtocol => 'tcp',
RemoteAddress => $heap->{'remoteserver'},
RemotePort => $heap->{'serverport'},
SuccessEvent => '_sock_up',
FailureEvent => '_sock_failed',
( $heap->{localaddr} ? (BindAddress => $heap->{localaddr}) : () ),
);
}
sub autoping {
my ($kernel,$heap) = @_[KERNEL,HEAP];
if ( $heap->{'socket'} ) {
$kernel->yield( 'sl_client', "PING :$heap->{serverlink}" );
$kernel->delay( 'autoping' => $heap->{pingfreq} );
}
}
sub squit {
my ($kernel, $heap) = @_[KERNEL,HEAP];
# Don't give a f**k about any parameters passed
if ( $heap->{'socket'} ) {
delete ( $heap->{'socket'} );
$kernel->yield( 'sl_client', "SQUIT $heap->{serverlink} :$heap->{servername}" );
}
}
# Internal function called when a socket is closed.
sub _sock_down {
my ($kernel, $heap) = @_[KERNEL, HEAP];
# Destroy the RW wheel for the socket.
delete $heap->{'socket'};
$heap->{connected} = 0;
# post a 'irc_disconnected' to each session that cares
foreach (keys %{$heap->{sessions}}) {
$kernel->post( $heap->{sessions}->{$_}->{'ref'},
'irc_hyb_disconnected', $heap->{server} );
}
}
sub _sock_up {
my ($kernel,$heap,$session,$socket) = @_[KERNEL,HEAP,SESSION,ARG0];
$heap->{connecttime} = time();
$heap->{State}->_burst_create();
delete $heap->{socketfactory};
$heap->{localaddr} = (unpack_sockaddr_in( getsockname $socket))[1];
$heap->{'socket'} = new POE::Wheel::ReadWrite
(
Handle => $socket,
Driver => POE::Driver::SysRW->new(),
Filter => POE::Filter::Line->new(),
InputEvent => '_parseline',
ErrorEvent => '_sock_down',
);
if ($heap->{'socket'}) {
$heap->{connected} = 1;
} else {
_send_event ( $kernel, $heap, 'irc_hyb_socketerr', "Couldn't create ReadWrite wheel for IRC socket" );
}
foreach (keys %{$heap->{sessions}}) {
$kernel->post( $heap->{sessions}->{$_}->{'ref'}, 'irc_hyb_connected', $heap->{remoteserver} );
}
$heap->{socket}->put("PASS $heap->{password} :TS\n");
$heap->{socket}->put("CAPAB :EOB\n");
$heap->{socket}->put("SERVER $heap->{servername} 1 :$heap->{serverdesc}\n");
$heap->{socket}->put("SVINFO 3 3 1 :$heap->{connecttime}\n");
}
sub _sock_failed {
my ($kernel, $heap, $op, $errno, $errstr) = @_[KERNEL, HEAP, ARG0..ARG2];
_send_event( $kernel, $heap, 'irc_hyb_socketerr', "$op error $errno: $errstr" );
}
# Parse each line from received at the socket
# Parse a message from the IRC server and generate the appropriate
# event(s) for listening sessions.
sub _parseline {
my ($kernel, $session, $heap, $line) = @_[KERNEL, SESSION, HEAP, ARG0];
my (@events, @cooked);
# Feed the proper Filter object the raw IRC text and get the
# "cooked" events back for sending, then deliver each event. We
# handle CTCPs separately from normal IRC messages here, to avoid
# silly module dependencies later.
@cooked = ($line =~ tr/\001// ? @{$heap->{ctcp_filter}->get( [$line] )}
: @{$heap->{irc_filter}->get( [$line] )} );
foreach my $ev (@cooked) {
$ev->{name} = 'irc_hyb_' . $ev->{name};
_send_event( $kernel, $heap, $ev->{name}, @{$ev->{args}} );
}
}
# Sends an event to all interested sessions. This is a separate sub
# because I do it so much, but it's not an actual POE event because it
# doesn't need to be one and I don't need the overhead.
sub _send_event {
my ($kernel, $heap, $event, @args) = @_;
my %sessions;
foreach (values %{$heap->{events}->{'irc_hyb_all'}},
values %{$heap->{events}->{$event}}) {
$sessions{$_} = $_;
}
foreach (values %sessions) {
$kernel->post( $_, $event, @args );
}
}
sub addnick {
my ($kernel, $heap, $session, $args) = @_[KERNEL, HEAP, SESSION, ARG0];
my $connecttime = time();
if ($args) {
my %arg;
if (ref $args eq 'ARRAY') {
%arg = @$args;
} elsif (ref $args eq 'HASH') {
%arg = %$args;
} else {
die "First argument to addnick() should be a hash or array reference";
}
# Gentlemen, lets get down to business
# Mandatory fields we must must must have these, damnit
my $nickname = $arg{'NickName'} if exists $arg{'NickName'};
my $username = $arg{'UserName'} if exists $arg{'UserName'};
my $hostname = $arg{'HostName'} if exists $arg{'HostName'};
my $umode = $arg{'Umode'} if exists $arg{'Umode'};
my $description = $arg{'Description'} if exists $arg{'Description'};
unless (defined $nickname) {
die "You must specify at least a NickName to addnick";
}
# Default everything else
my $cmd = "NICK $nickname 1 $connecttime ";
$umode = "+o" unless (defined $umode);
$umode = "+" . $umode unless ($umode =~ /^\+/ or not defined($umode));
$cmd .= "$umode " if defined($umode);
$cmd .= "+ " if not defined($umode);
$cmd .= lc $nickname . " " unless (defined $username);
$cmd .= "$username " if (defined $username);
$cmd .= "$heap->{servername} " unless (defined $hostname);
$cmd .= "$hostname " if (defined $hostname);
$cmd .= "$heap->{servername} ";
$cmd .= ":$heap->{serverdesc}" unless (defined $description);
$cmd .= ":$description" if defined($description);
$kernel->yield ( 'sl_client', $cmd ); # Kludge tbh :)
} else {
die "First argument to addnick() should be a hash or array reference";
}
}
# Generate an automatic pong in response to IRC Server's ping
sub irc_hyb_ping {
my ($heap, $arg) = @_[HEAP, ARG0];
$heap->{socket}->put("PONG :$heap->{servername}\n");
}
sub irc_hyb_server_link {
my ($kernel,$heap,$server) = @_[KERNEL,HEAP,ARG0];
$heap->{Bursting} = 1;
$heap->{State}->{serverlink} = $server;
$heap->{serverlink} = $server;
$heap->{State}->_server_add($server,1,$heap->{servername});
}
sub irc_hyb_eob {
my ($kernel,$heap,$who) = @_[KERNEL,HEAP,ARG0];
SWITCH: {
if ( $who eq $heap->{serverlink} ) {
foreach ( $heap->{State}->_burst_info() ) {
$kernel->yield( 'sl_server', $_ );
}
$kernel->yield( 'sl_server', "EOB" );
$heap->{State}->_burst_destroy();
last SWITCH;
}
if ( $who eq $heap->{servername} ) {
$heap->{Bursting} = 0;
last SWITCH;
}
}
}
sub irc_hyb_server {
my ($kernel,$heap,$link,$server,$hops) = @_[KERNEL,HEAP,ARG0,ARG1,ARG2];
$heap->{State}->_server_add($server,$hops,$link);
}
sub irc_hyb_squit {
my ($heap,$squit) = @_[HEAP,ARG0];
$heap->{State}->_server_del($squit);
}
sub irc_hyb_version {
my ($kernel, $heap, $who) = @_[KERNEL,HEAP,ARG0];
$kernel->yield( 'sl_server', "351 $who $heap->{version}. $heap->{servername} :" );
}
sub irc_hyb_sjoin {
my ($kernel,$heap,$who,$what) = @_[KERNEL,HEAP,ARG0,ARG1];
$heap->{State}->_channel_burst( $what );
}
sub irc_p10_quit {
my ($heap, $who) = @_[HEAP,ARG0];
$heap->{State}->_nick_del($who);
}
# Our event handlers for events sent to us
# The handler for commands which have N arguments, separated by commas.
sub commasep {
my ($kernel, $state) = @_[KERNEL, STATE];
my $args = join ',', @_[ARG0 .. $#_];
$state = uc( $state );
$state .= " $args" if defined $args;
$kernel->yield( 'sl_server', $state );
}
# The handler for commands which have N arguments, separated by commas. Client hacked.
sub commasep_client {
my ($kernel, $state, $numeric) = @_[KERNEL, STATE, ARG0];
my $args = join ',', @_[ARG1 .. $#_];
$state = uc( $state );
$state .= " $args" if defined $args;
$kernel->yield( 'sl_client', ":$numeric $state" );
}
# Send a CTCP query or reply, with the same syntax as a PRIVMSG event.
sub ctcp {
my ($kernel, $state, $heap, $numeric, $to) = @_[KERNEL, STATE, HEAP, ARG0, ARG1];
my $message = join ' ', @_[ARG2 .. $#_];
unless (defined $numeric and defined $to and defined $message) {
die "The POE::Component::IRC event \"$state\" requires three arguments";
}
# CTCP-quote the message text.
($message) = @{$heap->{ctcp_filter}->put([ $message ])};
# Should we send this as a CTCP request or reply?
$state = $state eq 'ctcpreply' ? 'notice' : 'privmsg';
$kernel->yield( $state, $numeric, $to, $message );
}
# Tell the IRC server to forcibly remove a user from a channel.
sub kick {
my ($kernel, $numeric, $chan, $nick) = @_[KERNEL, ARG0, ARG1, ARG2];
my $message = join '', @_[ARG3 .. $#_];
unless (defined $numeric and defined $chan and defined $nick) {
die "The POE::Component::IRC event \"kick\" requires at least three arguments";
}
$nick .= " :$message" if defined $message;
$kernel->yield('sl_client', ":$numeric KICK $chan $nick" );
}
# The handler for all IRC commands that take no arguments.
sub noargs {
my ($kernel, $state, $arg) = @_[KERNEL, STATE, ARG0];
if (defined $arg) {
die "The POE::Component::IRC event \"$state\" takes no arguments";
}
$kernel->yield( 'sl_server', uc( $state ) );
}
# The handler for all IRC commands that take no arguments. Client hacked.
sub noargs_client {
my ($kernel, $state, $numeric, $arg) = @_[KERNEL, STATE, ARG0, ARG1];
unless (defined $numeric) {
die "The POE::Component::IRC event \"$state\" requires at least one argument";
}
if (defined $arg) {
die "The POE::Component::IRC event \"$state\" takes no arguments";
}
$kernel->yield( 'sl_client', ":$numeric " . uc( $state ) );
}
# The handler for commands that take one required and two optional arguments.
sub oneandtwoopt {
my ($kernel, $state) = @_[KERNEL, STATE];
my $arg = join '', @_[ARG0 .. $#_];
$state = uc( $state );
if (defined $arg) {
$arg = ':' . $arg if $arg =~ /\s/;
$state .= " $arg";
}
$kernel->yield( 'sl_server', $state );
}
# The handler for commands that take one required and two optional arguments. Client hacked.
sub oneandtwoopt_client {
my ($kernel, $state, $numeric) = @_[KERNEL, STATE, ARG0];
my $arg = join '', @_[ARG1 .. $#_];
unless (defined $numeric) {
die "The POE::Component::IRC event \"$state\" requires at least one argument";
}
$state = uc( $state );
if (defined $arg) {
$arg = ':' . $arg if $arg =~ /\s/;
$state .= " $arg";
}
$kernel->yield( 'sl_client', ":$numeric $state" );
}
# The handler for commands that take at least one optional argument.
sub oneoptarg {
my ($kernel, $state) = @_[KERNEL, STATE];
my $arg = join '', @_[ARG0 .. $#_] if defined $_[ARG0];
$state = uc( $state );
if (defined $arg) {
$arg = ':' . $arg if $arg =~ /\s/;
$state .= " $arg";
}
$kernel->yield( 'sl_server', $state );
}
# The handler for commands that take at least one optional argument. Client hacked.
sub oneoptarg_client {
my ($kernel, $state, $numeric) = @_[KERNEL, STATE, ARG0];
my $arg = join '', @_[ARG1 .. $#_] if defined $_[ARG1];
unless (defined $numeric) {
die "The POE::Component::IRC event \"$state\" requires at least one argument";
}
$state = uc( $state );
if (defined $arg) {
$arg = ':' . $arg if $arg =~ /\s/;
$state .= " $arg";
}
$kernel->yield( 'sl_client', ":$numeric $state" );
}
# The handler for commands which take one required and one optional argument.
sub oneortwo {
my ($kernel, $state, $one) = @_[KERNEL, STATE, ARG0];
my $two = join '', @_[ARG1 .. $#_];
unless (defined $one) {
die "The POE::Component::IRC event \"$state\" requires at least one argument";
}
$state = uc( $state ) . " $one";
$state .= " $two" if defined $two;
$kernel->yield( 'sl_server', $state );
}
# The handler for commands which take one required and one optional argument. Client hacked.
sub oneortwo_client {
my ($kernel, $state, $numeric, $one) = @_[KERNEL, STATE, ARG0, ARG1];
my $two = join '', @_[ARG2 .. $#_];
unless (defined $numeric and defined $one) {
die "The POE::Component::IRC event \"$state\" requires at least two argument";
}
$state = uc( $state ) . " $one";
$state .= " $two" if defined $two;
$kernel->yield( 'sl_client', ":$numeric $state" );
}
# Handler for commands that take exactly one argument.
sub onlyonearg {
my ($kernel, $state) = @_[KERNEL, STATE];
my $arg = join '', @_[ARG0 .. $#_];
unless (defined $arg) {
die "The POE::Component::IRC event \"$state\" requires one argument";
}
$state = uc( $state );
$arg = ':' . $arg if $arg =~ /\s/;
$state .= " $arg";
$kernel->yield( 'sl_server', $state );
}
# Handler for commands that take exactly one argument. Client hacked.
sub onlyonearg_client {
my ($kernel, $state, $numeric) = @_[KERNEL, STATE, ARG0];
my $arg = join '', @_[ARG1 .. $#_];
unless (defined $numeric and defined $arg) {
die "The POE::Component::IRC::Service::P10 event \"$state\" requires two argument";
}
$state = uc( $state );
$arg = ':' . $arg if $arg =~ /\s/;
$state .= " $arg";
$kernel->yield( 'sl_client', ":$numeric $state" );
}
# Handler for commands that take exactly two arguments.
sub onlytwoargs {
my ($heap, $kernel, $state, $one) = @_[HEAP, KERNEL, STATE, ARG0];
my ($two) = join '', @_[ARG1 .. $#_];
unless (defined $one and defined $two) {
die "The POE::Component::IRC::Service::P10 event \"$state\" requires two arguments";
}
$state = uc( $state );
$two = ':' . $two if $two =~ /\s/;
$kernel->yield( 'sl_server', "$state $two" );
}
# Handler for commands that take exactly two arguments. Client hacked.
sub onlytwoargs_client {
my ($heap, $kernel, $state, $numeric, $one) = @_[HEAP, KERNEL, STATE, ARG0, ARG1];
my ($two) = join '', @_[ARG2 .. $#_];
unless (defined $numeric and defined $one and defined $two) {
die "The POE::Component::IRC::Service::P10 event \"$state\" requires three arguments";
}
$state = uc( $state );
$two = ':' . $two if $two =~ /\s/;
$kernel->yield( 'sl_client', ":$numeric $state $two" );
}
# Handler for privmsg or notice events.
sub privandnotice {
my ($kernel, $state, $numeric, $to) = @_[KERNEL, STATE, ARG0, ARG1];
my $message = join ' ', @_[ARG2 .. $#_];
unless (defined $numeric and defined $to and defined $message) {
die "The POE::Component::IRC event \"$state\" requires three arguments";
}
if (ref $to eq 'ARRAY') {
$to = join ',', @$to;
}
$state = uc( $state );
$state .= " $to :$message";
$kernel->yield( 'sl_client', ":$numeric $state" );
}
# Tell the IRC session to go away.
sub shutdown {
my ($kernel, $heap) = @_[KERNEL, HEAP];
foreach ($kernel->alias_list( $_[SESSION] )) {
$kernel->alias_remove( $_ );
}
foreach (qw(socket sock socketfactory dcc wheelmap)) {
delete $heap->{$_};
}
}
# The handler for commands which have N arguments, separated by spaces.
sub spacesep {
my ($kernel, $state) = @_[KERNEL, STATE];
my $args = join ' ', @_[ARG0 .. $#_];
$state = uc( $state );
$state .= " $args" if defined $args;
$kernel->yield( 'sl_server', $state );
}
# The handler for commands which have N arguments, separated by spaces. Client hacked.
sub spacesep_client {
my ($kernel, $state, $numeric) = @_[KERNEL, STATE, ARG0];
my $args = join ' ', @_[ARG1 .. $#_];
$state = uc( $state );
$state .= " $args" if defined $args;
$kernel->yield( 'sl_server', "$numeric $state" );
}
# Dish out server initiated commands
sub sl_server {
my ($kernel, $heap, $cmd) = @_[KERNEL, HEAP, ARG0];
$heap->{socket}->put(":$heap->{servername} $cmd\n");
$kernel->yield('_parseline',":$heap->{servername} $cmd");
}
# Dish out client (whichever is specified) initiated commands
sub sl_client {
my ($kernel, $heap, $cmd) = @_[KERNEL, HEAP, ARG0];
$heap->{socket}->put("$cmd\n");
$kernel->yield('_parseline',$cmd);
}
# Set or query the current topic on a channel.
sub topic {
my ($kernel,$heap, $numeric, $chan) = @_[KERNEL,HEAP, ARG0, ARG1];
my $topic = join '', @_[ARG2 .. $#_];
$chan .= " :$topic" if length $topic;
$kernel->yield('sl_client',":$numeric TOPIC $chan");
}
sub sjoin {
my ($kernel,$state,$heap,$nick,$channel) = @_[KERNEL,STATE,HEAP,ARG0,ARG1];
my ($ts) = time();
unless ( defined($nick) and defined($channel) ) {
die "The POE::Component::IRC event \"$state\" requires at least two argument";
}
# Under TSora joins are actually implemented as server initiated events *sigh*
$kernel->yield('sl_server',"SJOIN $ts $channel + :$nick");
}
# Our own little function to return a proper uppercase nickname or channel name IRC stylee
# See the RFC for the details
sub u_irc {
my ($value) = shift || return undef;
$value =~ tr/a-z{}|/A-Z[]\\/;
return $value;
}
# Return a correctly formatted string for STATS u requests
sub timestring {
my ($timeval) = shift || return 0;
my $uptime = time() - $timeval;
my $days = int $uptime / 86400;
my $remain = $uptime % 86400;
my $hours = int $remain / 3600;
$remain %= 3600;
my $mins = int $remain / 60;
$remain %= 60;
return sprintf("Server Up %d days, %2.2d:%2.2d:%2.2d",$days,$hours,$mins,$remain);
}
sub retOpflags {
my ($opflags) = shift || return undef;
my (@opflags) = ();
my ($action) = "";
for (my $i = 0; $i < length($opflags); $i++) {
my $char = substr($opflags,$i,1);
if ($char eq "+" or $char eq "-") {
$action = $char;
} else {
push (@opflags,"$action$char");
}
}
return @opflags;
}
# Object Methods
# Private methods begin with _
sub _server_add {
my ($self) = shift;
my ($server) = { Name => $_[0],
Hops => $_[1],
Link => $_[2]
};
$self->{servers_name}->{ $server->{Name} } = $server;
return 1;
}
sub _server_del {
my ($self) = shift;
my ($server) = shift || return 0;
$self->{servers_name}->{$server}->{ToDelete} = 1;
foreach ( keys %{ $self->{servers_name} } ) {
if ( $server eq $self->{servers_name}->{$_}->{Link} and not defined ( $self->{servers_name}->{$server}->{ToDelete} ) ) {
$self->_server_del($self->{servers_name}->{$_}->{Link});
}
}
foreach ( keys %{ $self->{byserver}->{$server} } ) {
$self->_nick_del($_);
}
delete ( $self->{servers_name}->{$server} );
return 1;
}
sub _nick_add {
my ($self) = shift;
my ($nickname) = $_[0] || return 0;
my ($server) = $_[1] || return 0;
my ($username) = $_[2] || return 0;
my ($hostname) = $_[3] || return 0;
my ($timestamp) = $_[4] || time();
my ($umode) = $_[5] || undef;
my ($ircname) = $_[6] || undef;
# Does the nickname already exist in our state, ie. one of our clients
# If so kludge the timestamp on ours so it is older and they will get KILLed mwuahahahaha :o)
if ( defined ( $self->{bynickname}->{ u_irc ($nickname) } ) ) {
my ($kludge) = $timestamp - 30;
$self->{bynickname}->{ u_irc ( $nickname ) }->{TimeStamp} = $kludge;
if ( defined ( $self->{burst_nicks}->{ u_irc( $nickname ) } ) ) {
$self->{burst_nicks}->{ u_irc( $nickname ) }->{TimeStamp} = $kludge;
}
}
if ( not defined ( $self->{bynickname}->{ u_irc( $nickname ) } ) ) {
my ($record) = { NickName => $nickname,
UserName => $username,
HostName => $hostname,
IRCName => $ircname,
TimeStamp => $timestamp,
Server => $server,
UMode => $umode, };
$self->{bynickname}->{ u_irc ( $record->{NickName} ) } = $record;
$self->{byserver}->{ $server }->{ u_irc ( $record->{NickName} ) } = $record;
}
return 1;
}
sub _nick_del {
my ($self) = shift;
my ($nickname) = u_irc ( $_[0] ) || return 0;
foreach ( keys %{ $self->{bynickname}->{$nickname}->{Channels} } ) {
delete ( $self->{channels}->{$_}->{Members}->{$nickname} );
if ( scalar ( keys % { $self->{channels}->{$_}->{Members} } ) == 0 ) {
delete ( $self->{channels}->{$_} );
}
}
my ($server) = $self->{bynickname}->{$nickname}->{Server};
delete ( $self->{bynickname}->{$nickname} );
delete ( $self->{byserver}->{$server}->{$nickname} );
return 1;
}
sub _nick_change {
my ($self) = shift;
my ($nickname) = u_irc ( $_[0] ) || return 0;
my ($newnick) = $_[1] || return 0;
my ($record) = $self->{bynickname}->{$nickname};
$record->{NickName} = $newnick;
$record->{TimeStamp} = time();
delete $self->{bynickname}->{$nickname};
$self->{bynickname}->{ u_irc( $record->{NickName} ) } = $record;
return 1;
}
sub _nick_umode {
my ($self) = shift;
my ($nickname) = u_irc ( $_[0] ) || return 0;
my ($umode) = $_[1] || return 0;
my ($currentumode) = $self->{bynickname}->{$nickname}->{UMode};
foreach (retOpflags($umode)) {
SWITCH: {
if (/^\+(.+)/) {
if ( not defined ($currentumode) ) {
$currentumode = $1;
} else {
$currentumode .= $1;
$currentumode = join("",sort(split(//,$currentumode)));
}
last SWITCH;
}
if (/^-(.+)/) {
if ( defined ($currentumode) ) {
$currentumode =~ s/$1//g;
}
last SWITCH;
}
}
}
if ( defined ($currentumode) and $currentumode ) {
$self->{bynickname}->{$nickname}->{UMode} = $currentumode;
} else {
delete ( $self->{bynickname}->{$nickname}->{UMode} );
}
return 1;
}
sub _channel_join {
my ($self) = shift;
my ($channel) = $_[0] || return 0;
my ($nickname) = u_irc ( $_[1] ) || return 0;
my ($timestamp) = $_[2];
my ($usermode) = 0;
my ($channelname) = $channel;
$channel = u_irc ( $channel );
if (not exists $self->{channels}->{$channel}) {
$self->{channels}->{$channel}->{Channel} = $channelname;
$self->{channels}->{$channel}->{TimeStamp} = $timestamp;
$usermode = 2;
}
$self->{channels}->{$channel}->{Members}->{$nickname} = $usermode;
$self->{bynickname}->{$nickname}->{Channels}->{$channel} = $usermode;
return 1;
}
sub _channel_part {
my ($self) = shift;
my ($channel) = u_irc ( $_[0] ) || return 0;
my ($nickname) = u_irc ( $_[1] ) || return 0;
delete ( $self->{channels}->{$channel}->{Members}->{$nickname} );
if ( scalar ( keys % { $self->{channels}->{$_}->{Members} } ) == 0 ) {
delete ( $self->{channels}->{$_} );
}
delete ( $self->{bynickname}->{$nickname}->{Channels}->{$channel} );
return 1;
}
sub _channel_topic {
my ($self) = shift;
my ($channel) = u_irc( $_[0] ) || return 0;
my ($topic) = $_[1] || return 0;
my ($set_by) = $_[2] || return 0;
my ($timestamp) = $_[3] || return 0;
$self->{channels}->{$channel}->{Topic} = $topic;
$self->{channels}->{$channel}->{Set_By} = $set_by;
$self->{channels}->{$channel}->{TopicTS} = $timestamp;
return 1;
}
sub _channel_untopic {
my ($self) = shift;
my ($channel) = u_irc( $_[0] ) || return 0;
delete ( $self->{channels}->{$channel}->{Topic} );
delete ( $self->{channels}->{$channel}->{Set_By} );
delete ( $self->{channels}->{$channel}->{TopicTS} );
return 1;
}
sub _channel_mode {
my ($self) = shift;
my ($channel) = u_irc( $_[0] ) || return 0;
my ($string) = $_[1] || return 0;
my ($who) = $_[2] || return 0; # This is either a server or client name only used for bans tbh
my ($modes,@args) = split(/ /,$string);
my (@modes) = retOpflags($modes);
my ($currentmode) = $self->{channels}->{$channel}->{Mode};
foreach (@modes) {
my $argument;
$argument = shift(@args) if (/\+[bkloveIh]/);
$argument = shift(@args) if (/-[boveIh]/);
SWITCH: {
if (/[eI]/) {
last SWITCH;
}
if (/b/) {
$self->_channel_ban($channel,$_,$argument,$who);
last SWITCH;
}
if (/l/) {
if (/^\+(.+)/) {
$self->{channels}->{$channel}->{ChanLimit} = $argument;
$currentmode .= $1;
} else {
delete ( $self->{channels}->{$channel}->{ChanLimit} );
$currentmode =~ s/$1//g;
}
last SWITCH;
}
if (/k/) {
if (/^\+(.+)/) {
$self->{channels}->{$channel}->{ChanKey} = $argument;
$currentmode .= $1;
} else {
delete ( $self->{channels}->{$channel}->{ChanKey} );
$currentmode =~ s/$1//g;
}
last SWITCH;
}
if (/[ov]/) {
my ($value) = 0;
if (/\+o/) { $value = 2; }
if (/-o/) { $value = -2; }
if (/\+v/) { $value = 1; }
if (/-v/) { $value = -1; }
$self->{channels}->{$channel}->{Members}->{$argument} += $value;
$self->{bynickname}->{ u_irc ( $argument ) }->{Channels}->{$channel} += $value;
last SWITCH;
}
if (/[h]/) {
if (/\+h/) {
$self->{channels}->{$channel}->{Members}->{$argument} = -1;
$self->{bynickname}->{ u_irc ( $argument ) }->{Channels}->{$channel} = -1;
} else {
$self->{channels}->{$channel}->{Members}->{$argument} = 0;
$self->{bynickname}->{ u_irc ( $argument ) }->{Channels}->{$channel} = 0;
}
last SWITCH;
}
if (/^\+(.+)/) {
$currentmode .= $1;
last SWITCH;
}
if (/^-(.+)/) {
$currentmode =~ s/$1//g;
last SWITCH;
}
}
}
$self->{channels}->{$channel}->{Mode} = join("",sort(split(//,$currentmode)));
return 1;
}
sub _channel_ban {
my ($self) = shift;
my ($channel) = u_irc( $_[0] ) || return 0;
my ($operation) = $_[1] || return 0;
my ($banmask) = $_[2] || return 0;
my ($who) = $_[3] || return 0;
if ($operation eq "+b") {
$self->{channels}->{$channel}->{Bans}->{$banmask}->{Time} = time();
$self->{channels}->{$channel}->{Bans}->{$banmask}->{Who} = $who;
} else {
delete ( $self->{channels}->{$channel}->{Bans}->{$banmask} );
}
return 1;
}
sub _channel_burst {
my ($self) = shift;
my ($args) = shift || return 0;
my ($first,$second) = split(/ :/,$args);
my (@args) = split(/ /,$first); my (@nicknames) = split(/ /,$second);
my ($timestamp,$channelname) = @args[0..1];
my ($channel) = u_irc ( $channelname );
if ( exists $self->{channels}->{$channel} and $timestamp < $self->{channels}->{$channel}->{TimeStamp} ) {
$self->{channels}->{$channel}->{TimeStamp} = $timestamp;
$self->{burst_channels}->{$channel}->{TimeStamp} = $timestamp;
} else {
$self->{channels}->{$channel}->{Channel} = $channelname;
$self->{channels}->{$channel}->{TimeStamp} = $timestamp;
}
if ( $args[2] =~ /^\+(.+)$/ ) {
$self->{channels}->{$channel}->{Mode} = $1;
my ($l) = index ( $1, "l" );
my ($k) = index ( $1, "k" );
SWITCH: {
if ( $l > $k and $k != -1 ) {
$self->{channels}->{$channel}->{ChanLimit} = $args[4];
$self->{channels}->{$channel}->{ChanKey} = $args[3];
last SWITCH;
}
if ( $l > $k and $k == -1 ) {
$self->{channels}->{$channel}->{ChanLimit} = $args[3];
last SWITCH;
}
if ( $k > $l and $l != -1 ) {
$self->{channels}->{$channel}->{ChanLimit} = $args[3];
$self->{channels}->{$channel}->{ChanKey} = $args[4];
last SWITCH;
}
if ( $k > $l and $l == -1 ) {
$self->{channels}->{$channel}->{ChanKey} = $args[3];
last SWITCH;
}
}
}
foreach ( @nicknames ) {
my ($value) = 0; my ($nickname);
if ( /^(\@|\+|%)+(.*)/ ) {
if ( $1 =~ /\@/ ) {
$value += 2;
}
if ( $1 =~ /\+/ ) {
$value += 1;
}
if ( $1 =~ /%/ ) {
$value = -1;
}
$nickname = $2;
} else {
$nickname = $_;
}
$self->{channels}->{$channel}->{Members}->{ u_irc ( $nickname ) } = $value;
$self->{bynickname}->{ u_irc ( $nickname ) }->{Channels}->{$channel} = $value;
}
}
sub _burst_create {
my ($self) = shift;
foreach ( keys %{ $self->{bynickname} } ) {
$self->{burst_nicks}->{$_}->{NickName} = $self->{bynickname}->{$_}->{NickName};
$self->{burst_nicks}->{$_}->{UserName} = $self->{bynickname}->{$_}->{UserName};
$self->{burst_nicks}->{$_}->{HostName} = $self->{bynickname}->{$_}->{HostName};
$self->{burst_nicks}->{$_}->{IRCName} = $self->{bynickname}->{$_}->{IRCName};
$self->{burst_nicks}->{$_}->{TimeStamp} = $self->{bynickname}->{$_}->{TimeStamp};
$self->{burst_nicks}->{$_}->{Server} = $self->{bynickname}->{$_}->{Server};
$self->{burst_nicks}->{$_}->{UMode} = $self->{bynickname}->{$_}->{UMode};
}
foreach ( keys %{ $self->{channels} } ) {
$self->{burst_channels}->{$_}->{Channel} = $self->{channels}->{$_}->{Channel};
$self->{burst_channels}->{$_}->{TimeStamp} = $self->{channels}->{$_}->{TimeStamp};
$self->{burst_channels}->{$_}->{Mode} = $self->{channels}->{$_}->{Mode};
$self->{burst_channels}->{$_}->{ChanKey} = $self->{channels}->{$_}->{ChanKey} if ( defined ( $self->{channels}->{$_}->{ChanKey} ) );
$self->{burst_channels}->{$_}->{ChanLimit} = $self->{channels}->{$_}->{ChanLimit} if ( defined ( $self->{channels}->{$_}->{ChanLimit} ) );
foreach my $ban ( keys %{ $self->{channels}->{$_}->{Bans} } ) {
push( @{ $self->{burst_channels}->{$_}->{Bans} }, $ban );
}
foreach my $user ( keys %{ $self->{channels}->{$_}->{Members} } ) {
$self->{burst_channels}->{$_}->{Members}->{$user} = $self->{channels}->{$_}->{Members}->{$user};
}
}
return 1;
}
sub _burst_info {
my ($self) = shift;
my (@burst);
my (@modes) = ( '', '+', '@', '@+' );
# Nicknames first
foreach ( keys %{ $self->{burst_nicks} } ) {
my ($burstline) = "NICK " . $self->{burst_nicks}->{$_}->{NickName} . " ";
$burstline .= "1 " . $self->{burst_nicks}->{$_}->{TimeStamp} . " ";
$burstline .= $self->{burst_nicks}->{$_}->{UserName} . " " . $self->{burst_nicks}->{$_}->{HostName} . " " . $self->{burst_nicks}->{$_}->{Server} . " :";
$burstline .= $self->{burst_nicks}->{$_}->{IRCName} if ( defined ( $self->{burst_nicks}->{$_}->{IRCName} ) );
push (@burst, $burstline);
}
foreach ( keys %{ $self->{burst_channels} } ) {
my ($burstline) = "SJOIN " . $self->{burst_channels}->{$_}->{TimeStamp} . " " . $self->{burst_channels}->{$_}->{Channel} . " +";
$burstline .= $self->{burst_channels}->{$_}->{Mode} if ( defined ( $self->{burst_channels}->{$_}->{Mode} ) );
$burstline .= " " . $self->{burst_channels}->{$_}->{ChanKey} if ( defined ( $self->{burst_channels}->{$_}->{ChanKey} ) );
$burstline .= " " . $self->{burst_channels}->{$_}->{ChanLimit} if ( defined ( $self->{burst_channels}->{$_}->{ChanLimit} ) );
$burstline .= " :"; my (@users);
foreach my $i ( keys %{ $self->{burst_channels}->{$_}->{Members} } ) {
if ( $self->{burst_channels}->{$_}->{Members}->{$i} == -1 ) {
push ( @users, "%" . $self->{burst_nicks}->{$i}->{NickName} );
} else {
push ( @users, $modes[ $self->{burst_channels}->{$_}->{Members}->{$i} ] . $self->{burst_nicks}->{$i}->{NickName} );
}
}
$burstline .= join(" ", @users);
push (@burst, $burstline);
my ($bans) = join(" ", @{ $self->{burst_channels}->{$_}->{Bans} });
if ( defined ($bans) ) {
$burstline = "MODE " . $self->{burst_channels}->{$_}->{Channel} . " +";
for (my $i = 0; $i <= $#{ $self->{burst_channels}->{$_}->{Bans} }; $i++) {
$burstline .= "b";
}
$burstline .= " $bans";
push (@burst, $burstline);
}
}
return @burst;
}
sub _burst_destroy {
my ($self) = shift;
delete ( $self->{burst_nicks} );
delete ( $self->{burst_channels} );
}
# Public Methods
1;
__END__
# POD should be next :)
=head1 NAME
POE::Component::IRC::Service::Hybrid - a fully event-driven IRC services module for Hybrid networks.
=head1 SYNOPSIS
use POE::Component::IRC::Service::Hybrid;
# Do this when you create your sessions. 'IRC-Service' is just a
# kernel alias to christen the new IRC connection with. (Returns
# only a true or false success flag, not an object.)
POE::Component::IRC::Service::Hybrid->new('IRC-Service') or die "Oh noooo! $!";
# Do stuff like this from within your sessions. This line tells the
# connection named "IRC-Service" to send your session the following
# events when they happen.
$kernel->post('IRC-Service', 'register', qw(connected msg public nick server));
# You can guess what this line does.
$kernel->post('IRC-Service', 'connect',
{ ServerName => 'services.lamenet.org',
ServerDesc => 'Services for LameNET',
RemoteServer => 'hub.lamenet.org',
ServerPort => 7666,
Password => 'password', } );
# Add a services identity to the network
$kernel->post('IRC-Service' => 'addnick',
{ NickName => 'Lame',
Umode => '+o',
Description => 'Lame Services Bot', } );
=head1 DESCRIPTION
POE::Component::IRC::Service::Hybrid is a POE component which
acts as an easily controllable IRC Services client for your other POE
components and sessions. You create an IRC Services component and tell it what
events your session cares about and where to connect to, and it sends
back interesting IRC events when they happen. You make the client do
things by sending it events.
[Note that this module requires a lot of familiarity with the details of the
IRC protocol. I'd advise you to read up on the gory details of RFC 1459
E<lt>http://cs-pub.bu.edu/pub/irc/support/rfc1459.txtE<gt> before starting.
Some knowledge of the Hybrid's IRC Server-to-Server protocol would also be advisable, most importantly
with TSora. Check out the documents that come with the Hybrid IRCd package.
So you want to write a POE program with POE::Component::IRC::Service::Hybrid?
Listen up. The short version is as follows: Create your session(s) and an
alias for a new POE::Component::IRC::Service::Hybrid client. (Conceptually, it helps if
you think of them as little IRC servers.) In your session's _start
handler, send the IRC service a 'register' event to tell it which IRC
events you want to receive from it. Send it a 'connect' event at some
point to tell it to join the IRC network, and it should start sending you
interesting events every once in a while. Use the 'addnick' event to add
an IRC client to your "server". The IRC Service accepts two different sets of
events, server and client. Server events are commands that are issued by (heh)
the server and client events are commands issued by clients.
# Example of a client command:
$kernel->post( 'IRC-Service', 'join', 'Lame' , '#LameNET' );
# Example of a server command:
$kernel->post( 'IRC-Service', 'sl_server', "MODE #LameNET +o Lame" );
Basically, client commands require a source nickname for the command, eg.
it doesn't make sense for a server to "join" a channel.
The long version is the rest of this document.
=head1 METHODS
Well, OK, there's only actually one, so it's more like "METHOD".
=over
=item new
Takes two arguments: a name (kernel alias) which this new connection
will be known by, the second argument is a hashref of options see C<connect> for more
details. B<WARNING:> This method, for all that it's named
"new" and called in an OO fashion, doesn't actually return an
object. It returns a true or false value which indicates if the new
session was created or not. If it returns false, check $! for the
POE::Session error code.
=back
=head1 INPUT
How to talk to your new IRC Services component... here's the events we'll accept.
=head2 Important Commands
=over
=item connect
Takes one argument: a hash reference of attributes for the new
connection (see the L<SYNOPSIS> section of this doc for an
example). This event tells the IRC Services client to connect to a
new/different hub and join an IRC network. If it has a connection already open, it'll close
it gracefully before reconnecting. Possible attributes for the new
connection are "ServerName", the name your IRC Service will
be called; "ServerDesc", a brief description of your IRC Service; "RemoteServer", the DNS or
IP address of your uplink/hub server; "ServerPort", the port to connect to on your uplink/hub
server; "Password", the password required to link to uplink/hub server; "LocalAddr",
which local IP address on a multihomed box to connect as; "EOB", set to '0' to disable automatic
generation of an End of Burst.
=item addnick
Takes one argument: a hash reference of attributes for the new service client
(see the L<SYNOPSIS> section of this doc for an example). This event adds a new
client to the IRC Service server. Multiple clients are allowed. Expect to receive
an appropriate irc_hyb_nick event for the new client, from which you can derive the
clients numeric token. Possible attributes for the new client are "NickName", (duh)
the nickname this client will appear as on the IRC network (only required attribute);
"UserName", the user part of ident@host (default is nick);
"HostName", the host part of ident@host (default is the name of the server);
"Umode", the user modes this client will have (defaults to +odk);
"Description", equivalent to the IRCName (default server description);
=item register
Takes N arguments: a list of event names that your session wants to
listen for, minus the "irc_hyb_" prefix. So, for instance, if you just
want a bot that keeps track of which people are on a channel, you'll
need to listen for CREATEs, JOINs, PARTs, QUITs, and KICKs to people on the
channel you're in. You'd tell POE::Component::IRC::Service::Hybrid that you want those
events by saying this:
$kernel->post( 'IRC-Service', 'register', qw(join part quit kick) );
Then, whenever people enter or leave a channel (forcibly
or not), your session will receive events with names like "irc_hyb_join",
"irc_hyb__kick", etc., which you can use to update a list of people on the
channel.
Registering for C<'all'> will cause it to send all IRC-related events to
you; this is the easiest way to handle it.
=item unregister
Takes N arguments: a list of event names which you I<don't> want to
receive. If you've previously done a 'register' for a particular event
which you no longer care about, this event will tell the IRC
connection to stop sending them to you. (If you haven't, it just
ignores you. No big deal.)
=back
=head2 Server initiated commands
These are commands that come from the IRC Service itself and not from clients.
=over
=item gline
Sets or removes a GLINE to the IRC network. A GLINE prevents matching users from connecting to the
network. Implemented as if the IRC Service is a U: lined server, so ircd must be configured
accordingly. Takes four arguments, the target for the gline which can be * (for all servers) or
a server numeric; the mask to gline [!][-|+]<mask> the presence of the ! prefix means "force",
+ means add/activate, - means remove/deactivate; the duration of the gline, ie. the time to expire the
gline in seconds since epoch ( ie. output from time() ); the reason for the gline.
Mask may be a user@host mask, or a channel name. In the later case (mask starts with a # or &) it is a "BADCHAN".
A BADCHAN prevents users from joining a channel with the same name.
=item jupe
A jupe prevents servers from joining the network. Takes five arguments, the target for the jupe,
either * for all servers or a server numeric; what to jupe [!][-|+]<server>, ! is force, + activate jupe,
- deactivate jupe; the duration of the jupe, ie. the time to expire the jupe in seconds since epoch; the
last modification timestamp, ie. the output of time(); the reason for the jupe.
=item kill
Server kill :) Takes two arguments, the client numeric of the victim; the reason for the kill.
If the numeric specified matches one of the IRC Service's internal clients, that client will
be automatically removed.
=item squit
This will disconnect the IRC Service from its uplink/hub server. Expect to receive an
"irc_hyb_disconnected" event. Takes no arguments.
=item sl_server
Send a raw server command. Exercise extreme caution. Takes one argument, a string
representing the raw command that the server will send. The module prepends the
appropriate server numeric for you, so don't worry about that. Note, IRC commands must be
specified as tokenised equivalents as per P10 specification.
$kernel->post( 'IRC-Service' => sl_server => "MODE #LameNET +o Lame" );
=back
=head2 Client initiated commands
These are commands that come from clients on the IRC Service.
=over
=item ctcp and ctcpreply
Sends a CTCP query or response to the nick(s) or channel(s) which you
specify. Takes 3 arguments: the numeric of the client who is sending the command;
the nickname or channel to send a message to
(use an array reference here to specify multiple recipients), and the
plain text of the message to send (the CTCP quoting will be handled
for you).
=item invite
Invites another user onto an invite-only channel. Takes 3 arguments:
the numeric of the inviting client, the nick of the user you wish to admit,
and the name of the channel to invite them to.
=item join
Tells a specified client to join a single channel of your choice. Takes
at least two args: the numeric of the client that you want to join,
the channel name (required) and the channel key
(optional, for password-protected channels).
=item mode
Request a mode change on a particular channel or user. Takes at least
two arguments: the mode changing client's nickname,
the mode changes to effect, as a single string (e.g.,
"+sm-p+o"), and any number of optional operands to the mode changes
(nicknames, hostmasks, channel keys, whatever.) Or just pass them all as one
big string and it'll still work, whatever.
=item nick
Allows you to change a client's nickname. Takes two arguments: the
nickname of the client who wishes to change nickname and the
new username that you'd like to be known as.
=item notice
Sends a NOTICE message to the nick(s) or channel(s) which you
specify. Takes 3 arguments: the nickname of the issuing client,
the nick or channel to send a notice to
(use an array reference here to specify multiple recipients), and the
text of the notice to send.
=item part
Tell a client to leave the channels which you pass to it. Takes
any number of arguments: the nickname of the client followed by the
channel names to depart from.
=item privmsg
Sends a public or private message to the nick(s) or channel(s) which
you specify. Takes 3 arguments: the nickname of the issuing client,
the numeric or channel to send a message
to (use an array reference here to specify multiple recipients), and
the text of the message to send.
=item quit
Tells the IRC service to remove a client. Takes one argument:
the nickname of the client to disconnect; and one optional argument:
some clever, witty string that other users in your channels will see
as you leave. The IRC Service will automatically remove the client from
its internal list of clients.
=item sl_client
Send a raw client command. Exercise extreme caution. Takes one argument, a string
representing the raw command that the server will send. Unlike "sl_server" you must specify
the full raw command prefixed with the appropriate client nickname.
$kernel->post( 'IRC-Service' => sl_client => ":Lame MODE #LameNET +o Lame2" );
=item stats
Returns some information about a server. Kinda complicated and not
terribly commonly used, so look it up in the RFC if you're
curious. Takes as many arguments as you please, but the first argument
must be the nickname of a client.
=back
=head1 OUTPUT
The events you will receive (or can ask to receive) from your running
IRC component. Note that all incoming event names your session will
receive are prefixed by "irc_hyb_", to inhibit event namespace pollution
( and Dennis had already taken irc_ :p ).
If you wish, you can ask the client to send you every event it
generates. Simply register for the event name "all". This is a lot
easier than writing a huge list of things you specifically want to
listen for.
The IRC Service deals with some events on your behalf, they will be duly noted
below.
=head2 Important Events
=over
=item irc_hyb_connected
The IRC component will send an "irc_hyb_connected" event as soon as it
establishes a connection to an IRC server, before attempting to log
in. ARG0 is whatever you passed to "connect" as RemoteServer.
B<NOTE:> When you get an "irc_hyb_connected" event, this doesn't mean you
can start sending commands to the server yet. The uplink/hub server and the IRC
Service will be in the process of synchronising by way of a net burst. Wait for
an "irc_hyb_eob" from your uplink/hub server before sending any events.
=item irc_hyb_sjoin
This event is generated during a net burst when the IRC Service first joins an
IRC network. It is basically a description of a channel and its state, ie. nicks, modes, bans, etc.
See TSora specification for the gory details. This is also what servers use to propogate channel JOINS,
don't expect to see irc_hyb_join events :(
=item irc_hyb_server_link
This is the response from the uplink/hub server we connected to. You can use this event
to discern the server numeric of the server we are connected to. ARG0 is server's name, ARG1 is
the hop count, ARG2 is the server description.
=item irc_hyb_svinfo
This follows an irc_hyb_server_link and tells you what version of TSora the uplink can do and its timestamp. ARG0 is
highest version of TSora the uplink will do and ARG1 is the lowest version. ARG2 is the timestamp from the uplink.
=item irc_hyb_server
Seen during a net burst and when a new server joins the network. ARG0 is the server name.
ARG1 is a single string made up of the data for the server, which has the following format <name> <hop> <boot-ts> <link-ts> <protocol> <max-clients> :<description>. See Hint above in irc_hyb_server-link.
=item irc_hyb_end_of_burst
Sent by a server when it finishes a net burst. The module will automatically respond to an end of burst by its uplink/hub with an
end_of_burst_ack. The module will also automatically send its own end_of_burst message to the uplink/hub unless you set the appropriate
option during CONNECT ( See above ).
=item irc_hyb_squit
Received when a server disconnects. ARG0 is the server numeric of the sender. ARG1 is a string with the data of the event, with the
following format: <servername> <timestamp> :<description>
=item irc_hyb_stats
The module takes care of "u" requests automagically.
=item irc_hyb_ping
The module takes care of ponging to these automagically.
=item Miscellaneous events
Events such as join, part, etc. should be same as POE::Component::IRC. See that documentation for details.
=item All numeric events (see RFC 1459)
Most messages from IRC servers are identified only by three-digit
numeric codes with undescriptive constant names like RPL_UMODEIS and
ERR_NOTOPLEVEL. (Actually, the list of codes in the RFC is kind of
out-of-date... the list in the back of Net::IRC::Event.pm is more
complete, and different IRC networks have different and incompatible
lists. Ack!) As an example, say you wanted to handle event 376
(RPL_ENDOFMOTD, which signals the end of the MOTD message). You'd
register for '376', and listen for 'irc_hyb_376' events. Simple, no? ARG0
is the numeric of the server which sent the message. ARG1 is the text of
the message.
=back
=head1 AUTHOR
Chris Williams, E<lt>chris@bingosnet.co.uk<gt>
Based on a hell of lot of POE::Component::IRC written by
Dennis Taylor, E<lt>dennis@funkplanet.comE<gt>
=head1 LICENSE
Copyright (c) Dennis Taylor and Chris Williams.
This module may be used, modified, and distributed under the same
terms as Perl itself. Please see the license that came with your Perl
distribution for details.
=head1 MAD PROPS
Greatest of debts to Dennis Taylor, E<lt>dennis@funkplanet.comE<gt> for
letting me "salvage" POE::Component::IRC to write this module.
And to ^kosh and FozzySon and others from #jeditips for allowing me to
inflict my coding on them :)
=head1 SEE ALSO
RFC 1459, http://www.irchelp.org/, http://poe.perl.org/,
http://www.xs4all.nl/~beware3/irc/bewarep10.html
=cut