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