File Coverage

blib/lib/POE/Component/IRC/Plugin/Eval.pm
Criterion Covered Total %
statement 27 29 93.1
branch n/a
condition n/a
subroutine 11 11 100.0
pod n/a
total 38 40 95.0


line stmt bran cond sub pod time code
1             package POE::Component::IRC::Plugin::Eval;
2             BEGIN {
3 2     2   564782 $POE::Component::IRC::Plugin::Eval::AUTHORITY = 'cpan:HINRIK';
4             }
5             BEGIN {
6 2     2   35 $POE::Component::IRC::Plugin::Eval::VERSION = '0.07';
7             }
8              
9 2     2   18 use strict;
  2         4  
  2         187  
10 2     2   13 use warnings FATAL => 'all';
  2         15  
  2         99  
11 2     2   11 use Carp 'croak';
  2         3  
  2         124  
12 2     2   1156 use Encode qw(is_utf8);
  2         12992  
  2         150  
13 2     2   16 use List::Util qw(first);
  2         4  
  2         202  
14 2     2   2021 use POE;
  2         54343  
  2         17  
15 2         258 use POE::Component::IRC::Common qw(strip_color strip_formatting),
16 2     2   154011 qw(parse_user irc_to_utf8 NORMAL DARK_GREEN ORANGE TEAL BROWN);
  2         13256  
17 2     2   1031 use POE::Component::IRC::Plugin qw(PCI_EAT_NONE);
  2         401  
  2         106  
18 2     2   2652 use POE::Filter::JSON;
  0            
  0            
19             use POE::Wheel::ReadWrite;
20             use POE::Wheel::SocketFactory;
21              
22             sub new {
23             my ($package, %args) = @_;
24             my $self = bless \%args, $package;
25              
26             $self->{Server_host} = 'localhost' if !defined $self->{Server_port};
27             $self->{Server_port} = 14400 if !defined $self->{Server_port};
28             $self->{Method} = 'notice' if !defined $self->{Method};
29             $self->{Color} = 1 if !defined $self->{Color};
30             return $self;
31             }
32              
33             sub PCI_register {
34             my ($self, $irc) = @_;
35              
36             my $botcmd;
37             if (!(($botcmd) = grep { $_->isa('POE::Component::IRC::Plugin::BotCommand') } values %{ $irc->plugin_list() })) {
38             die __PACKAGE__ . " requires an active BotCommand plugin\n";
39             }
40             $botcmd->add(eval => 'Usage: eval ');
41             $irc->plugin_register($self, 'SERVER', qw(botcmd_eval));
42             $self->{irc} = $irc;
43              
44             POE::Session->create(
45             object_states => [
46             $self => [qw(
47             _start
48             connect_failed
49             connected
50             new_eval
51             eval_read
52             eval_error
53             )],
54             ],
55             );
56              
57             return 1;
58             }
59              
60             sub PCI_unregister {
61             my ($self, $irc) = @_;
62             delete $self->{evals};
63             $poe_kernel->refcount_decrement($self->{session_id}, __PACKAGE__);
64             return 1;
65             }
66              
67             sub _start {
68             my ($kernel, $self, $session) = @_[KERNEL, OBJECT, SESSION];
69             $self->{session_id} = $session->ID;
70             $kernel->refcount_increment($self->{session_id}, __PACKAGE__);
71             return;
72             }
73              
74             sub S_botcmd_eval {
75             my ($self, $irc) = splice @_, 0, 2;
76             my $nick = parse_user( ${ $_[0] } );
77             my $chan = ${ $_[1] };
78             my ($lang, $code) = ${ $_[2] } =~ /^(\S+) (.*)/;
79              
80             return PCI_EAT_NONE if $self->_ignoring_channel($chan);
81             $poe_kernel->post($self->{session_id}, 'new_eval', $nick, $chan, $lang, $code);
82             return PCI_EAT_NONE;
83             }
84              
85             sub new_eval {
86             my ($self, $nick, $chan, $lang, $code) = @_[OBJECT, ARG0..$#_];
87              
88             my $sock_wheel = POE::Wheel::SocketFactory->new(
89             RemoteAddress => $self->{Server_host},
90             RemotePort => $self->{Server_port},
91             FailureEvent => 'connect_failed',
92             SuccessEvent => 'connected',
93             );
94              
95             $self->{evals}{$sock_wheel->ID} = {
96             nick => $nick,
97             chan => $chan,
98             lang => $lang,
99             code => $code,
100             sock_wheel => $sock_wheel,
101             };
102              
103             return PCI_EAT_NONE;
104             }
105              
106             sub connect_failed {
107             my ($self, $reason, $id) = @_[OBJECT, ARG2, ARG3];
108             my $irc = $self->{irc};
109              
110             my $eval = delete $self->{evals}{$id};
111             my $msg = "Error: Couldn't connect to eval server: $reason";
112             my $color = BROWN.'Error:'.NORMAL." Couldn't connect to eval server: $reason";
113             $irc->yield($self->{Method}, $eval->{chan}, ($self->{Color} ? $color : $msg));
114             return;
115             }
116              
117             sub connected {
118             my ($self, $socket, $id) = @_[OBJECT, ARG0, ARG3];
119              
120             my $eval = $self->{evals}{$id};
121              
122             $eval->{rw_wheel} = POE::Wheel::ReadWrite->new(
123             Handle => $socket,
124             Filter => POE::Filter::JSON->new(),
125             InputEvent => 'eval_read',
126             ErrorEvent => 'eval_error',
127             );
128              
129             $eval->{rw_wheel}->put({
130             lang => $eval->{lang},
131             code => $eval->{code},
132             });
133              
134             return;
135             }
136              
137             sub eval_error {
138             my ($self, $reason, $rw_id) = @_[OBJECT, ARG2, ARG3];
139             my $irc = $self->{irc};
140              
141             my $eval;
142             for my $eval_id (keys %{ $self->{evals} }) {
143             if ($self->{evals}{$eval_id}{rw_wheel}->ID == $rw_id) {
144             $eval = delete $self->{evals}{$eval_id};
145             last;
146             }
147             }
148              
149             my $msg = "Failed to read from evalserver socket: $reason";
150             my $color = BROWN.'Error:'.NORMAL." Failed to read from evalserver socket: $reason";
151             $irc->yield($self->{Method}, $eval->{chan}, ($self->{Color} ? $color : $msg));
152              
153             return;
154             }
155              
156             sub eval_read {
157             my ($self, $return, $rw_id) = @_[OBJECT, ARG0, ARG1];
158             my $irc = $self->{irc};
159              
160             my $eval;
161             for my $eval_id (keys %{ $self->{evals} }) {
162             if ($self->{evals}{$eval_id}{rw_wheel}->ID == $rw_id) {
163             $eval = delete $self->{evals}{$eval_id};
164             last;
165             }
166             }
167              
168             if ($return->{error}) {
169             my $msg = "Error: Failed to eval code: $return->{error}";
170             my $color = BROWN.'Error:'.NORMAL." Failed to eval code: $return->{error}";
171             $irc->yield($self->{Method}, $eval->{chan}, ($self->{Color} ? $color : $msg));
172             }
173             else {
174             $return->{result} = 'undef' if !defined $return->{result};
175             $return->{result} = _clean($return->{result});
176             $return->{output} = _clean($return->{output});
177              
178             my $msg = "Result: «$return->{result}» · Memory: $return->{memory}kB";
179             $msg .= " · Output: «$return->{output}»" if length $return->{output};
180              
181             my $color = 'Result: '.DARK_GREEN.'«'.NORMAL.$return->{result}.DARK_GREEN.'»'.NORMAL
182             .' Memory: '.ORANGE.$return->{memory}.NORMAL.'kB';
183             $color .= ' Output: '.TEAL.'«'.NORMAL.$return->{output}.TEAL.'»'.NORMAL if length $return->{output};
184              
185             $irc->yield($self->{Method}, $eval->{chan}, ($self->{Color} ? $color : $msg));
186             }
187              
188             return;
189             }
190              
191             sub _clean {
192             my ($string) = @_;
193             $string =~ s/\n/␤/gm;
194             $string = strip_color($string);
195             $string = strip_formatting($string);
196             return $string;
197             }
198              
199             sub _ignoring_channel {
200             my ($self, $chan) = @_;
201              
202             if ($self->{Channels}) {
203             return 1 if !first {
204             my $c = $chan;
205             $c = irc_to_utf8($c) if is_utf8($_);
206             $_ eq $c
207             } @{ $self->{Channels} };
208             }
209             return;
210             }
211              
212             1;
213              
214             =encoding utf8
215              
216             =head1 NAME
217              
218             POE::Component::IRC::Plugin::Eval - Evaluate code with App::EvalServer
219              
220             =head1 SYNOPSIS
221              
222             To quickly get an IRC bot with this plugin up and running, you can use
223             L:
224              
225             $ pocoirc -s irc.perl.org -j '#bots' -a BotCommand -a Eval
226              
227             Or use it in your code:
228              
229             use POE::Component::IRC::Plugin::BotCommand;
230             use POE::Component::IRC::Plugin::Eval;
231              
232             $irc->plugin_add(BotCommand => POE::Component::IRC::Plugin::BotCommand->new());
233              
234             # evaluate code in #foobar
235             $irc->plugin_add(Eval => POE::Component::IRC::Plugin::Eval->new(
236             Server_port => 14400,
237             Channels => ['#foobar'],
238             ));
239              
240             =head1 DESCRIPTION
241              
242             POE::Component::IRC::Plugin::Eval is a
243             L plugin. It reads 'eval' commands
244             from IRC users and evaluates code with L.
245              
246             You must add a
247             L
248             plugin to the IRC component before adding this plugin.
249              
250             =head1 METHODS
251              
252             =head2 C
253              
254             Takes the following arguments:
255              
256             B<'Server_host'>, the host where the L
257             instance is running. Default is 'localhost'.
258              
259             B<'Server_port'>, the host where the L
260             instance is running. Default is 14400.
261              
262             B<'Channels'>, an array reference of channel names. If you don't provide
263             this, the plugin will be active in all channels.
264              
265             B<'Method'>, how you want messages to be delivered. Valid options are
266             'notice' (the default) and 'privmsg'.
267              
268             Returns a plugin object suitable for feeding to
269             L's C method.
270              
271             =head1 AUTHOR
272              
273             Hinrik Ern SigurEsson, hinrik.sig@gmail.com
274              
275             =head1 LICENSE AND COPYRIGHT
276              
277             Copyright 2010 Hinrik Ern SigurEsson
278              
279             This program is free software, you can redistribute it and/or modify
280             it under the same terms as Perl itself.
281              
282             =cut