source: perl/modules/IRC/lib/BarnOwl/Module/IRC.pm @ 2b66c361

release-1.5release-1.6release-1.7release-1.8release-1.9
Last change on this file since 2b66c361 was 2b66c361, checked in by Nelson Elhage <nelhage@mit.edu>, 12 years ago
Eliminate newlines between parts of the buddylists. [nelhage@mit.edu: _get_blist: Force exactly one newline between components]
  • Property mode set to 100644
File size: 15.8 KB
Line 
1use strict;
2use warnings;
3
4package BarnOwl::Module::IRC;
5
6=head1 NAME
7
8BarnOwl::Module::IRC
9
10=head1 DESCRIPTION
11
12This module implements IRC support for barnowl.
13
14=cut
15
16use BarnOwl;
17use BarnOwl::Hooks;
18use BarnOwl::Message::IRC;
19use BarnOwl::Module::IRC::Connection qw(is_private);
20use BarnOwl::Module::IRC::Completion;
21
22use Net::IRC;
23use Getopt::Long;
24
25our $VERSION = 0.02;
26
27our $irc;
28
29# Hash alias -> BarnOwl::Module::IRC::Connection object
30our %ircnets;
31our %channels;
32our %reconnect;
33
34sub startup {
35    BarnOwl::new_variable_string('irc:nick', {
36        default     => $ENV{USER},
37        summary     => 'The default IRC nickname',
38        description => 'By default, irc-connect will use this nick '  .
39        'when connecting to a new server. See :help irc-connect for ' .
40        'more information.'
41       });
42
43    BarnOwl::new_variable_string('irc:user', {
44        default => $ENV{USER},
45        summary => 'The IRC "username" field'
46       });
47        BarnOwl::new_variable_string('irc:name', {
48        default => "",
49        summary     => 'A short name field for IRC',
50        description => 'A short (maybe 60 or so chars) piece of text, ' .
51        'originally intended to display your real name, which people '  .
52        'often use for pithy quotes and URLs.'
53       });
54
55    BarnOwl::new_variable_bool('irc:spew', {
56        default     => 0,
57        summary     => 'Show unhandled IRC events',
58        description => 'If set, display all unrecognized IRC events as ' .
59        'admin messages. Intended for debugging and development use only.'
60       });
61
62    BarnOwl::new_variable_string('irc:skip', {
63        default     => 'welcome yourhost created ' .
64        'luserclient luserme luserop luserchannels',
65        summary     => 'Skip messages of these types',
66        description => 'If set, each (space-separated) message type ' .
67        'provided will be hidden and ignored if received.'
68       });
69
70    register_commands();
71    register_handlers();
72    BarnOwl::filter(qw{irc type ^IRC$ or ( type ^admin$ and adminheader ^IRC$ )});
73}
74
75sub shutdown {
76    for my $conn (values %ircnets) {
77        $conn->conn->disconnect();
78    }
79}
80
81sub quickstart {
82    return <<'END_QUICKSTART';
83@b[IRC:]
84Use ':irc-connect @b[server]' to connect to an IRC server, and
85':irc-join @b[#channel]' to join a channel. ':irc-msg @b[#channel]
86@b[message]' sends a message to a channel.
87END_QUICKSTART
88}
89
90sub buddylist {
91    my $list = "";
92
93    for my $net (sort keys %ircnets) {
94        my $conn = $ircnets{$net};
95        my ($nick, $server) = ($conn->nick, $conn->server);
96        $list .= BarnOwl::Style::boldify("IRC channels for $net ($nick\@$server)\n");
97
98        for my $chan (keys %channels) {
99            next unless grep $_ eq $conn, @{$channels{$chan}};
100            $list .= "  $chan\n";
101        }
102    }
103
104    return $list;
105}
106
107#sub mainloop_hook {
108#    return unless defined $irc;
109#    eval {
110#        $irc->do_one_loop();
111#    };
112#    return;
113#}
114
115sub OwlProcess {
116    return unless defined $irc;
117    eval {
118        $irc->do_one_loop();
119    };
120    return;
121}
122
123
124sub register_handlers {
125    if(!$irc) {
126        $irc = Net::IRC->new;
127        $irc->timeout(0);
128    }
129}
130
131sub skip_msg {
132    my $class = shift;
133    my $type = lc shift;
134    my $skip = lc BarnOwl::getvar('irc:skip');
135    return grep {$_ eq $type} split ' ', $skip;
136}
137
138use constant OPTIONAL_CHANNEL => 1;
139use constant REQUIRE_CHANNEL => 2;
140
141sub register_commands {
142    BarnOwl::new_command(
143        'irc-connect' => \&cmd_connect,
144        {
145            summary => 'Connect to an IRC server',
146            usage =>
147'irc-connect [-a ALIAS ] [-s] [-p PASSWORD] [-n NICK] SERVER [port]',
148            description => <<END_DESCR
149Connect to an IRC server. Supported options are:
150
151 -a <alias>          Define an alias for this server
152 -s                  Use SSL
153 -p <password>       Specify the password to use
154 -n <nick>           Use a non-default nick
155
156The -a option specifies an alias to use for this connection. This
157alias can be passed to the '-a' argument of any other IRC command to
158control which connection it operates on.
159
160For servers with hostnames of the form "irc.FOO.{com,org,...}", the
161alias will default to "FOO"; For other servers the full hostname is
162used.
163END_DESCR
164        }
165    );
166
167    BarnOwl::new_command(
168        'irc-disconnect' => mk_irc_command( \&cmd_disconnect ),
169        {
170            summary => 'Disconnect from an IRC server',
171            usage   => 'irc-disconnect [-a ALIAS]',
172
173            description => <<END_DESCR
174Disconnect from an IRC server. You can specify a specific server with
175"-a SERVER-ALIAS" if necessary.
176END_DESCR
177        }
178    );
179
180    BarnOwl::new_command(
181        'irc-msg' => mk_irc_command( \&cmd_msg ),
182        {
183            summary => 'Send an IRC message',
184            usage   => 'irc-msg [-a ALIAS] DESTINATION MESSAGE',
185
186            description => <<END_DESCR
187Send an IRC message.
188END_DESCR
189        }
190    );
191
192    BarnOwl::new_command(
193        'irc-mode' => mk_irc_command( \&cmd_mode, OPTIONAL_CHANNEL ),
194        {
195            summary => 'Change an IRC channel or user mode',
196            usage   => 'irc-mode [-a ALIAS] TARGET [+-]MODE OPTIONS',
197
198            description => <<END_DESCR
199Change the mode of an IRC user or channel.
200END_DESCR
201        }
202    );
203
204    BarnOwl::new_command(
205        'irc-join' => mk_irc_command( \&cmd_join ),
206        {
207            summary => 'Join an IRC channel',
208            usage   => 'irc-join [-a ALIAS] #channel [KEY]',
209
210            description => <<END_DESCR
211Join an IRC channel.
212END_DESCR
213        }
214    );
215
216    BarnOwl::new_command(
217        'irc-part' => mk_irc_command( \&cmd_part, REQUIRE_CHANNEL ),
218        {
219            summary => 'Leave an IRC channel',
220            usage   => 'irc-part [-a ALIAS] #channel',
221
222            description => <<END_DESCR
223Part from an IRC channel.
224END_DESCR
225        }
226    );
227
228    BarnOwl::new_command(
229        'irc-nick' => mk_irc_command( \&cmd_nick ),
230        {
231            summary => 'Change your IRC nick on an existing connection.',
232            usage   => 'irc-nick [-a ALIAS] NEW-NICK',
233
234            description => <<END_DESCR
235Set your IRC nickname on an existing connect. To change it prior to
236connecting, adjust the `irc:nick' variable.
237END_DESCR
238        }
239    );
240
241    BarnOwl::new_command(
242        'irc-names' => mk_irc_command( \&cmd_names, REQUIRE_CHANNEL ),
243        {
244            summary => 'View the list of users in a channel',
245            usage   => 'irc-names [-a ALIAS] #channel',
246
247            description => <<END_DESCR
248`irc-names' displays the list of users in a given channel in a pop-up
249window.
250END_DESCR
251        }
252    );
253
254    BarnOwl::new_command(
255        'irc-whois' => mk_irc_command( \&cmd_whois ),
256        {
257            summary => 'Displays information about a given IRC user',
258            usage   => 'irc-whois [-a ALIAS] NICK',
259
260            description => <<END_DESCR
261Pops up information about a given IRC user.
262END_DESCR
263        }
264    );
265
266    BarnOwl::new_command(
267        'irc-motd' => mk_irc_command( \&cmd_motd ),
268        {
269            summary => 'Displays an IRC server\'s MOTD (Message of the Day)',
270            usage   => 'irc-motd [-a ALIAS]',
271
272            description => <<END_DESCR
273Displays an IRC server's message of the day.
274END_DESCR
275        }
276    );
277
278    BarnOwl::new_command(
279        'irc-list' => \&cmd_list,
280        {
281            summary => 'Show all the active IRC connections.',
282            usage   => 'irc-list',
283
284            description => <<END_DESCR
285Show all the currently active IRC connections with their aliases and
286server names.
287END_DESCR
288        }
289    );
290
291    BarnOwl::new_command( 'irc-who'   => mk_irc_command( \&cmd_who ) );
292    BarnOwl::new_command( 'irc-stats' => mk_irc_command( \&cmd_stats ) );
293
294    BarnOwl::new_command(
295        'irc-topic' => mk_irc_command( \&cmd_topic, REQUIRE_CHANNEL ),
296        {
297            summary => 'View or change the topic of an IRC channel',
298            usage   => 'irc-topic [-a ALIAS] #channel [TOPIC]',
299
300            description => <<END_DESCR
301Without extra arguments, fetches and displays a given channel's topic.
302
303With extra arguments, changes the target channel's topic string. This
304may require +o on some channels.
305END_DESCR
306        }
307    );
308
309    BarnOwl::new_command(
310        'irc-quote' => mk_irc_command( \&cmd_quote ),
311        {
312            summary => 'Send a raw command to the IRC servers.',
313            usage   => 'irc-quote [-a ALIAS] TEXT',
314
315            description => <<END_DESCR
316Send a raw command line to an IRC server.
317
318This can be used to perform some operation not yet supported by
319BarnOwl, or to define new IRC commands.
320END_DESCR
321        }
322    );
323}
324
325
326$BarnOwl::Hooks::startup->add('BarnOwl::Module::IRC::startup');
327$BarnOwl::Hooks::shutdown->add('BarnOwl::Module::IRC::shutdown');
328$BarnOwl::Hooks::getQuickstart->add('BarnOwl::Module::IRC::quickstart');
329$BarnOwl::Hooks::getBuddyList->add("BarnOwl::Module::IRC::buddylist");
330
331################################################################################
332######################## Owl command handlers ##################################
333################################################################################
334
335sub cmd_connect {
336    my $cmd = shift;
337
338    my $nick = BarnOwl::getvar('irc:nick');
339    my $username = BarnOwl::getvar('irc:user');
340    my $ircname = BarnOwl::getvar('irc:name');
341    my $host;
342    my $port;
343    my $alias;
344    my $ssl;
345    my $password = undef;
346
347    {
348        local @ARGV = @_;
349        GetOptions(
350            "alias=s"    => \$alias,
351            "ssl"        => \$ssl,
352            "password=s" => \$password,
353            "nick=s"     => \$nick,
354        );
355        $host = shift @ARGV or die("Usage: $cmd HOST\n");
356        if(!$alias) {
357            if($host =~ /^(?:irc[.])?([\w-]+)[.]\w+$/) {
358                $alias = $1;
359            } else {
360                $alias = $host;
361            }
362        }
363        $ssl ||= 0;
364        $port = shift @ARGV || ($ssl ? 6697 : 6667);
365    }
366
367    if(exists $ircnets{$alias}) {
368        die("Already connected to a server with alias '$alias'. Either disconnect or specify an alias with -a.\n");
369    }
370
371    my $conn = BarnOwl::Module::IRC::Connection->new($irc, $alias,
372        Nick      => $nick,
373        Server    => $host,
374        Port      => $port,
375        Username  => $username,
376        Ircname   => $ircname,
377        Port      => $port,
378        Password  => $password,
379        SSL       => $ssl
380       );
381
382    if ($conn->conn->connected) {
383        $conn->connected("Connected to $alias as $nick");
384    } else {
385        die("IRC::Connection->connect failed: $!");
386    }
387
388    return;
389}
390
391sub cmd_disconnect {
392    my $cmd = shift;
393    my $conn = shift;
394    $conn->conn->disconnect;
395    return;
396}
397
398sub cmd_msg {
399    my $cmd  = shift;
400    my $conn = shift;
401    my $to = shift or die("Usage: $cmd [NICK|CHANNEL]\n");
402    # handle multiple recipients?
403    if(@_) {
404        process_msg($conn, $to, join(" ", @_));
405    } else {
406        BarnOwl::start_edit_win(BarnOwl::quote('/msg', '-a', $conn->alias, $to), sub {process_msg($conn, $to, @_)});
407    }
408    return;
409}
410
411sub process_msg {
412    my $conn = shift;
413    my $to = shift;
414    my $body = shift;
415    # Strip whitespace. In the future -- send one message/line?
416    $body =~ tr/\n\r/  /;
417    if ($body =~ /^\/me (.*)/) {
418        $conn->conn->me($to, Encode::encode('utf-8', $1));
419        $body = '* '.$conn->nick.' '.$1;
420    } else {
421        $conn->conn->privmsg($to, Encode::encode('utf-8', $body));
422    }
423    my $msg = BarnOwl::Message->new(
424        type        => 'IRC',
425        direction   => is_private($to) ? 'out' : 'in',
426        server      => $conn->server,
427        network     => $conn->alias,
428        recipient   => $to,
429        body        => $body,
430        sender      => $conn->nick,
431        is_private($to) ?
432          (isprivate  => 'true') : (channel => $to),
433        replycmd    => BarnOwl::quote('irc-msg',  '-a', $conn->alias, $to),
434        replysendercmd => BarnOwl::quote('irc-msg', '-a', $conn->alias, $to),
435       );
436    BarnOwl::queue_message($msg);
437    return;
438}
439
440sub cmd_mode {
441    my $cmd = shift;
442    my $conn = shift;
443    my $target = shift;
444    $target ||= shift;
445    $conn->conn->mode($target, @_);
446    return;
447}
448
449sub cmd_join {
450    my $cmd = shift;
451    my $conn = shift;
452    my $chan = shift or die("Usage: $cmd channel\n");
453    $channels{$chan} ||= [];
454    push @{$channels{$chan}}, $conn;
455    $conn->conn->join($chan, @_);
456    return;
457}
458
459sub cmd_part {
460    my $cmd = shift;
461    my $conn = shift;
462    my $chan = shift;
463    $channels{$chan} = [grep {$_ ne $conn} @{$channels{$chan} || []}];
464    $conn->conn->part($chan);
465    return;
466}
467
468sub cmd_nick {
469    my $cmd = shift;
470    my $conn = shift;
471    my $nick = shift or die("Usage: $cmd <new nick>\n");
472    $conn->conn->nick($nick);
473    return;
474}
475
476sub cmd_names {
477    my $cmd = shift;
478    my $conn = shift;
479    my $chan = shift;
480    $conn->names_tmp([]);
481    $conn->conn->names($chan);
482    return;
483}
484
485sub cmd_whois {
486    my $cmd = shift;
487    my $conn = shift;
488    my $who = shift || die("Usage: $cmd <user>\n");
489    $conn->conn->whois($who);
490    return;
491}
492
493sub cmd_motd {
494    my $cmd = shift;
495    my $conn = shift;
496    $conn->conn->motd;
497    return;
498}
499
500sub cmd_list {
501    my $cmd = shift;
502    my $message = BarnOwl::Style::boldify('Current IRC networks:') . "\n";
503    while (my ($alias, $conn) = each %ircnets) {
504        $message .= '  ' . $alias . ' => ' . $conn->nick . '@' . $conn->server . "\n";
505    }
506    BarnOwl::popless_ztext($message);
507    return;
508}
509
510sub cmd_who {
511    my $cmd = shift;
512    my $conn = shift;
513    my $who = shift || die("Usage: $cmd <user>\n");
514    BarnOwl::error("WHO $cmd $conn $who");
515    $conn->conn->who($who);
516    return;
517}
518
519sub cmd_stats {
520    my $cmd = shift;
521    my $conn = shift;
522    my $type = shift || die("Usage: $cmd <chiklmouy> [server] \n");
523    $conn->conn->stats($type, @_);
524    return;
525}
526
527sub cmd_topic {
528    my $cmd = shift;
529    my $conn = shift;
530    my $chan = shift;
531    $conn->conn->topic($chan, @_ ? join(" ", @_) : undef);
532    return;
533}
534
535sub cmd_quote {
536    my $cmd = shift;
537    my $conn = shift;
538    $conn->conn->sl(join(" ", @_));
539    return;
540}
541
542################################################################################
543########################### Utilities/Helpers ##################################
544################################################################################
545
546sub mk_irc_command {
547    my $sub = shift;
548    my $use_channel = shift || 0;
549    return sub {
550        my $cmd = shift;
551        my $conn;
552        my $alias;
553        my $channel;
554        my $getopt = Getopt::Long::Parser->new;
555        my $m = BarnOwl::getcurmsg();
556
557        local @ARGV = @_;
558        $getopt->configure(qw(pass_through permute no_getopt_compat prefix_pattern=-|--));
559        $getopt->getoptions("alias=s" => \$alias);
560
561        if(defined($alias)) {
562            $conn = get_connection_by_alias($alias);
563        }
564        if($use_channel) {
565            $channel = $ARGV[0];
566            if(defined($channel) && $channel =~ /^#/) {
567                if($channels{$channel} && @{$channels{$channel}} == 1) {
568                    shift @ARGV;
569                    $conn = $channels{$channel}[0] unless $conn;
570                }
571            } elsif ($m && $m->type eq 'IRC' && !$m->is_private) {
572                $channel = $m->channel;
573            } else {
574                undef $channel;
575            }
576        }
577
578        if(!$channel && $use_channel == REQUIRE_CHANNEL) {
579            die("Usage: $cmd <channel>\n");
580        }
581        if(!$conn) {
582            if($m && $m->type eq 'IRC') {
583                $conn = get_connection_by_alias($m->network);
584            }
585        }
586        if(!$conn && scalar keys %ircnets == 1) {
587            $conn = [values(%ircnets)]->[0];
588        }
589        if(!$conn) {
590            die("You must specify an IRC network using -a.\n");
591        }
592        if($use_channel) {
593            $sub->($cmd, $conn, $channel, @ARGV);
594        } else {
595            $sub->($cmd, $conn, @ARGV);
596        }
597    };
598}
599
600sub get_connection_by_alias {
601    my $key = shift;
602    die("No such ircnet: $key\n") unless exists $ircnets{$key};
603    return $ircnets{$key};
604}
605
6061;
Note: See TracBrowser for help on using the repository browser.