File Coverage

blib/lib/POE/Component/IRC/Plugin/MultiProxy/Recall.pm
Criterion Covered Total %
statement 29 215 13.4
branch 0 74 0.0
condition 0 30 0.0
subroutine 11 27 40.7
pod 1 15 6.6
total 41 361 11.3


line stmt bran cond sub pod time code
1             package POE::Component::IRC::Plugin::MultiProxy::Recall;
2             BEGIN {
3 1     1   1421 $POE::Component::IRC::Plugin::MultiProxy::Recall::AUTHORITY = 'cpan:HINRIK';
4             }
5             BEGIN {
6 1     1   19 $POE::Component::IRC::Plugin::MultiProxy::Recall::VERSION = '0.01';
7             }
8              
9 1     1   10 use strict;
  1         3  
  1         40  
10 1     1   5 use warnings FATAL => 'all';
  1         3  
  1         44  
11 1     1   1209 use File::Temp qw(tempfile);
  1         11100  
  1         86  
12 1     1   8 use POE;
  1         1  
  1         8  
13 1     1   318 use POE::Component::IRC::Common qw( parse_user );
  1         2  
  1         54  
14 1     1   6 use POE::Component::IRC::Plugin qw( :ALL );
  1         2  
  1         146  
15 1     1   980 use POE::Component::IRC::Plugin::BotTraffic;
  1         6070  
  1         43  
16 1     1   13 use POE::Filter::IRCD;
  1         3  
  1         29  
17 1     1   1339 use Tie::File;
  1         13947  
  1         3026  
18              
19             sub new {
20 0     0 1   my ($package, %self) = @_;
21 0 0 0       if (!$self{Mode} || $self{Mode} !~ /missed|all|none/) {
22 0           $self{Mode} = 'missed';
23             }
24 0           return bless \%self, $package;
25             }
26              
27             sub PCI_register {
28 0     0 0   my ($self, $irc) = @_;
29              
30 0 0         if (!$irc->isa('POE::Component::IRC::State')) {
31 0           die __PACKAGE__ . " requires PoCo::IRC::State or a subclass thereof\n";
32             }
33              
34 0 0         if (!grep { $_->isa('POE::Component::IRC::Plugin::BotTraffic') } values %{ $irc->plugin_list() }) {
  0            
  0            
35 0           $irc->plugin_add('BotTraffic', POE::Component::IRC::Plugin::BotTraffic->new());
36             }
37              
38 0           ($self->{state}) = grep { $_->isa('POE::Component::IRC::Plugin::MultiProxy::State') } values %{ $irc->plugin_list() };
  0            
  0            
39 0           $self->{irc} = $irc;
40 0           $self->{filter} = POE::Filter::IRCD->new();
41 0           $self->{recall} = [ ];
42 0           $self->{clients} = 0;
43 0           $self->{last_detach} = 0;
44              
45 0 0         tie @{ $self->{recall} }, 'Tie::File', scalar tempfile() if $self->{Mode} =~ /all|missed/;
  0            
46              
47 0           $irc->raw_events(1);
48 0           $irc->plugin_register($self, 'SERVER', qw(cap bot_ctcp_action bot_public connected ctcp_action msg public part proxy_authed proxy_close raw));
49              
50 0           return 1;
51             }
52              
53             sub PCI_unregister {
54 0     0 0   my ($self, $irc) = @_;
55 0           delete $self->{irc};
56 0           return 1;
57             }
58              
59             sub S_cap {
60 0     0 0   my ($self, $irc) = splice @_, 0, 2;
61 0           my $cmd = ${ $_[0] };
  0            
62              
63 0 0         if ($cmd eq 'ACK') {
64 0 0         my $list = ${ $_[1] } eq '*' ? ${ $_[2] } : ${ $_[1] };
  0            
  0            
  0            
65 0           my @enabled = split / /, $list;
66              
67 0 0         if (grep { $_ =~ /^=?identify-msg$/ } @enabled) {
  0            
68 0           $self->{idmsg} = 1;
69             }
70 0 0         if (grep { $_ =~ /^-identify-msg$/ } @enabled) {
  0            
71 0           $self->{idmsg} = 0;
72             }
73             }
74 0           return PCI_EAT_NONE;
75             }
76              
77             sub S_bot_ctcp_action {
78 0     0 0   my ($self, $irc) = splice @_, 0, 2;
79 0           my $recipients = join (',', @{ ${ $_[0] } });
  0            
  0            
80 0           my $msg = ${ $_[1] };
  0            
81              
82 0 0         if ($self->{Mode} eq 'all') {
83 0           my $line = ':' . $irc->nick_long_form($irc->nick_name()) . " PRIVMSG $recipients :\x01ACTION $msg\x01";
84 0           push @{ $self->{recall} }, $line;
  0            
85             }
86              
87 0           return PCI_EAT_NONE;
88             }
89              
90             sub S_bot_public {
91 0     0 0   my ($self, $irc) = splice @_, 0, 2;
92 0           my $recipients = join (',', @{ ${ $_[0] } });
  0            
  0            
93 0           my $msg = ${ $_[1] };
  0            
94              
95 0 0         if ($self->{Mode} eq 'all') {
96 0           my $line = ':' . $irc->nick_long_form($irc->nick_name()) . " PRIVMSG $recipients :$msg";
97 0           push @{ $self->{recall} }, $line;
  0            
98             }
99              
100 0           return PCI_EAT_NONE;
101             }
102              
103             sub S_connected {
104 0     0 0   my ($self, $irc) = splice @_, 0, 2;
105              
106 0           $self->{stash} = [ ];
107 0           $self->{stashing} = 1;
108 0           $self->{idmsg} = 0;
109 0           return PCI_EAT_NONE;
110             }
111              
112             sub S_ctcp_action {
113 0     0 0   my ($self, $irc) = splice @_, 0, 2;
114 0           my $sender = ${ $_[0] };
  0            
115 0           my $recipients = ${ $_[1] };
  0            
116 0           my $msg = ${ $_[2] };
  0            
117              
118 0           for my $recipient (@{ $recipients }) {
  0            
119 0 0 0       if ($recipient eq $irc->nick_name()) {
    0 0        
120             # private ACTION
121 0 0         if (!$self->{clients}) {
122 0           my $line = ":$sender PRIVMSG $irc->nick_name :\x01ACTION $msg\x01";
123 0           push @{ $self->{recall} }, $line;
  0            
124             }
125             }
126             elsif ($self->{Mode} eq 'all' || $self->{Mode} eq 'missed' && !$self->{clients}) {
127             # channel ACTION
128 0           my $line = ":$sender PRIVMSG $recipient :\x01ACTION $msg\x01";
129 0           push @{ $self->{recall} }, $line;
  0            
130             }
131             }
132              
133 0           return PCI_EAT_NONE;
134             }
135              
136             sub S_msg {
137 0     0 0   my ($self, $irc) = splice @_, 0, 2;
138 0           my $sender = ${ $_[0] };
  0            
139 0           my $msg = ${ $_[2] };
  0            
140              
141 0 0         return PCI_EAT_NONE if $self->{clients};
142              
143 0           my $line = ":$sender PRIVMSG $irc->nick_name :$msg";
144 0           push @{ $self->{recall} }, $line;
  0            
145 0           return PCI_EAT_NONE;
146             }
147              
148             sub S_part {
149 0     0 0   my ($self, $irc) = splice @_, 0, 2;
150 0           my $chan = ${ $_[1] };
  0            
151              
152 0 0         if (my $cycle = grep { $_->isa('POE::Component::IRC::Plugin::CycleEmpty') } values %{ $irc->plugin_list() } ) {
  0            
  0            
153 0 0         return PCI_EAT_NONE if $cycle->cycling($chan);
154             }
155              
156             # too CPU-heavy
157             # if ($self->{Mode} eq 'all') {
158             # # remove all messages related to this channel
159             # my $input = $self->{filter}->get( $self->{recall} );
160             # for my $line (0..$#{ $self->{recall} }) {
161             # if (lc $input->[$line]{params}[0] eq lc $chan) {
162             # delete $self->{recall}[$line];
163             # }
164             # elsif ($input->[$line]{command} =~ /332|333|366/ && lc $input->[$line]{params}[1] eq lc $chan) {
165             # delete $self->{recall}[$line];
166             # }
167             # elsif ($input->[$line]{command} eq '353' && lc $input->[$line]{params}->[2] eq lc $chan) {
168             # delete $self->{recall}[$line];
169             # }
170             # }
171             # }
172              
173 0           return PCI_EAT_NONE;
174             }
175              
176             sub S_public {
177 0     0 0   my ($self, $irc) = splice @_, 0, 2;
178 0           my $sender = ${ $_[0] };
  0            
179 0           my $chan = ${ $_[1] }->[0];
  0            
180 0           my $msg = ${ $_[2] };
  0            
181              
182             # do this here instead rather than in S_raw so that IDENTIFY-MSG
183             # will by handled by POE::Filter::IRC::Compat
184 0 0 0       if ($self->{Mode} eq 'all' || $self->{Mode} eq 'missed' && !$self->{clients}) {
      0        
185 0           push @{ $self->{recall} }, ":$sender PRIVMSG $chan :$msg";
  0            
186             }
187              
188 0           return PCI_EAT_NONE;
189             }
190              
191             sub S_proxy_authed {
192 0     0 0   my ($self, $irc) = splice @_, 0, 2;
193 0           $self->{clients}++;
194 0           return PCI_EAT_NONE;
195             }
196              
197             sub S_proxy_close {
198 0     0 0   my ($self, $irc) = splice @_, 0, 2;
199 0           $self->{clients}--;
200 0 0         return if $self->{clients};
201              
202 0 0         $self->{recall} = [ ] if $self->{Mode} =~ /^(?:missed|none)$/;
203              
204 0 0         if ($self->{Mode} eq 'missed') {
    0          
205 0           push @{ $self->{recall} }, $self->_chan_info();
  0            
206             }
207             elsif ($self->{Mode} eq 'all') {
208 0           $self->{last_detach} = $#{ $self->{recall} };
  0            
209             }
210              
211 0           return PCI_EAT_NONE;
212             }
213              
214             sub S_raw {
215 0     0 0   my ($self, $irc) = splice @_, 0, 2;
216 0           my $raw_line = ${ $_[0] };
  0            
217 0           my $input = $self->{filter}->get( [ $raw_line ] )->[0];
218              
219 0 0         if ($self->{stashing}) {
220             # capture all numeric commands until we've got the MOTD
221 0 0         if ($input->{command} =~ /\d{3}/) {
222 0           push @{ $self->{stash} }, $raw_line;
  0            
223             }
224             # RPL_ENDOFMOTD / ERR_NOMOTD
225 0 0         if ($input->{command} =~ /376|422/) {
226 0           $self->{stashing} = 0;
227             }
228             }
229              
230 0 0 0       if ($self->{Mode} eq 'all' || $self->{Mode} eq 'missed' && !$self->{clients}) {
      0        
231 0 0 0       if ($input->{command} eq 'MODE' && $input->{params}[1] =~ /^[#&+!]/) {
    0          
    0          
    0          
232             # channel mode changes
233 0           push @{ $self->{recall} }, $raw_line;
  0            
234             }
235             elsif ($input->{command} =~ /JOIN|KICK|PART|QUIT|NICK|TOPIC/) {
236             # other channel-related things
237 0           push @{ $self->{recall} }, $raw_line;
  0            
238             }
239             elsif ($input->{command} eq '353') {
240             # only log this when we've just joined the channel
241 0 0         push @{ $self->{recall} }, $raw_line if $self->{state}->is_syncing($input->{params}[2]);
  0            
242             }
243             elsif ($input->{command} =~ /332|333|366/) {
244             # only log these when we've just joined the channel
245 0 0         push @{ $self->{recall} }, $raw_line if $self->{state}->is_syncing($input->{params}[1]);
  0            
246             }
247             }
248              
249 0           return PCI_EAT_NONE;
250             }
251              
252             # returns everything that an IRC server would send us upon joining
253             # the channels we're on
254             sub _chan_info {
255 0     0     my ($self) = @_;
256 0           my $irc = $self->{irc};
257 0           my $state = $self->{state};
258 0           my $me = $irc->nick_name();
259              
260 0           my @info;
261 0           for my $chan (keys %{ $irc->channels() }) {
  0            
262 0           push @info, ':' . $irc->nick_long_form($me) . " JOIN :$chan";
263 0 0         push @info, $state->topic_reply($chan) if keys %{ $irc->channel_topic($chan) };
  0            
264 0           push @info, $state->names_reply($chan);
265             }
266              
267 0           return @info;
268             }
269              
270             sub recall {
271 0     0 0   my ($self) = @_;
272 0           my $irc = $self->{irc};
273 0           my $me = $irc->nick_name();
274 0           my $server = $irc->server_name();
275 0           my @lines;
276              
277 0           for my $line (@{ $self->{stash} }) {
  0            
278 0           $line =~ s/^(\S+ +\S+) +\S+ +(.*)/$1 $me $2/;
279 0           push @lines, $line;
280             }
281              
282 0 0         push @lines, ":$server MODE $me :" . $irc->umode() if $irc->umode();
283 0           push @lines, @{ $self->{recall} };
  0            
284 0 0         push @lines, ":$server CAP * ACK :identify-msg" if $self->{idmsg};
285              
286 0 0 0       if ($self->{Mode} eq 'all' && $#{ $self->{recall} } > $self->{last_detach}) {
  0 0          
287             # remove all PMs received since we last detached
288 0           for my $line ($self->{last_detach} .. $#{ $self->{recall} }) {
  0            
289 0           my $in = shift @{ $self->{filter}->get( $self->{recall} ) };
  0            
290 0 0 0       if ($in->{command} eq 'PRIVMSG' && $in->{params}[0] !~ /^[#&+!]/) {
291 0           delete $self->{recall}[$line];
292             }
293             }
294             }
295             elsif ($self->{Mode} eq 'none') {
296 0           push @lines, $self->_chan_info();
297             }
298              
299 0           return @lines;
300             }
301              
302             1;
303              
304             =encoding utf8
305              
306             =head1 NAME
307              
308             POE::Compoent::IRC::Plugin::MultiProxy::Recall - A PoCo-IRC plugin which can greet proxy clients with the messages they missed while they were away
309              
310             =head1 SYNOPSIS
311              
312             use POE::Compoent::IRC::Plugin::MultiProxy::Recall;
313              
314             $irc->plugin_add('Recall', POE::Compoent::IRC::Plugin::MultiProxy::Recall->new( Mode => 'missed' ));
315              
316             =head1 DESCRIPTION
317              
318             This plugin requires the IRC component to be
319             L or a subclass thereof.
320             It also requires a
321             L
322             to be in the plugin pipeline. It will be added automatically if it is not present.
323              
324             =head1 METHODS
325              
326             =head2 C
327              
328             One optional argument:
329              
330             B<'Mode'>, which public messages you want it to recall. B<'missed'>, the
331             default, makes it only recall public messages that were received while no
332             proxy client was attached. B<'all'> will recall public messages from all
333             channels since they were joined. B<'none'> will recall none. The plugin will
334             always recall missed private messages, regardless of this option.
335              
336             Returns a plugin object suitable for feeding to
337             L's C method.
338              
339             =head1 AUTHOR
340              
341             Hinrik Ern SigurEsson, hinrik.sig@gmail.com
342              
343             =cut