File Coverage

blib/lib/POE/Component/IRC/State.pm
Criterion Covered Total %
statement 616 805 76.5
branch 177 300 59.0
condition 70 140 50.0
subroutine 57 71 80.2
pod 29 59 49.1
total 949 1375 69.0


line stmt bran cond sub pod time code
1             package POE::Component::IRC::State;
2             $POE::Component::IRC::State::VERSION = '6.95';
3 21     21   2562104 use strict;
  21         45  
  21         997  
4 21     21   110 use warnings FATAL => 'all';
  21         39  
  21         1694  
5 21     21   9606 use IRC::Utils qw(uc_irc parse_mode_line normalize_mask);
  21         508021  
  21         2727  
6 21     21   222 use POE;
  21         63  
  21         194  
7 21     21   21967 use POE::Component::IRC::Plugin qw(PCI_EAT_NONE);
  21         139  
  21         1647  
8 21     21   142 use base qw(POE::Component::IRC);
  21         42  
  21         25683  
9              
10             # Event handlers for tracking the STATE. $self->{STATE} is used as our
11             # namespace. uc_irc() is used to create unique keys.
12              
13             # RPL_WELCOME
14             # Make sure we have a clean STATE when we first join the network and if we
15             # inadvertently get disconnected.
16             sub S_001 {
17 28     28 0 9403 my $self = shift;
18 28         263 $self->SUPER::S_001(@_);
19 28         58 shift @_;
20              
21 28         87 delete $self->{STATE};
22 28         63 delete $self->{NETSPLIT};
23 28         114 $self->{STATE}{usermode} = '';
24 28         164 $self->yield(mode => $self->nick_name());
25 28         3958 return PCI_EAT_NONE;
26             }
27              
28             sub S_disconnected {
29 28     28 0 18560 my $self = shift;
30 28         237 $self->SUPER::S_disconnected(@_);
31 28         59 shift @_;
32              
33 28         104 my $nickinfo = $self->nick_info($self->nick_name());
34 28 100       102 $nickinfo = {} if !defined $nickinfo;
35 28         102 my $channels = $self->channels();
36 28         53 push @{ $_[-1] }, $nickinfo, $channels;
  28         95  
37 28         96 return PCI_EAT_NONE;
38             }
39              
40             sub S_error {
41 26     26 0 14469 my $self = shift;
42 26         225 $self->SUPER::S_error(@_);
43 26         52 shift @_;
44              
45 26         113 my $nickinfo = $self->nick_info($self->nick_name());
46 26 100       121 $nickinfo = {} if !defined $nickinfo;
47 26         107 my $channels = $self->channels();
48 26         51 push @{ $_[-1] }, $nickinfo, $channels;
  26         84  
49 26         91 return PCI_EAT_NONE;
50             }
51              
52             sub S_socketerr {
53 0     0 0 0 my ($self, undef) = splice @_, 0, 2;
54 0         0 my $nickinfo = $self->nick_info($self->nick_name());
55 0 0       0 $nickinfo = {} if !defined $nickinfo;
56 0         0 my $channels = $self->channels();
57 0         0 push @{ $_[-1] }, $nickinfo, $channels;
  0         0  
58 0         0 return PCI_EAT_NONE;
59             }
60              
61             sub S_join {
62 53     53 0 21726 my ($self, undef) = splice @_, 0, 2;
63 53         122 my ($nick, $user, $host) = split /[!@]/, ${ $_[0] };
  53         488  
64 53         309 my $map = $self->isupport('CASEMAPPING');
65 53         117 my $chan = ${ $_[1] };
  53         126  
66 53         235 my $uchan = uc_irc($chan, $map);
67 53         864 my $unick = uc_irc($nick, $map);
68              
69 53 100       841 if ($unick eq uc_irc($self->nick_name(), $map)) {
70 34         521 delete $self->{STATE}{Chans}{ $uchan };
71 34         348 $self->{CHANNEL_SYNCH}{ $uchan } = {
72             MODE => 0,
73             WHO => 0,
74             BAN => 0,
75             _time => time(),
76             };
77 34         196 $self->{STATE}{Chans}{ $uchan } = {
78             Name => $chan,
79             Mode => ''
80             };
81              
82             # fake a WHO sync if we're only interested in people's user@host
83             # and the server provides those in the NAMES reply
84 34 50 33     224 if (exists $self->{whojoiners} && !$self->{whojoiners}
      33        
85             && $self->isupport('UHNAMES')) {
86 0         0 $self->_channel_sync($chan, 'WHO');
87             }
88             else {
89 34         151 $self->yield(who => $chan);
90             }
91 34         5088 $self->yield(mode => $chan);
92 34         4550 $self->yield(mode => $chan => 'b');
93             }
94             else {
95             SWITCH: {
96 19         378 my $netsplit = "$unick!$user\@$host";
  19         148  
97 19 100       145 if ( exists $self->{NETSPLIT}{Users}{ $netsplit } ) {
98             # restore state from NETSPLIT if it hasn't expired.
99 1         3 my $nuser = delete $self->{NETSPLIT}{Users}{ $netsplit };
100 1 50       7 if ( ( time - $nuser->{stamp} ) < ( 60 * 60 ) ) {
101 1         7 $self->{STATE}{Nicks}{ $unick } = $nuser->{meta};
102 1         7 $self->send_event_next(irc_nick_sync => $nick, $chan);
103 1         51 last SWITCH;
104             }
105             }
106 18 100 33     202 if ( (!exists $self->{whojoiners} || $self->{whojoiners})
      66        
107             && !exists $self->{STATE}{Nicks}{ $unick }{Real}) {
108 15         75 $self->yield(who => $nick);
109 15         2098 push @{ $self->{NICK_SYNCH}{ $unick } }, $chan;
  15         84  
110             }
111             else {
112             # Fake 'irc_nick_sync'
113 3         17 $self->send_event_next(irc_nick_sync => $nick, $chan);
114             }
115             }
116             }
117              
118 53         5064 $self->{STATE}{Nicks}{ $unick }{Nick} = $nick;
119 53         215 $self->{STATE}{Nicks}{ $unick }{User} = $user;
120 53         172 $self->{STATE}{Nicks}{ $unick }{Host} = $host;
121 53         405 $self->{STATE}{Nicks}{ $unick }{CHANS}{ $uchan } = '';
122 53         210 $self->{STATE}{Chans}{ $uchan }{Nicks}{ $unick } = '';
123              
124 53         242 return PCI_EAT_NONE;
125             }
126              
127             sub S_chan_sync {
128 32     32 0 6556 my ($self, undef) = splice @_, 0, 2;
129 32         71 my $chan = ${ $_[0] };
  32         104  
130              
131 32 100       161 if ($self->{awaypoll}) {
132 2         14 $poe_kernel->state(_away_sync => $self);
133 2         83 $poe_kernel->delay_add(_away_sync => $self->{awaypoll} => $chan);
134             }
135              
136 32         302 return PCI_EAT_NONE;
137             }
138              
139             sub S_part {
140 5     5 0 2590 my ($self, undef) = splice @_, 0, 2;
141 5         18 my $map = $self->isupport('CASEMAPPING');
142 5         10 my $nick = uc_irc((split /!/, ${ $_[0] } )[0], $map);
  5         30  
143 5         62 my $uchan = uc_irc(${ $_[1] }, $map);
  5         14  
144              
145 5 100       59 if ($nick eq uc_irc($self->nick_name(), $map)) {
146 3         37 delete $self->{STATE}{Nicks}{ $nick }{CHANS}{ $uchan };
147 3         11 delete $self->{STATE}{Chans}{ $uchan }{Nicks}{ $nick };
148              
149 3         6 for my $member ( keys %{ $self->{STATE}{Chans}{ $uchan }{Nicks} } ) {
  3         13  
150 2         8 delete $self->{STATE}{Nicks}{ $member }{CHANS}{ $uchan };
151 2 50       3 if ( keys %{ $self->{STATE}{Nicks}{ $member }{CHANS} } <= 0 ) {
  2         10  
152 2         13 delete $self->{STATE}{Nicks}{ $member };
153             }
154             }
155              
156 3         17 delete $self->{STATE}{Chans}{ $uchan };
157             }
158             else {
159 2         27 delete $self->{STATE}{Nicks}{ $nick }{CHANS}{ $uchan };
160 2         7 delete $self->{STATE}{Chans}{ $uchan }{Nicks}{ $nick };
161 2 50       4 if ( !keys %{ $self->{STATE}{Nicks}{ $nick }{CHANS} } ) {
  2         11  
162 2         10 delete $self->{STATE}{Nicks}{ $nick };
163             }
164             }
165              
166 5         16 return PCI_EAT_NONE;
167             }
168              
169             sub S_quit {
170 3     3 0 1297 my ($self, undef) = splice @_, 0, 2;
171 3         203 my $map = $self->isupport('CASEMAPPING');
172 3         10 my $nick = (split /!/, ${ $_[0] })[0];
  3         13  
173 3         8 my $msg = ${ $_[1] };
  3         8  
174 3         12 my $unick = uc_irc($nick, $map);
175 3         36 my $netsplit = 0;
176              
177 3         6 push @{ $_[-1] }, [ $self->nick_channels( $nick ) ];
  3         19  
178              
179             # Check if it is a netsplit
180 3 100       15 $netsplit = 1 if _is_netsplit( $msg );
181              
182 3 50       15 if ($unick ne uc_irc($self->nick_name(), $map)) {
183 3         32 for my $uchan ( keys %{ $self->{STATE}{Nicks}{ $unick }{CHANS} } ) {
  3         15  
184 4         15 delete $self->{STATE}{Chans}{ $uchan }{Nicks}{ $unick };
185             # No don't stash the channel state.
186             #$self->{NETSPLIT}{Chans}{ $uchan }{NICKS}{ $unick } = $chanstate
187             # if $netsplit;
188             }
189              
190 3         9 my $nickstate = delete $self->{STATE}{Nicks}{ $unick };
191 3 100       21 if ( $netsplit ) {
192 1         2 delete $nickstate->{CHANS};
193 1         7 $self->{NETSPLIT}{Users}{ "$unick!" . join '@', @{$nickstate}{qw(User Host)} } =
  1         5  
194             { meta => $nickstate, stamp => time };
195             }
196             }
197              
198 3         17 return PCI_EAT_NONE;
199             }
200              
201             sub _is_netsplit {
202 3   50 3   12 my $msg = shift || return;
203 3 100       22 return 1 if $msg =~ /^\s*\S+\.[a-z]{2,} \S+\.[a-z]{2,}$/i;
204 2         8 return 0;
205             }
206              
207             sub S_kick {
208 8     8 0 4944 my ($self, undef) = splice @_, 0, 2;
209 8         20 my $chan = ${ $_[1] };
  8         23  
210 8         19 my $nick = ${ $_[2] };
  8         19  
211 8         39 my $map = $self->isupport('CASEMAPPING');
212 8         35 my $unick = uc_irc($nick, $map);
213 8         132 my $uchan = uc_irc($chan, $map);
214              
215 8         121 push @{ $_[-1] }, $self->nick_long_form( $nick );
  8         50  
216              
217 8 100       39 if ( $unick eq uc_irc($self->nick_name(), $map)) {
218 4         71 delete $self->{STATE}{Nicks}{ $unick }{CHANS}{ $uchan };
219 4         17 delete $self->{STATE}{Chans}{ $uchan }{Nicks}{ $unick };
220              
221 4         9 for my $member ( keys %{ $self->{STATE}{Chans}{ $uchan }{Nicks} } ) {
  4         25  
222 4         15 delete $self->{STATE}{Nicks}{ $member }{CHANS}{ $uchan };
223 4 100       9 if ( keys %{ $self->{STATE}{Nicks}{ $member }{CHANS} } <= 0 ) {
  4         23  
224 3         23 delete $self->{STATE}{Nicks}{ $member };
225             }
226             }
227              
228 4         31 delete $self->{STATE}{Chans}{ $uchan };
229             }
230             else {
231 4         68 delete $self->{STATE}{Nicks}{ $unick }{CHANS}{ $uchan };
232 4         18 delete $self->{STATE}{Chans}{ $uchan }{Nicks}{ $unick };
233 4 100       8 if ( keys %{ $self->{STATE}{Nicks}{ $unick }{CHANS} } <= 0 ) {
  4         27  
234 3         20 delete $self->{STATE}{Nicks}{ $unick };
235             }
236             }
237              
238 8         37 return PCI_EAT_NONE;
239             }
240              
241             sub S_nick {
242 2     2 0 2837 my $self = shift;
243 2         25 $self->SUPER::S_nick(@_);
244 2         5 shift @_;
245              
246 2         6 my $nick = (split /!/, ${ $_[0] })[0];
  2         7  
247 2         7 my $new = ${ $_[1] };
  2         4  
248 2         29 my $map = $self->isupport('CASEMAPPING');
249 2         9 my $unick = uc_irc($nick, $map);
250 2         35 my $unew = uc_irc($new, $map);
251              
252 2         26 push @{ $_[-1] }, [ $self->nick_channels( $nick ) ];
  2         15  
253              
254 2 50       11 if ($unick eq $unew) {
255             # Case Change
256 0         0 $self->{STATE}{Nicks}{ $unick }{Nick} = $new;
257             }
258             else {
259 2         7 my $user = delete $self->{STATE}{Nicks}{ $unick };
260 2         7 $user->{Nick} = $new;
261              
262 2         5 for my $channel ( keys %{ $user->{CHANS} } ) {
  2         9  
263 2         9 $self->{STATE}{Chans}{ $channel }{Nicks}{ $unew } = $user->{CHANS}{ $channel };
264 2         39 delete $self->{STATE}{Chans}{ $channel }{Nicks}{ $unick };
265             }
266              
267 2         10 $self->{STATE}{Nicks}{ $unew } = $user;
268             }
269              
270 2         8 return PCI_EAT_NONE;
271             }
272              
273             sub S_chan_mode {
274 64     64 0 14731 my ($self, undef) = splice @_, 0, 2;
275 64         147 pop @_;
276 64         366 my $who = ${ $_[0] };
  64         156  
277 64         188 my $chan = ${ $_[1] };
  64         158  
278 64         111 my $mode = ${ $_[2] };
  64         142  
279 64 100       220 my $arg = defined $_[3] ? ${ $_[3] } : '';
  26         62  
280 64         263 my $map = $self->isupport('CASEMAPPING');
281 64         233 my $me = uc_irc($self->nick_name(), $map);
282              
283 64 100 100     1375 return PCI_EAT_NONE if $mode !~ /\+[qoah]/ || $me ne uc_irc($arg, $map);
284              
285 1         19 my $excepts = $self->isupport('EXCEPTS');
286 1         5 my $invex = $self->isupport('INVEX');
287 1 50       9 $self->yield(mode => $chan, $excepts ) if $excepts;
288 1 50       183 $self->yield(mode => $chan, $invex ) if $invex;
289              
290 1         173 return PCI_EAT_NONE;
291             }
292              
293             # RPL_UMODEIS
294             sub S_221 {
295 29     29 0 53583 my ($self, undef) = splice @_, 0, 2;
296 29         67 my $mode = ${ $_[1] };
  29         77  
297 29         145 $mode =~ s/^\+//;
298 29         97 $self->{STATE}->{usermode} = $mode;
299 29         98 return PCI_EAT_NONE;
300             }
301              
302             # RPL_CHANNEL_URL
303             sub S_328 {
304 0     0 0 0 my ($self, undef) = splice @_, 0, 2;
305 0         0 my ($chan, $url) = @{ ${ $_[2] } };
  0         0  
  0         0  
306 0         0 my $map = $self->isupport('CASEMAPPING');
307 0         0 my $uchan = uc_irc($chan, $map);
308              
309 0 0       0 return PCI_EAT_NONE if !$self->_channel_exists($chan);
310 0         0 $self->{STATE}{Chans}{ $uchan }{Url} = $url;
311 0         0 return PCI_EAT_NONE;
312             }
313              
314             # RPL_UNAWAY
315             sub S_305 {
316 3     3 0 3676 my ($self, undef) = splice @_, 0, 2;
317 3         12 $self->{STATE}->{away} = 0;
318 3         13 return PCI_EAT_NONE;
319             }
320              
321             # RPL_NOWAWAY
322             sub S_306 {
323 3     3 0 668 my ($self, undef) = splice @_, 0, 2;
324 3         11 $self->{STATE}->{away} = 1;
325 3         8 return PCI_EAT_NONE;
326             }
327              
328             # this code needs refactoring
329             ## no critic (Subroutines::ProhibitExcessComplexity ControlStructures::ProhibitCascadingIfElse)
330             sub S_mode {
331 77     77 0 68642 my ($self, undef) = splice @_, 0, 2;
332 77         472 my $map = $self->isupport('CASEMAPPING');
333 77         181 my $who = ${ $_[0] };
  77         224  
334 77         146 my $chan = ${ $_[1] };
  77         185  
335 77         391 my $uchan = uc_irc($chan, $map);
336 77         1370 pop @_;
337 77         319 my @modes = map { ${ $_ } } @_[2 .. $#_];
  103         173  
  103         477  
338              
339             # CHANMODES is [$list_mode, $always_arg, $arg_when_set, $no_arg]
340             # A $list_mode always has an argument
341 77   50     445 my $prefix = $self->isupport('PREFIX') || { o => '@', v => '+' };
342 77         294 my $statmodes = join '', keys %{ $prefix };
  77         387  
343 77   50     1233 my $chanmodes = $self->isupport('CHANMODES') || [ qw(beI k l imnpstaqr) ];
344 77         186 my $alwaysarg = join '', $statmodes, @{ $chanmodes }[0 .. 1];
  77         271  
345              
346             # Do nothing if it is UMODE
347 77 100       386 if ($uchan ne uc_irc($self->nick_name(), $map)) {
348 49         896 my $parsed_mode = parse_mode_line( $prefix, $chanmodes, @modes );
349 49         11668 for my $mode (@{ $parsed_mode->{modes} }) {
  49         274  
350 64         550 my $orig_arg;
351 64 100 33     1721 if (length $chanmodes->[2] && length $alwaysarg && $mode =~ /^(.[$alwaysarg]|\+[$chanmodes->[2]])/) {
      66        
352 26         65 $orig_arg = shift @{ $parsed_mode->{args} };
  26         74  
353             }
354              
355 64         145 my $flag;
356 64         424 my $arg = $orig_arg;
357              
358 64 100 66     7398 if (length $statmodes && (($flag) = $mode =~ /\+([$statmodes])/)) {
    50 33        
    100 66        
    100 66        
    100 66        
    100 66        
    100          
    50          
359 3         14 $arg = uc_irc($arg, $map);
360 3 50 33     67 if (!$self->{STATE}{Nicks}{ $arg }{CHANS}{ $uchan } || $self->{STATE}{Nicks}{ $arg }{CHANS}{ $uchan } !~ /$flag/) {
361 3         12 $self->{STATE}{Nicks}{ $arg }{CHANS}{ $uchan } .= $flag;
362 3         15 $self->{STATE}{Chans}{ $uchan }{Nicks}{ $arg } = $self->{STATE}{Nicks}{ $arg }{CHANS}{ $uchan };
363             }
364             }
365             elsif (length $statmodes && (($flag) = $mode =~ /-([$statmodes])/)) {
366 0         0 $arg = uc_irc($arg, $map);
367 0 0       0 if ($self->{STATE}{Nicks}{ $arg }{CHANS}{ $uchan } =~ /$flag/) {
368 0         0 $self->{STATE}{Nicks}{ $arg }{CHANS}{ $uchan } =~ s/$flag//;
369 0         0 $self->{STATE}{Chans}{ $uchan }{Nicks}{ $arg } = $self->{STATE}{Nicks}{ $arg }{CHANS}{ $uchan };
370             }
371             }
372             elsif (length $chanmodes->[0] && (($flag) = $mode =~ /\+([$chanmodes->[0]])/)) {
373 5         65 $self->{STATE}{Chans}{ $uchan }{Lists}{ $flag }{ $arg } = {
374             SetBy => $who,
375             SetAt => time(),
376             };
377             }
378             elsif (length $chanmodes->[0] && (($flag) = $mode =~ /-([$chanmodes->[0]])/)) {
379 4         31 delete $self->{STATE}{Chans}{ $uchan }{Lists}{ $flag }{ $arg };
380             }
381              
382             # All unhandled modes with arguments
383             elsif (length $chanmodes->[3] && (($flag) = $mode =~ /\+([^$chanmodes->[3]])/)) {
384 12 100       239 $self->{STATE}{Chans}{ $uchan }{Mode} .= $flag if $self->{STATE}{Chans}{ $uchan }{Mode} !~ /$flag/;
385 12         55 $self->{STATE}{Chans}{ $uchan }{ModeArgs}{ $flag } = $arg;
386             }
387             elsif (length $chanmodes->[3] && (($flag) = $mode =~ /-([^$chanmodes->[3]])/)) {
388 4         82 $self->{STATE}{Chans}{ $uchan }{Mode} =~ s/$flag//;
389 4         19 delete $self->{STATE}{Chans}{ $uchan }{ModeArgs}{ $flag };
390             }
391              
392             # Anything else doesn't have arguments so just adjust {Mode} as necessary.
393             elsif (($flag) = $mode =~ /^\+(.)/ ) {
394 34 50       625 $self->{STATE}{Chans}{ $uchan }{Mode} .= $flag if $self->{STATE}{Chans}{ $uchan }{Mode} !~ /$flag/;
395             }
396             elsif (($flag) = $mode =~ /^-(.)/ ) {
397 2         29 $self->{STATE}{Chans}{ $uchan }{Mode} =~ s/$flag//;
398             }
399 64 100       493 $self->send_event_next(irc_chan_mode => $who, $chan, $mode, (defined $orig_arg ? $orig_arg : ()));
400             }
401              
402             # Lets make the channel mode nice
403 49 50       1340 if ( $self->{STATE}{Chans}{ $uchan }{Mode} ) {
404 49         402 $self->{STATE}{Chans}{ $uchan }{Mode} = join('', sort {uc $a cmp uc $b} ( split( //, $self->{STATE}{Chans}{ $uchan }{Mode} ) ) );
  103         686  
405             }
406             }
407             else {
408 28         485 my $parsed_mode = parse_mode_line( @modes );
409 28         1848 for my $mode (@{ $parsed_mode->{modes} }) {
  28         117  
410 28         55 my $flag;
411 28 50       175 if ( ($flag) = $mode =~ /^\+(.)/ ) {
    0          
412 28 50       473 $self->{STATE}{usermode} .= $flag if $self->{STATE}{usermode} !~ /$flag/;
413             }
414             elsif ( ($flag) = $mode =~ /^-(.)/ ) {
415 0         0 $self->{STATE}{usermode} =~ s/$flag//;
416             }
417 28         415 $self->send_event_next(irc_user_mode => $who, $chan, $mode );
418             }
419             }
420              
421 77         1380 return PCI_EAT_NONE;
422             }
423              
424             sub S_topic {
425 5     5 0 7111 my ($self, undef) = splice @_, 0, 2;
426 5         12 my $who = ${ $_[0] };
  5         15  
427 5         11 my $chan = ${ $_[1] };
  5         11  
428 5         11 my $topic = ${ $_[2] };
  5         11  
429 5         20 my $map = $self->isupport('CASEMAPPING');
430 5         22 my $uchan = uc_irc($chan, $map);
431 5         118 push @{ $_[-1] }, $self->{STATE}{Chans}{$uchan}{Topic};
  5         29  
432              
433             $self->{STATE}{Chans}{ $uchan }{Topic} = {
434 5         38 Value => $topic,
435             SetBy => $who,
436             SetAt => time(),
437             };
438              
439 5         20 return PCI_EAT_NONE;
440             }
441              
442             # RPL_NAMES
443             sub S_353 {
444 35     35 0 40823 my ($self, undef) = splice @_, 0, 2;
445 35         96 my @data = @{ ${ $_[2] } };
  35         76  
  35         146  
446 35 50       311 shift @data if $data[0] =~ /^[@=*]$/;
447 35         129 my $chan = shift @data;
448 35         160 my @nicks = split /\s+/, shift @data;
449 35         168 my $map = $self->isupport('CASEMAPPING');
450 35         164 my $uchan = uc_irc($chan, $map);
451 35   50     641 my $prefix = $self->isupport('PREFIX') || { o => '@', v => '+' };
452 35         295 my $search = join '|', map { quotemeta } values %$prefix;
  105         321  
453 35         1081 $search = qr/(?:$search)/;
454              
455 35         152 for my $nick (@nicks) {
456 54         93 my $status;
457 54 100       1151 if ( ($status) = $nick =~ /^($search+)/ ) {
458 35         702 $nick =~ s/^($search+)//;
459             }
460              
461 54         147 my ($user, $host);
462 54 50       192 if ($self->isupport('UHNAMES')) {
463 0         0 ($nick, $user, $host) = split /[!@]/, $nick;
464             }
465              
466 54         172 my $unick = uc_irc($nick, $map);
467 54 100       732 $status = '' if !defined $status;
468 54         141 my $whatever = '';
469 54   100     404 my $existing = $self->{STATE}{Nicks}{$unick}{CHANS}{$uchan} || '';
470              
471 54         269 for my $mode (keys %$prefix) {
472 162 100 66     2323 if ($status =~ /\Q$prefix->{$mode}/ && $existing !~ /\Q$prefix->{$mode}/) {
473 35         116 $whatever .= $mode;
474             }
475             }
476              
477 54 100 66     247 $existing .= $whatever if !length $existing || $existing !~ /$whatever/;
478 54         269 $self->{STATE}{Nicks}{$unick}{CHANS}{$uchan} = $existing;
479 54         217 $self->{STATE}{Chans}{$uchan}{Nicks}{$unick} = $existing;
480 54         223 $self->{STATE}{Nicks}{$unick}{Nick} = $nick;
481 54 50       192 if ($self->isupport('UHNAMES')) {
482 0         0 $self->{STATE}{Nicks}{$unick}{User} = $user;
483 0         0 $self->{STATE}{Nicks}{$unick}{Host} = $host;
484             }
485             }
486 35         286 return PCI_EAT_NONE;
487             }
488              
489             # RPL_WHOREPLY
490             sub S_352 {
491 76     76 0 124901 my ($self, undef) = splice @_, 0, 2;
492 76         246 my ($chan, $user, $host, $server, $nick, $status, $rest) = @{ ${ $_[2] } };
  76         176  
  76         390  
493 76         339 my ($hops, $real) = split /\x20/, $rest, 2;
494 76         333 my $map = $self->isupport('CASEMAPPING');
495 76         299 my $unick = uc_irc($nick, $map);
496 76         1122 my $uchan = uc_irc($chan, $map);
497              
498 76         984 $self->{STATE}{Nicks}{ $unick }{Nick} = $nick;
499 76         220 $self->{STATE}{Nicks}{ $unick }{User} = $user;
500 76         209 $self->{STATE}{Nicks}{ $unick }{Host} = $host;
501              
502 76 50 33     429 if ( !exists $self->{whojoiners} || $self->{whojoiners} ) {
503 76         266 $self->{STATE}{Nicks}{ $unick }{Hops} = $hops;
504 76         235 $self->{STATE}{Nicks}{ $unick }{Real} = $real;
505 76         260 $self->{STATE}{Nicks}{ $unick }{Server} = $server;
506 76 100       357 $self->{STATE}{Nicks}{ $unick }{IRCop} = 1 if $status =~ /\*/;
507             }
508              
509 76 100       298 if ( exists $self->{STATE}{Chans}{ $uchan } ) {
510 61         127 my $whatever = '';
511 61   100     318 my $existing = $self->{STATE}{Nicks}{ $unick }{CHANS}{ $uchan } || '';
512 61   50     189 my $prefix = $self->isupport('PREFIX') || { o => '@', v => '+' };
513              
514 61         144 for my $mode ( keys %{ $prefix } ) {
  61         263  
515 183 100 66     2739 if ($status =~ /\Q$prefix->{$mode}/ && $existing !~ /\Q$prefix->{$mode}/ ) {
516 36         122 $whatever .= $mode;
517             }
518             }
519              
520 61 100 66     487 $existing .= $whatever if !$existing || $existing !~ /$whatever/;
521 61         207 $self->{STATE}{Nicks}{ $unick }{CHANS}{ $uchan } = $existing;
522 61         173 $self->{STATE}{Chans}{ $uchan }{Nicks}{ $unick } = $existing;
523 61         162 $self->{STATE}{Chans}{ $uchan }{Name} = $chan;
524              
525 61 100 100     260 if ($self->{STATE}{Chans}{ $uchan }{AWAY_SYNCH} && $unick ne uc_irc($self->nick_name(), $map)) {
526 2 100 66     57 if ( $status =~ /G/ && !$self->{STATE}{Nicks}{ $unick }{Away} ) {
    50 33        
527 1         7 $self->send_event_next(irc_user_away => $nick, [ $self->nick_channels( $nick ) ] );
528             }
529             elsif ($status =~ /H/ && $self->{STATE}{Nicks}{ $unick }{Away} ) {
530 1         8 $self->send_event_next(irc_user_back => $nick, [ $self->nick_channels( $nick ) ] );
531             }
532             }
533              
534 61 100       311 if ($self->{awaypoll}) {
535 9 100       51 $self->{STATE}{Nicks}{ $unick }{Away} = $status =~ /G/ ? 1 : 0;
536             }
537             }
538              
539 76         335 return PCI_EAT_NONE;
540             }
541              
542             # RPL_ENDOFWHO
543             sub S_315 {
544 50     50 0 32488 my ($self, undef) = splice @_, 0, 2;
545 50         128 my $what = ${ $_[2] }->[0];
  50         170  
546 50         294 my $map = $self->isupport('CASEMAPPING');
547 50         274 my $uwhat = uc_irc($what, $map);
548              
549 50 100       975 if ( exists $self->{STATE}{Chans}{ $uwhat } ) {
550 35         84 my $chan = $what; my $uchan = $uwhat;
  35         68  
551 35 50       205 if ( $self->_channel_sync($chan, 'WHO') ) {
    100          
552 0         0 my $rec = delete $self->{CHANNEL_SYNCH}{ $uchan };
553 0         0 $self->send_event_next(irc_chan_sync => $chan, time() - $rec->{_time} );
554             }
555             elsif ( $self->{STATE}{Chans}{ $uchan }{AWAY_SYNCH} ) {
556 2         7 $self->{STATE}{Chans}{ $uchan }{AWAY_SYNCH} = 0;
557 2         16 $poe_kernel->delay_add(_away_sync => $self->{awaypoll} => $chan );
558 2         337 $self->send_event_next(irc_away_sync_end => $chan );
559             }
560             }
561             else {
562 15         43 my $nick = $what; my $unick = $uwhat;
  15         42  
563 15         30 my $chan = shift @{ $self->{NICK_SYNCH}{ $unick } };
  15         61  
564 15 50       36 delete $self->{NICK_SYNCH}{ $unick } if !@{ $self->{NICK_SYNCH}{ $unick } };
  15         78  
565 15         112 $self->send_event_next(irc_nick_sync => $nick, $chan );
566             }
567              
568 50         660 return PCI_EAT_NONE;
569             }
570              
571             # RPL_CREATIONTIME
572             sub S_329 {
573 33     33 0 18703 my ($self, undef) = splice @_, 0, 2;
574 33         134 my $map = $self->isupport('CASEMAPPING');
575 33         72 my $chan = ${ $_[2] }->[0];
  33         116  
576 33         69 my $time = ${ $_[2] }->[1];
  33         105  
577 33         137 my $uchan = uc_irc($chan, $map);
578              
579 33         783 $self->{STATE}->{Chans}{ $uchan }{CreationTime} = $time;
580 33         157 return PCI_EAT_NONE;
581             }
582              
583             # RPL_BANLIST
584             sub S_367 {
585 0     0 0 0 my ($self, undef) = splice @_, 0, 2;
586 0         0 my @args = @{ ${ $_[2] } };
  0         0  
  0         0  
587 0         0 my $chan = shift @args;
588 0         0 my $map = $self->isupport('CASEMAPPING');
589 0         0 my $uchan = uc_irc($chan, $map);
590 0         0 my ($mask, $who, $when) = @args;
591              
592 0         0 $self->{STATE}{Chans}{ $uchan }{Lists}{b}{ $mask } = {
593             SetBy => $who,
594             SetAt => $when,
595             };
596 0         0 return PCI_EAT_NONE;
597             }
598              
599             # RPL_ENDOFBANLIST
600             sub S_368 {
601 33     33 0 17085 my ($self, undef) = splice @_, 0, 2;
602 33         81 my @args = @{ ${ $_[2] } };
  33         63  
  33         140  
603 33         80 my $chan = shift @args;
604 33         149 my $map = $self->isupport('CASEMAPPING');
605 33         142 my $uchan = uc_irc($chan, $map);
606              
607 33 100       526 if ($self->_channel_sync($chan, 'BAN')) {
608 32         98 my $rec = delete $self->{CHANNEL_SYNCH}{ $uchan };
609 32         209 $self->send_event_next(irc_chan_sync => $chan, time() - $rec->{_time} );
610             }
611              
612 33         955 return PCI_EAT_NONE;
613             }
614              
615             # RPL_INVITELIST
616             sub S_346 {
617 0     0 0 0 my ($self, undef) = splice @_, 0, 2;
618 0         0 my ($chan, $mask, $who, $when) = @{ ${ $_[2] } };
  0         0  
  0         0  
619 0         0 my $map = $self->isupport('CASEMAPPING');
620 0         0 my $uchan = uc_irc($chan, $map);
621 0         0 my $invex = $self->isupport('INVEX');
622              
623 0         0 $self->{STATE}{Chans}{ $uchan }{Lists}{ $invex }{ $mask } = {
624             SetBy => $who,
625             SetAt => $when
626             };
627              
628 0         0 return PCI_EAT_NONE;
629             }
630              
631             # RPL_ENDOFINVITELIST
632             sub S_347 {
633 1     1 0 3099 my ($self, undef) = splice @_, 0, 2;
634 1         3 my ($chan) = @{ ${ $_[2] } };
  1         3  
  1         4  
635 1         6 my $map = $self->isupport('CASEMAPPING');
636 1         6 my $uchan = uc_irc($chan, $map);
637              
638 1         20 $self->send_event_next(irc_chan_sync_invex => $chan);
639 1         28 return PCI_EAT_NONE;
640             }
641              
642             # RPL_EXCEPTLIST
643             sub S_348 {
644 0     0 0 0 my ($self, undef) = splice @_, 0, 2;
645 0         0 my ($chan, $mask, $who, $when) = @{ ${ $_[2] } };
  0         0  
  0         0  
646 0         0 my $map = $self->isupport('CASEMAPPING');
647 0         0 my $uchan = uc_irc($chan, $map);
648 0         0 my $excepts = $self->isupport('EXCEPTS');
649              
650 0         0 $self->{STATE}{Chans}{ $uchan }{Lists}{ $excepts }{ $mask } = {
651             SetBy => $who,
652             SetAt => $when,
653             };
654 0         0 return PCI_EAT_NONE;
655             }
656              
657             # RPL_ENDOFEXCEPTLIST
658             sub S_349 {
659 1     1 0 485 my ($self, undef) = splice @_, 0, 2;
660 1         3 my ($chan) = @{ ${ $_[2] } };
  1         2  
  1         5  
661 1         5 my $map = $self->isupport('CASEMAPPING');
662 1         5 my $uchan = uc_irc($chan, $map);
663              
664 1         21 $self->send_event_next(irc_chan_sync_excepts => $chan);
665 1         29 return PCI_EAT_NONE;
666             }
667              
668             # RPL_CHANNELMODEIS
669             sub S_324 {
670 33     33 0 23026 my ($self, undef) = splice @_, 0, 2;
671 33         84 my @args = @{ ${ $_[2] } };
  33         69  
  33         140  
672 33         80 my $chan = shift @args;
673 33         216 my $map = $self->isupport('CASEMAPPING');
674 33         190 my $uchan = uc_irc($chan, $map);
675 33   50     536 my $modes = $self->isupport('CHANMODES') || [ qw(beI k l imnpstaqr) ];
676 33   50     152 my $prefix = $self->isupport('PREFIX') || { o => '@', v => '+' };
677              
678 33         239 my $parsed_mode = parse_mode_line($prefix, $modes, @args);
679 33         7564 for my $mode (@{ $parsed_mode->{modes} }) {
  33         115  
680 74         280 $mode =~ s/\+//;
681 74         147 my $arg = '';
682 74 100       840 if ($mode =~ /[^$modes->[3]]/) {
683             # doesn't match a mode with no args
684 6         30 $arg = shift @{ $parsed_mode->{args} };
  6         20  
685             }
686              
687 74 100       394 if ( $self->{STATE}{Chans}{ $uchan }{Mode} ) {
688 57 100       890 $self->{STATE}{Chans}{ $uchan }{Mode} .= $mode if $self->{STATE}{Chans}{ $uchan }{Mode} !~ /$mode/;
689             }
690             else {
691 17         60 $self->{STATE}{Chans}{ $uchan }{Mode} = $mode;
692             }
693              
694 74 100       499 $self->{STATE}{Chans}{ $uchan }{ModeArgs}{ $mode } = $arg if defined ( $arg );
695             }
696              
697 33 50       272 if ( $self->{STATE}{Chans}{ $uchan }{Mode} ) {
698 33         286 $self->{STATE}{Chans}{ $uchan }{Mode} = join('', sort {uc $a cmp uc $b} split //, $self->{STATE}{Chans}{ $uchan }{Mode} );
  49         253  
699             }
700              
701 33 50       151 if ( $self->_channel_sync($chan, 'MODE') ) {
702 0         0 my $rec = delete $self->{CHANNEL_SYNCH}{ $uchan };
703 0         0 $self->send_event_next(irc_chan_sync => $chan, time() - $rec->{_time} );
704             }
705              
706 33         222 return PCI_EAT_NONE;
707             }
708              
709             # RPL_TOPIC
710             sub S_332 {
711 4     4 0 1458 my ($self, undef) = splice @_, 0, 2;
712 4         28 my $chan = ${ $_[2] }->[0];
  4         18  
713 4         10 my $topic = ${ $_[2] }->[1];
  4         13  
714 4         20 my $map = $self->isupport('CASEMAPPING');
715 4         26 my $uchan = uc_irc($chan, $map);
716              
717 4         155 $self->{STATE}{Chans}{ $uchan }{Topic}{Value} = $topic;
718 4         21 return PCI_EAT_NONE;
719             }
720              
721             # RPL_TOPICWHOTIME
722             sub S_333 {
723 4     4 0 1660 my ($self, undef) = splice @_, 0, 2;
724 4         12 my ($chan, $who, $when) = @{ ${ $_[2] } };
  4         9  
  4         16  
725 4         22 my $map = $self->isupport('CASEMAPPING');
726 4         20 my $uchan = uc_irc($chan, $map);
727              
728 4         145 $self->{STATE}{Chans}{ $uchan }{Topic}{SetBy} = $who;
729 4         16 $self->{STATE}{Chans}{ $uchan }{Topic}{SetAt} = $when;
730              
731 4         18 return PCI_EAT_NONE;
732             }
733              
734             # Methods for STATE query
735             # Internal methods begin with '_'
736             #
737              
738             sub umode {
739 2     2 1 8 my ($self) = @_;
740 2         45 return $self->{STATE}{usermode};
741             }
742              
743             sub is_user_mode_set {
744 2     2 1 272 my ($self, $mode) = @_;
745              
746 2 50       9 if (!defined $mode) {
747 0         0 warn 'User mode is undefined';
748 0         0 return;
749             }
750              
751 2   50     11 $mode = (split //, $mode)[0] || return;
752 2         24 $mode =~ s/[^A-Za-z]//g;
753 2 50       6 return if !$mode;
754              
755 2 50       81 return 1 if $self->{STATE}{usermode} =~ /$mode/;
756 0         0 return;
757             }
758              
759             sub _away_sync {
760 2     2   1981971 my ($self, $chan) = @_[OBJECT, ARG0];
761 2         19 my $map = $self->isupport('CASEMAPPING');
762 2         19 my $uchan = uc_irc($chan, $map);
763              
764 2         55 $self->{STATE}{Chans}{ $uchan }{AWAY_SYNCH} = 1;
765 2         41 $self->yield(who => $chan);
766 2         380 $self->send_event(irc_away_sync_start => $chan);
767              
768 2         302 return;
769             }
770              
771             sub _channel_sync {
772 101     101   326 my ($self, $chan, $sync) = @_;
773 101         313 my $map = $self->isupport('CASEMAPPING');
774 101         352 my $uchan = uc_irc($chan, $map);
775              
776 101 100 66     1495 return if !$self->_channel_exists($chan) || !defined $self->{CHANNEL_SYNCH}{ $uchan };
777 96 50       371 $self->{CHANNEL_SYNCH}{ $uchan }{ $sync } = 1 if $sync;
778              
779 96         251 for my $item ( qw(BAN MODE WHO) ) {
780 160 100       846 return if !$self->{CHANNEL_SYNCH}{ $uchan }{ $item };
781             }
782              
783 32         127 return 1;
784             }
785              
786             sub _nick_exists {
787 162     162   348 my ($self, $nick) = @_;
788 162         486 my $map = $self->isupport('CASEMAPPING');
789 162         439 my $unick = uc_irc($nick, $map);
790              
791 162 100       2315 return 1 if exists $self->{STATE}{Nicks}{ $unick };
792 20         97 return;
793             }
794              
795             sub _channel_exists {
796 137     137   330 my ($self, $chan) = @_;
797 137         354 my $map = $self->isupport('CASEMAPPING');
798 137         316 my $uchan = uc_irc($chan, $map);
799              
800 137 50       2402 return 1 if exists $self->{STATE}{Chans}{ $uchan };
801 0         0 return;
802             }
803              
804             sub _nick_has_channel_mode {
805 8     8   22 my ($self, $chan, $nick, $flag) = @_;
806 8         39 my $map = $self->isupport('CASEMAPPING');
807 8         46 my $uchan = uc_irc($chan, $map);
808 8         104 my $unick = uc_irc($nick, $map);
809 8         88 $flag = (split //, $flag)[0];
810              
811 8 50       24 return if !$self->is_channel_member($uchan, $unick);
812 8 100       130 return 1 if $self->{STATE}{Nicks}{ $unick }{CHANS}{ $uchan } =~ /$flag/;
813 7         25 return;
814             }
815              
816             # Returns all the channels that the bot is on with an indication of
817             # whether it has operator, halfop or voice.
818             sub channels {
819 66     66 1 204 my ($self) = @_;
820 66         222 my $map = $self->isupport('CASEMAPPING');
821 66         260 my $unick = uc_irc($self->nick_name(), $map);
822              
823 66         2519 my %result;
824 66 100 100     307 if (defined $unick && $self->_nick_exists($unick)) {
825 56         104 for my $uchan ( keys %{ $self->{STATE}{Nicks}{ $unick }{CHANS} } ) {
  56         254  
826 67         499 $result{ $self->{STATE}{Chans}{ $uchan }{Name} } = $self->{STATE}{Nicks}{ $unick }{CHANS}{ $uchan };
827             }
828             }
829              
830 66         238 return \%result;
831             }
832              
833             sub nicks {
834 2     2 1 4007 my ($self) = @_;
835 2         6 return map { $self->{STATE}{Nicks}{$_}{Nick} } keys %{ $self->{STATE}{Nicks} };
  2         17  
  2         15  
836             }
837              
838             sub nick_info {
839 58     58 1 3797 my ($self, $nick) = @_;
840              
841 58 50       216 if (!defined $nick) {
842 0         0 warn 'Nickname is undefined';
843 0         0 return;
844             }
845              
846 58         221 my $map = $self->isupport('CASEMAPPING');
847 58         264 my $unick = uc_irc($nick, $map);
848              
849 58 100       914 return if !$self->_nick_exists($nick);
850              
851 49         119 my $user = $self->{STATE}{Nicks}{ $unick };
852 49         118 my %result = %{ $user };
  49         476  
853              
854             # maybe we haven't synced this user's info yet
855 49 50 33     372 if (defined $result{User} && defined $result{Host}) {
856 49         227 $result{Userhost} = "$result{User}\@$result{Host}";
857             }
858 49         164 delete $result{'CHANS'};
859              
860 49         196 return \%result;
861             }
862              
863             sub nick_long_form {
864 16     16 1 49 my ($self, $nick) = @_;
865              
866 16 50       61 if (!defined $nick) {
867 0         0 warn 'Nickname is undefined';
868 0         0 return;
869             }
870              
871 16         66 my $map = $self->isupport('CASEMAPPING');
872 16         66 my $unick = uc_irc($nick, $map);
873              
874 16 50       234 return if !$self->_nick_exists($nick);
875              
876 16         44 my $user = $self->{STATE}{Nicks}{ $unick };
877 16 50 33     146 return unless exists $user->{User} && exists $user->{Host};
878 16         98 return "$user->{Nick}!$user->{User}\@$user->{Host}";
879             }
880              
881             sub nick_channels {
882 10     10 1 32 my ($self, $nick) = @_;
883              
884 10 50       40 if (!defined $nick) {
885 0         0 warn 'Nickname is undefined';
886 0         0 return;
887             }
888 10         38 my $map = $self->isupport('CASEMAPPING');
889 10         36 my $unick = uc_irc($nick, $map);
890              
891 10 50       206 return if !$self->_nick_exists($nick);
892 10         45 return map { $self->{STATE}{Chans}{$_}{Name} } keys %{ $self->{STATE}{Nicks}{ $unick }{CHANS} };
  11         85  
  10         59  
893             }
894              
895             sub channel_list {
896 6     6 1 207 my ($self, $chan) = @_;
897              
898 6 50       34 if (!defined $chan) {
899 0         0 warn 'Channel is undefined';
900 0         0 return;
901             }
902              
903 6         49 my $map = $self->isupport('CASEMAPPING');
904 6         26 my $uchan = uc_irc($chan, $map);
905              
906 6 50       66 return if !$self->_channel_exists($chan);
907 6         26 return map { $self->{STATE}{Nicks}{$_}{Nick} } keys %{ $self->{STATE}{Chans}{ $uchan }{Nicks} };
  9         36  
  6         30  
908             }
909              
910             sub is_away {
911 4     4 1 15 my ($self, $nick) = @_;
912              
913 4 50       19 if (!defined $nick) {
914 0         0 warn 'Nickname is undefined';
915 0         0 return;
916             }
917              
918 4         20 my $map = $self->isupport('CASEMAPPING');
919 4         21 my $unick = uc_irc($nick, $map);
920              
921 4 50       72 if ($unick eq uc_irc($self->nick_name())) {
922             # more accurate
923 4 100       73 return 1 if $self->{STATE}{away};
924 3         40 return;
925             }
926              
927 0 0       0 return if !$self->_nick_exists($nick);
928 0 0       0 return 1 if $self->{STATE}{Nicks}{ $unick }{Away};
929 0         0 return;
930             }
931              
932             sub is_operator {
933 2     2 1 9 my ($self, $nick) = @_;
934              
935 2 50       10 if (!defined $nick) {
936 0         0 warn 'Nickname is undefined';
937 0         0 return;
938             }
939              
940 2         28 my $map = $self->isupport('CASEMAPPING');
941 2         14 my $unick = uc_irc($nick, $map);
942              
943 2 50       66 return if !$self->_nick_exists($nick);
944              
945 0 0       0 return 1 if $self->{STATE}{Nicks}{ $unick }{IRCop};
946 0         0 return;
947             }
948              
949             sub is_channel_mode_set {
950 8     8 1 157 my ($self, $chan, $mode) = @_;
951              
952 8 50 33     51 if (!defined $chan || !defined $mode) {
953 0         0 warn 'Channel or mode is undefined';
954 0         0 return;
955             }
956              
957 8         22 my $map = $self->isupport('CASEMAPPING');
958 8         21 my $uchan = uc_irc($chan, $map);
959 8         87 $mode = (split //, $mode)[0];
960              
961 8 50 33     19 return if !$self->_channel_exists($chan) || !$mode;
962 8         34 $mode =~ s/[^A-Za-z]//g;
963              
964 8 100 66     145 if (defined $self->{STATE}{Chans}{ $uchan }{Mode}
965             && $self->{STATE}{Chans}{ $uchan }{Mode} =~ /$mode/) {
966 2         18 return 1;
967             }
968              
969 6         31 return;
970             }
971              
972             sub is_channel_synced {
973 0     0 1 0 my ($self, $chan) = @_;
974              
975 0 0       0 if (!defined $chan) {
976 0         0 warn 'Channel is undefined';
977 0         0 return;
978             }
979              
980 0         0 return $self->_channel_sync($chan);
981             }
982              
983             sub channel_creation_time {
984 2     2 1 2776 my ($self, $chan) = @_;
985              
986 2 50       8 if (!defined $chan) {
987 0         0 warn 'Channel is undefined';
988 0         0 return;
989             }
990              
991 2         11 my $map = $self->isupport('CASEMAPPING');
992 2         9 my $uchan = uc_irc($chan, $map);
993              
994 2 50       26 return if !$self->_channel_exists($chan);
995 2 50       7 return if !exists $self->{STATE}{Chans}{ $uchan }{CreationTime};
996              
997 2         12 return $self->{STATE}{Chans}{ $uchan }{CreationTime};
998             }
999              
1000             sub channel_limit {
1001 3     3 1 11 my ($self, $chan) = @_;
1002              
1003 3 50       45 if (!defined $chan) {
1004 0         0 warn 'Channel is undefined';
1005 0         0 return;
1006             }
1007              
1008 3         12 my $map = $self->isupport('CASEMAPPING');
1009 3         12 my $uchan = uc_irc($chan, $map);
1010              
1011 3 50       39 return if !$self->_channel_exists($chan);
1012              
1013 3 100 66     10 if ( $self->is_channel_mode_set($chan, 'l')
1014             && defined $self->{STATE}{Chans}{ $uchan }{ModeArgs}{l} ) {
1015 1         10 return $self->{STATE}{Chans}{ $uchan }{ModeArgs}{l};
1016             }
1017              
1018 2         11 return;
1019             }
1020              
1021             sub channel_key {
1022 2     2 1 3 my ($self, $chan) = @_;
1023              
1024 2 50       5 if (!defined $chan) {
1025 0         0 warn 'Channel is undefined';
1026 0         0 return;
1027             }
1028              
1029 2         5 my $map = $self->isupport('CASEMAPPING');
1030 2         11 my $uchan = uc_irc($chan, $map);
1031 2 50       19 return if !$self->_channel_exists($chan);
1032              
1033 2 50 33     5 if ( $self->is_channel_mode_set($chan, 'k')
1034             && defined $self->{STATE}{Chans}{ $uchan }{ModeArgs}{k} ) {
1035 0         0 return $self->{STATE}{Chans}{ $uchan }{ModeArgs}{k};
1036             }
1037              
1038 2         7 return;
1039             }
1040              
1041             sub channel_modes {
1042 0     0 1 0 my ($self, $chan) = @_;
1043              
1044 0 0       0 if (!defined $chan) {
1045 0         0 warn 'Channel is undefined';
1046 0         0 return;
1047             }
1048              
1049 0         0 my $map = $self->isupport('CASEMAPPING');
1050 0         0 my $uchan = uc_irc($chan, $map);
1051 0 0       0 return if !$self->_channel_exists($chan);
1052              
1053 0         0 my %modes;
1054 0 0       0 if ( defined $self->{STATE}{Chans}{ $uchan }{Mode} ) {
1055 0         0 %modes = map { ($_ => '') } split(//, $self->{STATE}{Chans}{ $uchan }{Mode});
  0         0  
1056             }
1057 0 0       0 if ( defined $self->{STATE}{Chans}{ $uchan }->{ModeArgs} ) {
1058 0         0 my %args = %{ $self->{STATE}{Chans}{ $uchan }{ModeArgs} };
  0         0  
1059 0         0 @modes{keys %args} = values %args;
1060             }
1061              
1062 0         0 return \%modes;
1063             }
1064              
1065             sub is_channel_member {
1066 11     11 1 1979 my ($self, $chan, $nick) = @_;
1067              
1068 11 50 33     87 if (!defined $chan || !defined $nick) {
1069 0         0 warn 'Channel or nickname is undefined';
1070 0         0 return;
1071             }
1072              
1073 11         31 my $map = $self->isupport('CASEMAPPING');
1074 11         26 my $uchan = uc_irc($chan, $map);
1075 11         102 my $unick = uc_irc($nick, $map);
1076              
1077 11 50 33     205 return if !$self->_channel_exists($chan) || !$self->_nick_exists($nick);
1078 11 50       60 return 1 if defined $self->{STATE}{Chans}{ $uchan }{Nicks}{ $unick };
1079 0         0 return;
1080             }
1081              
1082             sub is_channel_operator {
1083 4     4 1 20 my ($self, $chan, $nick) = @_;
1084              
1085 4 50 33     32 if (!defined $chan || !defined $nick) {
1086 0         0 warn 'Channel or nickname is undefined';
1087 0         0 return;
1088             }
1089              
1090 4 100       20 return 1 if $self->_nick_has_channel_mode($chan, $nick, 'o');
1091 3         16 return;
1092             }
1093              
1094             sub has_channel_voice {
1095 2     2 1 7 my ($self, $chan, $nick) = @_;
1096              
1097 2 50 33     17 if (!defined $chan || !defined $nick) {
1098 0         0 warn 'Channel or nickname is undefined';
1099 0         0 return;
1100             }
1101              
1102 2 50       9 return 1 if $self->_nick_has_channel_mode($chan, $nick, 'v');
1103 2         9 return;
1104             }
1105              
1106             sub is_channel_halfop {
1107 2     2 1 8 my ($self, $chan, $nick) = @_;
1108              
1109 2 50 33     17 if (!defined $chan || !defined $nick) {
1110 0         0 warn 'Channel or nickname is undefined';
1111 0         0 return;
1112             }
1113              
1114 2 50       7 return 1 if $self->_nick_has_channel_mode($chan, $nick, 'h');
1115 2         11 return;
1116             }
1117              
1118             sub is_channel_owner {
1119 0     0 1 0 my ($self, $chan, $nick) = @_;
1120              
1121 0 0 0     0 if (!defined $chan || !defined $nick) {
1122 0         0 warn 'Channel or nickname is undefined';
1123 0         0 return;
1124             }
1125              
1126 0 0       0 return 1 if $self->_nick_has_channel_mode($chan, $nick, 'q');
1127 0         0 return;
1128             }
1129              
1130             sub is_channel_admin {
1131 0     0 1 0 my ($self, $chan, $nick) = @_;
1132              
1133 0 0 0     0 if (!defined $chan || !defined $nick) {
1134 0         0 warn 'Channel or nickname is undefined';
1135 0         0 return;
1136             }
1137              
1138 0 0       0 return 1 if $self->_nick_has_channel_mode($chan, $nick, 'a');
1139 0         0 return;
1140             }
1141              
1142             sub ban_mask {
1143 2     2 1 8 my ($self, $chan, $mask) = @_;
1144              
1145 2 50 33     13 if (!defined $chan || !defined $mask) {
1146 0         0 warn 'Channel or mask is undefined';
1147 0         0 return;
1148             }
1149              
1150 2         9 my $map = $self->isupport('CASEMAPPING');
1151 2         26 $mask = normalize_mask($mask);
1152 2         64 my @result;
1153              
1154 2 50       6 return if !$self->_channel_exists($chan);
1155              
1156             # Convert the mask from IRC to regex.
1157 2         5 $mask = uc_irc($mask, $map);
1158 2         18 $mask = quotemeta $mask;
1159 2         9 $mask =~ s/\\\*/[\x01-\xFF]{0,}/g;
1160 2         5 $mask =~ s/\\\?/[\x01-\xFF]{1,1}/g;
1161              
1162 2         7 for my $nick ( $self->channel_list($chan) ) {
1163 3 100       74 push @result, $nick if uc_irc($self->nick_long_form($nick)) =~ /^$mask$/;
1164             }
1165              
1166 2         62 return @result;
1167             }
1168              
1169              
1170             sub channel_ban_list {
1171 0     0 1 0 my ($self, $chan) = @_;
1172              
1173 0 0       0 if (!defined $chan) {
1174 0         0 warn 'Channel is undefined';
1175 0         0 return;
1176             }
1177              
1178 0         0 my $map = $self->isupport('CASEMAPPING');
1179 0         0 my $uchan = uc_irc($chan, $map);
1180 0         0 my %result;
1181              
1182 0 0       0 return if !$self->_channel_exists($chan);
1183              
1184 0 0       0 if ( defined $self->{STATE}{Chans}{ $uchan }{Lists}{b} ) {
1185 0         0 %result = %{ $self->{STATE}{Chans}{ $uchan }{Lists}{b} };
  0         0  
1186             }
1187              
1188 0         0 return \%result;
1189             }
1190              
1191             sub channel_except_list {
1192 0     0 1 0 my ($self, $chan) = @_;
1193              
1194 0 0       0 if (!defined $chan) {
1195 0         0 warn 'Channel is undefined';
1196 0         0 return;
1197             }
1198              
1199 0         0 my $map = $self->isupport('CASEMAPPING');
1200 0         0 my $uchan = uc_irc($chan, $map);
1201 0         0 my $excepts = $self->isupport('EXCEPTS');
1202 0         0 my %result;
1203              
1204 0 0       0 return if !$self->_channel_exists($chan);
1205              
1206 0 0       0 if ( defined $self->{STATE}{Chans}{ $uchan }{Lists}{ $excepts } ) {
1207 0         0 %result = %{ $self->{STATE}{Chans}{ $uchan }{Lists}{ $excepts } };
  0         0  
1208             }
1209              
1210 0         0 return \%result;
1211             }
1212              
1213             sub channel_invex_list {
1214 0     0 1 0 my ($self, $chan) = @_;
1215              
1216 0 0       0 if (!defined $chan) {
1217 0         0 warn 'Channel is undefined';
1218 0         0 return;
1219             }
1220              
1221 0         0 my $map = $self->isupport('CASEMAPPING');
1222 0         0 my $uchan = uc_irc($chan, $map);
1223 0         0 my $invex = $self->isupport('INVEX');
1224 0         0 my %result;
1225              
1226 0 0       0 return if !$self->_channel_exists($chan);
1227              
1228 0 0       0 if ( defined $self->{STATE}{Chans}{ $uchan }{Lists}{ $invex } ) {
1229 0         0 %result = %{ $self->{STATE}{Chans}{ $uchan }{Lists}{ $invex } };
  0         0  
1230             }
1231              
1232 0         0 return \%result;
1233             }
1234              
1235             sub channel_topic {
1236 2     2 1 98 my ($self, $chan) = @_;
1237              
1238 2 50       7 if (!defined $chan) {
1239 0         0 warn 'Channel is undefined';
1240 0         0 return;
1241             }
1242              
1243 2         7 my $map = $self->isupport('CASEMAPPING');
1244 2         5 my $uchan = uc_irc($chan, $map);
1245 2         17 my %result;
1246              
1247 2 50       5 return if !$self->_channel_exists($chan);
1248              
1249 2 100       7 if ( defined $self->{STATE}{Chans}{ $uchan }{Topic} ) {
1250 1         35 %result = %{ $self->{STATE}{Chans}{ $uchan }{Topic} };
  1         6  
1251             }
1252              
1253 2         7 return \%result;
1254             }
1255              
1256             sub channel_url {
1257 0     0 1   my ($self, $chan) = @_;
1258              
1259 0 0         if (!defined $chan) {
1260 0           warn 'Channel is undefined';
1261 0           return;
1262             }
1263              
1264 0           my $map = $self->isupport('CASEMAPPING');
1265 0           my $uchan = uc_irc($chan, $map);
1266              
1267 0 0         return if !$self->_channel_exists($chan);
1268 0           return $self->{STATE}{Chans}{ $uchan }{Url};
1269             }
1270              
1271             sub nick_channel_modes {
1272 0     0 1   my ($self, $chan, $nick) = @_;
1273              
1274 0 0 0       if (!defined $chan || !defined $nick) {
1275 0           warn 'Channel or nick is undefined';
1276 0           return;
1277             }
1278              
1279 0           my $map = $self->isupport('CASEMAPPING');
1280 0           my $uchan = uc_irc($chan, $map);
1281 0           my $unick = uc_irc($nick, $map);
1282              
1283 0 0         return if !$self->is_channel_member($chan, $nick);
1284              
1285 0           return $self->{STATE}{Nicks}{ $unick }{CHANS}{ $uchan };
1286             }
1287              
1288             1;
1289              
1290             =encoding utf8
1291              
1292             =head1 NAME
1293              
1294             POE::Component::IRC::State - A fully event-driven IRC client module with
1295             nickname and channel tracking
1296              
1297             =head1 SYNOPSIS
1298              
1299             # A simple Rot13 'encryption' bot
1300              
1301             use strict;
1302             use warnings;
1303             use POE qw(Component::IRC::State);
1304              
1305             my $nickname = 'Flibble' . $$;
1306             my $ircname = 'Flibble the Sailor Bot';
1307             my $ircserver = 'irc.blahblahblah.irc';
1308             my $port = 6667;
1309              
1310             my @channels = ( '#Blah', '#Foo', '#Bar' );
1311              
1312             # We create a new PoCo-IRC object and component.
1313             my $irc = POE::Component::IRC::State->spawn(
1314             nick => $nickname,
1315             server => $ircserver,
1316             port => $port,
1317             ircname => $ircname,
1318             ) or die "Oh noooo! $!";
1319              
1320             POE::Session->create(
1321             package_states => [
1322             main => [ qw(_default _start irc_001 irc_public) ],
1323             ],
1324             heap => { irc => $irc },
1325             );
1326              
1327             $poe_kernel->run();
1328              
1329             sub _start {
1330             my ($kernel, $heap) = @_[KERNEL, HEAP];
1331              
1332             # We get the session ID of the component from the object
1333             # and register and connect to the specified server.
1334             my $irc_session = $heap->{irc}->session_id();
1335             $kernel->post( $irc_session => register => 'all' );
1336             $kernel->post( $irc_session => connect => { } );
1337             return;
1338             }
1339              
1340             sub irc_001 {
1341             my ($kernel, $sender) = @_[KERNEL, SENDER];
1342              
1343             # Get the component's object at any time by accessing the heap of
1344             # the SENDER
1345             my $poco_object = $sender->get_heap();
1346             print "Connected to ", $poco_object->server_name(), "\n";
1347              
1348             # In any irc_* events SENDER will be the PoCo-IRC session
1349             $kernel->post( $sender => join => $_ ) for @channels;
1350             return;
1351             }
1352              
1353             sub irc_public {
1354             my ($kernel ,$sender, $who, $where, $what) = @_[KERNEL, SENDER, ARG0 .. ARG2];
1355             my $nick = ( split /!/, $who )[0];
1356             my $channel = $where->[0];
1357             my $poco_object = $sender->get_heap();
1358              
1359             if ( my ($rot13) = $what =~ /^rot13 (.+)/ ) {
1360             # Only operators can issue a rot13 command to us.
1361             return if !$poco_object->is_channel_operator( $channel, $nick );
1362              
1363             $rot13 =~ tr[a-zA-Z][n-za-mN-ZA-M];
1364             $kernel->post( $sender => privmsg => $channel => "$nick: $rot13" );
1365             }
1366             return;
1367             }
1368              
1369             # We registered for all events, this will produce some debug info.
1370             sub _default {
1371             my ($event, $args) = @_[ARG0 .. $#_];
1372             my @output = ( "$event: " );
1373              
1374             for my $arg ( @$args ) {
1375             if (ref $arg eq 'ARRAY') {
1376             push( @output, '[' . join(', ', @$arg ) . ']' );
1377             }
1378             else {
1379             push ( @output, "'$arg'" );
1380             }
1381             }
1382             print join ' ', @output, "\n";
1383             return 0;
1384             }
1385              
1386             =head1 DESCRIPTION
1387              
1388             POE::Component::IRC::State is a sub-class of L
1389             which tracks IRC state entities such as nicks and channels. See the
1390             documentation for L for general usage.
1391             This document covers the extra methods that POE::Component::IRC::State provides.
1392              
1393             The component tracks channels and nicks, so that it always has a current
1394             snapshot of what channels it is on and who else is on those channels. The
1395             returned object provides methods to query the collected state.
1396              
1397             =head1 CONSTRUCTORS
1398              
1399             POE::Component::IRC::State's constructors, and its C event, all
1400             take the same arguments as L does, as
1401             well as two additional ones:
1402              
1403             B<'AwayPoll'>, the interval (in seconds) in which to poll (i.e. C)
1404             the away status of channel members. Defaults to 0 (disabled). If enabled, you
1405             will receive C / L|/irc_user_away> /
1406             L|/irc_user_back> events, and will be able to use the
1407             L|/is_away> method for users other than yourself. This can cause
1408             a lot of increase in traffic, especially if you are on big channels, so if you
1409             do use this, you probably don't want to set it too low. For reference, X-Chat
1410             uses 300 seconds (5 minutes).
1411              
1412             B<'WhoJoiners'>, a boolean indicating whether the component should send a
1413             C for every person which joins a channel. Defaults to on
1414             (the C is sent). If you turn this off, L|/is_operator>
1415             will not work and L|/nick_info> will only return the keys
1416             B<'Nick'>, B<'User'>, B<'Host'> and B<'Userhost'>.
1417              
1418             =head1 METHODS
1419              
1420             All of the L methods are supported,
1421             plus the following:
1422              
1423             =head2 C
1424              
1425             Expects a channel and a ban mask, as passed to MODE +b-b. Returns a list of
1426             nicks on that channel that match the specified ban mask or an empty list if
1427             the channel doesn't exist in the state or there are no matches.
1428              
1429             =head2 C
1430              
1431             Expects a channel as a parameter. Returns a hashref containing the banlist
1432             if the channel is in the state, a false value if not. The hashref keys are the
1433             entries on the list, each with the keys B<'SetBy'> and B<'SetAt'>. These keys
1434             will hold the nick!hostmask of the user who set the entry (or just the nick
1435             if it's all the ircd gives us), and the time at which it was set respectively.
1436              
1437             =head2 C
1438              
1439             Expects a channel as parameter. Returns channel creation time or a false value.
1440              
1441             =head2 C
1442              
1443             Expects a channel as a parameter. Returns a hashref containing the ban
1444             exception list if the channel is in the state, a false value if not. The
1445             hashref keys are the entries on the list, each with the keys B<'SetBy'> and
1446             B<'SetAt'>. These keys will hold the nick!hostmask of the user who set the
1447             entry (or just the nick if it's all the ircd gives us), and the time at which
1448             it was set respectively.
1449              
1450             =head2 C
1451              
1452             Expects a channel as a parameter. Returns a hashref containing the invite
1453             exception list if the channel is in the state, a false value if not. The
1454             hashref keys are the entries on the list, each with the keys B<'SetBy'> and
1455             B<'SetAt'>. These keys will hold the nick!hostmask of the user who set the
1456             entry (or just the nick if it's all the ircd gives us), and the time at which
1457             it was set respectively.
1458              
1459             =head2 C
1460              
1461             Expects a channel as parameter. Returns the channel key or a false value.
1462              
1463             =head2 C
1464              
1465             Expects a channel as parameter. Returns the channel limit or a false value.
1466              
1467             =head2 C
1468              
1469             Expects a channel as parameter. Returns a list of all nicks on the specified
1470             channel. If the component happens to not be on that channel an empty list will
1471             be returned.
1472              
1473             =head2 C
1474              
1475             Expects a channel as parameter. Returns a hash ref keyed on channel mode, with
1476             the mode argument (if any) as the value. Returns a false value instead if the
1477             channel is not in the state.
1478              
1479             =head2 C
1480              
1481             Takes no parameters. Returns a hashref, keyed on channel name and whether the
1482             bot is operator, halfop or
1483             has voice on that channel.
1484              
1485             for my $channel ( keys %{ $irc->channels() } ) {
1486             $irc->yield( 'privmsg' => $channel => 'm00!' );
1487             }
1488              
1489             =head2 C
1490              
1491             Expects a channel as a parameter. Returns a hashref containing topic
1492             information if the channel is in the state, a false value if not. The hashref
1493             contains the following keys: B<'Value'>, B<'SetBy'>, B<'SetAt'>. These keys
1494             will hold the topic itself, the nick!hostmask of the user who set it (or just
1495             the nick if it's all the ircd gives us), and the time at which it was set
1496             respectively.
1497              
1498             If the component happens to not be on the channel, nothing will be returned.
1499              
1500             =head2 C
1501              
1502             Expects a channel as a parameter. Returns the channel's URL. If the channel
1503             has no URL or the component is not on the channel, nothing will be returned.
1504              
1505             =head2 C
1506              
1507             Expects a channel and a nickname as parameters. Returns a true value if
1508             the nick has voice on the specified channel. Returns false if the nick does
1509             not have voice on the channel or if the nick/channel does not exist in the state.
1510              
1511             =head2 C
1512              
1513             Expects a nick as parameter. Returns a true value if the specified nick is away.
1514             Returns a false value if the nick is not away or not in the state. This will
1515             only work for your IRC user unless you specified a value for B<'AwayPoll'> in
1516             L|POE::Component::IRC/spawn>.
1517              
1518             =head2 C
1519              
1520             Expects a channel and a nickname as parameters. Returns a true value if
1521             the nick is an admin on the specified channel. Returns false if the nick is
1522             not an admin on the channel or if the nick/channel does not exist in the state.
1523              
1524             =head2 C
1525              
1526             Expects a channel and a nickname as parameters. Returns a true value if
1527             the nick is a half-operator on the specified channel. Returns false if the nick
1528             is not a half-operator on the channel or if the nick/channel does not exist in
1529             the state.
1530              
1531             =head2 C
1532              
1533             Expects a channel and a nickname as parameters. Returns a true value if
1534             the nick is on the specified channel. Returns false if the nick is not on the
1535             channel or if the nick/channel does not exist in the state.
1536              
1537             =head2 C
1538              
1539             Expects a channel and a single mode flag C<[A-Za-z]>. Returns a true value
1540             if that mode is set on the channel.
1541              
1542             =head2 C
1543              
1544             Expects a channel and a nickname as parameters. Returns a true value if
1545             the nick is an operator on the specified channel. Returns false if the nick is
1546             not an operator on the channel or if the nick/channel does not exist in the state.
1547              
1548             =head2 C
1549              
1550             Expects a channel and a nickname as parameters. Returns a true value if
1551             the nick is an owner on the specified channel. Returns false if the nick is
1552             not an owner on the channel or if the nick/channel does not exist in the state.
1553              
1554             =head2 C
1555              
1556             Expects a channel as a parameter. Returns true if the channel has been synced.
1557             Returns false if it has not been synced or if the channel is not in the state.
1558              
1559             =head2 C
1560              
1561             Expects a nick as parameter. Returns a true value if the specified nick is
1562             an IRC operator. Returns a false value if the nick is not an IRC operator
1563             or is not in the state.
1564              
1565             =head2 C
1566              
1567             Expects single user mode flag C<[A-Za-z]>. Returns a true value if that user
1568             mode is set.
1569              
1570             =head2 C
1571              
1572             Expects a channel and a nickname as parameters. Returns the modes of the
1573             specified nick on the specified channel (ie. qaohv). If the nick is not on the
1574             channel in the state, a false value will be returned.
1575              
1576             =head2 C
1577              
1578             Expects a nickname. Returns a list of the channels that that nickname and the
1579             component are on. An empty list will be returned if the nickname does not
1580             exist in the state.
1581              
1582             =head2 C
1583              
1584             Expects a nickname. Returns a hashref containing similar information to that
1585             returned by WHOIS. Returns a false value if the nickname doesn't exist in the
1586             state. The hashref contains the following keys:
1587              
1588             B<'Nick'>, B<'User'>, B<'Host'>, B<'Userhost'>, B<'Hops'>, B<'Real'>,
1589             B<'Server'> and, if applicable, B<'IRCop'>.
1590              
1591             =head2 C
1592              
1593             Expects a nickname. Returns the long form of that nickname, ie. C
1594             or a false value if the nick is not in the state.
1595              
1596             =head2 C
1597              
1598             Takes no parameters. Returns a list of all the nicks, including itself, that it
1599             knows about. If the component happens to be on no channels then an empty list
1600             is returned.
1601              
1602             =head2 C
1603              
1604             Takes no parameters. Returns the current user mode set for the bot.
1605              
1606             =head1 OUTPUT EVENTS
1607              
1608             =head2 Augmented events
1609              
1610             New parameters are added to the following
1611             L events.
1612              
1613             =head3 C
1614              
1615             See also L|POE::Component::IRC/irc_quit> in
1616             L.
1617              
1618             Additional parameter C contains an arrayref of channel names that are
1619             common to the quitting client and the component.
1620              
1621             =head3 C
1622              
1623             See also L|POE::Component::IRC/irc_nick> in
1624             L.
1625              
1626             Additional parameter C contains an arrayref of channel names that are
1627             common to the nick hanging client and the component.
1628              
1629             =head3 C
1630              
1631             See also L|POE::Component::IRC/irc_kick> in
1632             L.
1633              
1634             Additional parameter C contains the full nick!user@host of the kicked
1635             individual.
1636              
1637             =head3 C
1638              
1639             See also L|POE::Component::IRC/irc_kick> in
1640             L.
1641              
1642             Additional parameter C contains the old topic hashref, like the one
1643             returned by L|/channel_topic>.
1644              
1645             =head3 C
1646              
1647             =head3 C
1648              
1649             =head3 C
1650              
1651             These three all have two additional parameters. C is a hash of
1652             information about your IRC user (see L|/nick_info>), while
1653             C is a hash of the channels you were on (see
1654             L|/channels>).
1655              
1656             =head2 New events
1657              
1658             As well as all the usual L C
1659             events, there are the following events you can register for:
1660              
1661             =head3 C
1662              
1663             Sent whenever the component starts to synchronise the away statuses of channel
1664             members. C is the channel name. You will only receive this event if you
1665             specified a value for B<'AwayPoll'> in L|POE::Component::IRC/spawn>.
1666              
1667             =head3 C
1668              
1669             Sent whenever the component has completed synchronising the away statuses of
1670             channel members. C is the channel name. You will only receive this event if
1671             you specified a value for B<'AwayPoll'> in L|POE::Component::IRC/spawn>.
1672              
1673             =head3 C
1674              
1675             This is almost identical to L|POE::Component::IRC/irc_mode>,
1676             except that it's sent once for each individual mode with it's respective
1677             argument if it has one (ie. the banmask if it's +b or -b). However, this
1678             event is only sent for channel modes.
1679              
1680             =head3 C
1681              
1682             Sent whenever the component has completed synchronising a channel that it has
1683             joined. C is the channel name and C is the time in seconds that
1684             the channel took to synchronise.
1685              
1686             =head3 C
1687              
1688             Sent whenever the component has completed synchronising a channel's INVEX
1689             (invite list). Usually triggered by the component being opped on a channel.
1690             C is the channel name.
1691              
1692             =head3 C
1693              
1694             Sent whenever the component has completed synchronising a channel's EXCEPTS
1695             (ban exemption list). Usually triggered by the component being opped on a
1696             channel. C is the channel.
1697              
1698             =head3 C
1699              
1700             Sent whenever the component has completed synchronising a user who has joined
1701             a channel the component is on. C is the user's nickname and C the
1702             channel they have joined.
1703              
1704             =head3 C
1705              
1706             Sent when an IRC user sets his/her status to away. C is the nickname,
1707             C is an arrayref of channel names that are common to the nickname
1708             and the component. You will only receive this event if you specified a value
1709             for B<'AwayPoll'> in L|POE::Component::IRC/spawn>.
1710              
1711             B This above is only for users I. To know when you
1712             change your own away status, register for the C and C events.
1713              
1714             =head3 C
1715              
1716             Sent when an IRC user unsets his/her away status. C is the nickname,
1717             C is an arrayref of channel names that are common to the nickname and
1718             the component. You will only receive this event if you specified a value for
1719             B<'AwayPoll'> in L|POE::Component::IRC/spawn>.
1720              
1721             B This above is only for users I. To know when you
1722             change your own away status, register for the C and C events.
1723              
1724             =head3 C
1725              
1726             This is almost identical to L|POE::Component::IRC/irc_mode>,
1727             except it is sent for each individual umode that is being set.
1728              
1729             =head1 CAVEATS
1730              
1731             The component gathers information by registering for C, C,
1732             C, C, C, C and various numeric replies.
1733             When the component is asked to join a channel, when it joins it will issue
1734             'WHO #channel', 'MODE #channel', and 'MODE #channel b'. These will solicit
1735             between them the numerics, C, C and C, respectively.
1736             When someone joins a channel the bot is on, it issues a 'WHO nick'. You may
1737             want to ignore these.
1738              
1739             Currently, whenever the component sees a topic or channel list change, it will
1740             use C
1741             for the SetBy value. When an ircd gives us its record of such changes, it will
1742             use its own time (obviously) and may only give us the nickname of the user,
1743             rather than their full address. Thus, if our C
1744             not match, or the ircd uses the nickname only, ugly inconsistencies can develop.
1745             This leaves the B<'SetAt'> and B<'SetBy'> values inaccurate at best, and you
1746             should use them with this in mind (for now, at least).
1747              
1748             =head1 AUTHOR
1749              
1750             Chris Williams
1751              
1752             With contributions from Lyndon Miller.
1753              
1754             =head1 LICENCE
1755              
1756             This module may be used, modified, and distributed under the same
1757             terms as Perl itself. Please see the license that came with your Perl
1758             distribution for details.
1759              
1760             =head1 SEE ALSO
1761              
1762             L
1763              
1764             L
1765              
1766             =cut