File Coverage

blib/lib/POE/Component/IRC/Qnet/State.pm
Criterion Covered Total %
statement 30 280 10.7
branch 0 80 0.0
condition 0 14 0.0
subroutine 8 22 36.3
pod 3 11 27.2
total 41 407 10.0


line stmt bran cond sub pod time code
1             package POE::Component::IRC::Qnet::State;
2             our $AUTHORITY = 'cpan:HINRIK';
3             $POE::Component::IRC::Qnet::State::VERSION = '6.92';
4 2     2   121346 use strict;
  2         13  
  2         75  
5 2     2   12 use warnings FATAL => 'all';
  2         5  
  2         86  
6 2     2   12 use Carp;
  2         4  
  2         149  
7 2     2   13 use POE;
  2         9  
  2         22  
8 2     2   1554 use IRC::Utils qw(uc_irc normalize_mask parse_user);
  2         22455  
  2         229  
9 2     2   729 use POE::Component::IRC::Plugin qw(:ALL);
  2         6  
  2         307  
10 2     2   15 use base qw(POE::Component::IRC::State POE::Component::IRC::Qnet);
  2         14  
  2         4543  
11              
12             sub _create {
13 1     1   2 my $self = shift;
14              
15 1         8 $self->SUPER::_create();
16              
17             # Stuff specific to IRC-Qnet
18 1         17 my @qbot_commands = qw(
19             hello
20             whoami
21             challengeauth
22             showcommands
23             auth
24             challenge
25             help
26             unlock
27             requestpassword
28             reset
29             newpass
30             email
31             authhistory
32             banclear
33             op
34             invite
35             removeuser
36             banlist
37             recover
38             limit
39             unbanall
40             whois
41             version
42             autolimit
43             ban
44             clearchan
45             adduser
46             settopic
47             chanflags
48             deopall
49             requestowner
50             bandel
51             chanlev
52             key
53             welcome
54             voice
55             );
56              
57 1         35 $self->{OBJECT_STATES_HASHREF}->{'qbot_' . $_} = '_qnet_bot_commands' for @qbot_commands;
58 1         6 $self->{OBJECT_STATES_HASHREF}->{'resync_chan'} = '_resync_chan';
59 1         3 $self->{OBJECT_STATES_HASHREF}->{'resync_nick'} = '_resync_nick';
60 1         3 $self->{server} = 'irc.quakenet.org';
61 1         2 $self->{QBOT} = 'Q@Cserve.quakenet.org';
62              
63 1         4 return 1;
64             }
65              
66             sub _resync_chan {
67 0     0     my ($kernel, $self, @channels) = @_[KERNEL, OBJECT, ARG0 .. $#_];
68              
69 0           my $mapping = $self->isupport('CASEMAPPING');
70 0           my $nickname = $self->nick_name();
71 0           my $flags = '%cunharsft';
72              
73 0           for my $channel ( @channels ) {
74 0 0         next if !$self->is_channel_member( $channel, $nickname );
75              
76 0           my $uchan = uc_irc $channel, $mapping;
77 0           delete $self->{STATE}->{Chans}->{ $uchan };
78 0           $self->{CHANNEL_SYNCH}->{ $uchan } = { MODE => 0, WHO => 0, BAN => 0, _time => time() };
79 0           $self->{STATE}->{Chans}->{ $uchan } = { Name => $channel, Mode => '' };
80              
81 0           $self->yield ( 'sl' => "WHO $channel $flags,101" );
82 0           $self->yield ( 'mode' => $channel );
83 0           $self->yield ( 'mode' => $channel => 'b');
84             }
85              
86 0           return;
87             }
88              
89             sub _resync_nick {
90 0     0     my ($kernel, $self, $nick, @channels) = @_[KERNEL ,OBJECT, ARG0 .. $#_];
91              
92 0           my $info = $self->nick_info( $nick );
93 0 0         return if !$info;
94 0           $nick = $info->{Nick};
95 0           my $user = $info->{User};
96 0           my $host = $info->{Host};
97 0           my $mapping = $self->isupport('CASEMAPPING');
98 0           my $unick = uc_irc $nick, $mapping;
99 0           my $flags = '%cunharsft';
100              
101 0           for my $channel ( @channels ) {
102 0 0         next if !$self->is_channel_member( $channel, $nick );
103              
104 0           my $uchan = uc_irc $channel, $mapping;
105 0           $self->yield ( 'sl' => "WHO $nick $flags,102" );
106 0           $self->{STATE}->{Nicks}->{ $unick }->{Nick} = $nick;
107 0           $self->{STATE}->{Nicks}->{ $unick }->{User} = $user;
108 0           $self->{STATE}->{Nicks}->{ $unick }->{Host} = $host;
109 0           $self->{STATE}->{Nicks}->{ $unick }->{CHANS}->{ $uchan } = '';
110 0           $self->{STATE}->{Chans}->{ $uchan }->{Nicks}->{ $unick } = '';
111 0           push @{ $self->{NICK_SYNCH}->{ $unick } }, $channel;
  0            
112             }
113              
114 0           return;
115             }
116              
117             # Qnet extension to RPL_WHOIS
118             sub S_330 {
119 0     0 0   my ($self, $irc) = splice @_, 0, 2;
120 0           my ($nick, $account) = ( split / /, ${ $_[1] } )[0..1];
  0            
121              
122 0           $self->{WHOIS}->{ $nick }->{account} = $account;
123 0           return PCI_EAT_NONE;
124             }
125              
126             # Qnet extension RPL_WHOEXT
127             sub S_354 {
128 0     0 0   my ($self, $irc) = splice @_, 0, 2;
129 0           my $mapping = $irc->isupport('CASEMAPPING');
130             my ($query, $channel, $user, $host, $server, $nick, $status, $auth, $real)
131 0           = @{ ${ $_[2] } };
  0            
  0            
132 0           my $unick = uc_irc $nick, $mapping;
133 0           my $uchan = uc_irc $channel, $mapping;
134              
135 0           $self->{STATE}->{Nicks}->{ $unick }->{Nick} = $nick;
136 0           $self->{STATE}->{Nicks}->{ $unick }->{User} = $user;
137 0           $self->{STATE}->{Nicks}->{ $unick }->{Host} = $host;
138 0           $self->{STATE}->{Nicks}->{ $unick }->{Real} = $real;
139 0           $self->{STATE}->{Nicks}->{ $unick }->{Server} = $server;
140 0 0         $self->{STATE}->{Nicks}->{ $unick }->{Auth} = $auth if ( $auth );
141              
142 0 0 0       if ( $auth and defined ( $self->{USER_AUTHED}->{ $unick } ) ) {
143 0           $self->{USER_AUTHED}->{ $unick } = $auth;
144             }
145              
146 0 0         if ( $query eq '101' ) {
147 0           my $whatever = '';
148 0 0         $whatever .= 'o' if $status =~ /\@/;
149 0 0         $whatever .= 'v' if $status =~ /\+/;
150 0 0         $whatever .= 'h' if $status =~ /\%/;
151 0           $self->{STATE}->{Nicks}->{ $unick }->{CHANS}->{ $uchan } = $whatever;
152 0           $self->{STATE}->{Chans}->{ $uchan }->{Name} = $channel;
153 0           $self->{STATE}->{Chans}->{ $uchan }->{Nicks}->{ $unick } = $whatever;
154             }
155              
156 0 0         if ( $status =~ /\*/ ) {
157 0           $self->{STATE}->{Nicks}->{ $unick }->{IRCop} = 1;
158             }
159              
160 0           return PCI_EAT_NONE;
161             }
162              
163             # RPL_ENDOFWHO
164             sub S_315 {
165 0     0 0   my ($self, $irc) = splice @_, 0, 2;
166 0           my $mapping = $irc->isupport('CASEMAPPING');
167 0           my $channel = ${ $_[2] }->[0];
  0            
168 0           my $uchan = uc_irc $channel, $mapping;
169              
170 0 0         if ( exists $self->{STATE}->{Chans}->{ $uchan } ) {
    0          
171 0 0         if ( $self->_channel_sync($channel, 'WHO' ) ) {
172 0           my $rec = delete $self->{CHANNEL_SYNCH}->{ $uchan };
173 0           $self->send_event_next( 'irc_chan_sync', $channel, time() - $rec->{_time} );
174             }
175             }
176             # it's apparently a nickname
177             elsif ( defined $self->{USER_AUTHED}->{ $uchan } ) {
178 0           $self->send_event_next( 'irc_nick_authed', $channel, delete $self->{USER_AUTHED}->{ $uchan } );
179             }
180             else {
181 0           my $chan = shift @{ $self->{NICK_SYNCH}->{ $uchan } };
  0            
182 0 0         delete $self->{NICK_SYNCH}->{ $uchan } if !@{ $self->{NICK_SYNCH}->{ $uchan } };
  0            
183 0           $self->send_event_next( 'irc_nick_sync', $channel, $chan );
184             }
185              
186 0           return PCI_EAT_NONE;
187             }
188              
189             sub S_join {
190 0     0 0   my ($self, $irc) = splice @_, 0, 2;
191 0           my ($nick, $user, $host) = parse_user(${ $_[0] } );
  0            
192 0           my $channel = ${ $_[1] };
  0            
193              
194 0           my $mapping = $irc->isupport('CASEMAPPING');
195 0           my $uchan = uc_irc $channel, $mapping;
196 0           my $unick = uc_irc $nick, $mapping;
197 0           my $flags = '%cunharsft';
198              
199 0 0         if ( $unick eq uc_irc ( $self->nick_name(), $mapping ) ) {
200 0           delete $self->{STATE}->{Chans}->{ $uchan };
201 0           $self->{CHANNEL_SYNCH}->{ $uchan } = {
202             MODE => 0,
203             WHO => 0,
204             BAN => 0,
205             _time => time()
206             };
207 0           $self->{STATE}->{Chans}->{ $uchan } = { Name => $channel, Mode => '' };
208              
209 0           $self->yield ( 'sl' => "WHO $channel $flags,101" );
210 0           $self->yield ( 'mode' => $channel );
211 0           $self->yield ( 'mode' => $channel => 'b');
212              
213             }
214             else {
215 0           my $netsplit = "$unick!$user\@$host";
216 0 0         if ( exists $self->{NETSPLIT}->{Users}->{ $netsplit } ) {
217             # restore state from NETSPLIT if it hasn't expired.
218 0           my $nuser = delete $self->{NETSPLIT}->{Users}->{ $netsplit };
219 0 0         if ( ( time - $nuser->{stamp} ) < ( 60 * 60 ) ) {
220 0           $self->{STATE}->{Nicks}->{ $unick } = $nuser->{meta};
221 0           $self->send_event_next(irc_nick_sync => $nick, $channel);
222             }
223 0           return PCI_EAT_NONE;
224             }
225 0 0         if ( exists $self->{STATE}->{Nicks}->{ $unick }->{Real} ) {
226 0           $self->send_event_next(irc_nick_sync => $nick, $channel);
227 0           return PCI_EAT_NONE;
228             }
229 0           $self->yield ( 'sl' => "WHO $nick $flags,102" );
230 0           $self->{STATE}->{Nicks}->{ $unick }->{Nick} = $nick;
231 0           $self->{STATE}->{Nicks}->{ $unick }->{User} = $user;
232 0           $self->{STATE}->{Nicks}->{ $unick }->{Host} = $host;
233 0           $self->{STATE}->{Nicks}->{ $unick }->{CHANS}->{ $uchan } = '';
234 0           $self->{STATE}->{Chans}->{ $uchan }->{Nicks}->{ $unick } = '';
235 0           push @{ $self->{NICK_SYNCH}->{ $unick } }, $channel;
  0            
236             }
237              
238 0           return PCI_EAT_NONE;
239             }
240              
241             sub S_chan_mode {
242 0     0 0   my ($self, $irc) = splice @_, 0, 2;
243 0           my $mapping = $irc->isupport('CASEMAPPING');
244 0           pop @_;
245 0           my $who = ${ $_[0] };
  0            
246 0           my $source = uc_irc ( ( split /!/, $who )[0], $mapping );
247 0           my $mode = ${ $_[2] };
  0            
248 0 0         my $arg = defined $_[3] ? ${ $_[3] } : '';
  0            
249 0           my $uarg = uc_irc $arg, $mapping;
250              
251 0 0 0       return PCI_EAT_NONE if $source !~ /^[Q]$/ || $mode !~ /[ov]/;
252              
253 0 0 0       if ( !$self->is_nick_authed($arg) && !$self->{USER_AUTHED}->{ $uarg } ) {
254 0           $self->{USER_AUTHED}->{ $uarg } = 0;
255 0           $self->yield ( 'sl' => "WHO $arg " . '%cunharsft,102' );
256             }
257              
258 0           return PCI_EAT_NONE;
259             }
260              
261             sub S_part {
262 0     0 0   my ($self, $irc) = splice @_, 0, 2;
263 0           my $mapping = $irc->isupport('CASEMAPPING');
264 0           my $nick = uc_irc ( ( split /!/, ${ $_[0] } )[0], $mapping );
  0            
265 0           my $channel = uc_irc ${ $_[1] }, $mapping;
  0            
266 0 0         if ( ref $_[2] eq 'ARRAY' ) {
267 0           push @{ $_[-1] }, '', $self->is_nick_authed( $nick );
  0            
268             }
269             else {
270 0           push @{ $_[-1] }, $self->is_nick_authed( $nick );
  0            
271             }
272              
273 0 0         if ( $nick eq uc_irc ( $self->nick_name(), $mapping ) ) {
274 0           delete $self->{STATE}->{Nicks}->{ $nick }->{CHANS}->{ $channel };
275 0           delete $self->{STATE}->{Chans}->{ $channel }->{Nicks}->{ $nick };
276 0           for my $member ( keys %{ $self->{STATE}->{Chans}->{ $channel }->{Nicks} } ) {
  0            
277 0           delete $self->{STATE}->{Nicks}->{ $member }->{CHANS}->{ $channel };
278 0 0         if ( keys %{ $self->{STATE}->{Nicks}->{ $member }->{CHANS} } <= 0 ) {
  0            
279 0           delete $self->{STATE}->{Nicks}->{ $member };
280             }
281             }
282 0           delete $self->{STATE}->{Chans}->{ $channel };
283             }
284             else {
285 0           delete $self->{STATE}->{Nicks}->{ $nick }->{CHANS}->{ $channel };
286 0           delete $self->{STATE}->{Chans}->{ $channel }->{Nicks}->{ $nick };
287 0 0         if ( keys %{ $self->{STATE}->{Nicks}->{ $nick }->{CHANS} } <= 0 ) {
  0            
288 0           delete $self->{STATE}->{Nicks}->{ $nick };
289             }
290             }
291              
292 0           return PCI_EAT_NONE;
293             }
294              
295             sub S_quit {
296 0     0 0   my ($self, $irc) = splice @_, 0, 2;
297 0           my $mapping = $irc->isupport('CASEMAPPING');
298 0           my $nick = ( split /!/, ${ $_[0] } )[0];
  0            
299 0           my $msg = ${ $_[1] };
  0            
300 0           push @{ $_[2] }, [ $self->nick_channels( $nick ) ];
  0            
301 0           push @{ $_[2] }, $self->is_nick_authed( $nick );
  0            
302 0           my $unick = uc_irc $nick, $mapping;
303 0           my $netsplit = 0;
304              
305             # Check if it is a netsplit
306 0 0         $netsplit = 1 if _is_netsplit( $msg );
307              
308 0 0         if ( $unick eq uc_irc ( $self->nick_name(), $mapping ) ) {
309 0           delete $self->{STATE};
310             }
311             else {
312 0           for my $channel ( keys %{ $self->{STATE}->{Nicks}->{ $unick }->{CHANS} } ) {
  0            
313 0           delete $self->{STATE}->{Chans}->{ $channel }->{Nicks}->{ $unick };
314             }
315 0           my $nickstate = delete $self->{STATE}->{Nicks}->{ $unick };
316 0 0         if ( $netsplit ) {
317 0           delete $nickstate->{CHANS};
318 0           $self->{NETSPLIT}->{Users}->{ "$unick!" . join '@', @{$nickstate}{qw(User Host)} } =
  0            
319             { meta => $nickstate, stamp => time };
320             }
321             }
322              
323 0           return PCI_EAT_NONE;
324             }
325              
326             sub _is_netsplit {
327 0   0 0     my $msg = shift || return;
328 0 0         return 1 if $msg =~ /^\s*\S+\.[a-z]{2,} \S+\.[a-z]{2,}$/i;
329 0           return 0;
330             }
331              
332             sub S_kick {
333 0     0 0   my ($self, $irc) = splice @_, 0, 2;
334 0           my $mapping = $irc->isupport('CASEMAPPING');
335 0           my $channel = ${ $_[1] };
  0            
336 0           my $nick = ${ $_[2] };
  0            
337 0           my $unick = uc_irc $nick, $mapping;
338 0           my $uchan = uc_irc $channel, $mapping;
339              
340 0           push @{ $_[-1] }, $self->nick_long_form( $nick );
  0            
341 0           push @{ $_[-1] }, $self->is_nick_authed( $nick );
  0            
342              
343 0 0         if ( $unick eq uc_irc ( $self->nick_name(), $mapping ) ) {
344 0           delete $self->{STATE}->{Nicks}->{ $unick }->{CHANS}->{ $uchan };
345 0           delete $self->{STATE}->{Chans}->{ $uchan }->{Nicks}->{ $unick };
346 0           for my $member ( keys %{ $self->{STATE}->{Chans}->{ $uchan }->{Nicks} } ) {
  0            
347 0           delete $self->{STATE}->{Nicks}->{ $member }->{CHANS}->{ $uchan };
348 0 0         if ( keys %{ $self->{STATE}->{Nicks}->{ $member }->{CHANS} } <= 0 ) {
  0            
349 0           delete $self->{STATE}->{Nicks}->{ $member };
350             }
351             }
352 0           delete $self->{STATE}->{Chans}->{ $uchan };
353             }
354             else {
355 0           delete $self->{STATE}->{Nicks}->{ $unick }->{CHANS}->{ $uchan };
356 0           delete $self->{STATE}->{Chans}->{ $uchan }->{Nicks}->{ $unick };
357 0 0         if ( keys %{ $self->{STATE}->{Nicks}->{ $unick }->{CHANS} } <= 0 ) {
  0            
358 0           delete $self->{STATE}->{Nicks}->{ $unick };
359             }
360             }
361              
362 0           return PCI_EAT_NONE;
363             }
364              
365             sub is_nick_authed {
366 0     0 1   my ($self, $nick) = @_;
367 0           my $mapping = $self->isupport('CASEMAPPING');
368 0           my $unick = uc_irc $nick, $mapping;
369              
370 0 0         return if !$self->_nick_exists($nick);
371              
372 0 0         if (defined $self->{STATE}->{Nicks}->{ $unick }->{Auth}) {
373 0           return $self->{STATE}->{Nicks}->{ $unick }->{Auth};
374             }
375              
376 0           return;
377             }
378              
379             sub find_auth_nicks {
380 0     0 1   my ($self, $auth, $channel) = @_;
381 0           my $mapping = $self->isupport('CASEMAPPING');
382 0           my $uchan = uc_irc $channel, $mapping;
383              
384 0 0         return if !$self->_channel_exists($channel);
385 0           my @results;
386              
387 0           for my $nick ( keys %{ $self->{STATE}->{Chans}->{ $uchan }->{Nicks} } ) {
  0            
388 0 0 0       if (defined ( $self->{STATE}->{Nicks}->{ $nick }->{Auth} )
389             && $self->{STATE}->{Nicks}->{ $nick }->{Auth} eq $auth) {
390 0           push @results, $self->{STATE}->{Nicks}->{ $nick }->{Nick};
391             }
392             }
393              
394 0           return @results;
395             }
396              
397             sub ban_mask {
398 0     0 1   my ($self, $channel, $mask) = @_;
399 0           $mask = normalize_mask($mask);
400 0           my $mapping = $self->isupport('CASEMAPPING');
401 0           my @result;
402              
403 0 0         return if !$self->_channel_exists($channel);
404              
405             # Convert the mask from IRC to regex.
406 0           $mask = u_irc ( $mask, $mapping );
407 0           $mask = quotemeta $mask;
408 0           $mask =~ s/\\\*/[\x01-\xFF]{0,}/g;
409 0           $mask =~ s/\\\?/[\x01-\xFF]{1,1}/g;
410              
411 0           for my $nick ( $self->channel_list($channel) ) {
412 0           my $long_form = $self->nick_long_form($nick);
413              
414 0 0         if ( uc_irc ( $long_form ) =~ /^$mask$/ ) {
415 0           push @result, $nick;
416 0           next;
417             }
418              
419 0 0         if ( my $auth = $self->is_nick_authed( $nick ) ) {
420 0           $long_form =~ s/\@(.+)$/\@$auth.users.quakenet.org/;
421 0 0         push @result, $nick if uc_irc ( $long_form ) =~ /^$mask$/;
422             }
423             }
424              
425 0           return @result;
426             }
427              
428             1;
429              
430             =encoding utf8
431              
432             =head1 NAME
433              
434             POE::Component::IRC::Qnet::State - A fully event-driven IRC client module
435             for Quakenet with nickname and channel tracking
436              
437             =head1 SYNOPSIS
438              
439             # A simple Rot13 'encryption' bot
440              
441             use strict;
442             use warnings;
443             use POE qw(Component::IRC::Qnet::State);
444              
445             my $nickname = 'Flibble' . $$;
446             my $ircname = 'Flibble the Sailor Bot';
447             my $ircserver = 'irc.blahblahblah.irc';
448             my $port = 6667;
449             my $qauth = 'FlibbleBOT';
450             my $qpass = 'fubar';
451              
452             my @channels = ( '#Blah', '#Foo', '#Bar' );
453              
454             # We create a new PoCo-IRC object and component.
455             my $irc = POE::Component::IRC::Qnet::State->spawn(
456             nick => $nickname,
457             server => $ircserver,
458             port => $port,
459             ircname => $ircname,
460             ) or die "Oh noooo! $!";
461              
462             POE::Session->create(
463             package_states => [
464             main => [ qw(_default _start irc_001 irc_public) ],
465             ],
466             heap => { irc => $irc },
467             );
468              
469             $poe_kernel->run();
470              
471             sub _start {
472             my ($kernel, $heap) = @_[KERNEL, HEAP];
473              
474             # We get the session ID of the component from the object
475             # and register and connect to the specified server.
476             my $irc_session = $heap->{irc}->session_id();
477             $kernel->post( $irc_session => register => 'all' );
478             $kernel->post( $irc_session => connect => { } );
479              
480             return;
481             }
482              
483             sub irc_001 {
484             my ($kernel, $sender) = @_[KERNEL, SENDER];
485              
486             # Get the component's object at any time by accessing the heap of
487             # the SENDER
488             my $poco_object = $sender->get_heap();
489             print "Connected to ", $poco_object->server_name(), "\n";
490              
491             # Lets authenticate with Quakenet's Q bot
492             $kernel->post( $sender => qbot_auth => $qauth => $qpass );
493              
494             # In any irc_* events SENDER will be the PoCo-IRC session
495             $kernel->post( $sender => join => $_ ) for @channels;
496              
497             return;
498             }
499              
500             sub irc_public {
501             my ($kernel, $sender, $who, $where, $what) = @_[KERNEL, SENDER, ARG0, .. ARG2];
502             my $nick = ( split /!/, $who )[0];
503             my $channel = $where->[0];
504             my $poco_object = $sender->get_heap();
505              
506             if ( my ($rot13) = $what =~ /^rot13 (.+)/ ) {
507             # Only operators can issue a rot13 command to us.
508             return if !$poco_object->is_channel_operator( $channel, $nick );
509              
510             $rot13 =~ tr[a-zA-Z][n-za-mN-ZA-M];
511             $kernel->post( $sender => privmsg => $channel => "$nick: $rot13" );
512             }
513              
514             return;
515             }
516              
517             # We registered for all events, this will produce some debug info.
518             sub _default {
519             my ($event, $args) = @_[ARG0 .. $#_];
520             my @output = ( "$event: " );
521              
522             for my $arg ( @$args ) {
523             if (ref $arg eq 'ARRAY') {
524             push( @output, '[' . join(', ', @$arg ) . ']' );
525             }
526             else {
527             push ( @output, "'$arg'" );
528             }
529             }
530              
531             print join ' ', @output, "\n";
532             return 0;
533             }
534              
535              
536             =head1 DESCRIPTION
537              
538             POE::Component::IRC::Qnet::State is an extension to
539             L specifically for use on
540             Quakenet L, which includes the nickname and channel
541             tracking from L. See
542             the documentation for L
543             and L for general usage.
544             This document covers the extensions.
545              
546             =head1 METHODS
547              
548             =over
549              
550             =item C
551              
552             Expects a channel and a ban mask, as passed to MODE +b-b. Returns a list of
553             nicks on that channel that match the specified ban mask or an empty list if the
554             channel doesn't exist in the state or there are no matches. Follows Quakenet
555             ircd rules for matching authed users.
556              
557             =item C
558              
559             Expects a nickname as parameter. Will return that users authname (account) if
560             that nick is in the state and have authed with Q. Returns a false value if
561             the user is not authed or the nick doesn't exist in the state.
562              
563             =item C
564              
565             Expects an authname and a channel name. Will return a list of nicks on the
566             given channel that have authed with the given authname.
567              
568             =item C
569              
570             Expects a nickname. Returns a hashref containing similar information to that
571             returned by WHOIS. Returns a false value if the nickname doesn't exist in the
572             state. The hashref contains the following keys: B<'Nick'>, B<'User'>,
573             B<'Host'>, B<'Server'>, B<'Auth'>, if authed, and, if applicable, B<'IRCop'>.
574              
575             =back
576              
577             =head1 INPUT
578              
579             These additional events are accepted:
580              
581             =over
582              
583             =item C
584              
585             Accepts a list of channels, will resynchronise each of those channels as if
586             they have been joined for the first time. Expect to see an
587             L|POE::Component::IRC::State/irc_chan_sync> event for each
588             channel given.
589              
590             =item C
591              
592             Accepts a nickname and a list of channels. Will resynchronise the given nickname
593             and issue an L|POE::Component::IRC::State/irc_nick_sync>
594             event for each of the given channels (assuming that nick is on each of those channels).
595              
596             =back
597              
598             =head1 OUTPUT EVENTS
599              
600             This module returns one additional event over and above the usual events:
601              
602             =over
603              
604             =item C
605              
606             Sent when the component detects that a user has authed with Q. Due to the
607             mechanics of Quakenet you will usually only receive this if an unauthed user
608             joins a channel, then at some later point auths with Q. The component 'detects'
609             the auth by seeing if Q decides to +v or +o the user. Klunky? Indeed. But
610             it is the only way to do it, unfortunately.
611              
612             =back
613              
614             The following two C events are the same as their
615             L counterparts, with
616             the additional parameters:
617              
618             =over
619              
620             =item C
621              
622             C contains the quitting clients auth name if applicable.
623              
624             =item C
625              
626             C contains the parting clients auth name if applicable.
627              
628             =item C
629              
630             C contains the kick victim's auth name if applicable.
631              
632             =back
633              
634             =head1 CAVEATS
635              
636             Like L this component
637             registers itself for a number of events. The main difference with
638             L is that it uses an
639             extended form of 'WHO' supported by the Quakenet ircd, asuka. This WHO returns
640             a different numeric reply than the original WHO, namely, C. Also, due
641             to the way Quakenet is configured all users will appear to be on the server
642             '*.quakenet.org'.
643              
644             =head1 BUGS
645              
646             A few have turned up in the past and they are sure to again. Please use
647             L to report any. Alternatively, email the current
648             maintainer.
649              
650             =head1 AUTHOR
651              
652             Chris 'BinGOs' Williams
653              
654             Based on the original POE::Component::IRC by:
655              
656             Dennis Taylor
657              
658             =head1 SEE ALSO
659              
660             L
661              
662             L
663              
664             L
665              
666             L
667              
668             =cut