File Coverage

blib/lib/Bot/IRC.pm
Criterion Covered Total %
statement 156 301 51.8
branch 38 128 29.6
condition 23 93 24.7
subroutine 31 51 60.7
pod 24 24 100.0
total 272 597 45.5


line stmt bran cond sub pod time code
1             package Bot::IRC;
2             # ABSTRACT: Yet Another IRC Bot
3              
4 13     13   561090 use 5.014;
  13         68  
5 13     13   1094 use exact;
  13         71208  
  13         89  
6              
7 13     13   19986 use Daemon::Device;
  13         155721  
  13         456  
8 13     13   6543 use Date::Format 'time2str';
  13         100555  
  13         974  
9 13     13   117 use Encode 'decode';
  13         36  
  13         697  
10 13     13   6544 use Encode::Detect::Detector 'detect';
  13         32276  
  13         892  
11 13     13   8563 use IO::Socket::IP -register;
  13         293731  
  13         71  
12 13     13   17637 use IO::Socket::SSL;
  13         651527  
  13         113  
13 13     13   8852 use Time::Crontab;
  13         239569  
  13         70932  
14              
15             our $VERSION = '1.40'; # VERSION
16              
17             sub new {
18 17     17 1 31216 my $class = shift;
19 17         68 my $self = bless( {@_}, $class );
20              
21 17 50       91 croak('Odd number of elements passed to new()') if ( @_ % 2 );
22             croak('connect/server not provided to new()')
23 17 100 66     401 unless ( ref $self->{connect} eq 'HASH' and $self->{connect}{server} );
24              
25 16   100     99 $self->{spawn} ||= 2;
26 16   50     100 $self->{encoding} //= 'UTF-8';
27              
28 16   100     85 $self->{connect}{nick} //= 'bot';
29 16   100     116 $self->{connect}{name} //= 'Yet Another IRC Bot';
30 16   100     142 $self->{connect}{port} ||= 6667;
31              
32 16   50 0   157 $self->{disconnect} //= sub {};
33              
34 16   100     91 $self->{daemon} //= {};
35 16   66     108 $self->{daemon}{name} //= $self->{connect}{nick};
36 16   66     100 $self->{daemon}{pid_file} //= $self->{daemon}{name} . '.pid';
37              
38 16         39 $self->{nick} = $self->{connect}{nick};
39              
40 16         57 $self->{hooks} = [];
41 16         52 $self->{ticks} = [];
42 16         42 $self->{helps} = {};
43 16         41 $self->{loaded} = {};
44 16         383 $self->{numerics} = [];
45              
46 16   50     113 $self->{send_user_nick} ||= 'on_parent';
47             croak('"send_user_nick" optional value set to invalid value') if (
48             $self->{send_user_nick} ne 'on_connect' and
49             $self->{send_user_nick} ne 'on_parent' and
50 16 50 33     122 $self->{send_user_nick} ne 'on_reply'
      33        
51             );
52              
53             $self->load(
54 2         8 ( ref $self->{plugins} eq 'ARRAY' ) ? @{ $self->{plugins} } : $self->{plugins}
55 16 50       71 ) if ( $self->{plugins} );
    100          
56              
57 15         81 return $self;
58             }
59              
60             sub run {
61 1     1 1 479 my $self = shift;
62 1         3 my $commands = \@_;
63              
64             $self->{socket} = ( ( $self->{connect}{ssl} ) ? 'IO::Socket::SSL' : 'IO::Socket::IP' )->new(
65             PeerAddr => $self->{connect}{server},
66             PeerPort => $self->{connect}{port},
67             Proto => 'tcp',
68 1 50       11 Family => ( $self->{connect}{ipv6} ? AF_INET6 : AF_INET ),
    50          
    50          
69             Type => SOCK_STREAM,
70             SSL_verify_mode => SSL_VERIFY_NONE,
71             ) or die $!;
72              
73 1 50       13 if ( $self->{encoding} ) {
74             try {
75             binmode( $self->{socket}, "encoding($self->{encoding})" );
76             }
77 1         4 catch ($e) {}
78             }
79              
80 1 50       5 if ( $self->{send_user_nick} eq 'on_connect' ) {
81 0         0 $self->{socket}->print("USER $self->{nick} 0 * :$self->{connect}{name}\r\n");
82 0         0 $self->{socket}->print("NICK $self->{nick}\r\n");
83             }
84              
85             try {
86             $self->{device} = Daemon::Device->new(
87             parent => \&_parent,
88             child => \&_child,
89             on_message => \&_on_message,
90             spawn => $self->{spawn},
91             daemon => $self->{daemon},
92             data => {
93             self => $self,
94             commands => $commands,
95             passwd => $self->{passwd},
96             },
97             );
98             }
99             catch ($e) {
100             croak("Daemon device instantiation failure: $e");
101             }
102              
103 1         2 $self->{device}->run;
  1         9  
104             }
105              
106             sub note {
107 4     4 1 11 my ( $self, $msg, $err ) = @_;
108 4         6 chomp($msg);
109 4         15 $msg = '[' . time2str( '%d/%b/%Y:%H:%M:%S %z', time() ) . '] ' . $msg . "\n";
110              
111 4 50       1009 if ($err) {
112 0 0       0 die $msg if ( $err eq 'die' );
113 0         0 warn $msg;
114             }
115             else {
116 4         153 print $msg;
117             }
118              
119 4         25 return;
120             }
121              
122             sub _parent {
123 0     0   0 my ($device) = @_;
124 0         0 my $self = $device->data('self');
125 0         0 my $session = { start => time };
126             my $delegate = sub {
127             my ($random_child) =
128 0         0 map { $_->[0] }
129 0         0 sort { $a->[1] <=> $b->[1] }
130 0         0 map { [ $_, rand() ] }
131 0     0   0 @{ $device->children };
  0         0  
132              
133 0         0 $device->message( $random_child, @_ );
134 0         0 };
135             my $broadcast = sub {
136 0     0   0 my @messages = @_;
137 0         0 $device->message( $_, @messages ) for ( @{ $device->children } );
  0         0  
138 0         0 };
139              
140             local $SIG{ALRM} = sub {
141 0     0   0 alarm 1;
142 0         0 my $time = time;
143              
144 0         0 for (
145             grep {
146             ref $_->{timing} and ( $time % 60 == 0 ) and $_->{timing}->match($time) or
147 0 0 0     0 not ref $_->{timing} and ( ( $time - $session->{start} ) % $_->{timing} == 0 )
      0        
      0        
148 0         0 } @{ $self->{ticks} }
149             ) {
150             try {
151             $_->{code}->($self);
152             }
153 0         0 catch ($e) {
154             warn "Tick execution failure: $e\n";
155             }
156             }
157 0         0 };
158              
159 0     0   0 local $SIG{__WARN__} = sub { note( undef, $_[0], 'warn' ) };
  0         0  
160 0     0   0 local $SIG{__DIE__} = sub { note( undef, $_[0], 'die' ) };
  0         0  
161              
162 0         0 srand();
163 0         0 my @lines;
164              
165             try {
166             if ( $self->{send_user_nick} eq 'on_parent' ) {
167             $self->say("USER $self->{nick} 0 * :$self->{connect}{name}");
168             $self->say("NICK $self->{nick}");
169             }
170              
171             while ( my $line = $self->{socket}->getline ) {
172             $line =~ s/\003\d{2}(?:,\d{2})?//g; # remove IRC color codes
173             $line =~ tr/\000-\037//d; # remove all control characters
174              
175             $self->note($line);
176             chomp($line);
177              
178             if ( not $session->{established} ) {
179             if ( not $session->{user} ) {
180             if ( $self->{send_user_nick} eq 'on_reply' ) {
181             $self->say("USER $self->{nick} 0 * :$self->{connect}{name}");
182             $self->say("NICK $self->{nick}");
183             }
184             elsif ( $self->{send_user_nick} eq 'on_connect' ) {
185             $self->note("<<< USER $self->{nick} 0 * :$self->{connect}{name}\r\n");
186             $self->note("<<< NICK $self->{nick}\r\n");
187             }
188             $session->{user} = 1;
189             }
190             if ( $line =~ /^ERROR.+onnect\w+ too fast/ ) {
191             warn "$line\n";
192             warn "Sleeping 20 and retrying...\n";
193             sleep 20;
194             $device->daemon->do_restart;
195             }
196             elsif ( $line =~ /^ERROR\s/ ) {
197             warn "$line\n";
198             $device->daemon->do_stop;
199             }
200             elsif ( $line =~ /^:\S+\s433\s/ ) {
201             $self->nick( $self->{nick} . '_' );
202             }
203             elsif ( $line =~ /^:\S+\s001\s/ ) {
204             $self->say($_) for ( map {
205             my $command = $_;
206             $command =~ s|^/msg |PRIVMSG |;
207             $command =~ s|^/(\w+)|uc($1)|e;
208             $command;
209             } @{ $device->data('commands') } );
210              
211             $self->join;
212             $session->{established} = 1;
213             alarm 1 if ( @{ $self->{ticks} } );
214             }
215             }
216              
217             shift @lines while ( @lines > 10 );
218             my $now = time();
219              
220             unless ( grep { $_->{line} eq $line and $_->{time} + 1 > $now } @lines ) {
221             unless ( $line =~ /^:\S+\s\d{3}\s/ ) {
222             $delegate->($line);
223             }
224             else {
225             $broadcast->($line);
226             }
227             }
228             else {
229             $self->note("### Skipped repeated line: $line");
230             }
231              
232             push @lines, { line => $line, time => $now };
233             }
234             }
235 0         0 catch ($e) {
236             warn "Daemon parent loop failure: $e\n";
237             }
238              
239 0         0 kill( 'KILL', $_ ) for ( @{ $device->children } );
  0         0  
240 0 0       0 $self->{disconnect}->($self) if ( ref $self->{disconnect} eq 'CODE' );
241             }
242              
243             sub _child {
244 0     0   0 local $SIG{__WARN__} = sub { note( undef, $_[0], 'warn' ) };
  0     0   0  
245 0     0   0 local $SIG{__DIE__} = sub { note( undef, $_[0], 'die' ) };
  0         0  
246              
247 0         0 srand();
248 0         0 sleep 1 while (1);
249             }
250              
251             sub _on_message {
252 0     0   0 my $device = shift;
253 0         0 my $self = $device->data('self');
254 0         0 my $passwd = $device->data('passwd');
255              
256 0         0 for my $line (@_) {
257 0 0       0 if ( $self->{encoding} ) {
258 0         0 my $charset = detect($line);
259 0 0 0     0 $line = decode( $charset => $line ) if ( $charset and $charset eq $self->{encoding} );
260             }
261              
262 0         0 push( @{ $self->{numerics} }, $line )
263 0 0 0     0 if ( $line =~ /^:\S+\s\d{3}\s/ and @{ $self->{numerics} } < 100 );
  0         0  
264              
265 0 0       0 if ( $line =~ /^>>>\sNICK\s(.*)/ ) {
    0          
266 0         0 $self->{nick} = $1;
267 0         0 next;
268             }
269             elsif ( $line =~ /^:\S+\s433\s/ ) {
270 0         0 $self->nick( $self->{nick} . '_' );
271 0         0 next;
272             }
273              
274 0         0 $self->{in} = { map { $_ => '' } qw( line source nick user server command forum text ) };
  0         0  
275 0         0 $self->{in}{$_} = 0 for ( qw( private to_me ) );
276 0         0 $self->{in}{line} = $line;
277              
278 0 0       0 if ( $line =~ /^(ERROR)\s/ ) {
    0          
    0          
    0          
    0          
    0          
    0          
279 0         0 warn $line . "\n";
280             }
281             elsif ( $line =~ /^:(\S+?)!~?(\S+?)@(\S+?)\s(\S+)\s(\S+)\s:(.*)/ ) {
282 0         0 @{ $self->{in} }{ qw( nick user server command forum text ) } = ( $1, $2, $3, $4, $5, $6 );
  0         0  
283 0         0 $self->{in}{full_text} = $self->{in}{text};
284             }
285             elsif ( $line =~ /^:(\S+?)!~?(\S+?)@(\S+?)\s(\S+)\s:(.*)/ ) {
286 0         0 @{ $self->{in} }{ qw( nick user server command text ) } = ( $1, $2, $3, $4, $5 );
  0         0  
287             ( $self->{in}{forum} = $self->{in}{text} ) =~ s/^://
288 0 0 0     0 if ( $self->{in}{command} eq 'JOIN' or $self->{in}{command} eq 'PART' );
289             }
290             elsif ( $line =~ /^:(\S+?)!~?(\S+?)@(\S+?)\s(\S+)\s(\S+)\s(.*)/ ) {
291 0         0 @{ $self->{in} }{ qw( nick user server command forum text ) } = ( $1, $2, $3, $4, $5, $6 );
  0         0  
292             }
293             elsif ( $line =~ /^:(\S+?)!~?(\S+?)@(\S+?)\s(\S+)\s(\S+)/ ) {
294 0         0 @{ $self->{in} }{ qw( nick user server command forum ) } = ( $1, $2, $3, $4, $5, $6 );
  0         0  
295             }
296             elsif ( $line =~ /^(PING)\s(.+)/ ) {
297 0         0 @{ $self->{in} }{ qw( command text ) } = ( $1, $2 );
  0         0  
298 0         0 $self->say( 'PONG ' . $self->{in}{text} );
299 0         0 next;
300             }
301             elsif ( $line =~ /^:(\S+)\s([A-Z]+|\d+)\s(\S+)\s(.*)/ ) {
302 0         0 @{ $self->{in} }{ qw( source command forum text ) } = ( $1, $2, $3, $4 );
  0         0  
303             }
304             else {
305 0         0 warn 'Unparsed line (probably a bug in Bot::IRC; please report it): ', $line . "\n";
306             }
307              
308 0 0       0 next unless ( $self->{in}{nick} ne $self->{nick} );
309              
310 0 0       0 if ( $self->{in}{command} eq 'PRIVMSG' ) {
311 0 0 0     0 $self->{in}{private} = 1 if ( $self->{in}{forum} and $self->{in}{forum} eq $self->{nick} );
312             $self->{in}{to_me} = 1 if (
313             $self->{in}{text} =~ s/^\s*\b$self->{nick}\b[\s\W]*//i or
314             $self->{in}{text} =~ s/[\s\W]*\b$self->{nick}\b[\s\W]*$//i or
315             $self->{in}{private}
316 0 0 0     0 );
      0        
317             }
318              
319 0 0       0 if ( $self->{in}{to_me} ) {
320 0 0       0 if ( $self->{in}{text} =~ /^\s*help\W*$/i ) {
    0          
    0          
321             $self->reply_to(
322             'Ask me for help with "help topic" where the topic is one of the following: ' .
323 0         0 $self->list( ', ', 'and', sort keys %{ $self->{helps} } ) . '.'
  0         0  
324             );
325 0         0 next;
326             }
327             elsif ( $self->{in}{text} =~ /^\s*help\s+(.+?)\W*$/i ) {
328             $self->reply_to(
329 0   0     0 ( $self->{helps}{$1} || "Couldn't find the help topic: $1." )
330             );
331 0         0 next;
332             }
333             elsif ( $self->{in}{text} =~ /Sorry. I don't understand./ ) {
334 0         0 next;
335             }
336             }
337              
338 0         0 hook: for my $hook (
339 0         0 @{ $self->{hooks} },
340              
341             {
342             when => {
343             to_me => 1,
344             text => qr/^\s*cmd\s+(?<passwd>\S+)\s+(?<cmd>.+)$/i,
345             },
346             code => sub {
347 0     0   0 my ( $bot, $in, $m ) = @_;
348              
349 0 0 0     0 if ( $m->{passwd} and $passwd and $m->{passwd} eq $passwd ) {
      0        
350 0         0 $bot->say($_) for (
351             map {
352 0         0 my $command = $_;
353 0         0 $command =~ s|^/msg |PRIVMSG |;
354 0         0 $command =~ s|^/(\w+)|uc($1)|e;
  0         0  
355 0         0 $command;
356             } split( /\s*;\s*/, $m->{cmd} )
357             );
358             }
359              
360 0         0 return 1;
361             },
362             },
363              
364             {
365             when => {
366             full_text => qr/^\s*$self->{nick}\s*[!\?]\W*$/i,
367             },
368             code => sub {
369 0     0   0 my ($bot) = @_;
370 0         0 $bot->reply_to('Yes?');
371             },
372             },
373              
374             {
375             when => {
376             to_me => 1,
377             text => qr/^(?<word>hello|greetings|hi|good\s+\w+)\W*$/i,
378             },
379             code => sub {
380 0     0   0 my ( $bot, $in, $m ) = @_;
381 0         0 $bot->reply_to( ucfirst( lc( $m->{word} ) ) . '.' );
382             },
383             },
384              
385             ( map {
386             {
387             when => $_,
388             code => sub {
389 0     0   0 my ($bot) = @_;
390 0         0 $bot->reply_to(qq{Sorry. I don't understand. (Try "$self->{nick} help" for help.)});
391             },
392             },
393 0         0 } (
394             {
395             private => 0,
396             full_text => qr/^\s*$self->{nick}\s*[,:\->~=]/i,
397             },
398             {
399             private => 1,
400             },
401             ) ),
402              
403             ) {
404 0         0 my $captured_matches = {};
405              
406 0         0 for my $type ( keys %{ $hook->{when} } ) {
  0         0  
407             next hook unless (
408             ref( $hook->{when}{$type} ) eq 'Regexp' and
409             $self->{in}{$type} and $self->{in}{$type} =~ $hook->{when}{$type} or
410             ref( $hook->{when}{$type} ) eq 'CODE' and $hook->{when}{$type}->(
411             $self,
412             $self->{in}{$type},
413 0         0 { %{ $self->{in} } },
414             ) or
415             (
416             defined $self->{in}{$type} and defined $hook->{when}{$type} and
417 0 0 0     0 $self->{in}{$type} eq $hook->{when}{$type}
      0        
      0        
      0        
      0        
      0        
      0        
418             )
419             );
420              
421 13 0   13   6701 $captured_matches = { %$captured_matches, %+ } if ( keys %+ );
  13         5198  
  13         23044  
  0         0  
422             }
423              
424 0         0 my $rv;
425             try {
426             $rv = $hook->{code}->(
427             $self,
428             { %{ $self->{in} } },
429             $captured_matches,
430             );
431             }
432 0         0 catch ($e) {
433             warn "Plugin hook execution failure: $e\n";
434             }
435              
436 0 0       0 last if ($rv);
437             }
438             }
439             }
440              
441             sub load {
442 7     7 1 17 my $self = shift;
443              
444 7         22 for my $plugin (@_) {
445 7 50       21 unless ( ref $plugin ) {
446 7 50       46 if ( $plugin =~ /^:core$/i ) {
447 0         0 $self->load(
448             'Ping',
449             'Join',
450             'Seen',
451             'Greeting',
452             'Infobot',
453             'Functions',
454             'Convert',
455             'Karma',
456             'Math',
457             'History',
458             );
459 0         0 next;
460             }
461              
462 7         13 my $namespace;
463 7         62 for (
464             $plugin,
465             __PACKAGE__ . "::Y::$plugin",
466             __PACKAGE__ . "::X::$plugin",
467             __PACKAGE__ . "::$plugin",
468             ) {
469 22         100 ( my $path = $_ ) =~ s|::|/|g;
470              
471 22         2009 eval "require $_";
472 22 100       1680 unless ($@) {
473 6         30 $namespace = $_;
474 6         20 last;
475             }
476             else {
477 16 50       332 croak("Plugin load failure: $@") unless ( $@ =~ /^Can't locate $path/ );
478             }
479             }
480 7 100       194 croak("Unable to find or properly load $plugin") unless ($namespace);
481              
482 6 50       34 next if ( $self->{loaded}{$namespace} );
483              
484 6 50       65 $namespace->import if ( $namespace->can('import') );
485 6 50       50 croak("$namespace does not implement init()") unless ( $namespace->can('init') );
486              
487 6         441 eval "${namespace}::init(\$self)";
488 6 50       61 die("Plugin init failure: $@\n") if ($@);
489              
490 6         43 $self->{loaded}{$namespace} = time;
491             }
492             else {
493 0         0 $self->$_( @{ $plugin->{$_} } ) for ( qw( hooks ticks ) );
  0         0  
494 0         0 $self->$_( $plugin->{$_} ) for ( qw( helps subs ) );
495             }
496             }
497              
498 6         17 return $self;
499             }
500              
501             sub reload {
502 1     1 1 288 my $self = shift;
503 1         5 delete $self->{loaded}{$_} for (@_);
504 1         4 return $self->load(@_);
505             }
506              
507             sub hook {
508 3     3 1 321 my ( $self, $when, $code, $attr ) = @_;
509              
510             push(
511 3   100     4 @{ $self->{hooks} },
  3         20  
512             {
513             when => $when,
514             code => $code,
515             attr => ( $attr // {} ),
516             },
517             );
518              
519 3 50       9 $self->subs( %{ $attr->{subs} } ) if ( ref $attr->{subs} eq 'HASH' );
  0         0  
520 3 50       10 $self->helps( %{ $attr->{helps} } ) if ( ref $attr->{helps} eq 'HASH' );
  0         0  
521              
522 3         7 return $self;
523             }
524              
525             sub hooks {
526 1     1 1 266 my $self = shift;
527 1         6 $self->hook(@$_) for (@_);
528 1         3 return $self;
529             }
530              
531             sub helps {
532 10     10 1 296 my ( $self, @input ) = @_;
533              
534             try {
535             $self->{helps} = { %{ $self->{helps} }, @input };
536             }
537 10         39 catch ($e) {
538             $self->note('Plugin helps called but not properly implemented');
539             }
540              
541 10         54 return $self;
542             }
543              
544             sub tick {
545 3     3 1 249 my ( $self, $timing, $code ) = @_;
546              
547 3 50       6 push( @{ $self->{ticks} }, {
  3         24  
548             timing => ( $timing =~ /^\d+$/ ) ? $timing : Time::Crontab->new($timing),
549             code => $code,
550             } );
551 3         8 return $self;
552             }
553              
554             sub ticks {
555 1     1 1 246 my $self = shift;
556 1         5 $self->tick(@$_) for (@_);
557 1         2 return $self;
558             }
559              
560             sub subs {
561 9     9 1 283 my $self = shift;
562              
563 9 50       37 if ( @_ % 2 ) {
564 0         0 $self->note('Plugin helps called but not properly implemented');
565 0         0 return $self;
566             }
567              
568 9         31 my $subs = {@_};
569              
570 9         35 for my $name ( keys %$subs ) {
571 13     13   125 no strict 'refs';
  13         50  
  13         635  
572 13     13   81 no warnings 'redefine';
  13         70  
  13         21907  
573 10         22 *{ __PACKAGE__ . '::' . $name } = $subs->{$name};
  10         59  
574             }
575              
576 9         48 return $self;
577             }
578              
579             sub register {
580 2     2 1 301 my $self = shift;
581 2         24 $self->{loaded}{$_} = time for (@_);
582 2         27 return $self;
583             }
584              
585             sub vars {
586 4     4 1 14 my ( $self, $name ) = @_;
587 4 100       38 ( $name = lc( substr( ( caller() )[0], length(__PACKAGE__) + 2 ) ) ) =~ s/::/\-/g unless ($name);
588 4 50       32 return ( defined $self->{vars}{$name} ) ? $self->{vars}{$name} : {};
589             }
590              
591             sub settings {
592 1     1 1 4 my ( $self, $name ) = @_;
593 1 50       5 return ( defined $name ) ? $self->{$name} : { %$self };
594             }
595              
596             sub reply {
597 1     1 1 837 my ( $self, $message ) = @_;
598              
599 1 50       5 if ( $self->{in}{forum} ) {
600             $self->msg(
601 0 0       0 ( ( $self->{in}{forum} eq $self->{nick} ) ? $self->{in}{nick} : $self->{in}{forum} ),
602             $message,
603             );
604             }
605             else {
606 1         10 warn "Didn't have a target to send reply to.\n";
607             }
608 1         9 return $self;
609             }
610              
611             sub reply_to {
612 0     0 1 0 my ( $self, $message ) = @_;
613 0 0       0 return $self->reply( ( ( not $self->{in}{private} ) ? "$self->{in}{nick}: " : '' ) . $message );
614             }
615              
616             sub msg {
617 1     1 1 1711 my ( $self, $target, $message ) = @_;
618 1 50       11 $self->say( "PRIVMSG $target :" . ( ( $message =~ s/^\/me\s+// ) ? "\001ACTION $message\001" : $message ) );
619 1         5 return $self;
620             }
621              
622             sub say {
623 3     3 1 1593 my $self = shift;
624              
625 3         8 for (@_) {
626 4         6 my $string = $_;
627 4         20 $self->{socket}->print( $string . "\r\n" );
628 4         17 $self->note("<<< $string");
629             }
630 3         8 return $self;
631             }
632              
633             sub nick {
634 1     1 1 1361 my ( $self, $nick ) = @_;
635              
636 1 50       4 if ($nick) {
637 1         4 $self->{nick} = $nick;
638             $self->{device}->message( $_, ">>> NICK $self->{nick}" )
639 1         4 for ( grep { $_ != $$ } $self->{device}->ppid, @{ $self->{device}->children } );
  3         18  
  1         5  
640 1         12 $self->say("NICK $self->{nick}");
641             }
642 1         10 return $self->{nick};
643             }
644              
645             sub join {
646 0     0 1 0 my $self = shift;
647              
648 0 0 0     0 $self->say("JOIN $_") for (
    0          
649             ( not @_ and $self->{connect}{join} )
650             ? (
651             ( ref $self->{connect}{join} eq 'ARRAY' )
652 0         0 ? @{ $self->{connect}{join} }
653             : $self->{connect}{join}
654             )
655             : @_
656             );
657              
658 0         0 return $self;
659             }
660              
661             sub part {
662 0     0 1 0 my $self = shift;
663 0         0 $self->say("PART $_") for (@_);
664 0         0 return $self;
665             }
666              
667             sub list {
668 2     2 1 833 my ( $self, $separator, $conjunction ) = ( shift, shift, shift );
669 2         6 my @list = @_;
670              
671 2 100       11 if ( @list > 2 ) {
    50          
672 1         11 return CORE::join( $separator, @list[ 0 .. @list - 2 ], $conjunction . ' ' . $list[-1] );
673             }
674             elsif ( @list > 1 ) {
675 1         8 return $list[0] . ' ' . $conjunction . ' ' . $list[1];
676             }
677             else {
678 0           return $list[0];
679             }
680             }
681              
682             sub health {
683 0     0 1   my ($self) = @_;
684              
685             return {
686             nick => $self->{nick},
687             server => $self->{connect}{server},
688             port => $self->{connect}{port},
689             ssl => $self->{connect}{ssl},
690             spawn => $self->{spawn},
691 0           hooks => scalar( @{ $self->{hooks} } ),
692 0           ticks => scalar( @{ $self->{ticks} } ),
693 0           plugins => scalar( keys %{ $self->{loaded} } ),
  0            
694             };
695             }
696              
697             sub numerics {
698 0     0 1   my ($self) = @_;
699 0           return $self->{numerics};
700             }
701              
702             1;
703              
704             __END__
705              
706             =pod
707              
708             =encoding UTF-8
709              
710             =head1 NAME
711              
712             Bot::IRC - Yet Another IRC Bot
713              
714             =head1 VERSION
715              
716             version 1.40
717              
718             =for markdown [![test](https://github.com/gryphonshafer/Bot-IRC/workflows/test/badge.svg)](https://github.com/gryphonshafer/Bot-IRC/actions?query=workflow%3Atest)
719             [![codecov](https://codecov.io/gh/gryphonshafer/Bot-IRC/graph/badge.svg)](https://codecov.io/gh/gryphonshafer/Bot-IRC)
720              
721             =head1 SYNOPSIS
722              
723             use Bot::IRC;
724              
725             # minimal bot instance that does basically nothing except join a channel
726             Bot::IRC->new(
727             connect => {
728             server => 'irc.perl.org',
729             join => '#test',
730             },
731             )->run;
732              
733             # illustrative example of most settings and various ways to get at them
734             my $bot = Bot::IRC->new(
735             spawn => 2,
736             daemon => {
737             name => 'bot',
738             lsb_sdesc => 'Yet Another IRC Bot',
739             pid_file => 'bot.pid',
740             stderr_file => 'bot.err',
741             stdout_file => 'bot.log',
742             },
743             connect => {
744             server => 'irc.perl.org',
745             port => '6667',
746             nick => 'yabot',
747             name => 'Yet Another IRC Bot',
748             join => [ '#test', '#perl' ],
749             ssl => 0,
750             ipv6 => 0,
751             },
752             plugins => [
753             ':core',
754             ],
755             vars => {
756             store => 'bot.yaml',
757             },
758             send_user_nick => 'on_parent', # or 'on_connect' or 'on_reply'
759             );
760              
761             $bot->load( 'Infobot', 'Karma' );
762              
763             ## Example inline plugin structure
764             # $bot->load({
765             # hooks => [ [ {}, sub {}, {} ] ],
766             # helps => { name => 'String' },
767             # subs => { name => sub {} },
768             # ticks => [ [ '0 * * * *', sub {} ] ],
769             # });
770              
771             $bot->run;
772              
773             =head1 DESCRIPTION
774              
775             Yet another IRC bot. Why? There are so many good bots and bot frameworks to
776             select from, but I wanted a bot framework that worked like a Unix service
777             out-of-the-box, operated in a pre-fork way to serve multiple concurrent
778             requests, and has a dirt-simple and highly extendable plugin mechanism. I also
779             wanted to keep the direct dependencies and core bot minimalistic, allowing as
780             much functionality as possible to be defined as optional plugins.
781              
782             =head2 Minimal Bot
783              
784             You can have a running IRC bot with as little as:
785              
786             use Bot::IRC;
787              
788             Bot::IRC->new(
789             connect => {
790             server => 'irc.perl.org',
791             },
792             )->run;
793              
794             This won't actually do much apart from connecting to the server and responding
795             to pings, but it's useful to understand how this works. Let's say you place the
796             above code into a "bot.pl" file. You start the bot with:
797              
798             ./bot.pl start
799              
800             This will startup the bot. Command-line commands include: start, stop, restart,
801             reload, status, help, and so on. (See L<Daemon::Control> for more details.)
802              
803             =head2 Pre-Forking Device
804              
805             When the bot is started, the parent process will fork or spawn a given number
806             of children workers. You can control their number along with setting locations
807             for things like PID file, log files, and so on.
808              
809             Bot::IRC->new(
810             spawn => 2,
811             daemon => {
812             name => 'bot',
813             lsb_sdesc => 'Yet Another IRC Bot',
814             pid_file => 'bot.pid',
815             stderr_file => 'bot.err',
816             stdout_file => 'bot.log',
817             },
818             )->run;
819              
820             (See L<Daemon::Device> for more details.)
821              
822             =head1 MAIN METHODS
823              
824             The following are the main or primary available methods from this class.
825              
826             =head2 new
827              
828             This method instantiates a bot object that's potentially ready to start running.
829             All bot settings can be specified to the C<new()> constructor, but some can be
830             set or added to through other methods off the instantiated object.
831              
832             Bot::IRC->new(
833             spawn => 2,
834             daemon => {},
835             connect => {
836             server => 'irc.perl.org',
837             port => '6667',
838             nick => 'yabot',
839             name => 'Yet Another IRC Bot',
840             join => [ '#test', '#perl' ],
841             ssl => 0,
842             ipv6 => 0,
843             },
844             plugins => [],
845             vars => {},
846             )->run;
847              
848             C<spawn> will default to 2. Under C<connect>, C<port> will default to 6667.
849             C<join> can be either a string or an arrayref of strings representing channels
850             to join after connnecting. C<ssl> is a true/false setting for whether to
851             connect to the server over SSL. C<ipv6> is also true/false setting for whether
852             to forcibly connect to the server over IPv6.
853              
854             You can optionally also provide an C<encoding> string representing a strict name
855             of an encoding standard. If you don't set this, it will default to "UTF-8"
856             internally. The encoding string is used to set the binmode for log files and for
857             message text decoding as necessary. If you want to turn off this functionality,
858             set C<encoding> to any defined false value.
859              
860             Read more about plugins below for more information about C<plugins> and C<vars>.
861             Consult L<Daemon::Device> and L<Daemon::Control> for more details about C<spawn>
862             and C<daemon>.
863              
864             There's also an optional C<send_user_nick> parameter, which you probably won't
865             need to use, which defines when the bot will send the C<USER> and initial
866             C<NICK> commands to the IRC server. There are 3 options: C<on_connect>,
867             C<on_parent> (the default), and C<on_reply>. C<on_connect> sends the C<USER>
868             and initial C<NICK> immediately upon establishing a connection to the IRC
869             server, prior to the parent runtime loop and prior to children creation.
870             C<on_parent> (the default) sends the 2 commands within the parent runtime loop
871             prior to any responses from the IRC server. C<on_reply> (the only option in
872             versions <= 1.23 of this module) sends the 2 commands after the IRC server
873             replies with some sort of content after connection.
874              
875             If you provide a C<disconnect> value as a reference to a subroutine, it will be
876             called when the bot is disconnected from a host. It's important to keep in mind
877             that this code is run from within the parent of the daemon, not your program, so
878             it's context will be different. The intent of this bot's design is to run as a
879             service, not a program. This hook is provided so the parent process can send a
880             signal to something that might want to take action.
881              
882             =head2 run
883              
884             This should be the last call you make, which will cause your program to operate
885             like a Unix service from the command-line. (See L<Daemon::Control> for
886             additional details.)
887              
888             C<run> can optionally be passed a list of strings that will be executed after
889             connection to the IRC server. These should be string commands similar to what
890             you'd type in an IRC client. For example:
891              
892             Bot::IRC->new( connect => { server => 'irc.perl.org' } )->run(
893             '/msg nickserv identify bot_password',
894             '/msg operserv identify bot_password',
895             '/oper bot_username bot_password',
896             '/msg chanserv identify #bot_talk bot_password',
897             '/join #bot_talk',
898             '/msg chanserv op #bot_talk',
899             );
900              
901             =head1 PLUGINS
902              
903             To do anything useful with a bot, you have to load plugins. You can do this
904             either by specifying a list of plugins with the C<plugins> key passed to
905             C<new()> or by calling C<load()>.
906              
907             Plugins are just simple packages (or optionally a hashref, but more on that
908             later). The only requirement for plugins is that they provide an C<init()>
909             method. This will get called by the bot prior to forking its worker children.
910             It will be passed the bot object. Within C<init()>, you can call any number of
911             plugin methods (see the list of methods below) to setup desired functionality.
912              
913             package Your::Plugin;
914             use strict;
915             use warnings;
916              
917             sub init {
918             my ($bot) = @_;
919              
920             $bot->hook(
921             {
922             to_me => 1,
923             text => qr/\b(?<word>w00t|[l1][e3]{2}[t7])\b/i,
924             },
925             sub {
926             my ( $bot, $in, $m ) = @_;
927             $bot->reply("$in->{nick}, don't use the word: $m->{word}.");
928             },
929             );
930             }
931              
932             1;
933              
934             When you load plugins, you can specify their packages a few different ways. When
935             attempting to load a plugin, the bot will start by looking for the name you
936             provided as a sub-class of itself. Then it will look for the plugin under the
937             assumption you provided it's full name.
938              
939             plugins => [
940             'Store', # matches "Bot::IRC::Store"
941             'Random', # matches "Bot::IRC::X::Random"
942             'Thing', # matches "Bot::IRC::Y::Thing"
943             'My::Own::Plugin', # matches "My::Own::Plugin"
944             ],
945              
946             An unenforced convention for public/shared plugins is to have non-core plugins
947             (all plugins not provided directly by this CPAN library) subclasses of
948             "Bot::IRC::X". For private/unshared plugins, you can specify whatever name you
949             want, but maybe consider something like "Bot::IRC::Y". Plugins set in the X or
950             Y subclass namespaces will get matched just like core plugins. "Y" plugins will
951             have precedence over "X" which in turn will have precedence over core.
952              
953             If you need to allow for variables to get passed to your plugins, an unenforced
954             convention is to do so via the C<vars> key to C<new()>.
955              
956             =head2 Core Plugins
957              
958             If you specify ":core" as a plugin name, it will be expanded to load all the
959             core plugins. Core plugins are all the plugins that are bundled and
960             distributed with L<Bot::IRC>.
961              
962             =over 4
963              
964             =item *
965              
966             L<Bot::IRC::Ping>
967              
968             =item *
969              
970             L<Bot::IRC::Join>
971              
972             =item *
973              
974             L<Bot::IRC::Seen>
975              
976             =item *
977              
978             L<Bot::IRC::Greeting>
979              
980             =item *
981              
982             L<Bot::IRC::Infobot>
983              
984             =item *
985              
986             L<Bot::IRC::Functions>
987              
988             =item *
989              
990             L<Bot::IRC::Convert>
991              
992             =item *
993              
994             L<Bot::IRC::Karma>
995              
996             =item *
997              
998             L<Bot::IRC::Math>
999              
1000             =item *
1001              
1002             L<Bot::IRC::History>
1003              
1004             =back
1005              
1006             Some core plugins require a storage plugin. If you don't specify one in your
1007             plugins list, then the default L<Bot::IRC::Store> will be used, which is
1008             probably not what you want (for performance reasons). Try
1009             L<Bot::IRC::Store::SQLite> instead.
1010              
1011             plugins => [
1012             'Store::SQLite',
1013             ':core',
1014             ],
1015              
1016             =head1 PLUGIN METHODS
1017              
1018             The following are methods available from this class related to plugins.
1019              
1020             =head2 load
1021              
1022             This method loads plugins. It is the exact equivalent of passing strings to the
1023             C<plugins> key in C<new()>. If a plugin has already been loaded, it'll get
1024             skipped.
1025              
1026             my $bot = Bot::IRC->new(
1027             connect => { server => 'irc.perl.org' },
1028             plugins => [ 'Store', 'Infobot', 'Karma' ],
1029             );
1030              
1031             $bot->load( 'Infobot', 'Seen' );
1032              
1033             From within your plugins, you can call C<load()> to specify plugin dependencies
1034             in your plugins.
1035              
1036             sub init {
1037             my ($bot) = @_;
1038             $bot->load('Dependency');
1039             }
1040              
1041             =head2 reload
1042              
1043             If you need to actually reload a plugin, call C<reload>. It operates in the same
1044             was as C<load>, only it won't skip already-loaded plugins.
1045              
1046             =head2 hook
1047              
1048             This is the method you'll call to add a hook, which is basically a message
1049             response handler. A hook includes a conditions trigger, some code to run
1050             when the trigger fires, and an optional additional attributes hashref.
1051              
1052             $bot->hook(
1053             {
1054             to_me => 1,
1055             text => qr/\b(?<word>w00t|[l1][e3]{2}[t7])\b/i,
1056             },
1057             sub {
1058             my ( $bot, $in, $m ) = @_;
1059             $bot->reply("$in->{nick}, don't use the word: $m->{word}.");
1060             },
1061             {
1062             subs => [],
1063             helps => [],
1064             },
1065             );
1066              
1067             The conditions trigger is a hashref of key-value pairs where the key is a
1068             component of the message and the value is either a value to exact match or a
1069             regular expression to match.
1070              
1071             The code block will receive a copy of the bot, a hashref of key-value pairs
1072             representing the message the hook is responding to, and an optionally-available
1073             hashref of any named matches from the regexes in the trigger.
1074              
1075             The hashref representing the message the hook will have the following keys:
1076              
1077             =over 4
1078              
1079             =item *
1080              
1081             C<text>: text component of the message
1082              
1083             =item *
1084              
1085             C<command>: IRC "command" like PRIVMSG, MODE, etc.
1086              
1087             =item *
1088              
1089             C<forum>: origin location like #channel or the nick who privately messaged
1090              
1091             =item *
1092              
1093             C<private>: 1 or 0 representing if the message is private or in a channel
1094              
1095             =item *
1096              
1097             C<to_me>: 1 or 0 representing if the message is addressing the bot or not
1098              
1099             =item *
1100              
1101             C<nick>: nick of the sender of the message
1102              
1103             =item *
1104              
1105             C<source>: the source server's label/name
1106              
1107             =item *
1108              
1109             C<user>: username of the sender of the message
1110              
1111             =item *
1112              
1113             C<server>: server of the sender of the message
1114              
1115             =item *
1116              
1117             C<line>: full message line/text
1118              
1119             =item *
1120              
1121             C<full_text>: text component of the message with nick included
1122              
1123             =back
1124              
1125             B<The return value from the code block is important.> If you return a positive
1126             value, all additional hooks are skipped because it will be assumed that this
1127             hook properly responded to the message and no additional work needs to be done.
1128             If the code block returns a false value, additional hooks will be checked as if
1129             this hook's trigger caused the code block to be skipped.
1130              
1131             The optional additional attributes hashref supports a handful of keys.
1132             You can specify C<subs> and C<helps>, which are exactly equivalent to
1133             calling C<subs()> and C<helps()>. (See below.)
1134              
1135             =head2 hooks
1136              
1137             This method accepts a list of arrayrefs, each containing a trigger, code, and
1138             attribute value and calls C<hook> for each set.
1139              
1140             ## Example hooks call structure
1141             # $bot->hooks(
1142             # [ {}, sub {}, {} ],
1143             # [ {}, sub {}, {} ],
1144             # );
1145              
1146             =head2 helps
1147              
1148             This method is how you'd setup any help text you'd like the bot to provide to
1149             users. It expects some number of key-value pairs where the key is the topic
1150             title of the set of functionality and the value is the string of instructions.
1151              
1152             $bot->helps(
1153             seen => 'Tracks when and where people were seen. Usage: seen <nick>, hide, unhide.',
1154             join => 'Join and leave channels. Usage: join <channel>, leave <channel>, channels.',
1155             );
1156              
1157             In the example above, let's say your bot had the nick of "bot" and you were in
1158             the same channel as your bot and you typed "bot, help" in your IRC channel. The
1159             bot would respond with a list of available topics. Then if you typed "bot, help
1160             seen" in the channel, the bot would reply with the "seen" string of
1161             instructions. If typing directly to the bot (in a private message directly to
1162             the bot), you don't need to specify the bot's name.
1163              
1164             =head2 tick
1165              
1166             Sometimes you'll want the bot to do something at a specific time or at some sort
1167             of interval. You can cause this to happen by filing ticks. A tick is similar to
1168             a hook in that it's a bit of code that gets called, but not based on a message
1169             but based on time. C<tick()> expects two values. The first is either an integer
1170             representing the number of seconds of interval between calls to the code or a
1171             crontab-like time expression. The second value is the code to call, which will
1172             receive a copy of the bot object.
1173              
1174             $bot->tick(
1175             10,
1176             sub {
1177             my ($bot) = @_;
1178             $bot->msg( '#test', '10-second interval.' );
1179             },
1180             );
1181              
1182             $bot->tick(
1183             '0 0 * * *',
1184             sub {
1185             my ($bot) = @_;
1186             $bot->msg( '#test', "It's midnight!" );
1187             },
1188             );
1189              
1190             =head2 ticks
1191              
1192             This method accepts a list of arrayrefs, each containing a time value and code
1193             block and calls C<tick> for each set.
1194              
1195             $bot->ticks(
1196             [ 10, sub {} ],
1197             [ '0 0 * * *', sub {} ],
1198             );
1199              
1200             =head2 subs
1201              
1202             A plugin can also provide functionality to the bot for use in other plugins.
1203             It can also override core methods of the bot. You do this with the C<subs()>
1204             method.
1205              
1206             $bot->subs(
1207             incr => sub {
1208             my ( $bot, $int ) = @_;
1209             return ++$int;
1210             },
1211             );
1212              
1213             my $value = $bot->incr(42); # value is 43
1214              
1215             =head2 register
1216              
1217             There are rare cases when you're writing your plugin where you want to claim
1218             that your plugin satisfies the requirements for a different plugin. In other
1219             words, you want to prevent the future loading of a specific plugin or plugins.
1220             You can do this by calling C<register()> with the list of plugins (by full
1221             namespace) that you want to skip.
1222              
1223             $bot->register('Bot::IRC::Storage');
1224              
1225             Note that this will not block the reloading of plugins with C<reload()>.
1226              
1227             =head2 vars
1228              
1229             When you are within a plugin, you can call C<vars()> to get the variables for
1230             the plugin by it's lower-case "simplified" name, which is the plugin's class
1231             name all lower-case, without the preceding "Bot::IRC::" bit, and with "::"s
1232             replaced with dashes. For example, let's say you were writing a
1233             "Bot::IRC::X::Something" plugin. You would have users set variables in their
1234             instantiation like so:
1235              
1236             Bot::IRC->new
1237             plugins => ['Something'],
1238             vars => { x-something => { answer => 42 } },
1239             )->run;
1240              
1241             Then from within the "Bot::IRC::X::Something" plugin, you would access these
1242             variables like so:
1243              
1244             my $my_vars = $bot->vars;
1245             say 'The answer to life, the universe, and everything is ' . $my_vars->{answer};
1246              
1247             If you want to access the variables for a different namespace, pass into
1248             C<vars()> the "simplified" name you want to access.
1249              
1250             my $my_other_vars = $bot->vars('x-something-else');
1251              
1252             =head2 settings
1253              
1254             If you need access to the bot's settings, you can do so with C<settings()>.
1255             Supply the setting name/key to get that setting, or provide no name/key to get
1256             all settings as a hashref.
1257              
1258             my $connection_settings_hashref = $bot->settings('connect');
1259              
1260             =head1 INLINE PLUGINS
1261              
1262             You can optionally inject inline plugins by providing them as hashref. This
1263             works both with C<load()> and the C<plugins> key.
1264              
1265             ## Example inline plugin structure
1266             # $bot->load(
1267             # {
1268             # hooks => [ [ {}, sub {}, {} ], [ {}, sub {}, {} ] ],
1269             # ticks => [ [ 10, sub {} ], [ '0 0 * * *', sub {} ] ],
1270             # helps => { title => 'Description.' },
1271             # subs => { name => sub {} },
1272             # },
1273             # {
1274             # hooks => [ [ {}, sub {}, {} ], [ {}, sub {}, {} ] ],
1275             # ticks => [ [ 10, sub {} ], [ '0 0 * * *', sub {} ] ],
1276             # helps => { title => 'Description.' },
1277             # subs => { name => sub {} },
1278             # },
1279             # );
1280              
1281             =head1 OPERATIONAL METHODS
1282              
1283             The following are operational methods available from this class, expected to be
1284             used inside various code blocks passed to plugin methds.
1285              
1286             =head2 reply
1287              
1288             If you're inside a hook, you can usually respond to most messages with the
1289             C<reply()> method, which accepts the text the bot should reply with. The method
1290             returns the bot object.
1291              
1292             $bot->reply('This is a reply. Impressive, huh?');
1293              
1294             If you want to emote something back or use any other IRC command, type it just
1295             as you would in your IRC client.
1296              
1297             $bot->reply('/me feels something, which for a bot is rather impressive.');
1298              
1299             =head2 reply_to
1300              
1301             C<reply_to> is exactly like C<reply> except that if the forum for the reply is
1302             a channel instead of to a specific person, the bot will prepend the message
1303             by addressing the nick who was the source of the response the bot is responding
1304             to.
1305              
1306             =head2 msg
1307              
1308             Use C<msg()> when you don't have a forum to reply to or want to reply in a
1309             different forum (i.e. to a different user or channel). The method accepts the
1310             forum for the message and the message text.
1311              
1312             $bot->msg( '#test', 'This is a message for everybody in #test.');
1313              
1314             =head2 say
1315              
1316             Use C<say()> to write low-level lines to the IRC server. The method expects a
1317             string that's a properly IRC message.
1318              
1319             $bot->say('JOIN #help');
1320             $bot->say('PRIVMSG #help :I need some help.');
1321              
1322             =head2 nick
1323              
1324             Use C<nick> to change the bot's nick. If the nick is already in use, the bot
1325             will try appending "_" to it until it finds an open nick.
1326              
1327             =head2 join
1328              
1329             Use C<join()> to join channels.
1330              
1331             $bot->join('#help');
1332              
1333             If some sort of persistent storage plugin is loaded, the bot will remember the
1334             channels it has joined or parted and use that as it's initial join on restart.
1335              
1336             =head2 part
1337              
1338             Use C<part()> to part channels.
1339              
1340             $bot->part('#help');
1341              
1342             If some sort of persistent storage plugin is loaded, the bot will remember the
1343             channels it has joined or parted and use that as it's initial join on restart.
1344              
1345             =head1 RANDOM HELPFUL METHODS
1346              
1347             The following are random additional methods that might be helpful in your
1348             plugins.
1349              
1350             =head2 list
1351              
1352             This method is a simple string method that takes a list and crafts it for
1353             readability. It expects a separator string, a final item conjunction string,
1354             and a list of items.
1355              
1356             $bot->list( ', ', 'and', 'Alpha', 'Beta', 'Delta', 'Gamma' );
1357             # returns "Alpha, Beta, Delta, and Gamma"
1358              
1359             $bot->list( ', ', 'and', 'Alpha', 'Beta' );
1360             # returns "Alpha and Beta"
1361              
1362             =head2 health
1363              
1364             This method returns a hashref of simple key value pairs for different "health"
1365             aspects (or current state) of the bot. It includes things like server and port
1366             connection, number of children, and so on.
1367              
1368             =head2 note
1369              
1370             While in theory you shouldn't ever need to use it, there is a method called
1371             "note" which is a handler for writing to the log and error files. If you
1372             C<warn> or C<die>, this handler steps in automatically. If you'd like to
1373             C<print> to STDOUT, which you really shouldn't need to do, then it's best to
1374             call this method instead. The reason being is that the log file is designed to
1375             be parsed in a specific way. If you write whatever you want to it, it will
1376             corrupt the log file. That said, if you really, really want to, here's how you
1377             use C<note>:
1378              
1379             $bot->note('Message'); # writes a message to the log file
1380             $bot->note( 'Message', 'warn' ); # writes a message to the error file
1381             $bot->note( 'Message', 'die' ); # writes a message to the error file the dies
1382              
1383             =head2 numerics
1384              
1385             This method will return an arrayref of scalar strings, each an IRC numeric line
1386             from the server. The arrayref is limited to the first 100 numerics from the
1387             server.
1388              
1389             =head1 SEE ALSO
1390              
1391             You can look for additional information at:
1392              
1393             =over 4
1394              
1395             =item *
1396              
1397             L<GitHub|https://github.com/gryphonshafer/Bot-IRC>
1398              
1399             =item *
1400              
1401             L<MetaCPAN|https://metacpan.org/pod/Bot::IRC>
1402              
1403             =item *
1404              
1405             L<GitHub Actions|https://github.com/gryphonshafer/Bot-IRC/actions>
1406              
1407             =item *
1408              
1409             L<Codecov|https://codecov.io/gh/gryphonshafer/Bot-IRC>
1410              
1411             =item *
1412              
1413             L<CPANTS|http://cpants.cpanauthors.org/dist/Bot-IRC>
1414              
1415             =item *
1416              
1417             L<CPAN Testers|http://www.cpantesters.org/distro/T/Bot-IRC.html>
1418              
1419             =back
1420              
1421             =head1 AUTHOR
1422              
1423             Gryphon Shafer <gryphon@cpan.org>
1424              
1425             =head1 COPYRIGHT AND LICENSE
1426              
1427             This software is Copyright (c) 2016-2050 by Gryphon Shafer.
1428              
1429             This is free software, licensed under:
1430              
1431             The Artistic License 2.0 (GPL Compatible)
1432              
1433             =cut