File Coverage

blib/lib/POE/Component/IRC/Plugin/PlugMan.pm
Criterion Covered Total %
statement 106 139 76.2
branch 27 56 48.2
condition 6 11 54.5
subroutine 18 22 81.8
pod 5 9 55.5
total 162 237 68.3


line stmt bran cond sub pod time code
1             package POE::Component::IRC::Plugin::PlugMan;
2             our $AUTHORITY = 'cpan:HINRIK';
3             $POE::Component::IRC::Plugin::PlugMan::VERSION = '6.93';
4 5     5   4656 use strict;
  5         14  
  5         186  
5 5     5   28 use warnings FATAL => 'all';
  5         28  
  5         287  
6 5     5   35 use Carp;
  5         9  
  5         381  
7 5     5   37 use IRC::Utils qw( matches_mask parse_user );
  5         10  
  5         419  
8 5     5   70 use POE::Component::IRC::Plugin qw( :ALL );
  5         11  
  5         1524  
9              
10             BEGIN {
11             # Turn on the debugger's symbol source tracing
12 5     5   51 $^P |= 0x10;
13              
14             # Work around bug in pre-5.8.7 perl where turning on $^P
15             # causes caller() to be confused about eval {}'s in the stack.
16             # (See http://rt.perl.org/rt3/Ticket/Display.html?id=35059 for more info.)
17 5 50       9697 eval 'sub DB::sub' if $] < 5.008007;
18             }
19              
20             sub new {
21 4     4 1 5115 my ($package) = shift;
22 4 50       24 croak "$package requires an even number of arguments" if @_ & 1;
23 4         16 my %args = @_;
24 4         22 $args{ lc $_ } = delete $args{ $_ } for keys %args;
25 4         23 return bless \%args, $package;
26             }
27              
28             ##########################
29             # Plugin related methods #
30             ##########################
31              
32             sub PCI_register {
33 4     4 0 1984 my ($self, $irc) = @_;
34              
35 4         24 $self->{irc} = $irc;
36 4         21 $irc->plugin_register( $self, 'SERVER', qw(public msg) );
37              
38             $self->{commands} = {
39             PLUGIN_ADD => sub {
40 2     2   9 my ($self, $method, $recipient, @cmd) = @_;
41 2 50       12 my $msg = $self->load(@cmd) ? 'Done.' : 'Nope';
42 2         10 $self->{irc}->yield($method => $recipient => $msg);
43             },
44             PLUGIN_DEL => sub {
45 1     1   5 my ($self, $method, $recipient, @cmd) = @_;
46 1 50       4 my $msg = $self->unload(@cmd) ? 'Done.' : 'Nope';
47 1         7 $self->{irc}->yield($method => $recipient => $msg);
48             },
49             PLUGIN_RELOAD => sub {
50 2     2   10 my ($self, $method, $recipient, @cmd) = @_;
51 2 50       12 my $msg = $self->reload(@cmd) ? 'Done.' : 'Nope';
52 2         10 $self->{irc}->yield($method => $recipient => $msg);
53             },
54             PLUGIN_LIST => sub {
55 0     0   0 my ($self, $method, $recipient, @cmd) = @_;
56 0         0 my @aliases = keys %{ $self->{irc}->plugin_list() };
  0         0  
57 0 0       0 my $msg = @aliases
58             ? 'Plugins [ ' . join(', ', @aliases ) . ' ]'
59             : 'No plugins loaded.';
60 0         0 $self->{irc}->yield($method => $recipient => $msg);
61             },
62             PLUGIN_LOADED => sub {
63 0     0   0 my ($self, $method, $recipient, @cmd) = @_;
64 0         0 my @aliases = $self->loaded();
65 0 0       0 my $msg = @aliases
66             ? 'Managed Plugins [ ' . join(', ', @aliases ) . ' ]'
67             : 'No managed plugins loaded.';
68 0         0 $self->{irc}->yield($method => $recipient => $msg);
69             },
70 4         291 };
71              
72 4         22 return 1;
73             }
74              
75             sub PCI_unregister {
76 4     4 0 1564 my ($self, $irc) = @_;
77 4         13 delete $self->{irc};
78 4         12 return 1;
79             }
80              
81             sub S_public {
82 6     6 0 282 my ($self, $irc) = splice @_, 0 , 2;
83 6         12 my $who = ${ $_[0] };
  6         15  
84 6         11 my $channel = ${ $_[1] }->[0];
  6         16  
85 6         11 my $what = ${ $_[2] };
  6         13  
86 6         25 my $me = $irc->nick_name();
87              
88 6         141 my ($command) = $what =~ m/^\s*\Q$me\E[:,;.!?~]?\s*(.*)$/i;
89 6 100 66     52 return PCI_EAT_NONE if !$command || !$self->_authed($who, $channel);
90              
91 5         238 my (@cmd) = split(/ +/, $command);
92 5         18 my $cmd = uc (shift @cmd);
93              
94 5 50       23 if (defined $self->{commands}->{$cmd}) {
95 5         20 $self->{commands}->{$cmd}->($self, 'privmsg', $channel, @cmd);
96             }
97              
98 5         591 return PCI_EAT_NONE;
99             }
100              
101             sub S_msg {
102 0     0 0 0 my ($self, $irc) = splice @_, 0 , 2;
103 0         0 my $who = ${ $_[0] };
  0         0  
104 0         0 my $nick = parse_user($who);
105 0         0 my $channel = ${ $_[1] }->[0];
  0         0  
106 0         0 my $command = ${ $_[2] };
  0         0  
107 0         0 my (@cmd) = split(/ +/,$command);
108 0         0 my $cmd = uc (shift @cmd);
109              
110 0 0       0 return PCI_EAT_NONE if !$self->_authed($who, $channel);
111              
112 0 0       0 if (defined $self->{commands}->{$cmd}) {
113 0         0 $self->{commands}->{$cmd}->($self, 'notice', $nick, @cmd);
114             }
115              
116 0         0 return PCI_EAT_NONE;
117             }
118              
119             ###############################
120             # Plugin manipulation methods #
121             ###############################
122              
123             sub load {
124 7     7 1 1047 my ($self, $desc, $plugin) = splice @_, 0, 3;
125 7 50 33     71 return if !$desc || !$plugin;
126              
127 7         18 my $object;
128 7   66     33 my $module = ref $plugin || $plugin;
129 7 100       24 if (! ref $plugin){
130 6 50       46 $module .= '.pm' if $module !~ /\.pm$/;
131 6         40 $module =~ s/::/\//g;
132              
133 6         637 eval "require $plugin";
134 6 50       1006 if ($@) {
135 0         0 my $error = $@;
136 0         0 delete $INC{$module};
137 0         0 $self->_unload_subs($plugin);
138 0         0 die $error;
139             }
140              
141 6         49 $object = $plugin->new( @_ );
142 6 50       64 return if !$object;
143             } else {
144 1         2 $object = $plugin;
145 1         5 $plugin = ref $object;
146             }
147              
148 7         23 my $args = [ @_ ];
149 7         31 $self->{plugins}->{ $desc }->{module} = $module;
150 7         20 $self->{plugins}->{ $desc }->{plugin} = $plugin;
151              
152 7         48 my $return = $self->{irc}->plugin_add( $desc, $object );
153 7 50       1507 if ( $return ) {
154             # Stash away arguments for use later by _reload.
155 7         21 $self->{plugins}->{ $desc }->{args} = $args;
156             }
157             else {
158             # Cleanup
159 0         0 delete $self->{plugins}->{ $desc };
160             }
161              
162 7         36 return $return;
163             }
164              
165             sub unload {
166 6     6 1 20 my ($self, $desc) = splice @_, 0, 2;
167 6 50       19 return if !$desc;
168              
169 6         54 my $plugin = $self->{irc}->plugin_del( $desc );
170 6 50       1112 return if !$plugin;
171 6         22 my $module = $self->{plugins}->{ $desc }->{module};
172 6         16 my $file = $self->{plugins}->{ $desc }->{plugin};
173 6         18 delete $INC{$module};
174 6         14 delete $self->{plugins}->{ $desc };
175 6         237 $self->_unload_subs($file);
176 6         35 return 1;
177             }
178              
179             sub _unload_subs {
180 6     6   14 my $self = shift;
181 6   50     24 my $file = shift || return;
182              
183 6         1261 for my $sym ( grep { index( $_, "$file:" ) == 0 } keys %DB::sub ) {
  3559         7077  
184 48         71 eval { undef &$sym };
  48         483  
185 48 50       111 warn "$sym: $@\n" if $@;
186 48         96 delete $DB::sub{$sym};
187             }
188              
189 6         200 return 1;
190             }
191              
192             sub reload {
193 3     3 1 13 my ($self, $desc) = splice @_, 0, 2;
194 3 50       15 return if !defined $desc;
195              
196 3         9 my $plugin_state = $self->{plugins}->{ $desc };
197 3 50       14 return if !$plugin_state;
198 3 50       13 warn "Unloading plugin $desc\n" if $self->{debug};
199 3 50       15 return if !$self->unload( $desc );
200              
201 3 50       14 warn "Loading plugin $desc " . $plugin_state->{plugin} . ' [ ' . join(', ',@{ $plugin_state->{args} }) . " ]\n" if $self->{debug};
  0         0  
202 3 50       11 return if !$self->load( $desc, $plugin_state->{plugin}, @{ $plugin_state->{args} } );
  3         17  
203 3         21 return 1;
204             }
205              
206             sub loaded {
207 0     0 1 0 my $self = shift;
208 0         0 return keys %{ $self->{plugins} };
  0         0  
209             }
210              
211             sub _authed {
212 6     6   18 my ($self, $who, $chan) = @_;
213              
214 6 100       32 return $self->{auth_sub}->($self->{irc}, $who, $chan) if $self->{auth_sub};
215 3 50       14 return 1 if matches_mask($self->{botowner}, $who);
216 0           return;
217             }
218              
219             1;
220              
221             =encoding utf8
222              
223             =head1 NAME
224              
225             POE::Component::IRC::Plugin::PlugMan - A PoCo-IRC plugin that provides plugin
226             management services.
227              
228             =head1 SYNOPSIS
229              
230             use strict;
231             use warnings;
232             use POE qw(Component::IRC::State);
233             use POE::Component::IRC::Plugin::PlugMan;
234              
235             my $botowner = 'somebody!*@somehost.com';
236             my $irc = POE::Component::IRC::State->spawn();
237              
238             POE::Session->create(
239             package_states => [
240             main => [ qw(_start irc_plugin_add) ],
241             ],
242             );
243              
244             sub _start {
245             $irc->yield( register => 'all' );
246             $irc->plugin_add( 'PlugMan' => POE::Component::IRC::Plugin::PlugMan->new( botowner => $botowner ) );
247             return;
248             }
249              
250             sub irc_plugin_add {
251             my ($desc, $plugin) = @_[ARG0, ARG1];
252              
253             if ($desc eq 'PlugMan') {
254             $plugin->load( 'Connector', 'POE::Component::IRC::Plugin::Connector' );
255             }
256             return;
257             }
258              
259             =head1 DESCRIPTION
260              
261             POE::Component::IRC::Plugin::PlugMan is a POE::Component::IRC plugin management
262             plugin. It provides support for 'on-the-fly' loading, reloading and unloading
263             of plugin modules, via object methods that you can incorporate into your own
264             code and a handy IRC interface.
265              
266             =head1 METHODS
267              
268             =head2 C
269              
270             Takes two optional arguments:
271              
272             B<'botowner'>, an IRC mask to match against for people issuing commands via the
273             IRC interface;
274              
275             B<'auth_sub'>, a sub reference which will be called to determine if a user
276             may issue commands via the IRC interface. Overrides B<'botowner'>. It will be
277             called with three arguments: the IRC component object, the nick!user@host and
278             the channel name as arguments. It should return a true value if the user is
279             authorized, a false one otherwise.
280              
281             B<'debug'>, set to a true value to see when stuff goes wrong;
282              
283             Not setting B<'botowner'> or B<'auth_sub'> effectively disables the IRC
284             interface.
285              
286             If B<'botowner'> is specified the plugin checks that it is being loaded into a
287             L or sub-class and will
288             fail to load otherwise.
289              
290             Returns a plugin object suitable for feeding to
291             L's C method.
292              
293             =head2 C
294              
295             Loads a managed plugin.
296              
297             Takes two mandatory arguments, a plugin descriptor and a plugin package name.
298             Any other arguments are used as options to the loaded plugin constructor.
299              
300             $plugin->load( 'Connector', 'POE::Component::IRC::Plugin::Connector', delay, 120 );
301              
302             Returns true or false depending on whether the load was successfully or not.
303              
304             =head2 C
305              
306             Unloads a managed plugin.
307              
308             Takes one mandatory argument, a plugin descriptor.
309              
310             $plugin->unload( 'Connector' );
311              
312             Returns true or false depending on whether the unload was successfully or not.
313              
314             =head2 C
315              
316             Unloads and loads a managed plugin, with applicable plugin options.
317              
318             Takes one mandatory argument, a plugin descriptor.
319              
320             $plugin->reload( 'Connector' );
321              
322             =head2 C
323              
324             Takes no arguments.
325              
326             $plugin->loaded();
327              
328             Returns a list of descriptors of managed plugins.
329              
330             =head1 INPUT
331              
332             An IRC interface is enabled by specifying a "botowner" mask to
333             L|/new>. Commands may be either invoked via a PRIVMSG directly to
334             your bot or in a channel by prefixing the command with the nickname of your
335             bot. One caveat, the parsing of the irc command is very rudimentary (it
336             merely splits the line on spaces).
337              
338             =head2 C
339              
340             Takes the same arguments as L|/load>.
341              
342             =head2 C
343              
344             Takes the same arguments as L|/unload>.
345              
346             =head2 C
347              
348             Takes the same arguments as L|/reload>.
349              
350             =head2 C
351              
352             Returns a list of descriptors of managed plugins.
353              
354             =head2 C
355              
356             Returns a list of descriptors of *all* plugins loaded into the current PoCo-IRC
357             component.
358              
359             =head1 AUTHOR
360              
361             Chris 'BinGOs' Williams
362              
363             =head1 SEE ALSO
364              
365             L
366              
367             L
368              
369             =cut