File Coverage

blib/lib/POE/Component/IRC/Plugin/CPAN/Info.pm
Criterion Covered Total %
statement 18 244 7.3
branch 0 140 0.0
condition 0 15 0.0
subroutine 6 26 23.0
pod 0 6 0.0
total 24 431 5.5


line stmt bran cond sub pod time code
1             package POE::Component::IRC::Plugin::CPAN::Info;
2              
3 1     1   246424 use warnings;
  1         2  
  1         38  
4 1     1   6 use strict;
  1         2  
  1         43  
5              
6             our $VERSION = '1.001002'; # VERSION
7              
8 1     1   4 use Carp;
  1         2  
  1         76  
9 1     1   6 use POE;
  1         2  
  1         9  
10 1     1   460 use POE::Component::IRC::Plugin (qw( :ALL ));
  1         3  
  1         160  
11 1     1   1217 use POE::Component::CPAN::SQLite::Info;
  1         262222  
  1         4784  
12              
13             sub new {
14 0     0 0   my $package = shift;
15 0           my %args = @_;
16 0           $args{ lc $_ } = delete $args{ $_ } for keys %args;
17              
18             # load defaults and override with user args if any
19 0           %args = (
20             mirror => 'http://cpan.perl.org/',
21             path => 'cpan_sqlite_info',
22             debug => 0,
23             freshen_interval => 43200,
24             got_info_event => 'cpaninfo_got_info',
25             no_result_event => 'cpaninfo_no_result',
26             response_event => 'cpaninfo_response',
27             respond_no_result => 1,
28             send_events => 1,
29             show_help => 1,
30             eat => 0,
31             listen_for_help => [ qw(public notice privmsg) ],
32             listen_for_input => [ qw(public notice privmsg) ],
33             no_result_responses => [ 'No clue', 'No idea' ],
34             max_modules_limit => 5,
35             max_modules_length => 300,
36             max_output_length => 600,
37             max_output_length_pub => 400,
38             output_line_length => 300,
39             banned => [],
40             %args,
41             );
42              
43 0           for my $listen_type (qw( listen_for_help listen_for_input )) {
44 0           $args{ $listen_type } = {
45 0           map { lc $_ => 1 }
46 0           @{ $args{ $listen_type } }
47             };
48             }
49              
50 0 0         unless ( exists $args{ua_args}{timeout} ) {
51 0           $args{ua_args}{timeout} = 30;
52             }
53              
54 0 0 0       if ( exists $args{channels} and ref $args{channels} ne 'ARRAY' ) {
55 0           carp "Argument `channels` must contain an arrayref..";
56 0           return;
57             }
58              
59             # assign default triggers to anything not specified by user.
60 0           my $default_triggers_ref = _make_default_triggers();
61 0 0         if ( exists $args{triggers} ) {
62              
63 0           foreach my $trigger_category (qw( mod dist auth )) {
64 0           my $cat_triggers = $default_triggers_ref->{ $trigger_category};
65              
66 0 0         $args{triggers}{ $trigger_category } = {
67             %$cat_triggers,
68 0           %{ $args{triggers}{ $trigger_category } || {} },
69             };
70              
71 0           my $cat_trigger = "${trigger_category}_cat";
72 0 0         unless ( exists $args{triggers}{ $cat_trigger } ) {
73 0           $args{trigger}{ $cat_trigger }
74             = $default_triggers_ref->{ $cat_trigger};
75             }
76             }
77             }
78             else {
79 0           $args{triggers} = $default_triggers_ref;
80             }
81              
82             # assign default help triggers for anything not specified by user
83 0           my $default_help_ref = _make_default_help_triggers();
84 0 0         if ( exists $args{help} ) {
85 0           foreach my $category (qw(mod dist auth)) {
86 0           my $cat_help = $default_help_ref->{ $category };
87              
88 0 0         $args{help}{ $category } = {
89             %$cat_help,
90 0           %{ $args{help}{ $category } || {} },
91             };
92              
93 0           my $cat_trigger = "${category}_cat";
94 0 0         unless ( exists $args{help}{ $cat_trigger } ) {
95 0           $args{help}{ $cat_trigger }
96             = $default_help_ref->{help}{ $cat_trigger };
97             }
98             }
99             }
100             else {
101 0           $args{help} = $default_help_ref;
102             }
103              
104 0 0         unless ( exists $args{help}{help_re} ) {
105 0           $args{help}{help_re} = $default_help_ref->{help_re};
106             }
107              
108 0           return bless \%args, $package;
109             }
110              
111             sub PCI_register {
112 0     0 0   my ( $self, $irc ) = splice @_, 0, 2;
113              
114 0           $self->{irc} = $irc;
115              
116 0           $irc->plugin_register( $self, 'SERVER', qw(notice public msg) );
117              
118 0           $self->{_session_id} = POE::Session->create(
119             object_states => [
120             $self => [
121             qw(
122             _start
123             _shutdown
124             _fetched
125             _freshen
126             _got_info
127             )
128             ]
129             ],
130             )->ID;
131              
132              
133 0           return 1;
134             }
135              
136             sub _start {
137 0     0     my ( $kernel, $self ) = @_[ KERNEL, OBJECT ];
138 0           $self->{_session_id} = $_[SESSION]->ID();
139 0           $kernel->refcount_increment( $self->{_session_id}, __PACKAGE__ );
140              
141 0           $self->{poco} = POE::Component::CPAN::SQLite::Info->spawn(
142 0           map { $_, $self->{$_} }
143             qw(mirror debug path)
144             );
145              
146 0 0         warn "Sending `freshen` request to PoCo::CPAN::SQLite::Info"
147             if $self->{debug};
148              
149 0           $self->{poco}->freshen( {
150             event => '_fetched',
151             ua_args => $self->{ua_args},
152             }
153             );
154 0           $self->{_freshen_alarm} = $kernel->delay(
155             '_freshen' => $self->{freshen_interval}
156             );
157 0           undef;
158             }
159              
160             sub _shutdown {
161 0     0     my ($kernel, $self) = @_[ KERNEL, OBJECT ];
162 0           $self->{poco}->shutdown;
163 0           $kernel->alarm_remove_all();
164 0           $kernel->refcount_decrement( $self->{_session_id}, __PACKAGE__ );
165 0           undef;
166             }
167              
168             sub PCI_unregister {
169 0     0 0   my $self = shift;
170              
171             # Plugin is dying make sure our POE session does as well.
172 0           $poe_kernel->call( $self->{_session_id} => '_shutdown' );
173              
174 0           delete $self->{irc};
175              
176 0           return 1;
177             }
178              
179              
180             sub _freshen {
181 0     0     my ( $kernel, $self ) = @_[ KERNEL, OBJECT ];
182              
183 0           $self->{poco}->freshen( {
184             event => '_fetched',
185             ua_args => $self->{ua_args},
186             }
187             );
188              
189 0           $self->{_freshen_alarm} = $kernel->delay(
190             '_freshen' => $self->{freshen_delay}
191             );
192             }
193              
194             sub _fetched {
195 0     0     my ( $kernel, $self, $input ) = @_[ KERNEL, OBJECT, ARG0 ];
196              
197 0 0         if ( $input->{freshen_error} ) {
198 0 0         if ( $self->{debug} ) {
199 0 0         if ( $input->{freshen_error} eq 'fetch' ) {
200 0           warn "Could not fetch file(s)\n";
201 0           foreach my $file ( keys %{ $input->{freshen_errors} } ) {
  0            
202 0           print "\t$file => $input->{freshen_errors}{ $file }\n";
203             }
204             }
205             else {
206 0           warn "Failed to create storage dir:"
207             . " $input->{freshen_error}\n";
208             }
209             }
210 0 0         $self->{_freshen_alarm} = $kernel->delay(
211             '_freshen' => $self->{freshen_interval} < 30
212             ? $self->{freshen_interval} : 30,
213             );
214             }
215             else {
216 0 0         warn "`freshen` success sending `fetch_info` request"
217             if $self->{debug};
218              
219 0           $self->{poco}->fetch_info( { event => '_got_info' } );
220             }
221             }
222              
223             sub _got_info {
224 0     0     my ( $self, $input ) = @_[ OBJECT, ARG0 ];
225              
226 0 0         warn "_got_info success"
227             if $self->{debug};
228              
229 0           delete $input->{path};
230              
231 0           $self->{_data} = $input;
232              
233 0 0         $self->{irc}->send_event( $self->{got_info_event} => time() )
234             if $self->{send_events};
235             }
236              
237             sub S_public {
238 0     0 0   my ( $self, $irc ) = splice @_, 0, 2;
239 0           my $who = ${ $_[0] };
  0            
240 0           my $channel = ${ $_[1] }->[0];
  0            
241 0           my $message = ${ $_[2] };
  0            
242 0           return $self->_parse_input( $irc, $who, $channel, $message, 'public' );
243             }
244              
245             sub S_notice {
246 0     0 0   my ( $self, $irc ) = splice @_, 0, 2;
247 0           my $who = ${ $_[0] };
  0            
248 0           my $channel = ${ $_[1] }->[0];
  0            
249 0           my $message = ${ $_[2] };
  0            
250 0           return $self->_parse_input( $irc, $who, $channel, $message, 'notice' );
251             }
252              
253             sub S_msg {
254 0     0 0   my ( $self, $irc ) = splice @_, 0, 2;
255 0           my $who = ${ $_[0] };
  0            
256 0           my $channel = ${ $_[1] }->[0];
  0            
257 0           my $message = ${ $_[2] };
  0            
258 0           return $self->_parse_input( $irc, $who, $channel, $message, 'privmsg' );
259             }
260              
261             sub _parse_input {
262 0     0     my ( $self, $irc, $who, $channel, $message, $type ) = @_;
263              
264 0 0 0       return PCI_EAT_NONE
265             if !exists $self->{listen_for_input}{ $type }
266             and !$self->{send_events};
267              
268 0 0         foreach my $ban_re ( @{ $self->{banned} || [] } ) {
  0            
269 0 0         return PCI_EAT_NONE
270             if $who =~ /$ban_re/;
271             }
272              
273 0           my $my_nick = $irc->nick_name();
274 0           my $what;
275 0 0         if ( $type eq 'public' ) {
276 0           ($what) = $message =~ m/^\s*\Q$my_nick\E[\:\,\;\.]?\s*(.*)$/i;
277             }
278             else {
279 0           $what = $message;
280             }
281              
282 0 0         return PCI_EAT_NONE
283             unless defined $what;
284              
285 0 0         warn "Got PUBLIC input: [ who => $who, channel => $channel, "
286             . "what => $what ]"
287             if $self->{debug};
288              
289 0           my $response;
290              
291 0           eval { $response = $self->_parse_commands( $what ); };
  0            
292             # if $@ => did not match trigger,
293             # if undef $response => no data available for this request
294 0 0         if ( $@ ) {
295 0 0 0       $response = $self->_parse_help( $what )
296             if $self->{help}
297             and exists $self->{listen_for_help}{ $type };
298              
299 0 0 0       warn "_parse_command( $what ) did not match anything"
300             if $self->{debug} and not defined $response;
301              
302 0 0         return PCI_EAT_NONE
303             unless defined $response;
304             }
305              
306              
307 0           my ( $nick ) = split /!/, $who;
308 0 0         unless ( defined $response ) {
309 0 0         if ( $self->{respond_no_result} ) {
310 0           my $responses_ref = $self->{no_result_responses};
311              
312 0 0         if ( $type eq 'public' ) {
313 0           $poe_kernel->post( $irc => privmsg => $channel =>
314             "$nick, " . $responses_ref->[ rand @$responses_ref ]
315             );
316             }
317             else {
318 0           $poe_kernel->post( $irc => $type => $nick =>
319             $responses_ref->[ rand @$responses_ref ]
320             );
321             }
322             }
323 0 0         $self->{irc}->send_event(
324             $self->{no_result_event} => {
325             'time' => time(),
326             who => $who,
327             channel => $channel,
328             what => $message,
329             type => $type,
330             }
331             ) if $self->{send_events};
332              
333 0           return PCI_EAT_NONE;
334             }
335              
336 0 0         warn "Got response `$response`"
337             if $self->{debug};
338              
339             my $max_length = $type eq 'public'
340             ? $self->{max_output_length_pub}
341 0 0         : $self->{max_output_length };
342              
343 0 0         if ( length $response > $max_length ) {
344 0           $response = substr $response, 0, $max_length - 3;
345 0           $response .= '...';
346             }
347              
348             # break long output into several lines to prefed "Excess Flood" drops
349 0           my @responses;
350 0           while ( length $response > $self->{output_line_length} ) {
351 0           push @responses, substr $response, 0, $self->{output_line_length};
352 0           $response = substr $response, $self->{output_line_length};
353             }
354 0           push @responses, $response;
355              
356 0 0         if ( exists $self->{ listen_for_input }{ $type } ) {
357 0 0         if ( $type eq 'public' ) {
358             $poe_kernel->post( $irc => privmsg => $channel =>
359             "$nick, $_"
360 0           ) for @responses;
361             }
362             else {
363             $poe_kernel->post( $irc => $type => $nick => $_ )
364 0           for @responses;
365             }
366             }
367              
368 0 0         if ( $self->{send_events} ) {
369 0           for ( @responses ) {
370 0           $self->{irc}->send_event(
371             $self->{response_event} => {
372             'time' => time(),
373             who => $who,
374             channel => $channel,
375             what => $message,
376             type => $type,
377             response => $_
378             }
379             );
380             }
381             }
382              
383 0 0         return $self->{eat} ? PCI_EAT_ALL : PCI_EAT_NONE;
384             }
385              
386             sub _parse_help {
387 0     0     my ( $self, $what ) = @_;
388 0           my $trigs = _make_default_help_triggers();
389              
390             return
391 0 0 0       unless defined $what and $what =~ s/$trigs->{help_re}//;
392              
393 0 0         return $self->_make_help_list
394             unless length $what;
395              
396              
397 0           my $help_data_ref = _make_help_data();
398 0           $what =~ s/^\s+|\s+$//g;
399 0           $what = lc $what;
400 0 0         if ( $what =~ s/^$trigs->{mod_cat}//i ) {
    0          
    0          
401 0           return $help_data_ref->{mod}{ $what };
402             }
403             elsif ( $what =~ s/^$trigs->{auth_cat}//i ) {
404 0           return $help_data_ref->{auth}{ $what };
405              
406             }
407             elsif ( $what =~ s/^$trigs->{dist_cat}//i ) {
408 0           return $help_data_ref->{dist}{ $what };
409             }
410 0           return;
411             }
412              
413             sub _parse_commands {
414 0     0     my ( $self, $what ) = @_;
415              
416 0           my $response;
417 0 0         if ( $what =~ s/$self->{triggers}{mod_cat}// ) {
    0          
    0          
418 0           my $triggers = $self->{triggers}{mod};
419              
420 0 0         if ( $what =~ s/$triggers->{distname}// ) {
    0          
    0          
    0          
    0          
421 0           $response = $self->_make_info( mods => $what => 'dist_name' );
422             }
423             elsif ( $what =~ s/$triggers->{version}// ) {
424 0           $response = $self->_make_info( mods => $what => 'mod_vers' );
425             }
426             elsif ( $what =~ s/$triggers->{desc}// ) {
427 0           $response = $self->_make_info( mods => $what => 'mod_abs' );
428             }
429             elsif ( $what =~ s/$triggers->{chapter}// ) {
430 0           $response = $self->_make_info( mods => $what => 'chapterid' );
431             }
432             elsif ( $what =~ s/$triggers->{dslip}// ) {
433 0           $response = $self->_make_info( mods => $what => 'dslip' );
434             }
435             else {
436 0           die;
437             }
438             }
439             elsif ( $what =~ s/$self->{triggers}{auth_cat}// ) {
440 0           my $triggers = $self->{triggers}{auth};
441              
442 0 0         if ( $what =~ s/$triggers->{email}// ) {
    0          
443 0           $response = $self->_make_info(auths => uc $what => 'email' );
444             }
445             elsif ( $what =~ s/$triggers->{name}// ) {
446 0           $response
447             = $self->_make_info( auths => uc $what => 'fullname' );
448             }
449             else {
450 0           die
451             }
452             }
453             elsif ( $what =~ s/$self->{triggers}{dist_cat}// ) {
454 0           my $triggers = $self->{triggers}{dist};
455 0 0         if ( $what =~ s/$triggers->{version}// ) {
    0          
    0          
    0          
    0          
    0          
456 0           $response = $self->_make_info( dists => $what => 'dist_vers');
457             }
458             elsif ( $what =~ s/$triggers->{file}// ) {
459 0           $response = $self->_make_info( dists => $what => 'dist_file');
460             }
461             elsif ( $what =~ s/$triggers->{auth}// ) {
462 0           $response = $self->_make_info( dists => $what => 'cpanid' );
463             }
464             elsif ( $what =~ s/$triggers->{desc}// ) {
465 0           $response = $self->_make_info( dists => $what => 'dist_abs' );
466             }
467             elsif ( $what =~ s/$triggers->{mods}// ) {
468 0           $response = $self->_make_info( dists => $what => 'modules' );
469             }
470             elsif ( $what =~ s/$triggers->{chapter}// ) {
471 0           $response = $self->_make_info( dists => $what => 'chapterid' );
472             }
473             else {
474 0           die;
475             }
476             }
477             else {
478 0           die;
479             }
480 0           return $response;
481             }
482              
483             sub _make_help_list {
484 0     0     my $self = shift;
485              
486 0           my $help_data_ref = _make_default_help_triggers();
487 0           my @help_list;
488 0           foreach my $category (qw( dist mod auth )) {
489 0           my $cat_prefix = $help_data_ref->{ $category . '_cat' };
490 0           push @help_list, join q|, |,
491 0           map { $cat_prefix . $_ }
492 0           sort keys %{ $help_data_ref->{ $category } };
493             }
494 0           return join q|, |, @help_list;
495             }
496              
497             sub _make_info {
498 0     0     my ( $self, $category, $item, $section ) = @_;
499             return
500 0 0         unless defined $section;
501              
502 0           $item =~ s/^\s+|\s+$//g;
503             return
504 0 0         unless length $item;
505              
506 0 0         unless ( exists $self->{_data}{ $category }{ $item } ) {
507 0 0         warn "Did not find {_data}{ $category }{ $item }"
508             if $self->{debug};
509              
510 0           return;
511             }
512              
513 0           my $data = $self->{_data}{ $category }{ $item };
514              
515              
516 0 0         if ( exists $data->{ $section } ) {
517 0 0         if ( $category eq 'dists' ) {
518 0 0         if ( $section eq 'modules' ) {
    0          
519 0           return $self->_prepare_dist_modules( $data->{ $section } );
520             }
521             elsif ( $section eq 'chapterid' ) {
522 0           return $self->_prepare_dist_chapterid($data->{ $section });
523             }
524             }
525 0           return $data->{ $section };
526             }
527             else {
528 0           return;
529             }
530             }
531              
532             sub _prepare_dist_modules {
533 0     0     my ( $self, $modules_ref ) = @_;
534             return
535 0 0         unless ref $modules_ref eq 'HASH';
536              
537 0           my @modules = keys %$modules_ref;
538              
539 0 0         if ( @modules > $self->{max_modules_limit} ) {
540 0           return "Uses " . @modules . " modules...";
541             }
542             else {
543 0           my $mods = join ' | ', @modules;
544 0 0         if ( length $mods > $self->{max_modules_length} ) {
545 0           $mods = "(total: " . @modules . " ) $mods";
546 0           $mods = substr $mods, 0, $self->{max_modules_length} - 3;
547 0           $mods .= '...';
548             }
549 0           return $mods;
550             }
551             }
552              
553             sub _make_default_help_triggers {
554             return {
555 0     0     help_re => qr/^help\s*/i,
556             mod_cat => 'mod_',
557             mod => {
558             distname => 'distname',
559             version => 'version',
560             desc => 'desc',
561             chapter => 'chapter',
562             dslip => 'dslip',
563             },
564             auth_cat => 'auth_',
565             auth => {
566             email => 'email',
567             name => 'name',
568             },
569              
570             dist_cat => 'dist_',
571             dist => {
572             version => 'version',
573             file => 'file',
574             auth => 'auth',
575             desc => 'desc',
576             mods => 'mods',
577             chapter => 'chapter',
578             },
579             };
580             }
581              
582             sub _make_help_data {
583             return {
584 0     0     mod => {
585             distname => q|Which distribution the module is in|,
586             version => q|Module's version|,
587             desc => q|Module's description|,
588             chapter => q|Module's chapter|,
589             dslip => q|Module's DSLIP code|,
590             },
591             auth => {
592             email => q|Author's e-mail address|,
593             name => q|Author's full name|,
594             },
595             dist => {
596             version => q|Distribution's version|,
597             file => q|Distribution's CPAN filename|,
598             auth => q|Distribution's author|,
599             desc => q|Distribution's description|,
600             mods => q|List modules included in the distribution|,
601             chapter => q|Chapter and subchapter of the distribution|,
602             },
603             };
604             }
605              
606             sub _make_default_triggers {
607             return {
608 0     0     mod_cat => qr/ ^ mod_ /xi,
609             mod => {
610             distname => qr/ ^ distname \s+ /xi,
611             version => qr/ ^ version \s+ /xi,
612             desc => qr/ ^ desc \s+ /xi,
613             chapter => qr/ ^ chapter \s+ /xi,
614             dslip => qr/ ^ dslip \s+ /xi,
615             },
616              
617             auth_cat => qr/ ^auth_ /xi,
618             auth => {
619             email => qr/ ^ email \s+ /xi,
620             name => qr/ ^ name \s+ /xi,
621             },
622              
623             dist_cat => qr/ ^ dist_ /xi,
624             dist => {
625             version => qr/ ^ version \s+ /xi,
626             file => qr/ ^ file \s+ /xi,
627             auth => qr/ ^ auth \s+ /xi,
628             desc => qr/ ^ desc \s+ /xi,
629             mods => qr/ ^ mods \s+ /xi,
630             chapter => qr/ ^ chapter \s+ /xi,
631             },
632             };
633             }
634              
635             1;
636             __END__