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