Ignore:
File:
1 edited

Legend:

Unmodified
Added
Removed
  • perl/modules/IRC/lib/BarnOwl/Module/IRC.pm

    rb8a3e00 r926c721  
    2121
    2222use AnyEvent::IRC;
     23use Encode;
     24use File::Spec;
    2325use Getopt::Long;
    24 use Encode;
     26use Text::Wrap;
    2527
    2628our $VERSION = 0.02;
     29
     30our $IRC_SUBS_FILENAME = "ircchannels";
    2731
    2832our $irc;
     
    6771       });
    6872
     73    BarnOwl::new_variable_int('irc:max-message-length', {
     74        default     => 450,
     75        summary     => 'Split messages to at most this many characters.' .
     76                       "If non-positive, don't split messages",
     77        description => 'If set to a positive number, any paragraph in an ' .
     78                       'IRC message will be split after this many characters.'
     79       });
     80
    6981    register_commands();
    7082    BarnOwl::filter(qw{irc type ^IRC$ or ( type ^admin$ and adminheader ^IRC$ )});
     
    129141argument listed above, and die if no channel argument can be found.
    130142
     143=item C<CHANNEL_OR_USER>
     144
     145Pass the channel argument, but accept it if it's a username (e.g.
     146has no hash).  Only relevant with C<CHANNEL_ARG>.
     147
    131148=item C<CHANNEL_OPTIONAL>
    132149
     
    145162use constant CHANNEL_ARG        => 1;
    146163use constant CHANNEL_OPTIONAL   => 2;
    147 
    148 use constant ALLOW_DISCONNECTED => 4;
     164use constant CHANNEL_OR_USER    => 4;
     165
     166use constant ALLOW_DISCONNECTED => 8;
    149167
    150168sub register_commands {
     
    154172            summary => 'Connect to an IRC server',
    155173            usage =>
    156 'irc-connect [-a ALIAS ] [-s] [-p PASSWORD] [-n NICK] SERVER [port]',
     174'irc-connect [-a ALIAS] [-s] [-p PASSWORD] [-n NICK] SERVER [port]',
    157175            description => <<END_DESCR
    158176Connect to an IRC server. Supported options are:
     
    188206
    189207    BarnOwl::new_command(
    190         'irc-msg' => mk_irc_command( \&cmd_msg ),
     208        'irc-msg' => mk_irc_command( \&cmd_msg, CHANNEL_OR_USER|CHANNEL_ARG|CHANNEL_OPTIONAL ),
    191209        {
    192210            summary => 'Send an IRC message',
     
    215233        {
    216234            summary => 'Join an IRC channel',
    217             usage   => 'irc-join [-a ALIAS] #channel [KEY]',
    218 
    219             description => <<END_DESCR
    220 Join an IRC channel.
     235            usage   => 'irc-join [-a ALIAS] [-t] #channel [KEY]',
     236
     237            description => <<END_DESCR
     238Join an IRC channel.  If the -t option is present the subscription will only be
     239temporary, i.e., it will not be written to the subscription file and will
     240therefore not be present the next time BarnOwl is started, and will disappear
     241if the connection is lost.
    221242END_DESCR
    222243        }
     
    227248        {
    228249            summary => 'Leave an IRC channel',
    229             usage   => 'irc-part [-a ALIAS] #channel',
    230 
    231             description => <<END_DESCR
    232 Part from an IRC channel.
     250            usage   => 'irc-part [-a ALIAS] [-t] #channel',
     251
     252            description => <<END_DESCR
     253Part from an IRC channel.  If the -t option is present the unsubscription will
     254only be temporary, i.e., it will not be updated in the subscription file and
     255will therefore not be in effect the next time BarnOwl is started, or if the
     256connection is lost.
    233257END_DESCR
    234258        }
     
    327351This can be used to perform some operation not yet supported by
    328352BarnOwl, or to define new IRC commands.
     353END_DESCR
     354        }
     355    );
     356
     357    BarnOwl::new_command(
     358        'irc-loadchannels' => \&cmd_loadchannels,
     359        {
     360            summary => 'Reload persistent channels',
     361            usage   => 'irc-loadchannels [-a ALIAS] [<file>]',
     362
     363            description => <<END_DESCR
     364Load persistent channels from a file.  The file defaults to
     365\$HOME/.owl/$IRC_SUBS_FILENAME.  If the ALIAS is present, only channels
     366on the given alias are loaded.  The ALIAS is case-sensitive.
     367
     368Each line of the file should describe a single channel, in the format
     369'\$alias \$channel' (without quotes).
    329370END_DESCR
    330371        }
     
    341382######################## Owl command handlers ##################################
    342383################################################################################
     384
     385sub make_autoconnect_filename {
     386    # can't use ||, or else we'll treat '0' as invalid.  We could check for eq "" ...
     387    # TODO(jgross): When we move to requiring perl 5.10, combine the
     388    # following two lines using //
     389    my $filename = shift;
     390    $filename = File::Spec->catfile(BarnOwl::get_config_dir(), $IRC_SUBS_FILENAME) unless defined $filename;
     391    if (!File::Spec->file_name_is_absolute($filename)) {
     392        $filename = File::Spec->catfile($ENV{HOME}, $filename);
     393    }
     394    return $filename;
     395}
     396
     397sub _get_autoconnect_lines {
     398    my $filename = shift;
     399
     400    # TODO(jgross): Write a C-side function to do this, asynchronously;
     401    #               AIUI, perl doesn't do asynchronous I/O in any useful way
     402    if (open (my $subsfile, "<:encoding(UTF-8)", $filename)) {
     403        my @lines = <$subsfile>;
     404        close($subsfile);
     405
     406        # strip trailing newlines
     407        local $/ = "";
     408        chomp(@lines);
     409
     410        return @lines;
     411    }
     412
     413    return ();
     414}
     415
     416sub get_autoconnect_channels {
     417    my $filename = make_autoconnect_filename(shift);
     418    my %channel_hash = ();
     419
     420    # Load the subs from the file
     421    my @lines = _get_autoconnect_lines($filename);
     422
     423    foreach my $line (@lines) {
     424        my @parsed_args = split(' ', $line);
     425        if (scalar @parsed_args == 2) {
     426            push @{$channel_hash{$parsed_args[0]}}, $parsed_args[1];
     427        } else {
     428            warn "Trouble parsing irc configuration file '$filename' line '$line'; the format is '\$alias \$channel', with no spaces in either\n";
     429        }
     430    }
     431
     432    return %channel_hash;
     433}
     434
     435sub add_autoconnect_channel {
     436    my $conn = shift;
     437    my $channel = shift;
     438    my $alias = $conn->alias;
     439    my $filename = make_autoconnect_filename(shift);
     440
     441    # we already checked for spaces in $channel in cmd_join, but we still need
     442    # to check $alias
     443    die "Alias name '$alias' contains a space; parsing will fail.  Use the -t flag.\n" unless index($alias, " ") == -1;
     444
     445    my $line = "$alias $channel";
     446
     447    my @lines = _get_autoconnect_lines($filename);
     448
     449    # We don't want to be noisy about duplicated joins.  For example, some
     450    # people might have :irc-join in startup files, even though that doesn't
     451    # work correctly anymore because connect is asynchronous and so join on
     452    # startup races with connect.  Regardless, just fail silently if the line
     453    # already exists.
     454    return if grep { $_ eq $line } @lines;
     455
     456    open (my $subsfile, ">>:encoding(UTF-8)", make_autoconnect_filename($filename))
     457        or die "Cannot open $filename for writing: $!\n";
     458    local $, = "";
     459    local $/ = "";
     460    print $subsfile "$line\n";
     461    close($subsfile);
     462}
     463
     464sub remove_autoconnect_channel {
     465    my $conn = shift;
     466    my $channel = shift;
     467    my $alias = $conn->alias;
     468    my $filename = make_autoconnect_filename(shift);
     469
     470    BarnOwl::Internal::file_deleteline($filename, "$alias $channel", 1);
     471}
     472
     473sub cmd_loadchannels {
     474    my $cmd = shift;
     475    my $alias;
     476    my $getopt = Getopt::Long::Parser->new;
     477
     478    local @ARGV = @_;
     479    $getopt->configure(qw(pass_through permute no_getopt_compat prefix_pattern=-|--));
     480    $getopt->getoptions("alias=s" => \$alias);
     481
     482    my %channel_hash = get_autoconnect_channels(@ARGV);
     483
     484    my $aliases = (defined $alias) ? [$alias] : [keys %channel_hash];
     485
     486    foreach my $cur_alias (@$aliases) {
     487        # get_connection_by_alias might die, and we don't want to
     488        eval {
     489            my $conn = get_connection_by_alias($cur_alias, 1);
     490            my %existing_channels = map { $_ => 1 } @{$conn->autoconnect_channels}, @{$channel_hash{$cur_alias}};
     491            $conn->autoconnect_channels([keys %existing_channels]);
     492        };
     493        foreach my $channel (@{$channel_hash{$cur_alias}}) {
     494            if ($cur_alias eq "") {
     495                BarnOwl::command("irc-join", "-t", $channel);
     496            } else {
     497                BarnOwl::command("irc-join", "-t", "-a", $cur_alias, $channel);
     498            }
     499        }
     500    }
     501}
    343502
    344503sub cmd_connect {
     
    378537    }
    379538
     539    my %channel_hash = get_autoconnect_channels;
     540
    380541    my $conn = BarnOwl::Module::IRC::Connection->new($alias, $host, $port, {
    381         nick      => $nick,
    382         user      => $username,
    383         real      => $ircname,
    384         password  => $password,
    385         SSL       => $ssl,
    386         timeout   => sub {0}
     542        nick                 => $nick,
     543        user                 => $username,
     544        real                 => $ircname,
     545        password             => $password,
     546        SSL                  => $ssl,
     547        timeout              => sub {0},
     548        autoconnect_channels => $channel_hash{$alias}
    387549       });
    388550    $ircnets{$alias} = $conn;
     
    427589    @msgs = split "\n\n", $fullbody;
    428590    map { tr/\n/ / } @msgs;
     591    # split each body at irc:max-message-length characters, if that number
     592    # is positive.  Only split at space boundaries.  Start counting a-fresh
     593    # at the beginning of each paragraph
     594    my $max_len = BarnOwl::getvar('irc:max-message-length');
     595    if ($max_len > 0) {
     596        local($Text::Wrap::columns) = $max_len;
     597        @msgs = split "\n", wrap("", "", join "\n", @msgs);
     598    }
    429599    for my $body (@msgs) {
    430600        if ($body =~ /^\/me (.*)/) {
     
    463633sub cmd_join {
    464634    my $cmd = shift;
    465     my $conn = shift;
    466     my $chan = shift or die("Usage: $cmd channel\n");
    467     $conn->conn->send_msg(join => $chan, @_);
     635    my $is_temporary;
     636
     637    my $getopt = Getopt::Long::Parser->new;
     638
     639    local @ARGV = @_;
     640    $getopt->configure(qw(pass_through permute no_getopt_compat prefix_pattern=-|--));
     641    $getopt->getoptions("temporary" => \$is_temporary);
     642
     643    my $conn = shift @ARGV;
     644    my $chan = shift @ARGV or die("Usage: $cmd channel\n");
     645
     646    die "Channel name '$chan' contains a space.  As per RFC 2812, IRC channel names may not contain spaces.\n" unless index($chan, " ") == -1;
     647
     648    $conn->conn->send_msg(join => $chan, @ARGV);
     649
     650    # regardless of whether or not this is temporary, we want to persist it
     651    # across reconnects.
     652
     653    # check if the channel is already in the list
     654    if (!grep { $_ eq $chan } @{$conn->autoconnect_channels}) {
     655        push @{$conn->autoconnect_channels}, $chan;
     656    }
     657
     658    if (!$is_temporary) {
     659        # add the line to the subs file
     660        add_autoconnect_channel($conn, $chan);
     661    }
     662
    468663    return;
    469664}
     
    471666sub cmd_part {
    472667    my $cmd = shift;
    473     my $conn = shift;
    474     my $chan = shift;
     668    my $is_temporary;
     669
     670    my $getopt = Getopt::Long::Parser->new;
     671
     672    local @ARGV = @_;
     673    $getopt->configure(qw(pass_through permute no_getopt_compat prefix_pattern=-|--));
     674    $getopt->getoptions("temporary" => \$is_temporary);
     675
     676    my $conn = shift @ARGV;
     677    my $chan = shift @ARGV or die("Usage: $cmd channel\n");
     678
    475679    $conn->conn->send_msg(part => $chan);
     680
     681    # regardless of whether or not this is temporary, we want to persist it
     682    # across reconnects
     683    my %existing_channels = map { $_ => 1 } @{$conn->autoconnect_channels};
     684    delete $existing_channels{$chan};
     685    $conn->autoconnect_channels([keys %existing_channels]);
     686
     687    if (!$is_temporary) {
     688        # remove the line from the subs file
     689        remove_autoconnect_channel($conn, $chan);
     690    }
     691
    476692    return;
    477693}
     
    573789        my $alias;
    574790        my $channel;
     791        my $is_temporary;
    575792        my $getopt = Getopt::Long::Parser->new;
    576793        my $m = BarnOwl::getcurmsg();
     
    578795        local @ARGV = @_;
    579796        $getopt->configure(qw(pass_through permute no_getopt_compat prefix_pattern=-|--));
    580         $getopt->getoptions("alias=s" => \$alias);
     797        $getopt->getoptions("alias=s" => \$alias,
     798                            "temporary" => \$is_temporary);
    581799
    582800        if(defined($alias)) {
     
    591809                    $conn ||= $c;
    592810                }
     811            } elsif (defined($channel) && ($flags & CHANNEL_OR_USER)) {
     812                shift @ARGV;
    593813            } elsif ($m && $m->type eq 'IRC' && !$m->is_private) {
    594814                $channel = $m->channel;
     
    598818        }
    599819
    600         if(!$channel &&
     820        if(!defined($channel) &&
    601821           ($flags & CHANNEL_ARG) &&
    602822           !($flags & CHANNEL_OPTIONAL)) {
     
    615835            die("You must specify an IRC network using -a.\n");
    616836        }
     837        push @ARGV, "-t" if $is_temporary;
    617838        if($flags & CHANNEL_ARG) {
    618839            $sub->($cmd, $conn, $channel, @ARGV);
Note: See TracChangeset for help on using the changeset viewer.