File Coverage

blib/lib/POE/Component/IRC/Plugin/Console.pm
Criterion Covered Total %
statement 66 122 54.1
branch 9 34 26.4
condition 1 11 9.0
subroutine 14 20 70.0
pod 2 4 50.0
total 92 191 48.1


line stmt bran cond sub pod time code
1             package POE::Component::IRC::Plugin::Console;
2             $POE::Component::IRC::Plugin::Console::VERSION = '6.95';
3 2     2   2202 use strict;
  2         4  
  2         93  
4 2     2   9 use warnings FATAL => 'all';
  2         5  
  2         139  
5 2     2   11 use Carp;
  2         3  
  2         218  
6 2     2   13 use IRC::Utils qw(decode_irc);
  2         3  
  2         121  
7 2     2   12 use POE qw(Wheel::SocketFactory Wheel::ReadWrite Filter::IRCD Filter::Line Filter::Stackable);
  2         3  
  2         14  
8 2     2   1761 use POE::Component::IRC::Plugin qw( :ALL );
  2         3  
  2         311  
9 2     2   13 use Scalar::Util qw(looks_like_number);
  2         4  
  2         3593  
10              
11             sub new {
12 1     1 1 255 my $package = shift;
13 1 50       3 croak "$package requires an even number of arguments" if @_ & 1;
14 1         8 my %self = @_;
15 1         3 return bless \%self, $package;
16             }
17              
18             sub PCI_register {
19 1     1 0 567 my ($self, $irc) = splice @_, 0, 2;
20              
21 1         4 $self->{irc} = $irc;
22              
23 1         3 $irc->plugin_register( $self, 'SERVER', qw(all) );
24 1         28 $irc->plugin_register( $self, 'USER', qw(all) );
25              
26 1         33 POE::Session->create(
27             object_states => [
28             $self => [ qw(_client_error _client_flush _client_input _listener_accept _listener_failed _start _shutdown) ],
29             ],
30             );
31              
32 1         104 return 1;
33             }
34              
35             sub PCI_unregister {
36 1     1 0 490 my ($self, $irc) = splice @_, 0, 2;
37              
38 1         3 delete $self->{irc};
39 1         5 $poe_kernel->post( $self->{SESSION_ID} => '_shutdown' );
40 1         83 $poe_kernel->refcount_decrement( $self->{SESSION_ID}, __PACKAGE__ );
41 1         36 return 1;
42             }
43              
44             sub _dump {
45 9     9   28 my ($arg) = @_;
46              
47 9 50       27 if (ref $arg eq 'ARRAY') {
    50          
    100          
    50          
48 0         0 my @elems;
49 0         0 for my $elem (@$arg) {
50 0         0 push @elems, _dump($elem);
51             }
52 0         0 return '['. join(', ', @elems) .']';
53             }
54             elsif (ref $arg eq 'HASH') {
55 0         0 my @pairs;
56 0         0 for my $key (keys %$arg) {
57 0         0 push @pairs, [$key, _dump($arg->{$key})];
58             }
59 0         0 return '{'. join(', ', map { "$_->[0] => $_->[1]" } @pairs) .'}';
  0         0  
60             }
61             elsif (ref $arg) {
62 4         20 require overload;
63 4         10 return overload::StrVal($arg);
64             }
65             elsif (defined $arg) {
66 5 50       16 return $arg if looks_like_number($arg);
67 5         13 return "'".decode_irc($arg)."'";
68             }
69             else {
70 0         0 return 'undef';
71             }
72             }
73              
74             sub _default {
75 5     5   160 my ($self, $irc, $event) = splice @_, 0, 3;
76 5 50       11 return PCI_EAT_NONE if $event eq 'S_raw';
77              
78 5         5 pop @_;
79 5         8 my @args = map { $$_ } @_;
  9         16  
80 5         5 my @output;
81              
82 5         10 for my $i (0..$#args) {
83 9         440 push @output, "ARG$i: " . _dump($args[$i]);
84             }
85              
86 5         88 for my $wheel_id ( keys %{ $self->{wheels} } ) {
  5         9  
87 0 0 0     0 next if ( $self->{exit}->{ $wheel_id } or ( not defined ( $self->{wheels}->{ $wheel_id } ) ) );
88 0 0       0 next if !$self->{authed}{ $wheel_id };
89 0         0 $self->{wheels}->{ $wheel_id }->put("$event: ".join(', ', @output));
90             }
91              
92 5         13 return PCI_EAT_NONE;
93             }
94              
95             sub _start {
96 1     1   182 my ($kernel, $self) = @_[KERNEL, OBJECT];
97              
98 1         3 $self->{SESSION_ID} = $_[SESSION]->ID();
99 1         6 $kernel->refcount_increment( $self->{SESSION_ID}, __PACKAGE__ );
100 1         30 $self->{ircd_filter} = POE::Filter::Stackable->new( Filters => [
101             POE::Filter::Line->new(),
102             POE::Filter::IRCD->new(),
103             ]);
104              
105             $self->{listener} = POE::Wheel::SocketFactory->new(
106             BindAddress => 'localhost',
107 1   50     83 BindPort => $self->{bindport} || 0,
108             SuccessEvent => '_listener_accept',
109             FailureEvent => '_listener_failed',
110             Reuse => 'yes',
111             );
112              
113 1 50       938 if ($self->{listener}) {
114 1         4 $self->{irc}->send_event( 'irc_console_service' => $self->{listener}->getsockname() );
115             }
116             else {
117 0         0 $self->{irc}->plugin_del( $self );
118             }
119              
120 1         114 return;
121             }
122              
123             sub _listener_accept {
124 0     0   0 my ($kernel, $self, $socket, $peeradr, $peerport)
125             = @_[KERNEL, OBJECT, ARG0 .. ARG2];
126              
127             my $wheel = POE::Wheel::ReadWrite->new(
128             Handle => $socket,
129             InputFilter => $self->{ircd_filter},
130 0         0 OutputFilter => POE::Filter::Line->new(),
131             InputEvent => '_client_input',
132             ErrorEvent => '_client_error',
133             FlushedEvent => '_client_flush',
134             );
135              
136 0 0       0 if ( !defined $wheel ) {
137 0         0 $self->{irc}->send_event( 'irc_console_rw_fail' => $peeradr => $peerport );
138 0         0 return;
139             }
140              
141 0         0 my $wheel_id = $wheel->ID();
142 0         0 $self->{wheels}->{ $wheel_id } = $wheel;
143 0         0 $self->{authed}->{ $wheel_id } = 0;
144 0         0 $self->{exit}->{ $wheel_id } = 0;
145 0         0 $self->{irc}->send_event( 'irc_console_connect' => $peeradr => $peerport => $wheel_id );
146              
147 0         0 return;
148             }
149              
150             sub _listener_failed {
151 0     0   0 delete $_[OBJECT]->{listener};
152 0         0 return;
153             }
154              
155             sub _client_input {
156 0     0   0 my ($kernel, $self, $input, $wheel_id) = @_[KERNEL, OBJECT, ARG0, ARG1];
157              
158 0 0 0     0 if ($self->{authed}->{ $wheel_id } && lc ( $input->{command} ) eq 'exit') {
159 0         0 $self->{exit}->{ $wheel_id } = 1;
160 0 0       0 if (defined $self->{wheels}->{ $wheel_id }) {
161 0         0 $self->{wheels}->{ $wheel_id }->put("ERROR * quiting *");
162             }
163 0         0 return;
164             }
165              
166 0 0       0 if ( $self->{authed}->{ $wheel_id } ) {
167 0         0 $self->{irc}->yield( lc ( $input->{command} ) => @{ $input->{params} } );
  0         0  
168 0         0 return;
169             }
170              
171 0 0 0     0 if (lc ( $input->{command} ) eq 'pass' && $input->{params}->[0] eq $self->{password} ) {
172 0         0 $self->{authed}->{ $wheel_id } = 1;
173 0         0 $self->{wheels}->{ $wheel_id }->put('NOTICE * Password accepted *');
174 0         0 $self->{irc}->send_event( 'irc_console_authed' => $wheel_id );
175 0         0 return;
176             }
177              
178 0         0 $self->{wheels}->{ $wheel_id }->put('NOTICE * Password required * enter PASS *');
179 0         0 return;
180             }
181              
182             sub _client_flush {
183 0     0   0 my ($self, $wheel_id) = @_[OBJECT, ARG0];
184 0 0       0 return if !$self->{exit}->{ $wheel_id };
185 0         0 delete $self->{wheels}->{ $wheel_id };
186 0         0 return;
187             }
188              
189             sub _client_error {
190 0     0   0 my ($self, $wheel_id) = @_[OBJECT, ARG3];
191              
192 0         0 delete $self->{wheels}->{ $wheel_id };
193 0         0 delete $self->{authed}->{ $wheel_id };
194 0         0 $self->{irc}->send_event( 'irc_console_close' => $wheel_id );
195 0         0 return;
196             }
197              
198             sub _shutdown {
199 1     1   364 my ($kernel, $self) = @_[KERNEL, OBJECT];
200              
201 1         45 delete $self->{listener};
202 1         222 delete $self->{wheels};
203 1         2 delete $self->{authed};
204 1         4 return;
205             }
206              
207             sub getsockname {
208 0     0 1   my $self = shift;
209 0 0         return if !$self->{listener};
210 0           return $self->{listener}->getsockname();
211             }
212              
213             1;
214              
215             =encoding utf8
216              
217             =head1 NAME
218              
219             POE::Component::IRC::Plugin::Console - A PoCo-IRC plugin that provides a
220             lightweight debugging and control console for your bot
221              
222             =head1 SYNOPSIS
223              
224             use POE qw(Component::IRC Component::IRC::Plugin::Console);
225              
226             my $nickname = 'Flibble' . $$;
227             my $ircname = 'Flibble the Sailor Bot';
228             my $ircserver = 'irc.blahblahblah.irc';
229             my $port = 6667;
230             my $bindport = 6969;
231              
232             my @channels = ( '#Blah', '#Foo', '#Bar' );
233              
234             my $irc = POE::Component::IRC->spawn(
235             nick => $nickname,
236             server => $ircserver,
237             port => $port,
238             ircname => $ircname,
239             ) or die "Oh noooo! $!";
240              
241             POE::Session->create(
242             package_states => [
243             main => [ qw(_start irc_001 irc_console_service irc_console_connect
244             irc_console_authed irc_console_close irc_console_rw_fail) ],
245             ],
246             );
247              
248             $poe_kernel->run();
249              
250             sub _start {
251             $irc->plugin_add( 'Console' => POE::Component::IRC::Plugin::Console->new(
252             bindport => $bindport,
253             password => 'opensesame'
254             );
255             $irc->yield( register => 'all' );
256             $irc->yield( connect => { } );
257             return;
258             }
259              
260             sub irc_001 {
261             $irc->yield( join => $_ ) for @channels;
262             return;
263             }
264              
265             sub irc_console_service {
266             my $getsockname = $_[ARG0];
267             return;
268             }
269              
270             sub irc_console_connect {
271             my ($peeradr, $peerport, $wheel_id) = @_[ARG0 .. ARG2];
272             return;
273             }
274              
275             sub irc_console_authed {
276             my $wheel_id = $_[ARG0];
277             return;
278             }
279              
280             sub irc_console_close {
281             my $wheel_id = $_[ARG0];
282             return;
283             }
284              
285             sub irc_console_rw_fail {
286             my ($peeradr, $peerport) = @_[ARG0, ARG1];
287             return;
288             }
289              
290             =head1 DESCRIPTION
291              
292             POE::Component::IRC::Plugin::Console is a L
293             plugin that provides an interactive console running over the loopback network.
294             One connects to the listening socket using a telnet client (or equivalent),
295             authenticate using the applicable password. Once authed one will receive all
296             events that are processed through the component. One may also issue all the
297             documented component commands.
298              
299             =head1 METHODS
300              
301             =head2 C
302              
303             Takes two arguments:
304              
305             B<'password'>, the password to set for *all* console connections;
306              
307             B<'bindport'>, specify a particular port to bind to, defaults to 0, ie. randomly
308             allocated;
309              
310             Returns a plugin object suitable for feeding to
311             L's C method.
312              
313             =head2 C
314              
315             Gives access to the underlying listener's C method. See
316             L for details.
317              
318             =head1 OUTPUT EVENTS
319              
320             The plugin generates the following additional
321             L events:
322              
323             =head2 C
324              
325             Emitted when a listener is successfully spawned. C is the result of
326             C, see above for details.
327              
328             =head2 C
329              
330             Emitted when a client connects to the console. C is the peeradr, C
331             is the peer port and C is the wheel id of the connection.
332              
333             =head2 C
334              
335             Emitted when a client has successfully provided a valid password. C is
336             the wheel id of the connection.
337              
338             =head2 C
339              
340             Emitted when a client terminates a connection. C is the wheel id of the
341             connection.
342              
343             =head2 C
344              
345             Emitted when a L could not be
346             created on a socket. C is the peer's address, C is the peer's port.
347              
348             =head1 AUTHOR
349              
350             Chris 'BinGOs' Williams
351              
352             =head1 SEE ALSO
353              
354             L
355              
356             L
357              
358             =cut