File Coverage

blib/lib/Net/Async/IRC.pm
Criterion Covered Total %
statement 112 131 85.5
branch 20 40 50.0
condition 16 31 51.6
subroutine 22 27 81.4
pod 9 12 75.0
total 179 241 74.2


line stmt bran cond sub pod time code
1             # You may distribute under the terms of either the GNU General Public License
2             # or the Artistic License (the same terms as Perl itself)
3             #
4             # (C) Paul Evans, 2008-2014 -- leonerd@leonerd.org.uk
5              
6             package Net::Async::IRC;
7              
8 5     5   466250 use strict;
  5         11  
  5         182  
9 5     5   29 use warnings;
  5         12  
  5         259  
10              
11             our $VERSION = '0.10';
12              
13             # We need to use C3 MRO to make the ->isupport etc.. methods work properly
14 5     5   5178 use mro 'c3';
  5         10662  
  5         33  
15 5     5   199 use base qw( Net::Async::IRC::Protocol Protocol::IRC::Client );
  5         9  
  5         3570  
16              
17 5     5   36 use Carp;
  5         8  
  5         325  
18              
19 5     5   27 use Socket qw( SOCK_STREAM );
  5         9  
  5         250  
20              
21 5     5   27 use constant HAVE_MSWIN32 => ( $^O eq "MSWin32" );
  5         8  
  5         8621  
22              
23             =head1 NAME
24              
25             C - use IRC with C
26              
27             =head1 SYNOPSIS
28              
29             use IO::Async::Loop;
30             use Net::Async::IRC;
31              
32             my $loop = IO::Async::Loop->new;
33              
34             my $irc = Net::Async::IRC->new(
35             on_message_text => sub {
36             my ( $self, $message, $hints ) = @_;
37              
38             print "$hints->{prefix_name} says: $hints->{text}\n";
39             },
40             );
41              
42             $loop->add( $irc );
43              
44             $irc->login(
45             nick => "MyName",
46             host => "irc.example.org",
47             )->get;
48              
49             $irc->send_message( "PRIVMSG", undef, "YourName", "Hello world!" );
50              
51             $loop->loop_forever;
52              
53             =head1 DESCRIPTION
54              
55             This object class implements an asynchronous IRC client, for use in programs
56             based on L.
57              
58             This documentation is very much still in a state of TODO; it is being released
59             now in the hope it is currently somewhat useful, with the intention of putting
60             more work into both the code and its documentation at some near point in the
61             future.
62              
63             =cut
64              
65             sub new
66             {
67 5     5 1 19338 my $class = shift;
68 5         28 my %args = @_;
69              
70 5         13 my $on_closed = delete $args{on_closed};
71              
72             return $class->SUPER::new(
73             %args,
74              
75             on_closed => sub {
76 0     0   0 my $self = shift;
77              
78 0 0       0 if( $self->{on_login_f} ) {
79 0         0 $_->fail( "Closed" ) for @{ $self->{on_login_f} };
  0         0  
80 0         0 undef $self->{on_login_f};
81             }
82              
83 0 0       0 $on_closed->( $self ) if $on_closed;
84             },
85 5         83 );
86             }
87              
88             sub _init
89             {
90 5     5   54 my $self = shift;
91 5         40 $self->SUPER::_init( @_ );
92              
93 5   33     3078 $self->{user} = $ENV{LOGNAME} ||
94             ( HAVE_MSWIN32 ? Win32::LoginName() : getpwuid($>) );
95              
96 5         33 $self->{realname} = "Net::Async::IRC client $VERSION";
97             }
98              
99             =head1 PARAMETERS
100              
101             The following named parameters may be passed to C or C:
102              
103             =over 8
104              
105             =item nick => STRING
106              
107             =item user => STRING
108              
109             =item realname => STRING
110              
111             Connection details. See also C, C.
112              
113             If C is not supplied, it will default to either C<$ENV{LOGNAME}> or the
114             current user's name as supplied by C or C.
115              
116             If unconnected, changing these properties will set the default values to use
117             when logging in.
118              
119             If logged in, changing the C property is equivalent to calling
120             C. Changing the other properties will not take effect until the
121             next login.
122              
123             =item use_caps => ARRAY of STRING
124              
125             Attempts to negotiate IRC v3.1 CAP at connect time. The array gives the names
126             of capabilities which will be requested, if the server supports them.
127              
128             =back
129              
130             =cut
131              
132             sub configure
133             {
134 6     6 1 11443 my $self = shift;
135 6         32 my %args = @_;
136              
137 6         20 for (qw( user realname use_caps )) {
138 18 100       85 $self->{$_} = delete $args{$_} if exists $args{$_};
139             }
140              
141 6 100       27 if( exists $args{nick} ) {
142 1         15 $self->_set_nick( delete $args{nick} );
143             }
144              
145 6         54 $self->SUPER::configure( %args );
146             }
147              
148             =head1 METHODS
149              
150             =cut
151              
152             =head2 $irc->connect( %args ) ==> $irc
153              
154             Connects to the IRC server. This method does not perform the complete IRC
155             login sequence; for that see instead the C method.
156              
157             =over 8
158              
159             =item host => STRING
160              
161             Hostname of the IRC server.
162              
163             =item service => STRING or NUMBER
164              
165             Optional. Port number or service name of the IRC server. Defaults to 6667.
166              
167             =back
168              
169             Any other arguments are passed into the underlying C
170             C method.
171              
172             =head2 $irc->connect( %args )
173              
174             The following additional arguments are used to provide continuations when not
175             returning a Future.
176              
177             =over 8
178              
179             =item on_connected => CODE
180              
181             Continuation to invoke once the connection has been established. Usually used
182             by the C method to perform the actual login sequence.
183              
184             $on_connected->( $irc )
185              
186             =item on_error => CODE
187              
188             Continuation to invoke in the case of an error preventing the connection from
189             taking place.
190              
191             $on_error->( $errormsg )
192              
193             =back
194              
195             =cut
196              
197             # TODO: Most of this needs to be moved into an abstract Net::Async::Connection role
198             sub connect
199             {
200 5     5 1 10 my $self = shift;
201 5         12 my %args = @_;
202              
203             # Largely for unit testing
204 5 100 66     70 return $self->{connect_f} ||= Future->new->done( $self ) if
205             $self->read_handle;
206              
207 1         8 my $on_error = delete $args{on_error};
208              
209 1   50     8 $args{service} ||= "6667";
210              
211             return $self->{connect_f} ||= $self->SUPER::connect(
212             %args,
213              
214             on_resolve_error => sub {
215 0     0   0 my ( $msg ) = @_;
216 0         0 chomp $msg;
217              
218 0 0       0 if( $args{on_resolve_error} ) {
    0          
219 0         0 $args{on_resolve_error}->( $msg );
220             }
221             elsif( $on_error ) {
222 0         0 $on_error->( "Cannot resolve - $msg" );
223             }
224             },
225              
226             on_connect_error => sub {
227 0 0   0   0 if( $args{on_connect_error} ) {
    0          
228 0         0 $args{on_connect_error}->( @_ );
229             }
230             elsif( $on_error ) {
231 0         0 $on_error->( "Cannot connect" );
232             }
233             },
234 1   33 0   32 )->on_fail( sub { undef $self->{connect_f} } );
  0         0  
235             }
236              
237             =head2 $irc->login( %args ) ==> $irc
238              
239             Logs in to the IRC network, connecting first using the C method if
240             required. Takes the following named arguments:
241              
242             =over 8
243              
244             =item nick => STRING
245              
246             =item user => STRING
247              
248             =item realname => STRING
249              
250             IRC connection details. Defaults can be set with the C or C
251             methods.
252              
253             =item pass => STRING
254              
255             Server password to connect with.
256              
257             =back
258              
259             Any other arguments that are passed, are forwarded to the C method if
260             it is required; i.e. if C is invoked when not yet connected to the
261             server.
262              
263             =head2 $irc->login( %args )
264              
265             The following additional arguments are used to provide continuations when not
266             returning a Future.
267              
268             =over 8
269              
270             =item on_login => CODE
271              
272             A continuation to invoke once login is successful.
273              
274             $on_login->( $irc )
275              
276             =back
277              
278             =cut
279              
280             sub login
281             {
282 4     4 1 2451 my $self = shift;
283 4         16 my %args = @_;
284              
285 4 50 66     28 my $nick = delete $args{nick} || $self->{nick} or croak "Need a login nick";
286 4 50 66     76 my $user = delete $args{user} || $self->{user} or croak "Need a login user";
287 4   66     33 my $realname = delete $args{realname} || $self->{realname};
288 4         9 my $pass = delete $args{pass};
289              
290 4 100       18 if( !defined $self->{nick} ) {
291 3         24 $self->_set_nick( $nick );
292             }
293              
294 4         7 my $on_login = delete $args{on_login};
295 4 50 66     43 !defined $on_login or ref $on_login eq "CODE" or
296             croak "Expected 'on_login' to be a CODE reference";
297              
298             return $self->{login_f} ||= $self->connect( %args )->then( sub {
299 4 100   4   559 $self->send_message( "CAP", undef, "LS" ) if $self->{use_caps};
300              
301 4 50       321 $self->send_message( "PASS", undef, $pass ) if defined $pass;
302 4         41 $self->send_message( "USER", undef, $user, "0", "*", $realname );
303 4         410 $self->send_message( "NICK", undef, $nick );
304              
305 4         148 my $f = $self->loop->new_future;
306              
307 4         3254 push @{ $self->{on_login_f} }, $f;
  4         26  
308 4 100       17 $f->on_done( $on_login ) if $on_login;
309              
310 4         18 return $f;
311 4   33 0   32 })->on_fail( sub { undef $self->{login_f} } );
  0         0  
312             }
313              
314             =head2 $irc->change_nick( $newnick )
315              
316             Requests to change the nick. If unconnected, the change happens immediately
317             to the stored defaults. If logged in, sends a C command to the server,
318             which may suceed or fail at a later point.
319              
320             =cut
321              
322             sub change_nick
323             {
324 3     3 1 28 my $self = shift;
325 3         5 my ( $newnick ) = @_;
326              
327 3 50       16 if( !$self->is_connected ) {
328 0         0 $self->_set_nick( $newnick );
329             }
330             else {
331 3         59 $self->send_message( "NICK", undef, $newnick );
332             }
333             }
334              
335             ############################
336             # Message handling methods #
337             ############################
338              
339             =head1 IRC v3.1 CAPABILITIES
340              
341             The following methods relate to IRC v3.1 capabilities negotiations.
342              
343             =cut
344              
345             sub on_message_cap_LS
346             {
347 1     1 0 12 my $self = shift;
348 1         2 my ( $message, $hints ) = @_;
349              
350 1         3 my $supported = $self->{caps_supported} = $hints->{caps};
351              
352 1         2 my @request = grep { $supported->{$_} } @{$self->{use_caps}};
  1         4  
  1         2  
353              
354 1 50       3 if( @request ) {
355 1         2 $self->{caps_enabled} = { map { $_ => undef } @request };
  1         5  
356 1         6 $self->send_message( "CAP", undef, "REQ", join( " ", @request ) );
357             }
358             else {
359 0         0 $self->send_message( "CAP", undef, "END" );
360             }
361              
362 1         108 return 1;
363             }
364              
365             *on_message_cap_ACK = *on_message_cap_NAK = \&_on_message_cap_reply;
366             sub _on_message_cap_reply
367             {
368 1     1   11 my $self = shift;
369 1         2 my ( $message, $hints ) = @_;
370 1         3 my $ack = $hints->{verb} eq "ACK";
371              
372 1         2 $self->{caps_enabled}{$_} = $ack for keys %{ $hints->{caps} };
  1         5  
373              
374             # Are any outstanding
375 1   50     2 !defined and return 1 for values %{ $self->{caps_enabled} };
  1         6  
376              
377 1         4 $self->send_message( "CAP", undef, "END" );
378 1         104 return 1;
379             }
380              
381             =head2 $caps = $irc->caps_supported
382              
383             Returns a HASH whose keys give the capabilities listed by the server as
384             supported in its C response. If the server ignored the C
385             negotiation then this method returns C.
386              
387             =cut
388              
389             sub caps_supported
390             {
391 2     2 1 1084 my $self = shift;
392 2         13 return $self->{caps_supported};
393             }
394              
395             =head2 $supported = $irc->cap_supported( $cap )
396              
397             Returns a boolean indicating if the server supports the named capability.
398              
399             =cut
400              
401             sub cap_supported
402             {
403 1     1 1 3 my $self = shift;
404 1         2 my ( $cap ) = @_;
405 1         6 return !!$self->{caps_supported}{$cap};
406             }
407              
408             =head2 $caps = $irc->caps_enabled
409              
410             Returns a HASH whose keys give the capabilities successfully enabled by the
411             server as part of the C login sequence. If the server ignored the
412             C negotiation then this method returns C.
413              
414             =cut
415              
416             sub caps_enabled
417             {
418 2     2 1 4 my $self = shift;
419 2         9 return $self->{caps_enabled};
420             }
421              
422             =head2 $enabled = $irc->cap_enabled( $cap )
423              
424             Returns a boolean indicating if the client successfully enabled the named
425             capability.
426              
427             =cut
428              
429             sub cap_enabled
430             {
431 1     1 1 3 my $self = shift;
432 1         1 my ( $cap ) = @_;
433 1         5 return !!$self->{caps_enabled}{$cap};
434             }
435              
436             =head1 MESSAGE HANDLING
437              
438             =cut
439              
440             sub on_message_NICK
441             {
442 1     1 0 15 my $self = shift;
443 1         2 my ( $message, $hints ) = @_;
444              
445 1 50       6 if( $hints->{prefix_is_me} ) {
446 1         6 $self->_set_nick( $hints->{new_nick} );
447 1         7 return 1;
448             }
449              
450 0         0 return 0;
451             }
452              
453             sub on_message_RPL_WELCOME
454             {
455 4     4 0 51 my $self = shift;
456 4         7 my ( $message ) = @_;
457              
458             # set our nick to be what the server logged us in as
459 4         18 $self->_set_nick( $message->{args}[0] );
460              
461 4 50 33     20 if( $self->{on_login_f} and @{ $self->{on_login_f} } ) {
  4         23  
462 4         7 my @futures = @{ $self->{on_login_f} };
  4         11  
463 4         8 undef $self->{on_login_f};
464              
465 4         8 foreach my $f ( @futures ) {
466 4         48 $f->done( $self );
467             }
468             }
469              
470             # Don't eat it
471 4         232 return 0;
472             }
473              
474             =head1 SEE ALSO
475              
476             =over 4
477              
478             =item *
479              
480             L - Internet Relay Chat: Client Protocol
481              
482             =back
483              
484             =head1 AUTHOR
485              
486             Paul Evans
487              
488             =cut
489              
490             0x55AA;