File Coverage

blib/lib/Mojo/IRC.pm
Criterion Covered Total %
statement 176 206 85.4
branch 44 66 66.6
condition 18 36 50.0
subroutine 40 48 83.3
pod 16 17 94.1
total 294 373 78.8


line stmt bran cond sub pod time code
1             package Mojo::IRC;
2 22     22   993071 use Mojo::Base 'Mojo::EventEmitter';
  22         492966  
  22         152  
3 22     22   18482 use Mojo::IOLoop;
  22         1079498  
  22         147  
4 22     22   800 use File::Basename 'dirname';
  22         49  
  22         1850  
5 22     22   161 use File::Spec::Functions 'catfile';
  22         48  
  22         970  
6 22     22   10699 use IRC::Utils ();
  22         159362  
  22         610  
7 22     22   10531 use Parse::IRC ();
  22         45999  
  22         481  
8 22     22   161 use Scalar::Util ();
  22         50  
  22         414  
9 22     22   8035 use Unicode::UTF8;
  22         8893  
  22         1128  
10 22   50 22   151 use constant DEBUG => $ENV{MOJO_IRC_DEBUG} || 0;
  22         52  
  22         1637  
11 22   33 22   144 use constant DEFAULT_CERT => $ENV{MOJO_IRC_CERT_FILE} || catfile dirname(__FILE__), 'mojo-irc-client.crt';
  22         49  
  22         2841  
12 22   33 22   131 use constant DEFAULT_KEY => $ENV{MOJO_IRC_KEY_FILE} || catfile dirname(__FILE__), 'mojo-irc-client.key';
  22         50  
  22         41453  
13              
14             our $VERSION = '0.45';
15              
16             our %NUMERIC2NAME = (470 => 'ERR_LINKCHANNEL');
17              
18             my %CTCP_QUOTE = ("\012" => 'n', "\015" => 'r', "\0" => '0', "\cP" => "\cP");
19              
20             my @DEFAULT_EVENTS = qw(
21             irc_ping irc_nick irc_notice irc_rpl_welcome err_nicknameinuse
22             irc_rpl_isupport ctcp_ping ctcp_time ctcp_version
23             );
24              
25             has connect_timeout => sub { $ENV{MOJO_IRC_CONNECT_TIMEOUT} || 30 };
26             has ioloop => sub { Mojo::IOLoop->singleton };
27             has local_address => '';
28             has name => 'Mojo IRC';
29             has nick => sub { shift->_build_nick };
30             has parser => sub { Parse::IRC->new; };
31             has pass => '';
32             has real_host => '';
33              
34             has server_settings => sub {
35             return {chantypes => '#', prefix => '(ov)@+'};
36             };
37              
38             has tls => undef;
39             has user => sub { $ENV{USER} || getlogin || getpwuid($<) || 'anonymous' };
40              
41             sub new {
42 21     21 1 5929 my $self = shift->SUPER::new(@_);
43 21         339 $self->on(message => \&_legacy_dispatch_message);
44 21         253 return $self;
45             }
46              
47             sub server {
48 25     25 1 4403 my ($self, $server) = @_;
49 25   100     104 my $old = $self->{server} || '';
50              
51 25         88 Scalar::Util::weaken($self);
52 25 100       141 return $old unless defined $server;
53 4 100 100     23 return $self if $old and $old eq $server;
54 3         7 $self->{server} = $server;
55 3 50       10 return $self unless $self->{stream_id};
56             $self->disconnect(sub {
57 0     0   0 $self->connect(sub { });
58 0         0 });
59 0         0 $self;
60             }
61              
62             sub connect {
63 20     20 1 5004 my ($self, $cb) = @_;
64 20         100 my ($host, $port) = split /:/, $self->server;
65 20         42 my @extra;
66              
67 20 50       62 if (!$host) {
68 0     0   0 Mojo::IOLoop->next_tick(sub { $self->$cb('server() is not set.') });
  0         0  
69 0         0 return $self;
70             }
71 20 50       78 if ($self->{stream_id}) {
72 0     0   0 Mojo::IOLoop->next_tick(sub { $self->$cb('') });
  0         0  
73 0         0 return $self;
74             }
75              
76 20 100       108 if ($self->local_address) {
77 1         7 push @extra, local_address => $self->local_address;
78             }
79 20 50       189 if (my $tls = $self->tls) {
80 0         0 push @extra, tls => 1;
81 0 0       0 push @extra, tls_ca => $tls->{ca} if $tls->{ca}; # not sure why this should be supported, but adding it anyway
82 0   0     0 push @extra, tls_cert => $tls->{cert} || DEFAULT_CERT;
83 0   0     0 push @extra, tls_key => $tls->{key} || DEFAULT_KEY;
84 0 0       0 push @extra, tls_verify => 0x00 if $tls->{insecure};
85             }
86              
87 20   100     158 $port ||= 6667;
88 20         51 $self->{buffer} = '';
89 20   66     165 $self->{debug_key} ||= "$host:$port";
90 20         85 $self->register_default_event_handlers;
91              
92 20         67 Scalar::Util::weaken($self);
93             $self->{stream_id} = $self->ioloop->client(
94             address => $host,
95             port => $port,
96             timeout => $self->connect_timeout,
97             @extra,
98             sub {
99 19     19   21239 my ($loop, $err, $stream) = @_;
100              
101 19 100       84 if ($err) {
102 1         3 delete $self->{stream_id};
103 1         8 return $self->$cb($err);
104             }
105              
106 18         120 $stream->timeout(0);
107             $stream->on(
108             close => sub {
109 14 50       10540 $self or return;
110 14         34 warn "[$self->{debug_key}] : close\n" if DEBUG;
111 14         46 delete $self->{stream};
112 14         31 delete $self->{stream_id};
113 14         73 $self->emit('close');
114             }
115 18         446 );
116             $stream->on(
117             error => sub {
118 0 0       0 $self or return;
119 0 0       0 $self->ioloop or return;
120 0         0 $self->ioloop->remove(delete $self->{stream_id});
121 0         0 $self->emit(error => $_[1]);
122             }
123 18         169 );
124 18         161 $stream->on(read => sub { $self->_read($_[1]) });
  168         20709  
125              
126 18         97 $self->{stream} = $stream;
127             $self->ioloop->delay(
128             sub {
129 18         6576 my $delay = shift;
130 18 100       161 $self->write(PASS => $self->pass, $delay->begin) if length $self->pass;
131 18         206 $self->write(NICK => $self->nick, $delay->begin);
132 18         88 $self->write(USER => $self->user, 8, '*', ':' . $self->name, $delay->begin);
133             },
134             sub {
135 16         391 $self->$cb('');
136             }
137 18         58 );
138             }
139 20         99 );
140              
141 20         3911 return $self;
142             }
143              
144             sub ctcp {
145 3     3 1 6 my $self = shift;
146 3         8 local $_ = join ' ', @_;
147 3         42 s/([\012\015\0\cP])/\cP$CTCP_QUOTE{$1}/g;
148 3         11 s/\001/\\a/g;
149 3         17 ":\001${_}\001";
150             }
151              
152             sub disconnect {
153 3     3 1 1920 my ($self, $cb) = @_;
154              
155 3 50       12 if (my $tid = delete $self->{ping_tid}) {
156 0         0 $self->ioloop->remove($tid);
157             }
158              
159 3 100       19 if ($self->{stream}) {
    50          
160 2         9 Scalar::Util::weaken($self);
161             $self->{stream}->write(
162             "QUIT\r\n",
163             sub {
164 2     2   43 $self->{stream}->close;
165 2 100       15 $self->$cb if $cb;
166             }
167 2         48 );
168             }
169             elsif ($cb) {
170 0     0   0 Mojo::IOLoop->next_tick(sub { $self->$cb });
  0         0  
171             }
172              
173 3         20 $self;
174             }
175              
176             sub register_default_event_handlers {
177 22     22 1 57 my $self = shift;
178              
179 22         59 for my $event (@DEFAULT_EVENTS) {
180 198 100       1850 $self->on($event => $self->can($event)) unless $self->has_subscribers($event);
181             }
182              
183 22         225 return $self;
184             }
185              
186             sub track_any {
187 0     0 0 0 warn 'DEPRECATED! Just listen to $self->on(message => sub {}) instead.';
188 0         0 my $self = shift;
189 0 0 0     0 return $self->{track_any} || 0 unless @_;
190 0         0 $self->{track_any} = shift;
191 0         0 $self;
192             }
193              
194             sub write {
195 22     22   196 no warnings 'utf8';
  22         61  
  22         34033  
196 66 100   7 1 957 my $cb = ref $_[-1] eq 'CODE' ? pop : sub { };
        66      
197 66         128 my $self = shift;
198 66     0   488 my $buf = Unicode::UTF8::encode_utf8(join(' ', @_), sub { $_[0] });
  0         0  
199              
200 66         253 Scalar::Util::weaken($self);
201 66 50       173 if (ref $self->{stream}) {
202 66         74 warn "[$self->{debug_key}] <<< $buf\n" if DEBUG;
203 66     61   404 $self->{stream}->write("$buf\r\n", sub { $self->$cb(''); });
  61         6417  
204             }
205             else {
206 0     0   0 Mojo::IOLoop->next_tick(sub { $self->$cb('Not connected.') });
  0         0  
207             }
208              
209 66         2375 $self;
210             }
211              
212             sub ctcp_ping {
213 1     1 1 15 my ($self, $message) = @_;
214 1         3 my $ts = $message->{params}[1];
215 1         6 my $nick = IRC::Utils::parse_user($message->{prefix});
216              
217 1 50       15 return $self unless $ts;
218 1         5 return $self->write('NOTICE', $nick, $self->ctcp(PING => $ts));
219             }
220              
221             sub ctcp_time {
222 1     1 1 10 my ($self, $message) = @_;
223 1         3 my $nick = IRC::Utils::parse_user($message->{prefix});
224              
225 1         13 $self->write(NOTICE => $nick, $self->ctcp(TIME => scalar localtime));
226             }
227              
228             sub ctcp_version {
229 1     1 1 12 my ($self, $message) = @_;
230 1         4 my $nick = IRC::Utils::parse_user($message->{prefix});
231              
232 1         16 $self->write(NOTICE => $nick, $self->ctcp(VERSION => 'Mojo-IRC', $VERSION));
233             }
234              
235             sub irc_nick {
236 8     8 1 3045 my ($self, $message) = @_;
237 8   50     82 my $old_nick = ($message->{prefix} =~ /^[~&@%+]?(.*?)!/)[0] || '';
238              
239 8 100       29 if (lc $old_nick eq lc $self->nick) {
240 6         31 $self->nick($message->{params}[0]);
241             }
242             }
243              
244             sub irc_notice {
245 50     50 1 2241 my ($self, $message) = @_;
246              
247             # NOTICE AUTH :*** Ident broken or disabled, to continue to connect you must type /QUOTE PASS 21105
248 50 100       174 if ($message->{params}[0] =~ m!Ident broken.*QUOTE PASS (\S+)!) {
249 1         6 $self->write(QUOTE => PASS => $1);
250             }
251             }
252              
253             sub irc_ping {
254 1     1 1 427 my ($self, $message) = @_;
255 1         4 $self->write(PONG => $message->{params}[0]);
256             }
257              
258             sub irc_rpl_isupport {
259 5     5 1 50 my ($self, $message) = @_;
260 5         10 my $params = $message->{params};
261 5         17 my $server_settings = $self->server_settings;
262 5         15 my %got;
263              
264 5         15 for my $i (1 .. @$params - 1) {
265 54 100       159 next unless $params->[$i] =~ /([A-Z]+)=?(\S*)/;
266 49         112 my ($k, $v) = (lc $1, $2);
267 49         103 $got{$k} = 1;
268 49   100     117 $server_settings->{$k} = $v || 1;
269             }
270             }
271              
272             sub irc_rpl_welcome {
273 2     2 1 474 my ($self, $message) = @_;
274 2         11 $self->nick($message->{params}[0]);
275              
276 2         16 Scalar::Util::weaken($self);
277 2         9 $self->real_host($message->{prefix});
278             $self->{ping_tid} ||= $self->ioloop->recurring(
279             $self->{ping_pong_interval} || 60, # $self->{ping_pong_interval} is EXPERIMENTAL
280             sub {
281 1     1   1804 $self->write(PING => $self->real_host);
282             }
283 2   50     28 );
      33        
284             }
285              
286             sub err_nicknameinuse {
287 2     2 1 431 my ($self, $message) = @_;
288 2         6 my $nick = $message->{params}[1];
289              
290 2         10 $self->nick($nick . '_');
291 2     1   11 $self->write(NICK => $self->nick, sub { });
292             }
293              
294             sub DESTROY {
295 18     18   22025 my $self = shift;
296 18 50       89 my $ioloop = $self->ioloop or return;
297 18         172 my $tid = $self->{ping_tid};
298 18         44 my $sid = $self->{stream_id};
299              
300 18 100       105 $ioloop->remove($sid) if $sid;
301 18 100       4121 $ioloop->remove($tid) if $tid;
302             }
303              
304             sub _build_nick {
305 13     13   82 my $nick = shift->user;
306 13         155 $nick =~ s![^a-z_]!_!g;
307 13         103 $nick;
308             }
309              
310             sub _dispatch_message {
311 142     142   221 my ($self, $msg) = @_;
312 142 50       264 $self->emit(irc_any => $msg) if $self->{track_any}; # will be deprecated
313 142         388 $self->emit(message => $msg);
314             }
315              
316             sub _legacy_dispatch_message {
317 142     142   1083 my ($self, $msg) = @_;
318 142         203 my $event = $msg->{event};
319              
320 142 100       503 $event = "irc_$event" unless $event =~ /^(ctcp|err)_/;
321 142         199 warn "[$self->{debug_key}] === $event\n" if DEBUG == 2;
322 142         313 $self->emit($event => $msg);
323             }
324              
325             # Can be used in unittest to mock input data:
326             # $irc->_read($bytes);
327             sub _read {
328 168     168   231 my $self = shift;
329              
330 22     22   182 no warnings 'utf8';
  22         56  
  22         6968  
331 168     0   1123 $self->{buffer} .= Unicode::UTF8::decode_utf8($_[0], sub { $_[0] });
  0         0  
332              
333             CHUNK:
334 168         1681 while ($self->{buffer} =~ s/^([^\015\012]+)[\015\012]//m) {
335 142         640 warn "[$self->{debug_key}] >>> $1\n" if DEBUG;
336 142         411 my $msg = $self->parser->parse($1);
337 142 50       6783 my $cmd = $msg->{command} or next CHUNK;
338 142 100 33     710 $msg->{command} = $NUMERIC2NAME{$cmd} || IRC::Utils::numeric_to_name($cmd) || $cmd if $cmd =~ /^\d+$/;
339 142         850 $msg->{event} = lc $msg->{command};
340 142         370 $self->_dispatch_message($msg);
341             }
342             }
343              
344             1;
345              
346             =encoding utf8
347              
348             =head1 NAME
349              
350             Mojo::IRC - IRC Client for the Mojo IOLoop
351              
352             =head1 VERSION
353              
354             0.45
355              
356             =head1 SYNOPSIS
357              
358             my $irc = Mojo::IRC->new(
359             nick => 'test123',
360             user => 'my name',
361             server => 'irc.perl.org:6667',
362             );
363              
364             $irc->on(irc_join => sub {
365             my($self, $message) = @_;
366             warn "yay! i joined $message->{params}[0]";
367             });
368              
369             $irc->on(irc_privmsg => sub {
370             my($self, $message) = @_;
371             say $message->{prefix}, " said: ", $message->{params}[1];
372             });
373              
374             $irc->connect(sub {
375             my($irc, $err) = @_;
376             return warn $err if $err;
377             $irc->write(join => '#mojo');
378             });
379              
380             Mojo::IOLoop->start;
381              
382             =head1 DESCRIPTION
383              
384             L is a non-blocking IRC client using L from the
385             wonderful L framework.
386              
387             It features IPv6 and TLS, with additional optional modules:
388             L and L.
389              
390             By default this module will only emit standard IRC events, but by
391             settings L to a custom object it will also emit CTCP events.
392             Example:
393              
394             my $irc = Mojo::IRC->new;
395             $irc->parser(Parse::IRC->new(ctcp => 1);
396             $irc->on(ctcp_action => sub {
397             # ...
398             });
399              
400             It will also set up some default events: L, L,
401             and L.
402              
403             This class inherits from L.
404              
405             =head1 TESTING
406              
407             The module L is useful if you want to write tests without
408             having a running IRC server.
409              
410             L (from v0.20) is now DEPRECATED in favor of
411             L.
412              
413             =head1 EVENTS
414              
415             =head2 close
416              
417             $self->on(close => sub { my ($self) = @_; });
418              
419             Emitted once the connection to the server closes.
420              
421             =head2 error
422              
423             $self->on(error => sub { my ($self, $err) = @_; });
424              
425             Emitted once the stream emits an error.
426              
427             =head2 message
428              
429             $self->on(message => sub { my ($self, $msg) = @_; });
430              
431             Emitted when a new IRC message arrives. Will dispatch to a default handler,
432             which will again emit L L and
433             L below.
434              
435             Here is an example C<$msg>:
436              
437             {
438             command => "PRIVMSG",
439             event => "privmsg",
440             params => ["#convos", "hey!"],
441             prefix => "jan_henning",
442             raw_line => ":jan_henning PRIVMSG #convos :hey",
443             }
444              
445             =head2 err_event_name
446              
447             Events that start with "err_" are emitted when there is an IRC response that
448             indicates an error. See L for sample events.
449              
450             =head2 ctcp_event_name
451              
452             Events that start with "ctcp_" are emitted if the L can understand
453             CTCP messages, and there is a CTCP response.
454              
455             $self->parser(Parse::IRC->new(ctcp => 1);
456              
457             See L for sample events.
458              
459             =head2 irc_event_name
460              
461             Events that start with "irc_" are emitted when there is a normal IRC response.
462             See L for sample events.
463              
464             =head1 ATTRIBUTES
465              
466             =head2 connect_timeout
467              
468             $int = $self->connect_timeout;
469             $self = $self->connect_timeout(60);
470              
471             Maximum amount of time in seconds establishing a connection may take before
472             getting canceled, defaults to the value of the C
473             environment variable or 30.
474              
475             =head2 ioloop
476              
477             Holds an instance of L.
478              
479             =head2 local_address
480              
481             $str = $self->local_address;
482             $self = $self->local_address("10.20.30.40");
483              
484             Local address to bind to. See L.
485              
486             =head2 name
487              
488             The name of this IRC client. Defaults to "Mojo IRC".
489              
490             =head2 nick
491              
492             IRC nick name accessor. Default to L.
493              
494             =head2 parser
495              
496             $self = $self->parser($obj);
497             $self = $self->parser(Parse::IRC->new(ctcp => 1));
498             $obj = $self->parser;
499              
500             Holds a L object by default.
501              
502             =head2 pass
503              
504             Password for authentication
505              
506             =head2 real_host
507              
508             Will be set by L. Holds the actual hostname of the IRC
509             server that we are connected to.
510              
511             =head2 server
512              
513             Server name and, optionally, a port to connect to. Changing this while
514             connected to the IRC server will issue a reconnect.
515              
516             =head2 server_settings
517              
518             $hash = $self->server_settings;
519              
520             Holds information about the server. See
521             L for
522             example data structure.
523              
524             Note that this attribute is EXPERIMENTAL and the structure of the values it
525             holds.
526              
527             =head2 user
528              
529             IRC username. Defaults to current logged in user or falls back to "anonymous".
530              
531             =head2 tls
532              
533             $self->tls(undef) # disable (default)
534             $self->tls({}) # enable
535              
536             Default is "undef" which disables TLS. Setting this to an empty hash will
537             enable TLS and this module will load in default certs. It is also possible
538             to set custom cert/key:
539              
540             $self->tls({ cert => "/path/to/client.crt", key => ... })
541              
542             This can be generated using
543              
544             # certtool --generate-privkey --outfile client.key
545             # certtool --generate-self-signed --load-privkey client.key --outfile client.crt
546              
547             To disable the verification of server certificates, the "insecure" option
548             can be set:
549              
550             $self->tls({insecure => 1});
551              
552             =head1 METHODS
553              
554             =head2 connect
555              
556             $self = $self->connect(\&callback);
557              
558             Will log in to the IRC L and call C<&callback>. The
559             C<&callback> will be called once connected or if connect fails. The second
560             argument will be an error message or empty string on success.
561              
562             =head2 ctcp
563              
564             $str = $self->ctcp(@str);
565              
566             This message will quote CTCP messages. Example:
567              
568             $self->write(PRIVMSG => nickname => $self->ctcp(TIME => time));
569              
570             The code above will write this message to IRC server:
571              
572             PRIVMSG nickname :\001TIME 1393006707\001
573              
574             =head2 disconnect
575              
576             $self->disconnect(\&callback);
577              
578             Will disconnect form the server and run the callback once it is done.
579              
580             =head2 new
581              
582             $self = Mojo::IRC->new(%attrs);
583              
584             Object constructor.
585              
586             =head2 register_default_event_handlers
587              
588             $self->register_default_event_handlers;
589              
590             This method sets up the default L unless someone has
591             already subscribed to the event.
592              
593             =head2 write
594              
595             $self->write(@str, \&callback);
596              
597             This method writes a message to the IRC server. C<@str> will be concatenated
598             with " " and "\r\n" will be appended. C<&callback> is called once the message is
599             delivered over the stream. The second argument to the callback will be
600             an error message: Empty string on success and a description on error.
601              
602             =head1 DEFAULT EVENT HANDLERS
603              
604             =head2 ctcp_ping
605              
606             Will respond to the sender with the difference in time.
607              
608             Ping reply from $sender: 0.53 second(s)
609              
610             =head2 ctcp_time
611              
612             Will respond to the sender with the current localtime. Example:
613              
614             TIME Fri Feb 21 18:56:50 2014
615              
616             NOTE! The localtime format may change.
617              
618             =head2 ctcp_version
619              
620             Will respond to the sender with:
621              
622             VERSION Mojo-IRC $VERSION
623              
624             NOTE! Additional information may be added later on.
625              
626             =head2 irc_nick
627              
628             Used to update the L attribute when the nick has changed.
629              
630             =head2 irc_notice
631              
632             Responds to the server with "QUOTE PASS ..." if the notice contains "Ident
633             broken...QUOTE PASS...".
634              
635             =head2 irc_ping
636              
637             Responds to the server with "PONG ...".
638              
639             =head2 irc_rpl_isupport
640              
641             Used to populate L with information about the server.
642              
643             =head2 irc_rpl_welcome
644              
645             Used to get the hostname of the server. Will also set up automatic PING
646             requests to prevent timeout and update the L attribute.
647              
648             =head2 err_nicknameinuse
649              
650             This handler will add "_" to the failed nick before trying to register again.
651              
652             =head1 COPYRIGHT
653              
654             This program is free software, you can redistribute it and/or modify it under
655             the terms of the Artistic License version 2.0.
656              
657             =head1 AUTHOR
658              
659             Marcus Ramberg - C
660              
661             Jan Henning Thorsen - C
662              
663             =cut