File Coverage

blib/lib/Bot/BasicBot.pm
Criterion Covered Total %
statement 42 427 9.8
branch 0 134 0.0
condition 0 42 0.0
subroutine 14 91 15.3
pod 45 72 62.5
total 101 766 13.1


line stmt bran cond sub pod time code
1             package Bot::BasicBot;
2             our $AUTHORITY = 'cpan:BIGPRESH';
3             $Bot::BasicBot::VERSION = '0.91';
4 2     2   31257 use strict;
  2         3  
  2         44  
5 2     2   6 use warnings;
  2         2  
  2         42  
6              
7 2     2   6 use Carp;
  2         4  
  2         132  
8 2     2   953 use Encode qw(encode);
  2         15660  
  2         130  
9 2     2   10 use Exporter;
  2         2  
  2         56  
10 2     2   1019 use IRC::Utils qw(decode_irc);
  2         14469  
  2         138  
11 2     2   1647 use POE::Kernel;
  2         95280  
  2         14  
12 2     2   50396 use POE::Session;
  2         5740  
  2         12  
13 2     2   1187 use POE::Wheel::Run;
  2         39475  
  2         56  
14 2     2   24 use POE::Filter::Line;
  2         3  
  2         33  
15 2     2   1167 use POE::Component::IRC::State;
  2         188753  
  2         107  
16 2     2   1048 use POE::Component::IRC::Plugin::Connector;
  2         2873  
  2         40  
17 2     2   827 use Text::Wrap ();
  2         3930  
  2         41  
18              
19 2     2   22 use base 'Exporter';
  2         3  
  2         7336  
20             our @EXPORT = qw(say emote);
21              
22             sub new {
23 0     0 1   my $class = shift;
24 0           my $self = bless {}, $class;
25              
26 0           $self->{IRCNAME} = 'wanna'.int(rand(100000));
27 0           $self->{ALIASNAME} = 'pony'.int(rand(100000));
28              
29             # call the set methods
30 0           my %args = @_;
31 0           for my $method (keys %args) {
32 0 0         if ($self->can($method)) {
33 0           $self->$method($args{$method});
34             }
35             else {
36 0           $self->{$method} = $args{$method};
37             #croak "Invalid argument '$method'";
38             }
39             }
40 0 0         $self->{charset} = 'utf8' if !defined $self->{charset};
41              
42 0 0         $self->init or die "init did not return a true value - dying";
43              
44 0           return $self;
45             }
46              
47             sub run {
48 0     0 1   my $self = shift;
49              
50             # create the callbacks to the object states
51 0           POE::Session->create(
52             object_states => [
53             $self => {
54             _start => "start_state",
55             die => "die_state",
56              
57             irc_001 => "irc_001_state",
58             irc_msg => "irc_said_state",
59             irc_public => "irc_said_state",
60             irc_ctcp_action => "irc_emoted_state",
61             irc_notice => "irc_noticed_state",
62              
63             irc_disconnected => "irc_disconnected_state",
64             irc_error => "irc_error_state",
65              
66             irc_join => "irc_chanjoin_state",
67             irc_part => "irc_chanpart_state",
68             irc_kick => "irc_kicked_state",
69             irc_nick => "irc_nick_state",
70             irc_quit => "irc_quit_state",
71              
72             fork_close => "fork_close_state",
73             fork_error => "fork_error_state",
74              
75             irc_366 => "names_done_state",
76              
77             irc_332 => "topic_raw_state",
78             irc_topic => "topic_state",
79              
80             irc_shutdown => "shutdown_state",
81              
82             irc_raw => "irc_raw_state",
83             irc_raw_out => "irc_raw_out_state",
84              
85             tick => "tick_state",
86             }
87             ]
88             );
89              
90             # and say that we want to recive said messages
91 0           $poe_kernel->post($self->{IRCNAME}, 'register', 'all');
92              
93             # run
94 0 0         $poe_kernel->run() if !$self->{no_run};
95 0           return;
96             }
97              
98 0     0 1   sub init { return 1; }
99              
100 0     0 1   sub said { return }
101              
102             sub emoted {
103 0     0 1   return shift->said(@_);
104             }
105              
106             sub noticed {
107 0     0 1   return shift->said(@_);
108             }
109              
110 0     0 1   sub chanjoin { return }
111              
112 0     0 1   sub chanpart { return }
113              
114 0     0 1   sub got_names { return }
115              
116 0     0 1   sub topic { return }
117              
118 0     0 1   sub nick_change { return }
119              
120 0     0 1   sub kicked { return }
121              
122 0     0 1   sub tick { return 0; }
123              
124 0     0 1   sub help { return "Sorry, this bot has no interactive help." }
125              
126 0     0 1   sub connected { return }
127              
128 0     0 0   sub raw_in { return }
129 0     0 0   sub raw_out { return }
130              
131             sub userquit {
132 0     0 1   my ($self, $mess) = @_;
133 0           return;
134             }
135              
136             sub schedule_tick {
137 0     0 1   my $self = shift;
138 0   0       my $time = shift || 5;
139 0           $poe_kernel->delay('tick', $time);
140 0           return;
141             }
142              
143             sub forkit {
144 0     0 1   my $self = shift;
145 0           my $args;
146              
147 0 0         if (ref($_[0])) {
148 0           $args = shift;
149             }
150             else {
151 0           my %args = @_;
152 0           $args = \%args;
153             }
154              
155 0 0         return if !$args->{run};
156              
157 0   0       $args->{handler} = $args->{handler} || "_fork_said";
158 0   0       $args->{arguments} = $args->{arguments} || [];
159              
160             #install a new handler in the POE kernel pointing to
161             # $self->{$args{handler}}
162 0   0       $poe_kernel->state( $args->{handler}, $args->{callback} || $self );
163              
164 0           my $run;
165 0 0         if (ref($args->{run}) =~ /^CODE/) {
166             $run = sub {
167 0     0     $args->{run}->($args->{body}, @{ $args->{arguments} })
  0            
168 0           };
169             }
170             else {
171 0           $run = $args->{run};
172             }
173              
174 0           my $wheel = POE::Wheel::Run->new(
175             Program => $run,
176             StdoutFilter => POE::Filter::Line->new(),
177             StderrFilter => POE::Filter::Line->new(),
178             StdoutEvent => "$args->{handler}",
179             StderrEvent => "fork_error",
180             CloseEvent => "fork_close"
181             );
182              
183             # Use a signal handler to reap dead processes
184 0           $poe_kernel->sig_child($wheel->PID, "got_sigchld");
185              
186             # store the wheel object in our bot, so we can retrieve/delete easily
187              
188             $self->{forks}{ $wheel->ID } = {
189             wheel => $wheel,
190             args => {
191             channel => $args->{channel},
192             who => $args->{who},
193             address => $args->{address}
194             }
195 0           };
196 0           return;
197             }
198              
199             sub _fork_said {
200 0     0     my ($self, $body, $wheel_id) = @_[OBJECT, ARG0, ARG1];
201 0           chomp $body; # remove newline necessary to move data;
202              
203             # pick up the default arguments we squirreled away earlier
204 0           my $args = $self->{forks}{$wheel_id}{args};
205 0           $args->{body} = $body;
206              
207 0           $self->say($args);
208 0           return;
209             }
210              
211             sub say {
212             # If we're called without an object ref, then we're handling saying
213             # stuff from inside a forked subroutine, so we'll freeze it, and toss
214             # it out on STDOUT so that POE::Wheel::Run's handler can pick it up.
215 0 0   0 1   if (!ref $_[0]) {
216 0           print $_[0], "\n";
217 0           return 1;
218             }
219              
220             # Otherwise, this is a standard object method
221              
222 0           my $self = shift;
223 0           my $args;
224 0 0         if (ref $_[0]) {
225 0           $args = shift;
226             }
227             else {
228 0           my %args = @_;
229 0           $args = \%args;
230             }
231              
232 0           my $body = $args->{body};
233              
234             # add the "Foo: bar" at the start
235 0 0 0       if ($args->{channel} ne "msg" && defined $args->{address}) {
236 0           $body = "$args->{who}: $body";
237             }
238              
239             # work out who we're going to send the message to
240 0 0         my $who = $args->{channel} eq "msg" ? $args->{who} : $args->{channel};
241              
242 0 0 0       if (!defined $who || !defined $body) {
243 0           $self->log("Can't send a message without target and body\n"
244             . " called from "
245             . ( [caller]->[0] )
246             . " line "
247             . ( [caller]->[2] ) . "\n"
248             . " who = '$who'\n body = '$body'\n");
249 0           return;
250             }
251              
252             # if we have a long body, split it up..
253 0           local $Text::Wrap::columns = 300;
254 0           local $Text::Wrap::unexpand = 0; # no tabs
255 0           my $wrapped = Text::Wrap::wrap('', '..', $body); # =~ m!(.{1,300})!g;
256             # I think the Text::Wrap docs lie - it doesn't do anything special
257             # in list context
258 0           my @bodies = split /\n+/, $wrapped;
259              
260             # Allows to override the default "PRIVMSG". Used by notice()
261             my $irc_command = defined $args->{irc_command}
262 0 0 0       && $args->{irc_command} eq 'notice'
263             ? 'notice'
264             : 'privmsg';
265              
266             # post an event that will send the message
267 0           for my $body (@bodies) {
268 0           my ($enc_who, $enc_body) = $self->charset_encode($who, $body);
269             #warn "$enc_who => $enc_body\n";
270             $poe_kernel->post(
271             $self->{IRCNAME},
272 0           $irc_command,
273             $enc_who,
274             $enc_body,
275             );
276             }
277              
278 0           return;
279             }
280              
281             sub emote {
282             # If we're called without an object ref, then we're handling emoting
283             # stuff from inside a forked subroutine, so we'll freeze it, and
284             # toss it out on STDOUT so that POE::Wheel::Run's handler can pick
285             # it up.
286 0 0   0 1   if (!ref $_[0]) {
287 0           print $_[0], "\n";
288 0           return 1;
289             }
290              
291             # Otherwise, this is a standard object method
292              
293 0           my $self = shift;
294 0           my $args;
295 0 0         if (ref $_[0]) {
296 0           $args = shift;
297             }
298             else {
299 0           my %args = @_;
300 0           $args = \%args;
301             }
302              
303 0           my $body = $args->{body};
304              
305             # Work out who we're going to send the message to
306             my $who = $args->{channel} eq "msg"
307             ? $args->{who}
308 0 0         : $args->{channel};
309              
310             # post an event that will send the message
311             # if there's a better way of sending actions i'd love to know - jw
312             # me too; i'll look at it in v0.5 - sb
313              
314             $poe_kernel->post(
315             $self->{IRCNAME},
316 0           'ctcp',
317             $self->charset_encode($who, "ACTION $body"),
318             );
319 0           return;
320             }
321              
322             sub notice {
323 0 0   0 1   if (!ref $_[0]) {
324 0           print $_[0], "\n";
325 0           return 1;
326             }
327              
328 0           my $self = shift;
329 0           my $args;
330 0 0         if (ref $_[0]) {
331 0           $args = shift;
332             }
333             else {
334 0           my %args = @_;
335 0           $args = \%args;
336             }
337              
338             # Don't modify '$args' hashref in-place, or we might
339             # make all subsequent calls into notices
340             return $self->say(
341 0           %{ $args },
  0            
342             irc_command => 'notice'
343             );
344              
345             }
346              
347             sub pocoirc {
348 0     0 1   my $self = shift;
349 0           return $self->{IRCOBJ};
350             }
351              
352             sub reply {
353 0     0 1   my $self = shift;
354 0           my ($mess, $body) = @_;
355 0           my %hash = %$mess;
356 0           $hash{body} = $body;
357 0           return $self->say(%hash);
358             }
359              
360             sub channel_data {
361 0     0 1   my $self = shift;
362 0 0         my $channel = shift or return;
363 0           my $irc = $self->{IRCOBJ};
364 0           my $channels = $irc->channels();
365 0 0         return if !exists $channels->{$channel};
366              
367             return {
368             map {
369 0   0       $_ => {
  0   0        
370             op => $irc->is_channel_operator($channel, $_) || 0,
371             voice => $irc->has_channel_voice($channel, $_) || 0,
372             }
373             } $irc->channel_list($channel)
374             };
375             }
376              
377             sub server {
378 0     0 1   my $self = shift;
379 0 0         $self->{server} = shift if @_;
380 0   0       return $self->{server} || "irc.perl.org";
381             }
382              
383             sub port {
384 0     0 1   my $self = shift;
385 0 0         $self->{port} = shift if @_;
386 0   0       return $self->{port} || "6667";
387             }
388              
389             sub password {
390 0     0 1   my $self = shift;
391 0 0         $self->{password} = shift if @_;
392 0   0       return $self->{password} || undef;
393             }
394              
395             sub ssl {
396 0     0 1   my $self = shift;
397 0 0         $self->{ssl} = shift if @_;
398 0   0       return $self->{ssl} || 0;
399             }
400              
401             sub localaddr {
402 0     0 1   my $self = shift;
403 0 0         $self->{localaddr} = shift if @_;
404 0   0       return $self->{localaddr} || 0;
405             }
406              
407             sub useipv6 {
408 0     0 1   my $self = shift;
409 0 0         $self->{useipv6} = shift if @_;
410 0   0       return $self->{useipv6} || 0;
411             }
412              
413             sub nick {
414 0     0 1   my $self = shift;
415 0 0         $self->{nick} = shift if @_;
416 0 0         return $self->{nick} if defined $self->{nick};
417 0           return _random_nick();
418             }
419              
420             sub _random_nick {
421 0     0     my @things = ( 'a' .. 'z' );
422 0           return join '', ( map { @things[ rand @things ] } 0 .. 4 ), "bot";
  0            
423             }
424              
425             sub alt_nicks {
426 0     0 1   my $self = shift;
427 0 0         if (@_) {
428             # make sure we copy
429 0 0         my @args = ( ref $_[0] eq "ARRAY" ) ? @{ $_[0] } : @_;
  0            
430 0           $self->{alt_nicks} = \@args;
431             }
432 0 0         return @{ $self->{alt_nicks} || [] };
  0            
433             }
434              
435             sub username {
436 0     0 1   my $self = shift;
437 0 0         $self->{username} = shift if @_;
438 0 0         return defined $self->{username} ? $self->{username} : $self->nick;
439             }
440              
441             sub name {
442 0     0 1   my $self = shift;
443 0 0         $self->{name} = shift if @_;
444 0 0         return defined $self->{name} ? $self->{name} : $self->nick . " bot";
445             }
446              
447             sub channels {
448 0     0 1   my $self = shift;
449 0 0         if (@_) {
450             # make sure we copy
451 0 0         my @args = ( ref $_[0] eq "ARRAY" ) ? @{ $_[0] } : @_;
  0            
452 0           $self->{channels} = \@args;
453             }
454 0 0         return @{ $self->{channels} || [] };
  0            
455             }
456              
457             sub quit_message {
458 0     0 1   my $self = shift;
459 0 0         $self->{quit_message} = shift if @_;
460 0 0         return defined $self->{quit_message} ? $self->{quit_message} : "Bye";
461             }
462              
463             sub ignore_list {
464 0     0 1   my $self = shift;
465 0 0         if (@_) {
466             # make sure we copy
467 0 0         my @args = ( ref $_[0] eq "ARRAY" ) ? @{ $_[0] } : @_;
  0            
468 0           $self->{ignore_list} = \@args;
469             }
470 0 0         return @{ $self->{ignore_list} || [] };
  0            
471             }
472              
473             sub charset {
474 0     0 1   my $self = shift;
475 0 0         if (@_) {
476 0           $self->{charset} = shift;
477             }
478 0           return $self->{charset};
479             }
480              
481             sub flood {
482 0     0 1   my $self = shift;
483 0 0         $self->{flood} = shift if @_;
484 0           return $self->{flood};
485             }
486              
487             sub no_run {
488 0     0 1   my $self = shift;
489 0 0         $self->{no_run} = shift if @_;
490 0           return $self->{no_run};
491             }
492              
493             sub start_state {
494 0     0 0   my ($self, $kernel, $session) = @_[OBJECT, KERNEL, SESSION];
495 0           $kernel->sig('DIE', 'die');
496 0           $self->{session} = $session;
497              
498             # Make an alias for our session, to keep it from getting GC'ed.
499 0           $kernel->alias_set($self->{ALIASNAME});
500 0           $kernel->delay('tick', 30);
501              
502             $self->{IRCOBJ} = POE::Component::IRC::State->spawn(
503             alias => $self->{IRCNAME},
504 0           );
505             $self->{IRCOBJ}->plugin_add(
506 0           'Connector',
507             POE::Component::IRC::Plugin::Connector->new(),
508             );
509 0           $kernel->post($self->{IRCNAME}, 'register', 'all');
510              
511             $kernel->post(
512             $self->{IRCNAME},
513 0           'connect',
514             {
515             Nick => $self->nick,
516             Server => $self->server,
517             Port => $self->port,
518             Password => $self->password,
519             UseSSL => $self->ssl,
520             Flood => $self->flood,
521             LocalAddr => $self->localaddr,
522             useipv6 => $self->useipv6,
523             $self->charset_encode(
524             Nick => $self->nick,
525             Username => $self->username,
526             Ircname => $self->name,
527             ),
528             },
529             );
530              
531 0           return;
532             }
533              
534             sub die_state {
535 0     0 0   my ($kernel, $self, $ex) = @_[KERNEL, OBJECT, ARG1];
536 0           warn $ex->{error_str};
537 0           $self->{IRCOBJ}->yield('shutdown');
538 0           $kernel->sig_handled();
539 0           return;
540             }
541              
542             sub irc_001_state {
543 0     0 0   my ($self, $kernel) = @_[OBJECT, KERNEL];
544              
545             # ignore all messages from ourselves
546             $kernel->post(
547             $self->{IRCNAME},
548 0           'ignore',
549             $self->charset_encode($self->nick),
550             );
551              
552             # connect to the channel
553 0           for my $channel ($self->channels) {
554 0           $self->log("Trying to connect to '$channel'\n");
555             $kernel->post(
556             $self->{IRCNAME},
557 0           'join',
558             $self->charset_encode($channel),
559             );
560             }
561              
562 0           $self->schedule_tick(5);
563 0           $self->connected();
564 0           return;
565             }
566              
567             sub irc_disconnected_state {
568 0     0 0   my ($self, $kernel, $server) = @_[OBJECT, KERNEL, ARG0];
569 0           $self->log("Lost connection to server $server.\n");
570 0           return;
571             }
572              
573             sub irc_error_state {
574 0     0 0   my ($self, $err, $kernel) = @_[OBJECT, ARG0, KERNEL];
575 0           $self->log("Server error occurred! $err\n");
576 0           return;
577             }
578              
579             sub irc_kicked_state {
580 0     0 0   my ($self, $kernel, $heap, $session) = @_[OBJECT, KERNEL, HEAP, SESSION];
581 0           my ($nickstring, $channel, $kicked, $reason) = @_[ARG0..$#_];
582 0           my $nick = $self->nick_strip($nickstring);
583 0           $_[OBJECT]->_remove_from_channel( $channel, $kicked );
584 0           $self->kicked(
585             {
586             channel => $channel,
587             who => $nick,
588             kicked => $kicked,
589             reason => $reason,
590             }
591             );
592 0           return;
593             }
594              
595             sub irc_join_state {
596 0     0 0   my ($self, $nick) = @_[OBJECT, ARG0];
597 0           return;
598             }
599              
600             sub irc_nick_state {
601 0     0 0   my ($self, $nick, $newnick) = @_[OBJECT, ARG0, ARG1];
602 0           $nick = $self->nick_strip($nick);
603 0           $self->nick_change($nick, $newnick);
604 0           return;
605             }
606              
607             sub irc_quit_state {
608 0     0 0   my ($self, $kernel, $session) = @_[OBJECT, KERNEL, SESSION];
609 0           my ($nick, $message) = @_[ARG0..$#_];
610              
611 0           $nick = $self->nick_strip($nick);
612 0           $self->userquit({ who => $nick, body => $message });
613 0           return;
614             }
615              
616             sub irc_said_state {
617 0     0 0   irc_received_state( 'said', 'say', @_ );
618 0           return;
619             }
620              
621             sub irc_emoted_state {
622 0     0 0   irc_received_state( 'emoted', 'emote', @_ );
623 0           return;
624             }
625              
626             sub irc_noticed_state {
627 0     0 0   irc_received_state( 'noticed', 'emote', @_ );
628 0           return;
629             }
630              
631             sub irc_received_state {
632 0     0 0   my $received = shift;
633 0           my $respond = shift;
634 0           my ($self, $nick, $to, $body) = @_[OBJECT, ARG0, ARG1, ARG2];
635              
636 0           ($nick, $to, $body) = $self->charset_decode($nick, $to, $body);
637              
638 0           my $return;
639 0           my $mess = {};
640              
641             # pass the raw body through
642 0           $mess->{raw_body} = $body;
643              
644             # work out who it was from
645 0           $mess->{who} = $self->nick_strip($nick);
646 0           $mess->{raw_nick} = $nick;
647              
648             # right, get the list of places this message was
649             # sent to and work out the first one that we're
650             # either a memeber of is is our nick.
651             # The IRC protocol allows messages to be sent to multiple
652             # targets, which is pretty clever. However, noone actually
653             # /does/ this, so we can get away with this:
654              
655 0           my $channel = $to->[0];
656 0 0         if (lc($channel) eq lc($self->nick)) {
657 0           $mess->{channel} = "msg";
658 0           $mess->{address} = "msg";
659             }
660             else {
661 0           $mess->{channel} = $channel;
662             }
663              
664             # okay, work out if we're addressed or not
665              
666 0           $mess->{body} = $body;
667 0 0         if ($mess->{channel} ne "msg") {
668 0           my $own_nick = $self->nick;
669              
670 0 0         if ($mess->{body} =~ s/^(\Q$own_nick\E)\s*[:,-]?\s*//i) {
671 0           $mess->{address} = $1;
672             }
673              
674 0           for my $alt_nick ($self->alt_nicks) {
675 0 0         last if $mess->{address};
676 0 0         if ($mess->{body} =~ s/^(\Q$alt_nick\E)\s*[:,-]?\s*//i) {
677 0           $mess->{address} = $1;
678             }
679             }
680             }
681              
682             # strip off whitespace before and after the message
683 0           $mess->{body} =~ s/^\s+//;
684 0           $mess->{body} =~ s/\s+$//;
685              
686             # check if someone was asking for help
687 0 0 0       if ($mess->{address} && $mess->{body} =~ /^help/i) {
688 0 0         $mess->{body} = $self->help($mess) or return;
689 0           $self->say($mess);
690 0           return;
691             }
692              
693             # okay, call the said/emoted method
694 0           $return = $self->$received($mess);
695              
696             ### what did we get back?
697              
698             # nothing? Say nothing then
699 0 0         return if !defined $return;
700              
701             # a string? Say it how we were addressed then
702 0 0 0       if (!ref $return && length $return) {
703 0           $mess->{body} = $return;
704 0           $self->$respond($mess);
705 0           return;
706             }
707             }
708              
709             sub irc_chanjoin_state {
710 0     0 0   my $self = $_[OBJECT];
711 0           my ($channel, $nick) = @_[ ARG1, ARG0 ];
712 0           $nick = $_[OBJECT]->nick_strip($nick);
713              
714 0 0         if ($self->nick eq $nick) {
715 0           my @channels = $self->channels;
716 0 0         push @channels, $channel unless grep { $_ eq $channel } @channels;
  0            
717 0           $self->channels(\@channels);
718             }
719 0           irc_chan_received_state('chanjoin', 'say', @_);
720 0           return;
721             }
722              
723             sub irc_chanpart_state {
724 0     0 0   my $self = $_[OBJECT];
725 0           my ($channel, $nick) = @_[ ARG1, ARG0 ];
726 0           $nick = $_[OBJECT]->nick_strip($nick);
727              
728 0 0         if ($self->nick eq $nick) {
729 0           my @channels = $self->channels;
730 0           @channels = grep { $_ ne $channel } @channels;
  0            
731 0           $self->channels(\@channels);
732             }
733 0           irc_chan_received_state('chanpart', 'say', @_);
734 0           return;
735             }
736              
737             sub irc_chan_received_state {
738 0     0 0   my $received = shift;
739 0           my $respond = shift;
740 0           my ($self, $nick, $channel) = @_[OBJECT, ARG0, ARG1];
741              
742 0           my $return;
743 0           my $mess = {};
744 0           $mess->{who} = $self->nick_strip($nick);
745 0           $mess->{raw_nick} = $nick;
746              
747 0           $mess->{channel} = $channel;
748 0           $mess->{body} = $received; #chanjoin or chanpart
749 0           $mess->{address} = "chan";
750              
751             # okay, call the chanjoin/chanpart method
752 0           $return = $self->$received($mess);
753              
754             ### what did we get back?
755              
756             # nothing? Say nothing then
757 0 0         return if !defined $return;
758              
759             # a string? Say it how we were addressed then
760 0 0         if (!ref $return) {
761 0           $mess->{body} = $return;
762 0           $self->$respond($mess);
763 0           return;
764             }
765             }
766              
767              
768             sub fork_close_state {
769 0     0 0   my ($self, $wheel_id) = @_[OBJECT, ARG0];
770 0           delete $self->{forks}{$wheel_id};
771 0           return;
772             }
773              
774       0 0   sub fork_error_state { }
775              
776             sub tick_state {
777 0     0 0   my ($self, $kernel, $heap) = @_[OBJECT, KERNEL, HEAP];
778 0           my $delay = $self->tick();
779 0 0         $self->schedule_tick($delay) if $delay;
780 0           return;
781             }
782              
783             sub names_done_state {
784 0     0 0   my ($self, $kernel, $server, $message) = @_[OBJECT, KERNEL, ARG0, ARG1];
785 0           my ($channel) = split /\s/, $message;
786 0           $self->got_names(
787             {
788             channel => $channel,
789             names => $self->channel_data($channel),
790             }
791             );
792 0           return;
793             }
794              
795             sub topic_raw_state {
796 0     0 0   my ($self, $kernel, $server, $raw) = @_[OBJECT, KERNEL, ARG0, ARG1];
797 0           my ($channel, $topic) = split / :/, $raw, 2;
798 0           $self->topic(
799             {
800             channel => $channel,
801             who => undef,
802             topic => $topic,
803             }
804             );
805 0           return;
806             }
807              
808             sub topic_state {
809 0     0 0   my ($self, $kernel, $nickraw, $channel, $topic)
810             = @_[OBJECT, KERNEL, ARG0, ARG1, ARG2];
811 0           my $nick = $self->nick_strip($nickraw);
812 0           $self->topic(
813             {
814             channel => $channel,
815             who => $nick,
816             topic => $topic,
817             }
818             );
819 0           return;
820             }
821              
822             sub shutdown_state {
823 0     0 0   my ($kernel, $self) = @_[KERNEL, OBJECT];
824 0           $kernel->delay('tick');
825 0           $kernel->alias_remove($self->{ALIASNAME});
826 0           for my $fork (values %{ $self->{forks} }) {
  0            
827 0           $fork->{wheel}->kill();
828             }
829 0           return;
830             }
831              
832              
833             sub irc_raw_state {
834 0     0 0   my ($self, $kernel, $raw_line) = @_[OBJECT, KERNEL, ARG0];
835 0           $self->raw_in($raw_line);
836             }
837             sub irc_raw_out_state {
838 0     0 0   my ($self, $kernel, $raw_line) = @_[OBJECT, KERNEL, ARG0];
839 0           $self->raw_out($raw_line);
840             }
841              
842              
843              
844             sub AUTOLOAD {
845 0     0     my $self = shift;
846 0           our $AUTOLOAD;
847 0           $AUTOLOAD =~ s/.*:://;
848             $poe_kernel->post(
849             $self->{IRCNAME},
850 0           $AUTOLOAD,
851             $self->charset_encode(@_),
852             );
853 0           return;
854             }
855              
856             # so it won't get AUTOLOADed
857 0     0     sub DESTROY { return }
858              
859             sub log {
860 0     0 1   my $self = shift;
861 0           for (@_) {
862 0           my $log_entry = $_;
863 0           chomp $log_entry;
864 0           print STDERR "$log_entry\n";
865             }
866 0           return;
867             }
868              
869             sub ignore_nick {
870 0     0 1   local $_ = undef;
871 0           my $self = shift;
872 0           my $nick = shift;
873 0           return grep { $nick eq $_ } @{ $self->{ignore_list} };
  0            
  0            
874             }
875              
876             sub nick_strip {
877 0     0 1   my $self = shift;
878 0   0       my $combined = shift || "";
879 0           my ($nick) = $combined =~ m/(.*?)!/;
880 0           return $nick;
881             }
882              
883             sub charset_decode {
884 0     0 1   my $self = shift;
885              
886 0           my @r;
887 0           for (@_) {
888 0 0         if (ref($_) eq 'ARRAY') {
    0          
    0          
889 0           push @r, [ $self->charset_decode(@$_) ];
890             }
891             elsif (ref($_) eq "HASH") {
892 0           push @r, { $self->charset_decode(%$_) };
893             }
894             elsif (ref($_)) {
895 0           die "Can't decode object $_\n";
896             }
897             else {
898 0           push @r, decode_irc($_);
899             }
900             }
901             #warn Dumper({ decoded => \@r });
902 0           return @r;
903             }
904              
905             sub charset_encode {
906 0     0 1   my $self = shift;
907              
908 0           my @r;
909 0           for (@_) {
910 0 0         if (ref($_) eq 'ARRAY') {
    0          
    0          
911 0           push @r, [ $self->charset_encode(@$_) ];
912             }
913             elsif (ref($_) eq "HASH") {
914 0           push @r, { $self->charset_encode(%$_) };
915             }
916             elsif (ref($_)) {
917 0           die "Can't encode object $_\n";
918             }
919             else {
920 0           push @r, encode($self->charset, $_);
921             }
922             }
923             #warn Dumper({ encoded => \@r });
924 0           return @r;
925             }
926              
927             1;
928              
929             =head1 NAME
930              
931             Bot::BasicBot - simple irc bot baseclass
932              
933             =head1 SYNOPSIS
934              
935             #!/usr/bin/perl
936             use strict;
937             use warnings;
938              
939             # Subclass Bot::BasicBot to provide event-handling methods.
940             package UppercaseBot;
941             use base qw(Bot::BasicBot);
942              
943             sub said {
944             my $self = shift;
945             my $arguments = shift; # Contains the message that the bot heard.
946              
947             # The bot will respond by uppercasing the message and echoing it back.
948             $self->say(
949             channel => $arguments->{channel},
950             body => uc $arguments->{body},
951             );
952              
953             # The bot will shut down after responding to a message.
954             $self->shutdown('I have done my job here.');
955             }
956              
957             # Create an object of your Bot::BasicBot subclass and call its run method.
958             package main;
959              
960             my $bot = UppercaseBot->new(
961             server => 'irc.example.com',
962             port => '6667',
963             channels => ['#bottest'],
964             nick => 'UppercaseBot',
965             name => 'John Doe',
966             ignore_list => [ 'laotse', 'georgeburdell' ],
967             );
968             $bot->run();
969              
970             =head1 DESCRIPTION
971              
972             Basic bot system designed to make it easy to do simple bots, optionally
973             forking longer processes (like searches) concurrently in the background.
974              
975             There are several examples of bots using Bot::BasicBot in the examples/
976             folder in the Bot::BasicBot tarball.
977              
978             A quick summary, though - You want to define your own package that
979             subclasses Bot::BasicBot, override various methods (documented below),
980             then call L|/new> and L|/run> on it.
981              
982             =head1 STARTING THE BOT
983              
984             =head2 C
985              
986             Creates a new instance of the class. Key/value pairs may be passed
987             which will have the same effect as calling the method of that name
988             with the value supplied. Returns a Bot::BasicBot object, that you can
989             call 'run' on later.
990              
991             eg:
992              
993             my $bot = Bot::BasicBot->new( nick => 'superbot', channels => [ '#superheroes' ] );
994              
995             =head2 C
996              
997             Runs the bot. Hands the control over to the POE core.
998              
999             =head1 STOPPING THE BOT
1000              
1001             To shut down the bot cleanly, use the L|/shutdown> method,
1002             which will (through L|/AUTOLOAD>) send an
1003             L of the same name to
1004             POE::Component::IRC, so it takes the same arguments:
1005              
1006             $bot->shutdown( $bot->quit_message() );
1007              
1008             =head1 METHODS TO OVERRIDE
1009              
1010             In your Bot::BasicBot subclass, you want to override some of the following
1011             methods to define how your bot works. These are all object methods - the
1012             (implicit) first parameter to all of them will be the bot object.
1013              
1014             =head2 C
1015              
1016             called when the bot is created, as part of new(). Override to provide
1017             your own init. Return a true value for a successful init, or undef if
1018             you failed, in which case new() will die.
1019              
1020             =head2 C
1021              
1022             This is the main method that you'll want to override in your subclass -
1023             it's the one called by default whenever someone says anything that we
1024             can hear, either in a public channel or to us in private that we
1025             shouldn't ignore.
1026              
1027             You'll be passed a hashref that contains the arguments described below.
1028             Feel free to alter the values of this hash - it won't be used later on.
1029              
1030             =over 4
1031              
1032             =item who
1033              
1034             Who said it (the nick that said it)
1035              
1036             =item raw_nick
1037              
1038             The raw IRC nick string of the person who said it. Only really useful
1039             if you want more security for some reason.
1040              
1041             =item channel
1042              
1043             The channel in which they said it. Has special value "msg" if it was in
1044             a message. Actually, you can send a message to many channels at once in
1045             the IRC spec, but no-one actually does this so this is just the first
1046             one in the list.
1047              
1048             =item body
1049              
1050             The body of the message (i.e. the actual text)
1051              
1052             =item address
1053              
1054             The text that indicates how we were addressed. Contains the string
1055             "msg" for private messages, otherwise contains the string off the text
1056             that was stripped off the front of the message if we were addressed,
1057             e.g. "Nick: ". Obviously this can be simply checked for truth if you
1058             just want to know if you were addressed or not.
1059              
1060             =back
1061              
1062             You should return what you want to say. This can either be a simple
1063             string (which will be sent back to whoever was talking to you as a
1064             message or in public depending on how they were talking) or a hashref
1065             that contains values that are compatible with say (just changing
1066             the body and returning the structure you were passed works very well.)
1067              
1068             Returning undef will cause nothing to be said.
1069              
1070             =head2 C
1071              
1072             This is a secondary method that you may wish to override. It gets called
1073             when someone in channel 'emotes', instead of talking. In its default
1074             configuration, it will simply pass anything emoted on channel through to
1075             the C handler.
1076              
1077             C receives the same data hash as C.
1078              
1079             =head2 C
1080              
1081             This is like C, except for notices instead of normal messages.
1082              
1083             =head2 C
1084              
1085             Called when someone joins a channel. It receives a hashref argument
1086             similar to the one received by said(). The key 'who' is the nick of the
1087             user who joined, while 'channel' is the channel they joined.
1088              
1089             This is a do-nothing implementation, override this in your subclass.
1090              
1091             =head2 C
1092              
1093             Called when someone parts a channel. It receives a hashref argument
1094             similar to the one received by said(). The key 'who' is the nick of the
1095             user who parted, while 'channel' is the channel they parted.
1096              
1097             This is a do-nothing implementation, override this in your subclass.
1098              
1099             =head2 C
1100              
1101             Whenever we have been given a definitive list of 'who is in the channel',
1102             this function will be called. It receives a hash reference as an argument.
1103             The key 'channel' will be the channel we have information for, 'names' is a
1104             hashref where the keys are the nicks of the users, and the values are more
1105             hashes, containing the two keys 'op' and 'voice', indicating if the user is
1106             a chanop or voiced respectively.
1107              
1108             The reply value is ignored.
1109              
1110             Normally, I wouldn't override this method - instead, just use the L
1111             call when you want to know who's in the channel. Override this only if you
1112             want to be able to do something as soon as possible. Also be aware that
1113             the names list can be changed by other events - kicks, joins, etc, and this
1114             method won't be called when that happens.
1115              
1116             =head2 C
1117              
1118             Called when the topic of the channel changes. It receives a hashref
1119             argument. The key 'channel' is the channel the topic was set in, and 'who'
1120             is the nick of the user who changed the channel, 'topic' will be the new
1121             topic of the channel.
1122              
1123             =head2 C
1124              
1125             When a user changes nicks, this will be called. It receives two arguments:
1126             the old nickname and the new nickname.
1127              
1128             =head2 C
1129              
1130             Called when a user is kicked from the channel. It receives a hashref which
1131             will look like this:
1132              
1133             {
1134             channel => "#channel",
1135             who => "nick",
1136             kicked => "kicked",
1137             reason => "reason",
1138             }
1139              
1140             The reply value is ignored.
1141              
1142             =head2 C
1143              
1144             This is an event called every regularly. The function should return the
1145             amount of time until the tick event should next be called. The default
1146             tick is called 5 seconds after the bot starts, and the default
1147             implementation returns '0', which disables the tick. Override this and
1148             return non-zero values to have an ongoing tick event.
1149              
1150             Use this function if you want the bot to do something periodically, and
1151             don't want to mess with 'real' POE things.
1152              
1153             Call the L event to schedule a tick event without waiting
1154             for the next tick.
1155              
1156             =head2 C
1157              
1158             This is the other method that you should override. This is the text
1159             that the bot will respond to if someone simply says help to it. This
1160             should be considered a special case which you should not attempt to
1161             process yourself. Saying help to a bot should have no side effects
1162             whatsoever apart from returning this text.
1163              
1164             =head2 C
1165              
1166             An optional method to override, gets called after we have connected
1167             to the server
1168              
1169             =head2 C
1170              
1171             Receives a hashref which will look like:
1172              
1173             {
1174             who => "nick that quit",
1175             body => "quit message",
1176             }
1177              
1178             =head1 BOT METHODS
1179              
1180             There are a few methods you can call on the bot object to do things. These
1181             are as follows:
1182              
1183             =head2 C
1184              
1185             Takes an integer as an argument. Causes the L event to be called
1186             after that many seconds (or 5 seconds if no argument is provided). Note
1187             that if the tick event is due to be called already, this will override it.
1188             You can't schedule multiple future events with this funtction.
1189              
1190             =head2 C
1191              
1192             This method allows you to fork arbitrary background processes. They
1193             will run concurrently with the main bot, returning their output to a
1194             handler routine. You should call C in response to specific
1195             events in your C routine, particularly for longer running
1196             processes like searches, which will block the bot from receiving or
1197             sending on channel whilst they take place if you don't fork them.
1198              
1199             Inside the subroutine called by forkit, you can send output back to the
1200             channel by printing lines (followd by C<\n>) to STDOUT. This has the same
1201             effect as calling Lsay >>|say>.
1202              
1203             C takes the following arguments:
1204              
1205             =over 4
1206              
1207             =item run
1208              
1209             A coderef to the routine which you want to run. Bear in mind that the
1210             routine doesn't automatically get the text of the query - you'll need
1211             to pass it in C (see below) if you want to use it at all.
1212              
1213             Apart from that, your C routine just needs to print its output to
1214             C, and it will be passed on to your designated handler.
1215              
1216             =item handler
1217              
1218             Optional. A method name within your current package which we can
1219             return the routine's data to. Defaults to the built-in method
1220             C (which simply sends data to channel).
1221              
1222             =item callback
1223              
1224             Optional. A coderef to execute in place of the handler. If used, the value
1225             of the handler argument is used to name the POE event. This allows using
1226             closures and/or having multiple simultanious calls to forkit with unique
1227             handler for each call.
1228              
1229             =item body
1230              
1231             Optional. Use this to pass on the body of the incoming message that
1232             triggered you to fork this process. Useful for interactive proceses
1233             such as searches, so that you can act on specific terms in the user's
1234             instructions.
1235              
1236             =item who
1237              
1238             The nick of who you want any response to reach (optional inside a
1239             channel.)
1240              
1241             =item channel
1242              
1243             Where you want to say it to them in. This may be the special channel
1244             "msg" if you want to speak to them directly
1245              
1246             =item address
1247              
1248             Optional. Setting this to a true value causes the person to be
1249             addressed (i.e. to have "Nick: " prepended to the front of returned
1250             message text if the response is going to a public forum.
1251              
1252             =item arguments
1253              
1254             Optional. This should be an anonymous array of values, which will be
1255             passed to your C routine. Bear in mind that this is not
1256             intelligent - it will blindly spew arguments at C in the order
1257             that you specify them, and it is the responsibility of your C
1258             routine to pick them up and make sense of them.
1259              
1260             =back
1261              
1262             =head2 C
1263              
1264             Say something to someone. Takes a list of key/value pairs as arguments.
1265             You should pass the following arguments:
1266              
1267             =over 4
1268              
1269             =item who
1270              
1271             The nick of who you are saying this to (optional inside a channel.)
1272              
1273             =item channel
1274              
1275             Where you want to say it to them in. This may be the special channel
1276             "msg" if you want to speak to them directly
1277              
1278             =item body
1279              
1280             The body of the message. I.e. what you want to say.
1281              
1282             =item address
1283              
1284             Optional. Setting this to a true value causes the person to be
1285             addressed (i.e. to have "Nick: " prepended to the front of the message
1286             text if this message is going to a pulbic forum.
1287              
1288             =back
1289              
1290             You can also make non-OO calls to C, which will be interpreted as
1291             coming from a process spawned by C. The routine will serialise
1292             any data it is sent, and throw it to STDOUT, where L can
1293             pass it on to a handler.
1294              
1295             =head2 C
1296              
1297             C will return data to channel, but emoted (as if you'd said "/me
1298             writes a spiffy new bot" in most clients). It takes the same arguments
1299             as C, listed above.
1300              
1301             =head2 C
1302              
1303             C will send a IRC notice to the channel. This is typically used by
1304             bots to not break the IRC conversations flow. The message will appear as:
1305              
1306             -nick- message here
1307              
1308             It takes the same arguments as C, listed above. Example:
1309              
1310             $bot->notice(
1311             channel => '#bot_basicbot_test',
1312             body => 'This is a notice'
1313             );
1314              
1315             =head2 C
1316              
1317             Takes two arguments, a hashref containing information about an incoming
1318             message, and a reply message. It will reply in a privmsg if the incoming
1319             one was a privmsg, in channel if not, and with prefixes if the incoming
1320             one was prefixed. Mostly a shortcut method - it's roughly equivalent to
1321              
1322             $mess->{body} = $body;
1323             $self->say($mess);
1324              
1325             =head2 C
1326              
1327             Takes no arguments. Returns the underlying
1328             L object used by
1329             Bot::BasicBot. Useful for accessing various state methods and for posting
1330             commands to the component. For example:
1331              
1332             # get the list of nicks in the channel #someplace
1333             my @nicks = $bot->pocoirc->channel_list("#someplace");
1334              
1335             # join the channel #otherplace
1336             $bot->pocoirc->yield('join', '#otherplace');
1337              
1338             =head2 C
1339              
1340             Takes a channel names as a parameter, and returns a hash of hashes. The
1341             keys are the nicknames in the channel, the values are hashes containing
1342             the keys "voice" and "op", indicating whether these users are voiced or
1343             opped in the channel. This method is only here for backwards compatability.
1344             You'll probably get more use out of
1345             L's methods (which
1346             this method is merely a wrapper for). You can access the
1347             POE::Component::IRC::State object through Bot::BasicBot's C
1348             method.
1349              
1350             =head1 ATTRIBUTES
1351              
1352             Get or set methods. Changing most of these values when connected
1353             won't cause sideffects. e.g. changing the server will not
1354             cause a disconnect and a reconnect to another server.
1355              
1356             Attributes that accept multiple values always return lists and
1357             either accept an arrayref or a complete list as an argument.
1358              
1359             The usual way of calling these is as keys to the hash passed to the
1360             'new' method.
1361              
1362             =head2 C
1363              
1364             The server we're going to connect to. Defaults to
1365             "irc.perl.org".
1366              
1367             =head2 C
1368              
1369             The port we're going to use. Defaults to "6667"
1370              
1371             =head2 C
1372              
1373             The server password for the server we're going to connect to. Defaults to
1374             undef.
1375              
1376             =head2 C
1377              
1378             A boolean to indicate whether or not the server we're going to connect to
1379             is an SSL server. Defaults to 0.
1380              
1381             =head2 C
1382              
1383             The local address to use, for multihomed boxes. Defaults to undef (use whatever
1384             source IP address the system deigns is appropriate).
1385              
1386             =head2 C
1387              
1388             A boolean to indicate whether IPv6 should be used. Defaults to undef (use
1389             IPv4).
1390              
1391             =head2 C
1392              
1393             The nick we're going to use. Defaults to five random letters
1394             and numbers followed by the word "bot"
1395              
1396             =head2 C
1397              
1398             Alternate nicks that this bot will be known by. These are not nicks
1399             that the bot will try if it's main nick is taken, but rather other
1400             nicks that the bot will recognise if it is addressed in a public
1401             channel as the nick. This is useful for bots that are replacements
1402             for other bots...e.g, your bot can answer to the name "infobot: "
1403             even though it isn't really.
1404              
1405             =head2 C
1406              
1407             The username we'll claim to have at our ip/domain. By default this
1408             will be the same as our nick.
1409              
1410             =head2 C
1411              
1412             The name that the bot will identify itself as. Defaults to
1413             "$nick bot" where $nick is the nick that the bot uses.
1414              
1415             =head2 C
1416              
1417             The channels we're going to connect to.
1418              
1419             =head2 C
1420              
1421             The quit message. Defaults to "Bye".
1422              
1423             =head2 C
1424              
1425             The list of irc nicks to ignore B messages from (normally
1426             other bots.) Useful for stopping bot cascades.
1427              
1428             =head2 C
1429              
1430             IRC has no defined character set for putting high-bit chars into channel.
1431             This attribute sets the encoding to be used for outgoing messages. Defaults
1432             to 'utf8'.
1433              
1434             =head2 C
1435              
1436             Set to '1' to disable the built-in flood protection of POE::Compoent::IRC
1437              
1438             =head2 C
1439              
1440             Tells Bot::BasicBot to B run the L at the end
1441             of L|/run>, in case you want to do that yourself.
1442              
1443             =head1 OTHER METHODS
1444              
1445             =head2 C
1446              
1447             Bot::BasicBot implements AUTOLOAD for sending arbitrary states to the
1448             underlying L component. So for a
1449             C<$bot> object, sending
1450              
1451             $bot->foo("bar");
1452              
1453             is equivalent to
1454              
1455             $poe_kernel->post(BASICBOT_ALIAS, "foo", "bar");
1456              
1457             =head2 C
1458              
1459             Logs the message. This method merely prints to STDERR - If you want smarter
1460             logging, override this method - it will have simple text strings passed in
1461             @_.
1462              
1463             =head2 C
1464              
1465             Takes a nick name as an argument. Return true if this nick should be
1466             ignored. Ignores anything in the ignore list
1467              
1468             =head2 C
1469              
1470             Takes a nick and hostname (of the form "nick!hostname") and
1471             returns just the nick
1472              
1473             =head2 C
1474              
1475             Converts a string of bytes from IRC (uses
1476             L|IRC::Utils/decode_irc> from L
1477             internally) and returns a Perl string.
1478              
1479             It can also takes a list (or arrayref or hashref) of strings, and return
1480             a list of strings
1481              
1482             =head2 C
1483              
1484             Converts a list of perl strings into a list of byte sequences, using
1485             the bot's charset. See L|/charset_decode>.
1486              
1487             =head1 HELP AND SUPPORT
1488              
1489             If you have any questions or issues, you can drop by in #poe or
1490             #bot-basicbot @ irc.perl.org, where I (Hinrik) am usually around.
1491              
1492             =head1 AUTHOR
1493              
1494             Tom Insam Etom@jerakeen.orgE
1495              
1496             This program is free software; you can redistribute it
1497             and/or modify it under the same terms as Perl itself.
1498              
1499             =head1 CREDITS
1500              
1501             The initial version of Bot::BasicBot was written by Mark Fowler,
1502             and many thanks are due to him.
1503              
1504             Nice code for dealing with emotes thanks to Jo Walsh.
1505              
1506             Various patches from Tom Insam, including much improved rejoining,
1507             AUTOLOAD stuff, better interactive help, and a few API tidies.
1508              
1509             Maintainership for a while was in the hands of Simon Kent
1510             Esimon@hitherto.netE. Don't know what he did. :-)
1511              
1512             I (Tom Insam) recieved patches for tracking joins and parts from Silver,
1513             sat on them for two months, and have finally applied them. Thanks, dude.
1514             He also sent me changes for the tick event API, which made sense.
1515              
1516             In November 2010, maintainership moved to Hinrik Ern
1517             SigurEsson (L).
1518              
1519             In April 2017, maintainership moved to David Precious
1520             (L).
1521              
1522             =head1 SEE ALSO
1523              
1524             L, L
1525              
1526             Possibly Infobot, at http://www.infobot.org
1527              
1528             =cut