source: perl/modules/IRC/lib/BarnOwl/Module/IRC/Connection.pm @ 59425a3

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