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