Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 322 lines (259 sloc) 8.42 kb
1e6d50b @msparks Adding automode.pl, version 1.3.
authored
1 # automode.pl
2 #
3 # Passively learn and actively maintain the ops/voices/halfops in channels.
4 # This is a no-maintenance auto-op/auto-voice script for irssi.
5 #
6 # INSTALL:
7 # 1) /script load automode.pl
8 # 2) Be a channel operator
9 #
10 # HOW IT WORKS:
11 # When someone joins a channel and is given ops/voice/halfop, the script
12 # will record that user's mask, as a combination of their nickname and part
13 # of their hostname or IP address. When that person leaves and rejoins, the
14 # script will check against its database and regrant the user the modes
15 # he or she had before leaving.
16 #
17 # If a user is kicked from a channel, all modes for that person are removed.
18 # They must therefore be regiven by another operator manually. Note this.
19 #
20 # Also, this script relies on the "chatnet" attribute being set for a
21 # particular connection. Use /network (or /ircnet) to set up your networks
22 # and such. This script will spit out (lots of) warnings if the chatnet is
23 # not set.
24 #
25 # IGNORING CHANNELS:
26 # If you do not wish to maintain modes on a channel, add it to the setting
27 # "automode_ignore" in the form <tag>:<channel>, separated by spaces.
28 #
29 # For example: /set automode_ignore FreeNode:#perl EFnet:#irssi
30 # (should) not maintain modes in #perl on FreeNode or #irssi
31 # on EFnet, provided FreeNode and EFnet are the tags for those
32 # connections.
33 #
34 # NOTES:
35 # The Perl module Data::Serializer is needed for this script.
36 # The database file is not written instantaneously; it is on a timer and is
37 # written to every five minutes or so. If the script is reloaded before it has
38 # had a chance to save will result in forgotten modes.
39 #
40 use strict;
41 use Irssi;
42 use Data::Serializer;
43 use Data::Dumper;
44
45 use vars qw($VERSION %IRSSI);
46
47 $VERSION = '1.3';
48 %IRSSI = (
49 authors => 'Matt "f0rked" Sparks',
50 contact => 'ms+irssi@quadpoint.org',
51 name => 'automode',
52 description => 'Mode maintainer',
53 license => 'BSD',
54 url => 'http://quadpoint.org',
55 changed => '2008-06-14',
56 );
57
58 # show debug lines
59 my $debug = 0;
60
61 my $s = new Data::Serializer;
62 my $file = Irssi::get_irssi_dir."/automode_list";
63
64 if (!-e $file) {
65 print "[automode] creating $file";
66 system("touch $file");
67 }
68
69 my $listref = $s->retrieve($file);
70 my %list = $listref ? %{$listref} : ();
71
72 #print Dumper %list;
73
74 my $save_tag;
75 my %buffer_tags;
76 my %buffer;
77
78
79 sub save_list
80 {
81 $s->store(\%list,$file);
82 }
83
84
85 sub clear_list
86 {
87 %list = ();
88 }
89
90
91 sub make_mask
92 {
93 my($address) = @_;
94 return if !$address;
95 my($ident, $host) = split /@/, $address;
96 my @split = split /\./, $host;
97
98 if (@split <= 2) {
99 # host is something like "foo.com". We cannot make the mask *.com.
100 } else {
101 if ($split[$#split] =~ /^\d+$/) {
102 # Looks like an IP address.
103 pop @split;
104 $host = join(".", @split) . ".\d{1,3}";
105 } else {
106 # Mask the first segment.
107 shift @split;
108 $host = ".+?." . join(".", @split);
109 }
110 }
111
112 return ".+?!.*${ident}@" . "${host}";
113 }
114
115
116 sub show
117 {
118 my($net, $channel) = @_;
119 print Dumper %{$list{$net}->{$channel}};
120 }
121
122
123 sub show_all
124 {
125 my $list;
126 print Dumper %list;
127 }
128
129
130 sub clear_channel
131 {
132 my($net, $channel) = @_;
133 delete $list{$net}->{$channel};
134 }
135
136
137 sub set_modes
138 {
139 my($net, $channel) = @{$_[0]};
140 return if !$buffer{$net}->{$channel};
141
142 my($nicks, $modes) = values(%{$buffer{$net}->{$channel}});
143 print "[automode] modes: $modes, nicks: $nicks" if $debug;
144 my $c = Irssi::server_find_chatnet($net)->channel_find($channel);
145
146 # iterate through the modes and see which ones we don't have to set
147 my($final_modes, $final_nicks);
148
149 my $i = 0;
150 for (split //,$modes) {
151 my $m = $_;
152 my $n = (split / /, $nicks)[$i];
153 $i++;
154
155 next if (!$c->nick_find($n));
156 next if ($m eq "o" && $c->nick_find($n)->{"op"});
157 next if ($m eq "v" && $c->nick_find($n)->{"voice"});
158 next if ($m eq "h" && $c->nick_find($n)->{"halfop"});
159
160 # if we made it this far, add this to the final modes
161 $final_modes .= $m;
162 $final_nicks .= "$n ";
163 }
164
165 print "[automode] final modes: +$final_modes $final_nicks" if $debug;
166
167 $c->command("MODE $channel +$final_modes $final_nicks")
168 if ($final_modes && $final_nicks);
169 delete $buffer{$net}->{$channel};
170 }
171
172
173 sub mode2letter
174 {
175 my($mode) = @_;
176 if ($mode eq "@") {
177 return "o";
178 } elsif ($mode eq "+") {
179 return "v";
180 } elsif ($mode eq "%") {
181 return "h";
182 }
183 return -1;
184 }
185
186
187 sub remove_mode
188 {
189 my($net, $channel, $mask, $mode) = @_;
190 my $letter = mode2letter($mode);
191 $list{$net}->{$channel}->{$mask} =~ s/$letter//
192 if user_modes($net, $channel, $mask);
193 delete $list{$net}->{$channel}->{$mask}
194 if exists $list{$net}->{$channel}->{$mask}
195 and !$list{$net}->{$channel}->{$mask};
196 }
197
198
199 sub remove_all
200 {
201 my($net, $channel, $mask) = @_;
202 delete $list{$net}->{$channel}->{$mask}
203 if exists $list{$net}->{$channel}->{$mask};
204 }
205
206
207 sub user_modes
208 {
209 my($net, $channel, $mask) = @_;
210 return $list{$net}->{$channel}->{$mask};
211 }
212
213
214 sub add_mode
215 {
216 my($net, $channel, $mask, $mode) = @_;
217 return if !$mask or !$net or !$channel or !$mode;
218
219 my $letter = mode2letter($mode);
220 $list{$net}->{$channel}->{$mask} .= $letter
221 if $list{$net}->{$channel}->{$mask} !~ /$letter/;
222
223 Irssi::timeout_remove($save_tag);
224 $save_tag = Irssi::timeout_add_once(300, "save_list", []);
225 }
226
227
228 sub event_mode
229 {
230 my($channel, $nick, $setby, $mode, $type) = @_;
231 return if check_ignore($channel->{server}, $channel->{name});
232 my $w = Irssi::active_win;
233 return if $mode != '@' and $mode != '%' and $mode != '+';
234
235 my $chatnet = $channel->{server}->{chatnet};
236 my $tag = $channel->{server}->{tag};
237 print ("[automode] The 'chatnet' attribute is missing for the tag '$tag'. " .
238 "Use /network (or /ircnet) to properly manage this.") if !$chatnet;
239 return if !$chatnet;
240
241 my $mask = make_mask($nick->{host});
242 print "[automode] failed to make mask ($mask)" if (!$mask && $debug);
243 return if !$mask;
244
245 if ($type eq "+") {
246 print ("[automode] adding mode '$mode' for $mask in $channel->{name} on " .
247 $chatnet) if $debug;
248 add_mode($chatnet, $channel->{name}, $mask, $mode);
249 } else {
250 # don't remove op if they deop themselves.
251 return if $setby eq $nick->{nick};
252 print ("[automode] removing mode '$mode' for $mask in $channel->{name} " .
253 " on $chatnet") if $debug;
254 remove_mode($chatnet, $channel->{name}, $mask, $mode);
255 }
256
257 #show($chatnet, $channel->{name});
258 }
259
260
261 sub event_join
262 {
263 my($server, $channel, $nick, $address) = @_;
264 return if check_ignore($server, $channel);
265 my $mask = make_mask($address);
266 return if !user_modes($server->{chatnet}, $channel, $mask);
267 my $c = $server->channel_find($channel);
268 return if not $c->{chanop};
269
270 if (my $modes = user_modes($server->{chatnet}, $channel, $mask)) {
271 print "[automode] Matched mask ($mask) with modes: $modes" if $debug;
272 my $nick_list = "$nick " x length($modes);
273 my %buf = ($buffer{$server->{chatnet}}->{$channel} ?
274 %{$buffer{$server->{chatnet}}->{$channel}} : ());
275 $buf{modes} .= $modes;
276 $buf{nicks} .= $nick_list;
277 $buffer{$server->{chatnet}}->{$channel} = \%buf;
278 my $tag = $server->{chatnet} . "_$channel";
279 Irssi::timeout_remove($buffer_tags{$tag});
280 $buffer_tags{$tag} = Irssi::timeout_add_once(1000 + int(rand(250) * 3),
281 "set_modes",
282 [$server->{chatnet},
283 $channel]);
284 #print Dumper %buffer;
285 }
286 }
287
288
289 sub event_kick
290 {
291 my($server, $channel, $nick, $kicker, $address, $reason) = @_;
292 my $n = $server->channel_find($channel)->nick_find($nick);
293 #print Dumper $n;
294 my $mask = make_mask($n->{host});
295 remove_all($server->{chatnet}, $channel, $mask) if $mask;
296 }
297
298
299 sub check_ignore
300 {
301 my($server, $channel) = @_;
302 my $chatnet = $server->{chatnet};
303 my $ignore = Irssi::settings_get_str("automode_ignore") . " ";
304 return ($ignore =~ /$chatnet:$channel /i) ? 1 : 0;
305 }
306
307
308 # I don't think this does what I want it to do.
309 sub event_exit
310 {
311 save_list;
312 }
313
314
315 Irssi::signal_add("gui exit", "event_exit");
316
317 Irssi::signal_add("message kick", "event_kick");
318 Irssi::signal_add("message join", "event_join");
319 Irssi::signal_add("nick mode changed", "event_mode");
320
321 Irssi::settings_add_str("automode", "automode_ignore", "IM:&bitlbee");
Something went wrong with that request. Please try again.