File Coverage

blib/lib/Hubot/Adapter/Irc.pm
Criterion Covered Total %
statement 1 3 33.3
branch n/a
condition n/a
subroutine 1 1 100.0
pod n/a
total 2 4 50.0


line stmt bran cond sub pod time code
1             package Hubot::Adapter::Irc;
2             $Hubot::Adapter::Irc::VERSION = '0.2.7';
3 1     1   1795 use Moose;
  0            
  0            
4             use namespace::autoclean;
5              
6             extends 'Hubot::Adapter';
7              
8             use AnyEvent;
9             use AnyEvent::IRC::Client;
10             use Time::HiRes 'time';
11             use Encode qw/encode_utf8 decode_utf8/;
12              
13             use Hubot::Message;
14              
15             has 'robot' => ( is => 'ro', isa => 'Hubot::Robot', );
16              
17             has 'cv' => ( is => 'ro', lazy_build => 1, );
18              
19             has 'irc' => ( is => 'ro', lazy_build => 1, );
20              
21             sub _build_cv { AnyEvent->condvar }
22             sub _build_irc { AnyEvent::IRC::Client->new }
23              
24             sub notice { }
25              
26             sub join {
27             my ( $self, $channel ) = @_;
28             $self->irc->send_srv( JOIN => $channel );
29             }
30             sub part { }
31             sub kick { }
32             sub command { }
33              
34             sub whois {
35             my ( $self, $nick ) = @_;
36             return $self->irc->nick_ident($nick);
37             }
38              
39             sub parse_msg {
40             my ( $self, $irc_msg ) = @_;
41              
42             my ($nickname) = $irc_msg->{prefix} =~ m/^([^!]+)/;
43             my ($ip) = $irc_msg->{prefix} =~ m/\b((?:[0-9]{1,3}\.){3}[0-9]{1,3})\b/;
44             my $message = decode_utf8( $irc_msg->{params}[1] );
45             return ( $nickname, $message, $ip );
46             }
47              
48             sub send {
49             my ( $self, $user, @strings ) = @_;
50             for my $str (@strings) {
51             $self->irc->send_srv( 'PRIVMSG', $user->{room}, encode_utf8($str) );
52             Time::HiRes::sleep(0.1);
53             }
54             }
55              
56             sub whisper {
57             my ( $self, $user, $to, @strings ) = @_;
58             $self->irc->send_srv( 'PRIVMSG', $to->{name}, encode_utf8($_) )
59             for @strings;
60             }
61              
62             sub reply {
63             my ( $self, $user, @strings ) = @_;
64             @strings = map { $user->{name} . ": $_" } @strings;
65             $self->send( $user, @strings );
66             }
67              
68             sub run {
69             my $self = shift;
70              
71             $self->checkCanStart;
72              
73             my %options = (
74             nick => $ENV{HUBOT_IRC_NICK} || $self->robot->name,
75             port => $ENV{HUBOT_IRC_PORT} || 6667,
76             rooms => [split( /,/, $ENV{HUBOT_IRC_ROOMS} )],
77             server => $ENV{HUBOT_IRC_SERVER},
78             user => $ENV{HUBOT_IRC_USER},
79             password => $ENV{HUBOT_IRC_PASSWORD},
80             realname => $ENV{HUBOT_IRC_REALNAME},
81             nickserv => $ENV{HUBOT_IRC_NICKSERV} || 'NickServ',
82             nickservpw => $ENV{HUBOT_IRC_NICKSERV_PASSWORD},
83             );
84              
85             $self->robot->name( $options{nick} );
86              
87             $self->irc->reg_cb(
88             connect => sub {
89             my ( $con, $err ) = @_;
90              
91             if ( defined $options{nickservpw} ) {
92             $self->irc->send_srv(
93             'PRIVMSG' => $options{nickserv},
94             "identify $options{nickservpw}"
95             );
96             }
97             else {
98             $self->join($_) for @{ $options{rooms} };
99             }
100             },
101             join => sub {
102             my ( $cl, $nick, $channel, $is_myself ) = @_;
103             print "$nick joined $channel\n";
104             my $user = $self->createUser( $channel, $nick );
105             $self->receive( new Hubot::EnterMessage( user => $user ) );
106             },
107             publicmsg => sub {
108             my ( $cl, $channel, $ircmsg ) = @_;
109             my ( $nick, $msg, $ip ) = $self->parse_msg($ircmsg);
110             my $user = $self->createUser( $channel, $nick );
111             $user->{room} = $channel if $channel =~ m/^#/;
112             $user->{ip} = $ip if $ip;
113              
114             my $is_notice = $ircmsg->{command} eq 'NOTICE';
115             my $class
116             = $is_notice ? 'Hubot::NoticeMessage' : 'Hubot::TextMessage';
117             $self->receive( $class->new( user => $user, text => $msg, ) );
118             },
119             privatemsg => sub {
120             my ( $cl, $nick, $ircmsg ) = @_;
121             my ( $from, $msg ) = $self->parse_msg($ircmsg);
122             my ($channel) = $msg =~ m/^\#/ ? split / /, $msg : '';
123             $msg =~ s/^$channel\s*//;
124              
125             my $is_notice = $ircmsg->{command} eq 'NOTICE';
126              
127             # -NickServ- You are now identified for <nick>.
128             if ( $is_notice
129             && $from eq $options{nickserv}
130             && $msg =~ /identified/ )
131             {
132             $self->join($_) for @{ $options{rooms} };
133             return;
134             }
135              
136             my $class
137             = $is_notice
138             ? 'Hubot::NoticeMessage'
139             : 'Hubot::WhisperMessage';
140             my $user = $self->createUser( $channel, $from );
141             $self->receive( $class->new( user => $user, text => $msg, ) );
142             },
143             part => sub {
144             my ( $cl, $nick, $channel, $is_myself, $msg ) = @_;
145              
146             $msg = "no quit message" unless $msg;
147              
148             print "$nick leaves $channel: $msg\n";
149             my $user = $self->createUser( $channel, $nick );
150             $self->receive(
151             new Hubot::LeaveMessage( user => $user, text => $msg ) );
152             },
153             quit => sub {
154             my ( $cl, $nick, $msg ) = @_;
155              
156             $msg = "no quit message" unless $msg;
157              
158             print "$nick quit: $msg\n";
159             my $user = $self->createUser( '', $nick )
160             ; # room is empty, maybe raise a error case.
161             $self->receive(
162             new Hubot::LeaveMessage( user => $user, text => $msg ) );
163             },
164             irc_330 => sub {
165             ## 330 is RPL_WHOWAS_TIME
166             my ( $cl, $ircmsg ) = @_;
167              
168             my $user = $self->createUser( '', '*' );
169             $self->receive(
170             new Hubot::NoticeMessage(
171             user => $user,
172             text =>
173             sprintf( "%s %s %s", @{ $ircmsg->{params} }[1, 3, 2] ),
174             )
175             );
176             },
177             irc_mode => sub {
178             my ( $cl, $ircmsg ) = @_;
179             my ( $channel, $mode, $target ) = @{ $ircmsg->{params} };
180              
181             $self->robot->mode( $mode || '' )
182             if $target && $target eq $self->robot->name;
183             },
184             );
185              
186             $self->emit('connected');
187             $self->cv->begin;
188             if ( $ENV{HUBOT_IRC_ENABLE_SSL} ) {
189             eval "require Net::SSLeay; 1";
190             if ($@) {
191             die "HUBOT_IRC_ENABLE_SSL requires `Net::SSLeay`: $@\n";
192             }
193             else {
194             $self->irc->enable_ssl;
195             }
196             }
197             $self->irc->connect(
198             $options{server},
199             $options{port},
200             {
201             nick => $options{nick},
202             user => $options{user},
203             real => $options{realname},
204             password => $options{password},
205             timeout => 10, # wait 10 seconds
206             }
207             );
208              
209             $self->cv->recv;
210             }
211              
212             sub close {
213             my $self = shift;
214             $self->irc->disconnect;
215             $self->cv->send;
216             }
217              
218             sub exist {
219             my ( $self, $user, $nick ) = @_;
220             return $self->findUser( $user->{room}, $nick );
221             }
222              
223             sub createUser {
224             my ( $self, $channel, $from ) = @_;
225             my $user = $self->userForName($from);
226             unless ($user) {
227             my $id = time;
228             $id =~ s/\.//;
229             $user = $self->userForId( $id, { name => $from, room => $channel, } );
230             }
231              
232             return $user;
233             }
234              
235             sub findUser {
236             my ( $self, $channel, $nick ) = @_;
237             return $self->irc->nick_modes( $channel, $nick );
238             }
239              
240             sub checkCanStart {
241             my $self = shift;
242              
243             if ( !$ENV{HUBOT_IRC_NICK} && !$self->robot->name ) {
244             ## use die?
245             print STDERR
246             "HUBOT_IRC_NICK is not defined, try: export HUBOT_IRC_NICK='mybot'\n";
247             exit(2); # TODO: research standard exit value
248             }
249             elsif ( !$ENV{HUBOT_IRC_ROOMS} ) {
250             print STDERR
251             "HUBOT_IRC_ROOMS is not defined, try: export HUBOT_IRC_ROOMS='#myroom'\n";
252             exit(2);
253             }
254             elsif ( !$ENV{HUBOT_IRC_SERVER} ) {
255             print STDERR
256             "HUBOT_IRC_SERVER is not defined, try: export HUBOT_IRC_SERVER='irc.myserver.com'\n";
257             exit(2);
258             }
259             }
260              
261             __PACKAGE__->meta->make_immutable;
262              
263             1;
264              
265             =pod
266              
267             =encoding utf-8
268              
269             =head1 NAME
270              
271             Hubot::Adapter::Irc - IRC adapter for L<Hubot>
272              
273             =head1 VERSION
274              
275             version 0.2.7
276              
277             =head1 SYNOPSIS
278              
279             $ export HUBOT_IRC_SERVER='irc.myserver.com'
280             $ export HUBOT_IRC_ROOMS='#mychannel'
281             $ export HUBOT_IRC_ENABLE_SSL=1 # use SSL connection?
282             $ hubot -a irc
283              
284             =head1 DESCRIPTION
285              
286             IRC is a fairly old protocol for Internet chat.
287              
288             =head1 CONFIGURATION
289              
290             =head2 REQUIRED
291              
292             =over
293              
294             =item HUBOT_IRC_SERVER
295              
296             This is the full hostname or IP address of the IRC server you want your hubot to connect to. Make a note of it.
297              
298             =item HUBOT_IRC_ROOMS
299              
300             This is a comma separated list of the IRC channels you want your hubot to join. They must include the C<#>. Make a note of them.
301              
302             =back
303              
304             =head2 OPTIONAL
305              
306             =over
307              
308             =item HUBOT_IRC_NICK
309              
310             This is the optional nick you want your hubot to join with. If omitted it will default to the name of your hubot.
311              
312             =item HUBOT_IRC_PORT
313              
314             This is the optional port of the IRC server you want your hubot to connect to. If omitted the default is C<6667>. Make a note of it if required.
315              
316             =item HUBOT_IRC_USER
317              
318             This is the optional username of the IRC server you want your hubot to connect to.
319              
320             =item HUBOT_IRC_PASSWORD
321              
322             This is the optional password of the IRC server you want your hubot to connect to. If the IRC server doesn't require a password, this can be omitted. Make a note of it if required.
323              
324             =item HUBOT_IRC_REALNAME
325              
326             Your realname on IRC server.
327              
328             =item HUBOT_IRC_ENABLE_SSL
329              
330             using L<AnyEvent::IRC::Connection> C<enable_ssl> at connect.
331              
332             requires L<Net::SSLeay>.
333              
334             =back
335              
336             =head1 SEE ALSO
337              
338             L<https://github.com/github/hubot/wiki/Adapter:-IRC>
339              
340             =head1 AUTHOR
341              
342             Hyungsuk Hong <hshong@perl.kr>
343              
344             =head1 COPYRIGHT AND LICENSE
345              
346             This software is copyright (c) 2012 by Hyungsuk Hong.
347              
348             This is free software; you can redistribute it and/or modify it under
349             the same terms as the Perl 5 programming language system itself.
350              
351             =cut