package POE::Component::IRC::Tracking; =head1 NAME POE::Component::IRC::Tracking =head1 SYNOPSIS use POE qw( Component::IRC::Tracking ); POE::Component::IRC::Tracking->new("alias"); ...See perldoc POE::Component::IRC... my $botheap = POE::Kernel->alias_resolve("alias")->get_heap; # $botheap->{connected} is true if the bot's connected. my $connected = $botheap->{connected}; # $botheap->{nick} is the current nickname of the bot. my $nick = $botheap->{nick}; # $botheap->{channels} is a hashref whose keys are the channels # the bot is on. my @channels = $connected ? keys %{ $botheap->{channels} } : (); =head1 DEPENDENCY POE::Component::IRC::Tracking isa subclass of POE::Component::IRC. =head1 DESCRIPTION POE::Component::IRC::Tracking adds tracking support to POE::Component::IRC, i.e. the bot knows its current nick and the channels it joined. Use it exactly like POE::Component::IRC. =head1 AUTHOR Ingo Blechschmidt, L. =head1 LICENSE This package is free software; you can redistribute it and/or modify it under the same terms as Perl itself, i.e., under the terms of the GNU General Public License of the Artistic License. =cut use warnings; use strict; use POE; use base "POE::Component::IRC"; # We've to keep track of the traffic we got. That's necessary for autoping and # livecheck. Additionally, we update our nick. sub _parseline { my $line = $_[ARG0]; # Hackery. # # :thestars.gnus 001 nick :Welcome to the Internet Relay Network nick!~a@thestars.gnus # :thestars.gnus 376 nick :End of MOTD command. # # Why do we do this? "You *are* already recording the nick! [see C # and C subs]" But: If the nick we NICKed to is too long for the # server, it's silently cut off. We can't know that. So, we update our # information from the server, which is guaranteed to be correct. if($line =~ /^:[^ ]+ (?:001|376) ([^ ]+) :/) { $_[HEAP]->{nick} = $1; } # We received some data, record that. $_[HEAP]->{last_traffic} = time; $_[0]->SUPER::_parseline(@_[1..$#_]); } # We ping the server if we haven't seen any traffic for 60s. use constant AUTOPING_INTERVAL => 60 * 1; # A connection is considered dead if we haven't seen any traffic von 5min. use constant CONNDEAD_TIMEOUT => 60 * 5; sub _start { $_[0]->SUPER::_start(@_[1..$#_]); # We have to register some eventhandler. POE::Kernel->state(autoping => \&bot_autoping); POE::Kernel->state(livecheck => \&bot_livecheck); POE::Kernel->state(irc_001 => \&irc_001); POE::Kernel->state(irc_433 => \&irc_433); POE::Kernel->state(irc_join => \&irc_join); POE::Kernel->state(irc_part => \&irc_part); POE::Kernel->state(irc_kick => \&irc_kick); POE::Kernel->state(irc_nick => \&irc_nick); # And, we have to register our interest. POE::Kernel->yield(register => qw( 001 433 join part kick nick )); # We check every 10s if we've seen traffic for AUTOPING_INTERVAL seconds. POE::Kernel->delay_set(autoping => 10); # A connection is considered dead after CONNDEAD_TIMEOUT seconds. POE::Kernel->delay_set(livecheck => 10); } sub connect { $_[0]->SUPER::connect(@_[1..$#_]); # Our nick. my $nick = $_[HEAP]->{nick}; # $_[HEAP]->nickgen is a coderef which returns our nick pre- or postfixed # with C<_>s at each invocation (nick, nick_, _nick, etc.). $_[HEAP]->{nickgen} = $_[0]->permute_nick($nick); # $_[HEAP]->{channels} is a hashref which contains channel names as keys. If # $_[HEAP]->{channels}->{"#parrot"} is true, the bot is in #parrot. $_[HEAP]->{channels} = {}; # livecheck would kill our connection if we don't "lie" about the # last_traffic seen. $_[HEAP]->{last_traffic} = time; } # If we haven't seen traffic for AUTOPING_INTERVAL seconds, we ping the server. sub bot_autoping { # There should be only one "instance" of autoping. POE::Kernel->alarm_remove( $_[HEAP]->{autoping_id} ) if $_[HEAP]->{autoping_id}; if( # We have to know the server's name (not the hostname). defined $_[HEAP]->{irc_servername} and # We can only PING if we're connected $_[HEAP]->{connected} and # PING only, if we haven't seen traffic for AUTOPING_INTERVAL seconds. time - $_[HEAP]->{last_traffic} >= AUTOPING_INTERVAL ) { POE::Kernel->yield(sl_login => "PING :" . $_[HEAP]->{irc_servername}); } # Check again in 10s. $_[HEAP]->{autoping_id} = POE::Kernel->delay_set(autoping => 10); } # Properly mark the connection as disconnected if it's stalled. sub bot_livecheck { # There should be only one "instance" of livecheck. POE::Kernel->alarm_remove( $_[HEAP]->{livecheck_id} ) if $_[HEAP]->{livecheck_id}; # If we haven't seen traffic for CONNDEAD_TIMEOUT seconds... if( $_[HEAP]->{connected} and time - $_[HEAP]->{last_traffic} >= CONNDEAD_TIMEOUT ) { POE::Kernel->yield(sl_login => "QUIT"); $_[HEAP]->{connected} = 0; } # Check again in 10s. $_[HEAP]->{livecheck_id} = POE::Kernel->delay_set(livecheck => 10); } sub irc_001 { # ARG0 contains the name of the server. We have to store that in order to be # able to autoping. $_[HEAP]->{irc_servername} = $_[ARG0]; } # 433: Nick taken. sub irc_433 { # So, we take the next permuted nick. my $new = $_[HEAP]->{nickgen}->(); POE::Kernel->yield(nick => $new); $_[HEAP]->{nick} = $new; } # irc_join is triggered whenever somebody joins a channel. sub irc_join { my ($mask, $channel) = @_[ARG0, ARG1]; # If the thing which joined is something without a nick (e.g. a # pseudo-server, etc.), skip. $mask =~ /^([^!]+)/ or return; # $1 is the nickname of the person who joined. if($1 eq $_[HEAP]->{nick}) { # We joined $channel. $_[HEAP]->{channels}->{$channel}++; } } # irc_part is triggered whenever somebody parts a channel. sub irc_part { my ($mask, $channel) = @_[ARG0, ARG1]; $channel =~ /^([^ ]+) ?:?(.*)/; ($channel, my $why) = ($1, defined $2 ? $2 : ""); # Because of a bug in PoCo::IRC (author notified), the PART message gets # concatenated with the channel name. We have to strip that out. # Same thing like above, if the "person" who joined is special, skip. $mask =~ /^([^!]+)/ or return; if($1 eq $_[HEAP]->{nick}) { # We left $channel. delete $_[HEAP]->{channels}->{$channel}; } } # irc_kick is triggered whenever somebody is kicked off a channel. sub irc_kick { my ($kicker, $channel, $kicked, $why) = @_[ARG0 .. $#_]; # Again, skip special things. $kicked =~ /^([^!]+)/ or return; if($1 eq $_[HEAP]->{nick}) { # We were kicked from $channel. delete $_[HEAP]->{channels}->{$channel}; } } # irc_nick is triggered whenever somebody changes his nick. sub irc_nick { my ($mask, $new) = @_[ARG0, ARG1]; # ...Skip special things... $mask =~ /^([^!]+)/ or return; if($1 eq $_[HEAP]->{nick}) { # We changed our nick. $_[HEAP]->{nick} = $new; } } # Input: A nick # Output: A coderef which returns the nick permuted upon invocation. sub permute_nick { my $n = $_[1]; my @nicks = split /\s+/, <create(inline_states => { _start => sub { POE::Component::IRC::Tracking->new("bot"); POE::Kernel->post("bot", register => qw( 376 join )); POE::Kernel->post("bot", connect => { Server => "thestars", Nick => "testlongnicktestlongnick", }); }, # End of /MOTD, e.g.: We're inside. irc_376 => sub { warn "Inside.\n"; local $_; POE::Kernel->post("bot", join => $_) for "#test1", "#test2", "#test3"; POE::Kernel->delay_set(info => 3); }, info => sub { # We should add nice Cable events to query this # information. my $heap = POE::Kernel->alias_resolve("bot")->get_heap; if($heap->{connected}) { warn "We are on: " . join(", ", keys %{ $heap->{channels} }) . "\n"; warn "Our nick is: " . $heap->{nick} . "\n"; } else { warn "Not connected.\n"; } POE::Kernel->delay_set(info => 3); }, }); POE::Kernel->run; } 1;