=39= $_[KERNEL]->yield("heartbeat"); # start heartbeat #### my %FOLLOWS = ( ACCESS => "/var/log/messages", ); my $IRC_ALIAS = $nick; my $SKIPPING = 0; # if skipping, how many we've done my $SEND_QUEUE; # cache my $CHANNEL = $channel; POE::Session->create( package_states => [ main => [ qw( _default _start irc_public irc_join my_add my_tailed my_heartbeat ) ], ], ); $poe_kernel->run(); sub _start { my $irc = POE::Component::IRC::State->spawn( nick => $nick, Username => $username, ircname => $desc, server => $server_address, ); $_[HEAP]{dns} = POE::Component::Client::DNS->spawn; $_[HEAP]{irc} = $irc; $irc->plugin_add( 'NickServID', POE::Component::IRC::Plugin::NickServID->new( Password => 'iamrobot' ), ); $irc->plugin_add( 'AutoJoin', POE::Component::IRC::Plugin::AutoJoin->new( Channels => $channel, ), ); $irc->yield( register => 'all' ); $irc->yield( 'connect' ); $irc->yield("my_heartbeat"); # start heartbeat $irc->yield("my_add", $_) for keys %FOLLOWS; # start following return; } sub my_add { my $trailing = $_[ARG0]; my $session = $_[SESSION]; POE::Session->create( inline_states => { _start => sub { $_[HEAP]->{wheel} = POE::Wheel::FollowTail->new( Filename => $FOLLOWS{$trailing}, InputEvent => 'got_line', ); }, got_line => sub { $_[KERNEL]->post( $session => my_tailed => time, $trailing, $_[ARG0] ); }, }, ); } sub my_tailed { my ($time, $file, $line) = @_[ARG0..ARG2]; ## $time will be undef on a probe, or a time value if a real line ## PoCo::IRC has throttling built in, but no external visibility ## so this is reaching "under the hood" $SEND_QUEUE ||= $_[KERNEL]->alias_resolve($IRC_ALIAS)->get_heap->{send_queue}; ## handle "no need to keep skipping" transition if ($SKIPPING and @$SEND_QUEUE < 1) { $_[KERNEL]->post($IRC_ALIAS => privmsg => $CHANNEL => "[discarded $SKIPPING messages]"); $SKIPPING = 0; } ## handle potential message display if ($time) { if ($SKIPPING or @$SEND_QUEUE > 3) { # 3 msgs per 10 seconds $SKIPPING++; } else { my @time = localtime $time; $_[KERNEL]->post( $IRC_ALIAS => privmsg => $CHANNEL => sprintf "%02d:%02d:%02d: %s: %s", ($time[2] + 11) % 12 + 1, $time[1], $time[0], $file, $line); } } ## handle re-probe/flush if skipping if ($SKIPPING) { $_[KERNEL]->delay($_[STATE] => 0.5); # $time will be undef } } sub my_heartbeat { $_[KERNEL]->yield(my_tailed => time, "heartbeat", "beep"); $_[KERNEL]->delay($_[STATE] => 10); } # Default event handler, for some reason, stops output after logged in sub _default { my ($event, $args) = @_[ARG0 .. $#_]; my @output = ( "$event: " ); for my $arg (@$args) { if (ref $arg eq 'ARRAY') { push @output, '[' . join ', ', @$arg . ']'; } else { push @output, "'$arg'"; } } print join ' ', @output, "\n"; return; } # Event when a public message is broadcast to a channel sub irc_public { my ($sender, $where, $what) = @_[SENDER, ARG1, ARG2]; my $nick = parse_nick $_[ARG0]; my $irc = $_[SENDER]->get_heap; } # Event when someone joins a channel sub irc_join { my $nick = parse_nick $_[ARG0]; my $channel = $_[ARG1]; my $irc = $_[SENDER]->get_heap; if ($nick eq $irc->nick_name) { $irc->yield(privmsg => $channel, "I am online and responding to commands"); } } #### #!/usr/bin/perl use 5.010; use strict; use warnings; use POE; use Config::Any; use File::HomeDir; use POE::Wheel::FollowTail; use Getopt::Long::Descriptive; use POE::Component::IRC::State; use POE::Component::IRC::Plugin::AutoJoin; use POE::Component::IRC::Plugin::NickServID; sub parse_nick { (split /!/, shift)[0] } my $home = File::HomeDir->my_home; # take options from command line my ($opt, $usage) = describe_options( "\%c \%o", [ "server-address|s=s" => 'IRC Server Address', ], [ "server-port|p=s" => 'IRC Server Port [default: 6667]', ], [ "protocol|P=s" => 'IRC Server Protocol [default: TCP]', ], [ "timeout|t=s" => 'IRC Server Connection Timeout (in seconds) [default: 30]', ], [ "channel|c=s" => 'IRC Channel', ], [ "config-name|n=s" => 'Config File Name', { default => 'testbot' }, ], [ "nick|N=s" => 'IRC Nick', ], [ "password|A=s" => 'IRC Password', ], [ "username|u=s" => 'IRC Username, defaults to Nick if unset', ], [ "hostname|h=s" => 'IRC Hostname', ], [ "desc|d=s" => 'IRC User Desc.', ], [], [ "verbose|v" => 'Print Status Messages' ], [ "help|h" => 'Print This message and Exit', ], ); ( print $usage->text ), exit if $opt->help; # get config name my $config = $opt->config_name; # look for config file my $cfg = Config::Any->load_stems( { stems => ["/etc/$config", "$home/etc/$config", "$home/etc/$config", "./etc/$config", "./$config", ], use_ext => 1, } ); # take only the last loaded config file my $last_config = shift @$cfg; my ($file_name, $loaded_config); if ($last_config) { ($file_name, $loaded_config) = %$last_config; } else { ($file_name, $loaded_config) = ('NONE', {} ); } say "[*] Loaded config file $file_name" if $opt->verbose; # Thinking about config file design, my $server_address = $opt->server_address || $loaded_config->{'server'}->{'address'} || 'myserver'; my $server_port = $opt->server_port || $loaded_config->{'server'}->{'port'} || '6667'; my $server_protocol = $opt->protocol || $loaded_config->{'server'}->{'protocol'} || 'tcp'; my $timeout = $opt->timeout || $loaded_config->{'server'}->{'timeout'} || '30'; my $channel = $opt->channel || $loaded_config->{'server'}->{'channel'} || [ '#foo',]; my $nick = $opt->nick || $loaded_config->{'user'}->{'nick'} || 'testbot2'; my $password = $opt->password || $loaded_config->{'user'}->{'password'} || ''; my $username = $opt->username || $loaded_config->{'user'}->{'username'} || $nick; my $hostname = $opt->hostname || $loaded_config->{'user'}->{'hostname'} || 'foo.test.irc'; my $desc = $opt->desc || $loaded_config->{'user'}->{'desc'}; my %FOLLOWS = ( ACCESS => "/var/log/messages", ); my $IRC_ALIAS = $nick; my $SKIPPING = 0; # if skipping, how many we've done my $SEND_QUEUE; # cache my $CHANNEL = $channel; POE::Session->create( package_states => [ main => [ qw( _default _start irc_public irc_join my_add my_tailed my_heartbeat ) ], ], ); $poe_kernel->run(); sub _start { my $irc = POE::Component::IRC::State->spawn( nick => $nick, Username => $username, ircname => $desc, server => $server_address, ); $_[HEAP]{dns} = POE::Component::Client::DNS->spawn; $_[HEAP]{irc} = $irc; $irc->plugin_add( 'NickServID', POE::Component::IRC::Plugin::NickServID->new( Password => 'iamrobot' ), ); $irc->plugin_add( 'AutoJoin', POE::Component::IRC::Plugin::AutoJoin->new( Channels => $channel, ), ); $irc->yield( register => 'all' ); $irc->yield( 'connect' ); $irc->yield("my_heartbeat"); # start heartbeat $irc->yield("my_add", $_) for keys %FOLLOWS; # start following return; } sub my_add { my $trailing = $_[ARG0]; my $session = $_[SESSION]; POE::Session->create( inline_states => { _start => sub { $_[HEAP]->{wheel} = POE::Wheel::FollowTail->new( Filename => $FOLLOWS{$trailing}, InputEvent => 'got_line', ); }, got_line => sub { $_[KERNEL]->post( $session => my_tailed => time, $trailing, $_[ARG0] ); }, }, ); } sub my_tailed { my ($time, $file, $line) = @_[ARG0..ARG2]; ## $time will be undef on a probe, or a time value if a real line ## PoCo::IRC has throttling built in, but no external visibility ## so this is reaching "under the hood" $SEND_QUEUE ||= $_[KERNEL]->alias_resolve($IRC_ALIAS)->get_heap->{send_queue}; ## handle "no need to keep skipping" transition if ($SKIPPING and @$SEND_QUEUE < 1) { $_[KERNEL]->post($IRC_ALIAS => privmsg => $CHANNEL => "[discarded $SKIPPING messages]"); $SKIPPING = 0; } ## handle potential message display if ($time) { if ($SKIPPING or @$SEND_QUEUE > 3) { # 3 msgs per 10 seconds $SKIPPING++; } else { my @time = localtime $time; $_[KERNEL]->post( $IRC_ALIAS => privmsg => $CHANNEL => sprintf "%02d:%02d:%02d: %s: %s", ($time[2] + 11) % 12 + 1, $time[1], $time[0], $file, $line); } } ## handle re-probe/flush if skipping if ($SKIPPING) { $_[KERNEL]->delay($_[STATE] => 0.5); # $time will be undef } } sub my_heartbeat { $_[KERNEL]->yield(my_tailed => time, "heartbeat", "beep"); $_[KERNEL]->delay($_[STATE] => 10); } # Default event handler, for some reason, stops output after logged in sub _default { my ($event, $args) = @_[ARG0 .. $#_]; my @output = ( "$event: " ); for my $arg (@$args) { if (ref $arg eq 'ARRAY') { push @output, '[' . join ', ', @$arg . ']'; } else { push @output, "'$arg'"; } } print join ' ', @output, "\n"; return; } # Event when a public message is broadcast to a channel sub irc_public { my ($sender, $where, $what) = @_[SENDER, ARG1, ARG2]; my $nick = parse_nick $_[ARG0]; my $irc = $_[SENDER]->get_heap; } # Event when someone joins a channel sub irc_join { my $nick = parse_nick $_[ARG0]; my $channel = $_[ARG1]; my $irc = $_[SENDER]->get_heap; if ($nick eq $irc->nick_name) { $irc->yield(privmsg => $channel, "I am online and responding to commands"); } }