File Coverage

blib/lib/Mojo/IRC.pm
Criterion Covered Total %
statement 184 210 87.6
branch 44 66 66.6
condition 18 34 52.9
subroutine 43 50 86.0
pod 17 17 100.0
total 306 377 81.1


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