File Coverage

blib/lib/Irssi/Bot/BasicBot/Pluggable.pm
Criterion Covered Total %
statement 55 436 12.6
branch 0 134 0.0
condition 0 47 0.0
subroutine 17 66 25.7
pod 3 35 8.5
total 75 718 10.4


line stmt bran cond sub pod time code
1             # ABSTRACT: Run Bot::BasicBot::Pluggable::Module_s in Irssi
2             use strict;
3 1     1   85765 use warnings;
  1         3  
  1         26  
4 1     1   5 use experimental 'signatures';
  1         2  
  1         25  
5 1     1   457  
  1         2975  
  1         5  
6             use Irssi::Log::Log4perl;
7 1     1   494 use Module::Pluggable
  1         2  
  1         28  
8             sub_name => '_available',
9 1         5 search_path => 'Bot::BasicBot::Pluggable::Module',
10             except => 'Bot::BasicBot::Pluggable::Module::Base';
11 1     1   406  
  1         8968  
12             use Bot::BasicBot::Pluggable::Module;
13 1     1   513 use Bot::BasicBot::Pluggable::Store;
  1         843  
  1         37  
14 1     1   382 use Try::Tiny;
  1         13928  
  1         27  
15 1     1   7 use Capture::Tiny 'capture';
  1         1  
  1         47  
16 1     1   444 use Text::Wrap ();
  1         21800  
  1         56  
17 1     1   430  
  1         2187  
  1         2363  
18             #use Carp::Always;
19              
20             # POE::Session constants
21              
22 0     0 0   my $self = bless +{ @config } => $cls;
  0            
  0            
23 0     0 0    
  0            
  0            
24 0     0 0   print "These modules are available for loading: "
  0            
  0            
25 0     0 0   . join( ", ", $self->available_modules );
  0            
  0            
26              
27 0     0 0   $self->init or die "init did not return a true value - dying";
  0            
  0            
  0            
28 0            
29             return $self;
30 0           }
31              
32              
33 0 0          
34             $self->init_logging();
35 0            
36             my $logger = Irssi::Log::Log4perl->get_logger( ref $self );
37             $logger->info( 'Starting initialization of ' . ref $self );
38 0     0 0    
39             return 1;
40 0     0 0   }
41              
42 0     0 0   my $self = shift;
  0            
  0            
43 0           my $logger = Irssi::Log::Log4perl->get_logger( ref $self );
44             for my $log_entry (@_) {
45 0           chomp $log_entry;
46 0           $logger->warn($log_entry);
47             }
48 0           return;
49             }
50              
51             my @modules = sort
52 0     0 1   map {
53 0           my $mod = $_;
54 0           $mod =~ s/^Bot::BasicBot::Pluggable::Module:://;
55 0           $mod;
56 0           } $self->_available();
57             return @modules;
58 0           }
59              
60             my $logger = Irssi::Log::Log4perl->get_logger( ref $self );
61 0     0 0    
  0            
  0            
62             $logger->info("Dispatching $method")
63             unless $method eq 'tick';
64 0           for my $who ( $self->modules ) {
  0            
65 0           ## Otherwise we would see tick every five seconds
66 0           if ( $method eq 'tick' ) {
67             $logger->trace("Trying to dispatch $method to $who");
68 0           }
69             else {
70             $logger->debug("Trying to dispatch $method to $who");
71 0     0 0   }
  0            
  0            
  0            
  0            
72 0           $logger->trace( "... with " . Dumper(@args) )
73             if $logger->is_trace && @args;
74 0 0          
75             next unless $self->module($who)->can($method);
76 0           try {
77             $logger->trace(
78 0 0         "Dispatching $method to $who with " . Dumper(@args) )
79 0           if $logger->is_trace;
80             $self->module($who)->$method(@args);
81             }
82 0           catch {
83             $logger->warn($_);
84 0 0 0       }
85             }
86             return;
87 0 0         }
88              
89 0 0   0     $self->dispatch('tick');
90             return 5;
91             }
92 0            
93             my $response;
94             my $who;
95 0     0      
96             my $logger = Irssi::Log::Log4perl->get_logger( ref $self );
97 0           $logger->info("Dispatching $event event");
98 0            
99             for my $priority ( 0 .. 3 ) {
100             for my $module ( $self->modules ) {
101 0     0 0   my $response;
  0            
  0            
102 0           $logger->debug(
103 0           "Trying to dispatch $event to $module on priority $priority");
104             $logger->trace( '... with arguments ' . Dumper($mess) )
105             if $logger->is_trace and $mess;
106 0     0 0   try {
  0            
  0            
  0            
  0            
107 0           $response =
108             $self->module($module)->$event( $mess, $priority );
109             }
110 0           catch {
111 0           $logger->warn($_);
112             };
113 0           if ( $priority and $response ) {
114 0           $logger->debug("Response by $module on $priority");
115 0           $logger->trace( 'Response is ' . Dumper($response) )
116 0           if $logger->is_trace;
117             return if $response eq '1';
118 0 0 0       $self->reply( $mess, $response );
119             return;
120             }
121 0     0     }
122             }
123             return;
124             }
125 0     0      
126 0           $self->dispatch( 'replied', {%$mess}, @other );
127 0 0 0       if ( $mess->{reply_hook} ) {
128 0           return $mess->{reply_hook}->( $mess, @other );
129 0 0         }
130             else {
131 0 0         my ($body) = @other;
132 0           my %hash = %$mess;
133 0           $hash{body} = $body;
134             return $self->say(%hash);
135             }
136             }
137 0            
138             # If we're called without an object ref, then we're handling saying
139             # stuff from inside a forked subroutine, so we'll freeze it, and toss
140 0     0 0   # it out on STDOUT so that POE::Wheel::Run's handler can pick it up.
  0            
  0            
  0            
  0            
141 0           if (!ref $_[0]) {
142 0 0         print $_[0], "\n";
143 0           return 1;
144             }
145              
146 0           # Otherwise, this is a standard object method
147 0            
148 0           my $self = shift;
149 0           my $args;
150             if (ref $_[0]) {
151             $args = shift;
152             }
153             else {
154             my %args = @_;
155             $args = \%args;
156             }
157 0 0   0 0    
158 0           my $body = $args->{body};
159 0            
160             # add the "Foo: bar" at the start
161             if ($args->{channel} ne "msg" && defined $args->{address}) {
162             $body = "$args->{who}: $body";
163             }
164 0            
165 0           # work out who we're going to send the message to
166 0 0         my $who = $args->{channel} eq "msg" ? $args->{who} : $args->{channel};
167 0            
168             if (!defined $who || !defined $body) {
169             $self->log("Can't send a message without target and body\n"
170 0           . " called from "
171 0           . ( [caller]->[0] )
172             . " line "
173             . ( [caller]->[2] ) . "\n"
174 0           . " who = '$who'\n body = '$body'\n");
175             return;
176             }
177 0 0 0        
178 0           # if we have a long body, split it up..
179             #local $Text::Wrap::columns = 300;
180             local $Text::Wrap::columns = 294;
181             local $Text::Wrap::unexpand = 0; # no tabs
182 0 0         my $wrapped = Text::Wrap::wrap('', '..', $body); # =~ m!(.{1,300})!g;
183             # I think the Text::Wrap docs lie - it doesn't do anything special
184 0 0 0       # in list context
185 0           my @bodies = split /\n+/, $wrapped;
186              
187             # Allows to override the default "PRIVMSG". Used by notice()
188             my $irc_command = defined $args->{irc_command}
189             && $args->{irc_command} eq 'notice'
190             ? 'notice'
191 0           : 'privmsg';
192              
193             # possibility to set the network
194             local $self->{conn_tag} = $args->{network}
195             if $args->{network};
196 0            
197 0           # post an event that will send the message
198 0           my $last_color;
199             for my $body (@bodies) {
200             if (defined $last_color) {
201 0           $body = "..\cC$last_color".substr $body, 2;
202             }
203             if ((my $bcol = rindex $body, "\cC") > -1) {
204             $last_color = (substr $body, $bcol + 1) =~ /^(\d{1,2}(?:,\d{1,2})?)/ ? $1 : undef;
205 0 0 0       }
206             if ($who eq '@@@ local user @@@') {
207             $self->localresponse({ body => $body });
208             } else {
209             $self->{irssi}->$irc_command($self, $who, $body);
210             }
211 0 0         }
212              
213             return;
214 0           }
215 0            
216 0 0         my $return;
217 0           my $mess = {};
218             return unless $nick && $address;
219 0 0         $mess->{who} = $nick;
220 0 0         $mess->{raw_nick} = "$nick!$address";
221              
222 0 0         $mess->{channel} = $channel;
223 0           $mess->{body} = $received; #chanjoin or chanpart
224             $mess->{address} = "chan";
225 0            
226             # okay, call the chanjoin/chanpart method
227             $return = $self->$received($mess);
228              
229 0           ### what did we get back?
230              
231             # nothing? Say nothing then
232 0     0 0   return if !defined $return;
  0            
  0            
  0            
  0            
  0            
  0            
233 0            
234 0           # a string? Say it how we were addressed then
235 0 0 0       if (!ref $return) {
236 0           $mess->{body} = $return;
237 0           $self->say($mess);
238             return;
239 0           }
240 0           }
241 0            
242             my $return;
243             my $mess = {};
244 0            
245             # pass the raw body through
246             $mess->{raw_body} = $body;
247              
248             # work out who it was from
249 0 0         return unless $nick && $address;
250             $mess->{who} = $nick;
251             $mess->{raw_nick} = "$nick!$address";
252 0 0          
253 0           # right, get the list of places this message was
254 0           # sent to and work out the first one that we're
255 0           # either a memeber of is is our nick.
256             # The IRC protocol allows messages to be sent to multiple
257             # targets, which is pretty clever. However, noone actually
258             # /does/ this, so we can get away with this:
259 0     0 0    
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
260 0           my $channel = $target;
261 0           if (lc($channel) eq lc($self->nick)) {
262             $mess->{channel} = "msg";
263             $mess->{address} = "msg";
264 0           }
265             else {
266             $mess->{channel} = $channel;
267 0 0 0       }
268 0            
269 0           # okay, work out if we're addressed or not
270              
271             $mess->{body} = $body;
272             if ($mess->{channel} ne "msg") {
273             my $own_nick = $self->nick;
274              
275             if ($mess->{body} =~ s/^(\Q$own_nick\E)\s*[:,-]?\s*//i) {
276             $mess->{address} = $1;
277             }
278 0            
279 0 0         for my $alt_nick ($self->alt_nicks) {
280 0           last if $mess->{address};
281 0           if ($mess->{body} =~ s/^(\Q$alt_nick\E)\s*[:,-]?\s*//i) {
282             $mess->{address} = $1;
283             }
284 0           }
285             }
286              
287             # strip off whitespace before and after the message
288             $mess->{body} =~ s/^\s+//;
289 0           $mess->{body} =~ s/\s+$//;
290 0 0          
291 0           # check if someone was asking for help
292             if ($mess->{address} && $mess->{body} =~ /^help/i) {
293 0 0         $mess->{body} = $self->help($mess) or return;
294 0           $self->say($mess);
295             return;
296             }
297 0            
298 0 0         # okay, call the said/emoted method
299 0 0         $return = $self->$received($mess);
300 0            
301             ### what did we get back?
302              
303             # nothing? Say nothing then
304             return if !defined $return;
305              
306 0           # a string? Say it how we were addressed then
307 0           if (!ref $return && length $return) {
308             $mess->{body} = $return;
309             $self->$respond($mess);
310 0 0 0       return;
311 0 0         }
312 0           }
313 0            
314             print "[ => ] " . ($self->{server} ? "[$self->{server}{tag}] " : "") . $mess->{body};
315             }
316              
317 0           local $self->{conn_tag} = $network
318             if $network;
319             $self->{irssi}->own_nick($self)
320             }
321              
322 0 0         local $self->{conn_tag} = $network
323             if $network;
324             $self->{irssi}->find_alternate_nicks($self)
325 0 0 0       }
326 0            
327 0           my $self = shift;
328 0           my $args;
329              
330             if (ref($_[0])) {
331             $args = shift;
332 0     0 0   }
  0            
  0            
  0            
333 0 0         else {
334             my %args = @_;
335             $args = \%args;
336 0     0 0   }
  0            
  0            
  0            
337 0 0          
338             return if !$args->{run};
339 0            
340             $args->{handler} = $args->{handler} || "_fork_said";
341             $args->{arguments} = $args->{arguments} || [];
342 0     0 0    
  0            
  0            
  0            
343 0 0         #$poe_kernel->state( $args->{handler}, $args->{callback} || $self );
344              
345 0           my $run;
346             $run = sub {
347             my ($stdout, $stderr, $result) = capture {
348             return scalar $args->{run}->($args->{body}, @{ $args->{arguments} });
349 0     0 0   };
350 0           return $stdout || $result;
351             };
352 0 0          
353 0           my $pt = $self->{irssi}->bg_do($run, $args->{handler});
354              
355             # store the wheel object in our bot, so we can retrieve/delete easily
356 0            
357 0           $self->{forks}{ $pt } = {
358             args => {
359             conn_tag => $self->{conn_tag},
360 0 0         channel => $args->{channel},
361             who => $args->{who},
362 0   0       address => $args->{address}
363 0   0       }
364             };
365             return;
366             }
367 0            
368             my $func = $rhandler =~ /::/ ? $rhandler : "Bot::BasicBot::Pluggable::$rhandler";
369             my $args = $self->{forks}{$pipetag};
370 0           $args = $args->{args} if $args;
  0            
371 0     0     local $self->{conn_tag} = $args->{conn_tag};
372 0   0       local $self->{server} = Irssi::server_find_tag($args->{conn_tag})
373 0           if $args->{conn_tag};
374              
375 0           my @args;
376             $args[OBJECT] = $self;
377             $args[ARG0] = $data;
378             $args[ARG1] = $pipetag;
379             {
380             no strict 'refs';
381             &$func(@args);
382             }
383              
384             delete $self->{forks}{$pipetag};
385             }
386 0            
387 0           my ($bot, $body, $wheel_id) = @_[OBJECT, ARG0, ARG1];
388             chomp $body; # remove newline necessary to move data;
389              
390 0     0 0   # pick up the default arguments we squirreled away earlier
  0            
  0            
  0            
  0            
  0            
391 0 0         my $args = $bot->{forks}{$wheel_id}{args};
392 0           $args->{body} = $body;
393 0 0          
394 0           $bot->say($args);
395             return;
396 0 0         }
397              
398 0           BEGIN {
399 0           my @dispatchable_events = (
400 0           qw/
401 0           connected chanjoin chanpart userquit nick_change
402             topic kicked raw_in raw_out
403 1     1   7 /
  1         2  
  1         180  
  0            
404 0           );
405             my @priority_events = (qw/ said emoted /);
406             {
407 0           ## no critic qw(ProhibitNoStrict)
408             no strict 'refs';
409             for my $event (@dispatchable_events) {
410             *$event = sub {
411 0     0     shift->dispatch( $event, @_ );
412 0           };
413             }
414             for my $event (@priority_events) {
415 0           *$event = sub {
416 0           shift->dispatch_priorities( $event, @_ );
417             };
418 0           }
419 0           }
420             }
421              
422             $mess->{body} =~ s/^help\s*//i;
423 1     1   6 my $logger = Irssi::Log::Log4perl->get_logger( ref $self );
424              
425             unless ( $mess->{body} ) {
426             return
427             "Ask me for help about: "
428             . join( ", ", $self->modules() )
429 1         3 . " (say 'help <modulename>').";
430             }
431             else {
432 1     1   8 if ( my $module = $self->module( $mess->{body} ) ) {
  1         1  
  1         115  
  1         2  
433 1         3 try {
434             return $module->help($mess);
435 0     0   0 }
436 9         41 catch {
437             $logger->warn(
438 1         2 "Error calling help for module $mess->{body}: $_");
439             }
440 0     0   0 }
441 2         355 else {
442             return "I don't know anything about '$mess->{body}'.";
443             }
444             }
445             }
446 0     0 0    
  0            
  0            
  0            
447 0           return bless +{ bot => $self, irssi => $self->{irssi}, conn_tag => ($network // $self->{conn_tag}) }
448 0           => 'Irssi::Bot::BasicBot::Pluggable::PoCoIrc';
449             }
450 0 0          
451             my $logger = Irssi::Log::Log4perl->get_logger( ref $self );
452 0            
453             # it's safe to die here, mostly this call is eval'd.
454             $logger->logdie("Cannot load module without a name") unless $module;
455             $logger->logdie("Module $module already loaded") if $self->module($module);
456              
457 0 0         # This is possible a leeeetle bit evil.
458             $logger->info("Loading module $module");
459 0     0     my $filename = $module;
460             $filename =~ s{::}{/}g;
461             my $file = "Bot/BasicBot/Pluggable/Module/$filename.pm";
462 0     0     $file = "Irssi/Bot/BasicBot/Pluggable/Module/Auth.pm"
463             if lc $module eq 'auth';
464             $logger->debug("Loading module $module from file $file");
465 0            
466             # force a reload of the file (in the event that we've already loaded it).
467 0           no warnings 'redefine';
468             delete $INC{$file};
469              
470             try { require $file } catch { die "Can't load $module: $_"; };
471              
472 0     0 0   # Ok, it's very evil. Don't bother me, I'm working.
  0            
  0            
  0            
473 0   0        
474             my $m = "Bot::BasicBot::Pluggable::Module::$module"->new(
475             Bot => $self,
476             );
477 0     0 0    
  0            
  0            
  0            
478 0           $logger->logdie("->new didn't return an object") unless ( $m and ref($m) );
479             $logger->logdie( ref($m) . " isn't a $module" )
480             unless ref($m) =~ /\Q$module/;
481 0 0          
482 0 0         $self->add_module( $m, $module );
483              
484             return $m;
485 0           }
486 0            
487 0           my $logger = Irssi::Log::Log4perl->get_logger( ref $self );
488 0           $logger->logdie("Cannot reload module without a name") unless $module;
489 0 0         $self->remove_module($module) if $self->module($module);
490             return $self->load($module);
491 0           }
492              
493             my $logger = Irssi::Log::Log4perl->get_logger( ref $self );
494 1     1   7 $logger->logdie("Need name") unless $module;
  1         2  
  1         725  
495 0           $logger->logdie("Not loaded") unless $self->module($module);
496             $logger->info("Unloading module $module");
497 0     0     $self->remove_module($module);
  0            
  0            
498             }
499              
500             return $self->{store};
501 0           }
502              
503             return $self->{modules}{ lc($name) };
504             }
505 0 0 0        
506 0 0         my @keys = sort {
507             my $xa = $self->module($a);
508             my $xb = $self->module($b);
509 0           (
510             ($xb->get('user_priority') || $xb->get('priority') || 0)
511 0           <=>
512             ($xa->get('user_priority') || $xa->get('priority') || 0)
513             ) || ($a cmp $b)
514 0     0 0   } keys( %{ $self->{modules} } );
  0            
  0            
  0            
515 0           return @keys if wantarray;
516 0 0         return \@keys;
517 0 0         }
518 0            
519             # deprecated
520              
521 0     0 0   my $logger = Irssi::Log::Log4perl->get_logger( ref $self );
  0            
  0            
  0            
522 0           $logger->logdie("Need a name for adding a module") unless $name;
523 0 0         $logger->logdie("Can't load a module with a duplicate name $name")
524 0 0         if $self->{modules}{ lc($name) };
525 0           $self->{modules}{ lc($name) } = $module;
526 0           }
527              
528             my $logger = Irssi::Log::Log4perl->get_logger( ref $self );
529 0     0 0   $logger->logdie("Need a name for removing a module") unless $name;
  0            
  0            
530 0           $logger->logdie("Module $name not defined")
531             unless $self->{modules}{ lc($name) };
532             $self->{modules}{ lc($name) }->stop();
533 0     0 1   delete $self->{modules}{ lc($name) };
  0            
  0            
  0            
534 0           }
535              
536             for my $module (reverse $self->modules) {
537 0     0 1   $self->remove_module($module);
  0            
  0            
538             }
539 0           return;
540 0           }
541              
542 0 0 0        
      0        
543             1;
544              
545             use strict;
546 0           use warnings;
  0            
547 0 0         use experimental 'signatures';
548 0            
549             return 0 unless $self->{conn_tag};
550             my $server = Irssi::server_find_tag($self->{conn_tag})
551             or return 0;
552 0     0 0   my $ch = $server->channel_find($channel)
  0            
  0            
  0            
553             or return 0;
554 0     0 0   my $n = $ch->nick_find($nick)
  0            
  0            
  0            
  0            
555 0           or return 0;
556 0 0         return 1;
557             }
558 0 0          
559 0           warn "connection lost: $channel",
560             return () unless $self->{conn_tag};
561             my $server = Irssi::server_find_tag($self->{conn_tag})
562 0     0 0   or warn "connection lost: $self->{conn_tag}", return ();
  0            
  0            
  0            
563 0           my $ch = $server->channel_find($channel)
564 0 0         or return ();
565             return map { $_->{nick} } $ch->nicks;
566 0 0         }
567 0            
568 0            
569             1;