1#!/usr/local/bin/perl -w
2
3## Bugreports and Licence disclaimer.
4#
5# For bugreports and other improvements contact Geert Hauwaerts <geert@irssi.org>
6#
7#    This program is free software; you can redistribute it and/or modify
8#    it under the terms of the GNU General Public License as published by
9#    the Free Software Foundation; either version 2 of the License, or
10#    (at your option) any later version.
11#
12#    This program is distributed in the hope that it will be useful,
13#    but WITHOUT ANY WARRANTY; without even the implied warranty of
14#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15#    GNU General Public License for more details.
16#
17#    You should have received a copy of the GNU General Public License
18#    along with this script; if not, write to the Free Software
19#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20#
21##
22
23use strict;
24use Irssi;
25use vars qw($VERSION %IRSSI);
26
27$VERSION = '0.02';
28
29%IRSSI = (
30    authors     => 'Geert Hauwaerts',
31    contact     => 'geert@irssi.org',
32    name        => 'invitejoin.pl',
33    description => 'This script will join a channel if somebody invites you to it.',
34    license     => 'Public Domain',
35    url         => 'https://github.com/irssi/scripts.irssi.org/blob/master/scripts/invitejoin.pl',
36    changed     => 'Di 17. Jan 19:32:45 CET 2017',
37);
38
39my $help = <<EOF;
40
41/SET    invitejoin 0|1
42/TOGGLE invitejoin
43          Description: If this setting is turned on, you will join the channel
44          when invited to.
45
46Default is to follow every invite, you can specify a list of allowed nicks.
47
48/INVITEJOIN [addnick <ircnet> <nick>]
49            [delnick <ircnet> <nick>]
50            [listnick]
51            [help]
52
53addnick:     Add a new nickname on the given net as allowed autoinvite source.
54delnick:     Delete a nickname from the allowed list.
55listnick:    Display the contents of the allowed nickname list.
56help:        Display this useful little helptext.
57
58Examples: (all on one line)
59/INVITEJOIN addnick Freenode ChanServ
60
61Note: This script doesn't allow wildcards
62EOF
63
64my @allowed_nicks = ();
65my $allowed_nicks_file = "invitejoin.nicks";
66
67my $irssidir = Irssi::get_irssi_dir();
68
69Irssi::theme_register([
70    'invitejoin_usage', '%R>>%n %_Invitejoin:%_ Insufficient parameters: Use "%_/INVITEJOIN help%_" for further instructions.',
71    'invitejoin_help', '$0',
72    'invitejoin_loaded', '%R>>%n %_Scriptinfo:%_ Loaded $0 version $1 by $2.',
73    'invitejoin_invited', '%R>>%n %_Invitejoin:%_ Joined $1 (Invited by $0).',
74    'invitejoin_usage_add_nick', '%R>>%n %_Invitejoin:%_ Insufficient parameters: Usage "%_/INVITEJOIN addnick ircnet ChanServ%_".',
75    'invitejoin_no_net', '%R>>%n %_Invitejoin:%_ Unknown Irssi ircnet %_$0%_.',
76    'saved_nick', '%R>>%n %_Invitejoin:%_ Added allowed nick "%_$1%_" on %_$0%_.',
77    'nick_already_present', '%R>>%n %_Invitejoin:%_ Nick already present.',
78    'invitejoin_delusage', '%R>>%n %_Invitejoin:%_ Insufficient parameters: Usage "%_/INVITEJOIN delnick ircnet nick%_".',
79    'invitejoin_delled', '%R>>%n %_Invitejoin:%_ Deleted %_$1%_ on %_$0%_ from allowed list.',
80    'invitejoin_nfound', '%R>>%n %_Invitejoin:%_ The nick %_$1%_ on %_$0%_ could not be found.',
81    'allowed_nicks_info', '%_Ircnet             Nick%_',
82    'allowed_nicks_empty', '%R>>%n %_Invitejoin:%_ Your allowed nick list is empty. All invites will be followed.',
83    'allowed_nicks_print', '$[18]0 $1',
84    'invite_denied', '%R>>%n %_Invitejoin:%_ Invite from nick %_$1%_ on %_$0%_ to %_$2%_ not followed because it is not in the allowed list.',
85]);
86
87sub load_allowed_nicks {
88    my ($file) = @_;
89
90    @allowed_nicks = load_file($file, sub {
91        my $new_allowed = new_allowed_nick(@_);
92
93        return undef if ($new_allowed->{net} eq '' || $new_allowed->{nick} eq '');
94        return $new_allowed;
95    });
96}
97
98sub save_allowed_nicks {
99    my ($file) = @_;
100    save_file($file, \@allowed_nicks, \&allowed_nick_to_list);
101}
102
103sub allowed_nick_to_list {
104    my $allowed_nick = shift;
105
106    return (
107        $allowed_nick->{net},
108        $allowed_nick->{nick}
109    );
110}
111
112sub new_allowed_nick {
113    return {
114        net   => shift,
115        nick  => shift
116    };
117}
118
119# file: filename to be read
120# parse_line_fn: receives array of entries of a single line as input, should
121#     return parsed data object or undef in the data is incomplete
122# returns: parsed data array
123sub load_file {
124    my ($file, $parse_line_fn) = @_;
125    my @parsed_data = ();
126
127    if (-e $file) {
128        open(my $fh, "<", $file);
129        local $/ = "\n";
130
131        while (<$fh>) {
132            chomp;
133            my $data = $parse_line_fn->(split("\t"));
134            push(@parsed_data, $data) if $data;
135        }
136
137        close($fh);
138    }
139
140    return @parsed_data;
141}
142
143# file: filename to be written, is created accessable only by the user
144# data_ref: array ref of data entries
145# serialize_fn: receives a data reference and should return an array or tuples
146#     for that data that will be serialized into one line
147sub save_file {
148    my ($file, $data_ref, $serialize_fn) = @_;
149
150    create_private_file($file) unless -e $file;
151
152    open(my $fh, ">", $file) or die "Can't create $file. Reason: $!";
153
154    for my $data (@$data_ref) {
155        print($fh join("\t", $serialize_fn->($data)), "\n");
156    }
157
158    close($fh);
159}
160
161sub create_private_file {
162    my ($file) = @_;
163    my $umask = umask 0077; # save old umask
164    open(my $fh, ">", $file) or die "Can't create $file. Reason: $!";
165    close($fh);
166    umask $umask;
167}
168
169sub add_allowed_nick {
170    my ($network, $nick) = split(" ", $_[0], 2);
171    my ($correct_net);
172
173    if ($network eq '' || $nick eq '') {
174        Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'invitejoin_usage_add_nick');
175        return;
176    }
177
178    if ($network) {
179        my ($ircnet) = Irssi::chatnet_find($network);
180        if (!$ircnet) {
181            Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'invitejoin_no_net', $network);
182            return;
183        } else {
184            $correct_net = 1;
185        }
186    }
187
188    if ($correct_net && $nick) {
189        if (is_nick_in_list($network, $nick)) {
190            Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'nick_already_present');
191            return;
192        }
193
194        push(@allowed_nicks, new_allowed_nick($network, $nick));
195        save_allowed_nicks("$irssidir/$allowed_nicks_file");
196
197        Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'saved_nick', $network, $nick);
198    }
199}
200
201sub del_allowed_nick {
202    my ($ircnet, $nick) = split(" ", $_[0], 2);
203
204    if ($ircnet eq '' || $nick eq '') {
205        Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'invitejoin_delusage');
206        return;
207    }
208
209    my $size_before = scalar(@allowed_nicks);
210    @allowed_nicks = grep { ! ($_->{net} eq $ircnet && $_->{nick} eq $nick) } @allowed_nicks;
211    my $size_after = scalar(@allowed_nicks);
212
213    if ($size_after != $size_before) {
214        Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'invitejoin_delled', $ircnet, $nick);
215        save_allowed_nicks("$irssidir/$allowed_nicks_file");
216    } else {
217        Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'invitejoin_nfound', $ircnet, $nick);
218    }
219
220    if ($size_after == 0) {
221        Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'allowed_nicks_empty');
222    }
223}
224
225sub list_allowed_nicks {
226    if (@allowed_nicks == 0) {
227        Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'allowed_nicks_empty');
228    } else {
229        Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'allowed_nicks_info');
230
231        for my $allowed (@allowed_nicks) {
232            Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'allowed_nicks_print', $allowed->{net}, $allowed->{nick});
233        }
234    }
235}
236
237sub invitejoin_runsub {
238    my ($data, $server, $item) = @_;
239    $data =~ s/\s+$//g;
240
241    if ($data) {
242        Irssi::command_runsub('invitejoin', $data, $server, $item);
243    } else {
244        Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'invitejoin_usage');
245    }
246}
247
248sub is_nick_in_list {
249    my ($net, $nick) = @_;
250
251    return (grep {
252        $_->{net}  eq $net &&
253        $_->{nick} eq $nick
254    } @allowed_nicks) > 0;
255}
256
257sub is_allowed_nick {
258    my ($net, $nick) = @_;
259
260    # If no allowed nicks are specified (initial configuration) accept
261    # all invite requests.
262    # # (This mimics previous behavior of this script
263    # before there was an allowed list)
264    return 1 if @allowed_nicks == 0;
265
266    return is_nick_in_list($net, $nick);
267}
268
269sub invitejoin {
270    my ($server, $channel, $nick, $address) = @_;
271    my $invitejoin = Irssi::settings_get_bool('invitejoin');
272
273    if ($invitejoin) {
274        if (is_allowed_nick($server->{tag}, $nick)) {
275            $server->command("join $channel");
276
277            Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'invitejoin_invited', $nick, $channel);
278            Irssi::signal_stop();
279        }
280        else {
281            Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'invite_denied', $server->{tag}, $nick, $channel);
282        }
283    }
284}
285
286Irssi::signal_add('message invite', 'invitejoin');
287
288Irssi::settings_add_bool('invitejoin', 'invitejoin' => 1);
289
290load_allowed_nicks("$irssidir/$allowed_nicks_file");
291
292Irssi::command_bind('invitejoin',           'invitejoin_runsub');
293Irssi::command_bind('invitejoin addnick',   'add_allowed_nick');
294Irssi::command_bind('invitejoin delnick',   'del_allowed_nick');
295Irssi::command_bind('invitejoin listnick',  'list_allowed_nicks');
296Irssi::command_bind('invitejoin help' => sub { Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'invitejoin_help', $help) });
297
298Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'invitejoin_loaded', $IRSSI{name}, $VERSION, $IRSSI{authors});
299