source: perl/modules/IRC/lib/BarnOwl/Module/IRC/Connection.pm @ 8ba9313

release-1.10release-1.8release-1.9
Last change on this file since 8ba9313 was 8ba9313, checked in by Nelson Elhage <nelhage@mit.edu>, 13 years ago
Port IRC support to AnyEvent::IRC.
  • Property mode set to 100644
File size: 13.2 KB
Line 
1use strict;
2use warnings;
3
4package BarnOwl::Module::IRC::Connection;
5use BarnOwl::Timer;
6
7=head1 NAME
8
9BarnOwl::Module::IRC::Connection
10
11=head1 DESCRIPTION
12
13This module is a wrapper around Net::IRC::Connection for BarnOwl's IRC
14support
15
16=cut
17
18use AnyEvent::IRC::Client;
19use AnyEvent::IRC::Util qw(split_prefix prefix_nick);
20
21use base qw(Class::Accessor);
22use Exporter 'import';
23__PACKAGE__->mk_accessors(qw(conn alias motd names_tmp whois_tmp nick server autoconnect_channels));
24our @EXPORT_OK = qw(is_private);
25
26use BarnOwl;
27use Scalar::Util qw(weaken);
28
29sub new {
30    my $class = shift;
31    my $alias = shift;
32    my $host  = shift;
33    my $port  = shift;
34    my $args  = shift;
35    my $nick = $args->{nick};
36    my $conn = AnyEvent::IRC::Client->new();
37    if(delete $args->{SSL}) {
38        $conn->enable_ssl;
39    }
40    $conn->connect($host, $port, $args);
41    my $self = bless({}, $class);
42    $self->conn($conn);
43    $self->autoconnect_channels([]);
44    $self->alias($alias);
45    $self->server($host);
46    $self->nick($nick);
47    $self->motd("");
48    $self->names_tmp(0);
49    $self->whois_tmp("");
50
51    # $self->conn->add_default_handler(sub { shift; $self->on_event(@_) });
52    $self->conn->reg_cb(registered => sub { shift; $self->connected("Connected to $alias as $nick") },
53                        connfail   => sub { shift; BarnOwl::error("Connection to $host failed!") },
54                        disconnect => sub { shift; $self->on_disconnect(@_) },
55                        publicmsg  => sub { shift; $self->on_msg(@_) },
56                        privatemsg => sub { shift; $self->on_msg(@_) });
57    for my $m (qw(welcome yourhost created
58                  luserclient luserop luserchannels luserme
59                  error)) {
60        $self->conn->reg_cb("irc_$m" => sub { shift;  $self->on_admin_msg(@_) });
61    }
62    $self->conn->reg_cb(irc_375       => sub { shift; $self->on_motdstart(@_) },
63                        irc_372       => sub { shift; $self->on_motd(@_) },
64                        irc_376       => sub { shift; $self->on_endofmotd(@_) },
65                        irc_join      => sub { shift; $self->on_join(@_) },
66                        irc_part      => sub { shift; $self->on_part(@_) },
67                        irc_quit      => sub { shift; $self->on_quit(@_) },
68                        irc_433       => sub { shift; $self->on_nickinuse(@_) },
69                        channel_topic => sub { shift; $self->on_topic(@_) },
70                        irc_333       => sub { shift; $self->on_topicinfo(@_) },
71                        irc_353       => sub { shift; $self->on_namreply(@_) },
72                        irc_366       => sub { shift; $self->on_endofnames(@_) },
73                        irc_311       => sub { shift; $self->on_whois(@_) },
74                        irc_312       => sub { shift; $self->on_whois(@_) },
75                        irc_319       => sub { shift; $self->on_whois(@_) },
76                        irc_320       => sub { shift; $self->on_whois(@_) },
77                        irc_318       => sub { shift; $self->on_endofwhois(@_) },
78                        irc_mode      => sub { shift; $self->on_mode(@_) },
79                        irc_401       => sub { shift; $self->on_nosuch(@_) },
80                        irc_402       => sub { shift; $self->on_nosuch(@_) },
81                        irc_403       => sub { shift; $self->on_nosuch(@_) },
82                        'irc_*' => sub { BarnOwl::debug("IRC: " . $_[1]->{command}) });
83
84    return $self;
85}
86
87sub getSocket
88{
89    my $self = shift;
90    return $self->conn->socket;
91}
92
93################################################################################
94############################### IRC callbacks ##################################
95################################################################################
96
97sub new_message {
98    my $self = shift;
99    my $evt = shift;
100    my ($nick, $user, $host) = split_prefix($evt);
101    return BarnOwl::Message->new(
102        type        => 'IRC',
103        server      => $self->server,
104        network     => $self->alias,
105        sender      => $nick,
106        defined($host) ? (hostname    => $host) : (),
107        from        => $evt->{prefix},
108        @_
109       );
110}
111
112sub on_msg {
113    my ($self, $recipient, $evt) = @_;
114    my $body = strip_irc_formatting($evt->{params}->[1]);
115
116    my $msg = $self->new_message($evt,
117        direction   => 'in',
118        recipient   => $recipient,
119        body        => $body,
120        $evt->{command} eq 'notice' ?
121          (notice     => 'true') : (),
122        is_private($recipient) ?
123          (private  => 'true') : (channel => $recipient),
124        replycmd    => BarnOwl::quote('irc-msg', '-a', $self->alias,
125           (is_private($recipient) ? prefix_nick($evt) : $recipient)),
126        replysendercmd => BarnOwl::quote('irc-msg', '-a', $self->alias, prefix_nick($evt)),
127       );
128
129    BarnOwl::queue_message($msg);
130}
131
132sub on_admin_msg {
133    my ($self, $evt) = @_;
134    return if BarnOwl::Module::IRC->skip_msg($evt->{command});
135    BarnOwl::admin_message("IRC",
136            BarnOwl::Style::boldify('IRC ' . $evt->{command} . ' message from '
137                . $self->alias) . "\n"
138            . strip_irc_formatting(join ' ', cdr($evt->{params})));
139}
140
141sub on_motdstart {
142    my ($self, $evt) = @_;
143    $self->motd(join "\n", cdr(@{$evt->{params}}));
144}
145
146sub on_motd {
147    my ($self, $evt) = @_;
148    $self->motd(join "\n", $self->motd, cdr(@{$evt->{params}}));
149}
150
151sub on_endofmotd {
152    my ($self, $evt) = @_;
153    $self->motd(join "\n", $self->motd, cdr(@{$evt->{params}}));
154    BarnOwl::admin_message("IRC",
155            BarnOwl::Style::boldify('MOTD for ' . $self->alias) . "\n"
156            . strip_irc_formatting($self->motd));
157}
158
159sub on_join {
160    my ($self, $evt) = @_;
161    my $chan = $evt->{params}[0];
162    my $msg = $self->new_message($evt,
163        loginout   => 'login',
164        action     => 'join',
165        channel    => $chan,
166        replycmd   => BarnOwl::quote('irc-msg', '-a', $self->alias, $chan),
167        replysendercmd => BarnOwl::quote('irc-msg', '-a', $self->alias, prefix_nick($evt)),
168        );
169    BarnOwl::queue_message($msg);
170}
171
172sub on_part {
173    my ($self, $evt) = @_;
174    my $chan = $evt->{params}[0];
175    my $msg = $self->new_message($evt,
176        loginout   => 'logout',
177        action     => 'part',
178        channel    => $chan,
179        replycmd   => BarnOwl::quote('irc-msg', '-a', $self->alias, $chan),
180        replysendercmd => BarnOwl::quote('irc-msg', '-a', $self->alias, prefix_nick($evt)),
181        );
182    BarnOwl::queue_message($msg);
183}
184
185sub on_quit {
186    my ($self, $evt) = @_;
187    my $msg = $self->new_message($evt,
188        loginout   => 'logout',
189        action     => 'quit',
190        from       => $evt->{prefix},
191        reason     => $evt->{params}->[1],
192        replycmd   => BarnOwl::quote('irc-msg', '-a', $self->alias, prefix_nick($evt)),
193        replysendercmd => BarnOwl::quote('irc-msg', '-a', $self->alias, prefix_nick($evt)),
194        );
195    BarnOwl::queue_message($msg);
196}
197
198sub disconnect {
199    my $self = shift;
200    delete $BarnOwl::Module::IRC::ircnets{$self->alias};
201    for my $k (keys %BarnOwl::Module::IRC::channels) {
202        my @conns = grep {$_ ne $self} @{$BarnOwl::Module::IRC::channels{$k}};
203        if(@conns) {
204            $BarnOwl::Module::IRC::channels{$k} = \@conns;
205        } else {
206            delete $BarnOwl::Module::IRC::channels{$k};
207        }
208    }
209    $self->motd("");
210}
211
212sub on_disconnect {
213    my ($self, $why) = @_;
214    $self->disconnect;
215    BarnOwl::admin_message('IRC',
216                           "[" . $self->alias . "] Disconnected from server");
217    if ($why && $why =~ m{error in connection}) {
218        $self->schedule_reconnect;
219    }
220}
221
222sub on_nickinuse {
223    my ($self, $evt) = @_;
224    BarnOwl::admin_message("IRC",
225                           "[" . $self->alias . "] " .
226                           $evt->{params}->[1] . ": Nick already in use");
227}
228
229sub on_topic {
230    my ($self, $channel, $topic, $who) = @_;
231    if ($channel) {
232        BarnOwl::admin_message("IRC",
233                "Topic for $channel on " . $self->alias . " is $topic");
234    } else {
235        BarnOwl::admin_message("IRC",
236                "Topic changed to $channel");
237    }
238}
239
240sub on_topicinfo {
241    my ($self, $evt) = @_;
242    my @args = @{$evt->{params}};
243    BarnOwl::admin_message("IRC",
244        "Topic for $args[1] set by $args[2] at " . localtime($args[3]));
245}
246
247# IRC gives us a bunch of namreply messages, followed by an endofnames.
248# We need to collect them from the namreply and wait for the endofnames message.
249# After this happens, the names_tmp variable is cleared.
250
251sub on_namreply {
252    my ($self, $evt) = @_;
253    return unless $self->names_tmp;
254    $self->names_tmp([@{$self->names_tmp}, split(' ', $evt->{params}[3])]);
255}
256
257sub cmp_user {
258    my ($lhs, $rhs) = @_;
259    my ($sigil_l) = ($lhs =~ m{^([+@]?)});
260    my ($sigil_r) = ($rhs =~ m{^([+@]?)});
261    my %rank = ('@' => 1, '+' => 2, '' => 3);
262    return ($rank{$sigil_l} <=> $rank{$sigil_r}) ||
263            $lhs cmp $rhs;
264}
265
266sub on_endofnames {
267    my ($self, $evt) = @_;
268    return unless $self->names_tmp;
269    my $names = BarnOwl::Style::boldify("Members of " . $evt->{params}->[1] . ":\n");
270    for my $name (sort {cmp_user($a, $b)} @{$self->names_tmp}) {
271        $names .= "  $name\n";
272    }
273    BarnOwl::popless_ztext($names);
274    $self->names_tmp(0);
275}
276
277sub on_whois {
278    my ($self, $evt) = @_;
279    my %names = (
280        311 => 'user',
281        312 => 'server',
282        319 => 'channels',
283        330 => 'whowas',
284       );
285    $self->whois_tmp(
286        $self->whois_tmp . "\n" . $names{$evt->{command}} . ":\n  " .
287        join("\n  ", cdr(cdr(@{$evt->{params}}))) . "\n"
288       );
289}
290
291sub on_endofwhois {
292    my ($self, $evt) = @_;
293    BarnOwl::popless_ztext(
294        BarnOwl::Style::boldify("/whois for " . $evt->{params}->[1] . ":\n") .
295        $self->whois_tmp
296    );
297    $self->whois_tmp('');
298}
299
300sub on_mode {
301    my ($self, $evt) = @_;
302    BarnOwl::admin_message("IRC",
303                           "[" . $self->alias . "] User " . (prefix_nick($evt)) . + " set mode " .
304                           join(" ", cdr(@{$evt->{params}})) . "on " . $evt->{params}->[0]
305                          );
306}
307
308sub on_nosuch {
309    my ($self, $evt) = @_;
310    my %things = (401 => 'nick', 402 => 'server', 403 => 'channel');
311    BarnOwl::admin_message("IRC",
312                           "[" . $self->alias . "] " .
313                           "No such @{[$things{$evt->{command}}]}: @{[$evt->{params}->[1]]}")
314}
315
316sub on_event {
317    my ($self, $evt) = @_;
318    return on_whois(@_) if ($evt->type =~ /^whois/);
319    BarnOwl::admin_message("IRC",
320            "[" . $self->alias . "] Unhandled IRC event of type " . $evt->type . ":\n"
321            . strip_irc_formatting(join("\n", $evt->args)))
322        if BarnOwl::getvar('irc:spew') eq 'on';
323}
324
325sub schedule_reconnect {
326    my $self = shift;
327    my $interval = shift || 5;
328    delete $BarnOwl::Module::IRC::ircnets{$self->alias};
329    $BarnOwl::Module::IRC::reconnect{$self->alias} = $self;
330    my $weak = $self;
331    weaken($weak);
332    if (defined $self->{reconnect_timer}) {
333        $self->{reconnect_timer}->stop;
334    }
335    $self->{reconnect_timer} = 
336        BarnOwl::Timer->new( {
337            name  => 'IRC (' . $self->alias . ') reconnect_timer',
338            after => $interval,
339            cb    => sub {
340                $weak->reconnect( $interval ) if $weak;
341            },
342        } );
343}
344
345sub cancel_reconnect {
346    my $self = shift;
347    delete $BarnOwl::Module::IRC::reconnect{$self->alias};
348    if (defined $self->{reconnect_timer}) {
349        $self->{reconnect_timer}->stop;
350    }
351    delete $self->{reconnect_timer};
352}
353
354sub connected {
355    my $self = shift;
356    my $msg = shift;
357    BarnOwl::admin_message("IRC", $msg);
358    $self->cancel_reconnect;
359    $BarnOwl::Module::IRC::ircnets{$self->alias} = $self;
360    if ($self->autoconnect_channels) {
361        for my $c (@{$self->autoconnect_channels}) {
362            $self->conn->send_msg(join => $c);
363        }
364        $self->autoconnect_channels([]);
365    }
366    $self->conn->enable_ping(60, sub {
367                                 $self->disconnect("Connection timed out.");
368                                 $self->schedule_reconnect;
369                             });
370}
371
372sub reconnect {
373    my $self = shift;
374    my $backoff = shift;
375
376    $self->autoconnect_channels([keys(%{$self->channel_list})]);
377    $self->conn->connect;
378    if ($self->conn->connected) {
379        $self->connected("Reconnected to ".$self->alias);
380        return;
381    }
382
383    $backoff *= 2;
384    $backoff = 60*5 if $backoff > 60*5;
385    $self->schedule_reconnect( $backoff );
386}
387
388################################################################################
389########################### Utilities/Helpers ##################################
390################################################################################
391
392sub strip_irc_formatting {
393    my $body = shift;
394    # Strip mIRC colors. If someone wants to write code to convert
395    # these to zephyr colors, be my guest.
396    $body =~ s/\cC\d+(?:,\d+)?//g;
397    $body =~ s/\cO//g;
398   
399    my @pieces = split /\cB/, $body;
400    my $out = '';
401    while(@pieces) {
402        $out .= shift @pieces;
403        $out .= BarnOwl::Style::boldify(shift @pieces) if @pieces;
404    }
405    return $out;
406}
407
408# Determines if the given message recipient is a username, as opposed to
409# a channel that starts with # or &.
410sub is_private {
411    return shift !~ /^[\#\&]/;
412}
413
414sub cdr {
415    shift;
416    return @_;
417}
418
4191;
Note: See TracBrowser for help on using the repository browser.