html/modelist-r.pl
1 # $Id: modelist-r.pl,v 0.8.0-rc4 2004/11/04 19:56 derwan Exp $
2 #
3 # This script creates cache of channel invites, ban exceptions and reops.
4 # Reop list is included only in ircd >= 2.11.0 (in IRCnet) - for other servers
5 # and networks use modelist.pl ( http://derwan.irssi.pl/modelist.pl).
6 #
7 # Script commands:
8 # /si - shows channel invites
9 # /se - shows ban exception
10 # /sr - shows reop list
11 #
12 # /uninvite [<index and masks separated with spaces>]
13 # - removes the specified invite(s) from the channel
14 # /unexcept [<index and masks separated with spaces>]
15 # - removes the specified ban exception(s) from the channel
16 # /unreop [<index and masks separated with spaces>]
17 # - removes the specified reop(s) from the channel
18 #
19 # Examples:
20 # /si
21 # /uninvite 1
22 # /unexcept *!*@127.0.0.1
23 # /unreop 1 *!*@127.0.0.1 5
24 #
25 # After loading modelist-r.pl run command
26 # /statusbar window add -priority 0 -after usercount modelist
27 #
28 # You can customize the look of this item from theme file:
29 # sb_modelist = "{sb $0 modes ($1-)}";
30 # sb_ml_b = "b/%r$*%n"; # bans
31 # sb_ml_e = "e/%c$*%n"; # ban exceptions
32 # sb_ml_I = "I/%G$*%n"; # invites
33 # sb_ml_R = "R/%R$*%n"; # reops
34 # sb_ml_space = " "; # separator
35 #
36 # Theme formats:
37 # modelist $0 - index, $1 - channel, $2 - hostmask, 3 - mode
38 # modelist_long $4 - nick, $5 - time
39 # modelist_empty $0 - channel, $1 - mode
40 # modelist_chan_not_synced $0 - channel
41 # modelist_not_joined
42 # modelist_server_version $0 - version
43 #
44
45 use strict;
46 use vars ('$VERSION', '%IRSSI');
47
48 use Irssi 20020600 ();
49 use Irssi::Irc;
50 use Irssi::TextUI;
51
52 $VERSION = '0.8.0-rc4';
53 %IRSSI =
54 (
55 'authors' => 'Marcin Rozycki',
56 'contact' => 'derwan@irssi.pl',
57 'name' => 'modelist-r',
58 'description' => 'Cache of invites, ban exceptions and reops in channel. Script commands: '.
59 '/si, /se, /sr, /unexcept, /uninvite, /unreop (version only for ircd >= 2.11.0).',
60 'license' => 'GNU GPL v2',
61 'modules' => '',
62 'url' => 'http://derwan.irssi.pl',
63 'changed' => 'Thu Nov 4 17:56:17 2004',
64 );
65
66 Irssi::theme_register
67 ([
68 # $0 - index, $1 - channel name, $2 - hostmask, $3 - mode (invite, ban exception, reop)
69 'modelist', '$0 - {channel $1}: $3 {ban $2}',
70 # $0 - index, $1 - channel name, $2 - hostmask, $3 - mode, $4 - nick, $5 - time
71 'modelist_long', '$0 - {channel $1}: $3 {ban $2} {comment by {nick $4}, $5 secs ago}',
72 # $0 - channel name, $1 - mode
73 'modelist_empty', 'No $1s in channel {channel $0}',
74 # $0 - channel name
75 'modelist_chan_not_synced', 'Channel not fully synchronized yet, try again after a while',
76 # $0 - channel name
77 'modelist_chan_no_modes', 'Channel {channel $0} doesn\'t support modes',
78 'modelist_not_joined', 'Not joined to any channel',
79 # $0 - version
80 'modelist_server_version', 'This script working only in ircd {hilight >= 2.11.0} with reop list {comment active ircd $0}'
81 ]);
82
83 # $modelist{str servertag}->{lc str channel}->{str mode} = [ $moderec, ... ]
84 # $moderec = [ str hostmask, str nick, str time ]
85 my %modelist = ();
86
87 # $synced{str servertag}->{lc str channel} = int synced
88 my %synced = ();
89
90 # $visible{str mode} = str list
91 my %visible =
92 (
93 'e' => 'ban exception',
94 'I' => 'invite',
95 'R' => 'reop'
96 );
97
98 # $sb->{str mode} = int modes
99 my $sb = {};
100
101 # server redirections:
102 # 'modelist I' ( 346, 347, 403, 442, 472, 479, 482)
103 # 'modelist e' ( 348, 349, 403, 442, 472, 479, 482)
104 # 'modelist R' ( 344, 345, 403, 442, 472, 479, 482)
105 Irssi::Irc::Server::redirect_register('modelist I', 0, 0, { 'event 346' => 1 }, {
106 'event 347' => 1, # end of channel invite list
107 'event 403' => 1, # no such channel
108 'event 442' => 1, # you're not on that channel
109 'event 472' => 1, # unknown mode
110 'event 479' => 1, # illegal channel name
111 'event 482' => 1 # you're not channel operator
112 }, undef );
113
114 Irssi::Irc::Server::redirect_register('modelist e', 0, 0, { 'event 348' => 1 }, {
115 'event 349' => 1, # end of channel exception list
116 'event 403' => 1,
117 'event 442' => 1,
118 'event 472' => 1,
119 'event 479' => 1,
120 'event 482' => 1
121 }, undef );
122
123 Irssi::Irc::Server::redirect_register('modelist R', 0, 0, { 'event 344' => 1 }, {
124 'event 345' => 1, # end of channel reop list
125 'event 403' => 1,
126 'event 442' => 1,
127 'event 472' => 1,
128 'event 479' => 1,
129 'event 482' => 1
130 }, undef );
131
132 # create_channel (rec channel, int sync)
133 sub create_channel ($;$)
134 {
135 destroy_channel($_[0]);
136 sb_update();
137
138 my ($server, $tag, $channel) = ($_[0]->{server}, $_[0]->{server}->{tag}, lc $_[0]->{name});
139
140
141 if ( !test_version($server) or $_[0]->{no_modes} )
142 {
143 $synced{$tag}->{$channel} = 1;
144 return;
145 }
146 $synced{$tag}->{$channel} = ( defined $_[1] ) ? $_[1] : 0;
147
148 $modelist{$tag}->{$channel}->{I} = [];
149 $server->redirect_event('modelist I', 1, $channel, 0, undef, {
150 'event 346' => 'redir modelist invite',
151 '' => 'event empty'
152 });
153 $server->send_raw(sprintf('mode %s +I', $channel));
154
155 $modelist{$tag}->{$channel}->{e} = [];
156 $server->redirect_event('modelist e', 1, $channel, 0, undef, {
157 'event 348' => 'redir modelist except',
158 '' => 'event empty'
159 });
160 $server->send_raw(sprintf('mode %s +e', $channel));
161
162 $modelist{$tag}->{$channel}->{R} = [];
163 $server->redirect_event('modelist R', 1, $channel, 0, undef, {
164 'event 344' => 'redir modelist reop',
165 'event 345' => 'redir modelist sync',
166 'event 403' => 'redir modelist sync',
167 'event 442' => 'redir modelist sync',
168 'event 472' => 'redir modelist sync',
169 'event 479' => 'redir modelist sync',
170 'event 482' => 'redir modelist sync',
171 '' => 'event empty'
172 });
173 $server->send_raw(sprintf('mode %s +R', $channel));
174 }
175
176 # destroy_channel (rec channel)
177 sub destroy_channel ($)
178 {
179 my ($tag, $channel) = ($_[0]->{server}->{tag}, lc $_[0]->{name});
180 delete $synced{$tag}->{$channel};
181 delete $modelist{$tag}->{$channel};
182 sb_update();
183 }
184
185 # sig_redir_modelist (rec server, str data, str mode)
186 sub sig_redir_modelist ($$$)
187 {
188 my $chanrec = $_[0]->channel_find(((split(' ', $_[1], 3))[1]));
189 if ( ref $chanrec )
190 {
191 mode($chanrec, 1, $_[2], ((split(/ +/, $_[1], 4))[2]), undef);
192 }
193 }
194
195 # mode (rec channel, int type, str mode, str hostmask, str setby)
196 sub mode ($$$$$)
197 {
198 my $rec = get_list($_[0], $_[2]);
199 if ( ref $rec and $_[1] eq 1 )
200 {
201 push @{$rec}, [ $_[3], $_[4], time ];
202 }
203 elsif ( ref $rec and $_[1] eq 0 )
204 {
205 for ( my $idx = 0; $idx <= $#{$rec}; $idx++ )
206 {
207 if ( lc $rec->[$idx]->[0] eq lc $_[3] )
208 {
209 splice @{$rec}, $idx, 1;
210 last;
211 }
212 }
213 }
214 sb_update();
215 }
216
217 # sig_channel_sync (rec channel)
218 sub sig_channel_sync ($)
219 {
220 if ( ++$synced{$_[0]->{server}->{tag}}->{lc $_[0]->{name}} < 2 )
221 {
222 Irssi::signal_stop();
223 }
224 }
225
226 # sig_modelist_sync (rec server, str data)
227 sub sig_modelist_sync ($$)
228 {
229 my $chanrec = $_[0]->channel_find(((split(/ +/, $_[1], 3))[1]));
230 if ( ref $chanrec )
231 {
232 Irssi::signal_emit('channel sync', $chanrec);
233 sb_update();
234 }
235 }
236
237 # sig_message_irc_mode (rec server, str channel, str nick, str userhost, str mode)
238 sub sig_message_irc_mode ($$$$$)
239 {
240 my $chanrec = $_[0]->channel_find($_[1]);
241 unless ( ref $chanrec )
242 {
243 return;
244 }
245
246 my ($q, $mods, @a) = (1, split(/ +/, $_[4]));
247 foreach my $mod ( split('', $mods) )
248 {
249 ( $mod eq '+' ) and $q = 1, next;
250 ( $mod eq '-' ) and $q = 0, next;
251 my $a = ( rindex('beIkloRvhx', $mod) >= 0 && $q eq 1 or rindex('beIkoRvhx', $mod) >= 0 && $q eq 0 ) ? shift(@a) : undef;
252 if ( rindex('eIR', $mod) >= 0 )
253 {
254 mode($chanrec, $q, $mod, $a, $_[2]);
255 }
256 }
257 }
258
259 # get_list (rec channel, str mode), rec list
260 sub get_list ($$)
261 {
262 if ( ref $_[0] and defined $modelist{$_[0]->{server}->{tag}}->{lc $_[0]->{name}}->{$_[1]} )
263 {
264 return $modelist{$_[0]->{server}->{tag}}->{lc $_[0]->{name}}->{$_[1]};
265 }
266 }
267
268 # test_version (rec server), bool 0/1
269 sub test_version ($)
270 {
271 if ( $_[0] and ref $_[0] and $_[0]->{version} =~ m/^(\d+\.\d+)\./ and $1 >= 2.11 )
272 {
273 return 1;
274 }
275 return 0;
276 }
277
278
279 # test_channel (rec channel, bool quiet), bool 0/1
280 sub test_channel ($;$)
281 {
282 unless ( ref $_[0] and $_[0]->{type} eq 'CHANNEL' )
283 {
284 Irssi::printformat(MSGLEVEL_CRAP, 'modelist_not_joined') unless ( $_[1] );
285 return 0;
286 }
287 if ( $_[0]->{no_modes} )
288 {
289 $_[0]->printformat(MSGLEVEL_CRAP, 'modelist_chan_no_modes', $_[0]->{name}) unless ( $_[1] );
290 return 0;
291 }
292 if ( !test_version($_[0]->{server}) )
293 {
294 $_[0]->printformat(MSGLEVEL_CRAP, 'modelist_server_version', $_[0]->{server}->{version}) unless ( $_[1] );
295 return 0;
296
297 }
298 if ( $synced{$_[0]->{server}->{tag}}->{lc $_[0]->{name}} < 2 )
299 {
300 $_[0]->printformat(MSGLEVEL_CRAP, 'modelist_chan_not_synced', $_[0]->{name}) unless ( $_[1] );
301 return 0;
302 }
303 return 1;
304 }
305
306 # cmd_modelist_show (str mode)
307 sub cmd_modelist_show ($)
308 {
309 my $chanrec = Irssi::active_win() ? Irssi::active_win()->{active} : undef;
310 unless ( test_channel($chanrec) )
311 {
312 return;
313 }
314 my $rec = get_list($chanrec, $_[0]);
315 unless ( $#{$rec} >= 0 )
316 {
317 $chanrec->printformat
318 (
319 MSGLEVEL_CRAP, 'modelist_empty', $chanrec->{name}, $visible{$_[0]}
320 );
321 return;
322 }
323 for ( my $idx = 0; $idx <= $#{$rec}; $idx++ )
324 {
325 $chanrec->printformat
326 (
327 MSGLEVEL_CRAP, ( defined $rec->[$idx]->[1] ? 'modelist_long' : 'modelist'),
328 ($idx + 1), $chanrec->{name}, visible($rec->[$idx]->[0]), $visible{$_[0]},
329 $rec->[$idx]->[1], (time() - $rec->[$idx]->[2])
330 );
331 }
332 }
333
334 # cmd_modelist_del (str mode, str data)
335 sub cmd_modelist_del ($$)
336 {
337 my $chanrec = Irssi::active_win() ? Irssi::active_win()->{active} : undef;
338 unless ( test_channel($chanrec) )
339 {
340 return;
341 }
342 my ($rec, @m) = (get_list($chanrec, $_[0]));
343 foreach my $search ( split /[,;\s]+/, $_[1] )
344 {
345 if ( $search =~ m/^\d+$/ )
346 {
347 next unless ( $search-- and $search <= $#{$rec} );
348 $search = $rec->[$search]->[0];
349 }
350 push @m, $search;
351 }
352 if ( $#m >= 0 )
353 {
354 $chanrec->{server}->command(sprintf("mode %s -%s %s", $chanrec->{name}, $_[0] x scalar(@m), join(' ', @m)));
355 }
356 }
357
358 # visible (str data), str data
359 sub visible ($)
360 {
361 my $str = shift();
362 $str =~ tr/\240\002\003\037\026/\206\202\203\237\226/;
363 return $str;
364 }
365
366 # sb_update ()
367 sub sb_update ()
368 {
369 $sb->{b} = $sb->{e} = $sb->{I} = $sb->{R} = $sb->{T} = 0;
370
371 my $chanrec = Irssi::active_win() ? Irssi::active_win()->{active} : undef;
372 unless ( test_channel($chanrec, 1) )
373 {
374 return;
375 }
376
377 $sb->{b} = scalar @{[$chanrec->bans]};
378 $sb->{e} = scalar @{get_list($chanrec, 'e')};
379 $sb->{I} = scalar @{get_list($chanrec, 'I')};
380 $sb->{R} = scalar @{get_list($chanrec, 'R')};
381 $sb->{T} = $sb->{b} + $sb->{e} + $sb->{I} + $sb->{R};
382
383 Irssi::statusbar_items_redraw('modelist');
384 }
385
386 # sb_modelist(rec item, bool get_size_only)
387 # tahnks usercount.pl!
388 sub sb_modelist ($$)
389 {
390 unless ( $sb->{T} )
391 {
392 $_[0]->{min_size} = $_[0]->{max_size} = 0 if ( ref $_[0] );
393 return;
394 }
395
396 my $theme = Irssi::current_theme();
397 my $format = $theme->format_expand('{sb_modelist}');
398
399 if ( $format )
400 {
401 my ($str, $space) = ('', $theme->format_expand('{sb_ml_space}'));
402 foreach my $mod ( 'b', 'e', 'I', 'R' )
403 {
404 next unless ( $sb->{$mod} > 0 );
405 my $tmp = $theme->format_expand
406 (
407 sprintf('{sb_ml_%s %d}', $mod, $sb->{$mod}), Irssi::EXPAND_FLAG_IGNORE_EMPTY
408 );
409 $str .= $tmp . $space;
410 }
411 $str =~ s/\Q$space\E$//;
412 $format = $theme->format_expand
413 (
414 sprintf('{sb_modelist %d %s}', $sb->{T}, $str), Irssi::EXPAND_FLAG_IGNORE_REPLACES
415 );
416 }
417 else
418 {
419 my $str = undef;
420 foreach my $mod ( 'b', 'e', 'I', 'R' )
421 {
422 next unless ( $sb->{$mod} > 0 );
423 $str .= sprintf('%s%d ', $mod, $sb->{$mod})
424 }
425 chop($str);
426 $format = sprintf('{sb \%%_%d\%%_ modes ', $sb->{T});
427 $format .= sprintf('\%%c(\%%n%s\%%c)', $str) if ( $str );
428 }
429
430 $_[0]->default_handler($_[1], $format, undef, 1);
431 }
432
433 Irssi::signal_add_first('channel sync', 'sig_channel_sync');
434 Irssi::signal_add('channel joined' => sub { create_channel($_[0], 0) });
435 Irssi::signal_add('channel destroyed' => sub { destroy_channel($_[0]) });
436 Irssi::signal_add('redir modelist invite' => sub { sig_redir_modelist($_[0], $_[1], 'I'); });
437 Irssi::signal_add('redir modelist except' => sub { sig_redir_modelist($_[0], $_[1], 'e'); });
438 Irssi::signal_add('redir modelist reop' => sub { sig_redir_modelist($_[0], $_[1], 'R'); });
439 Irssi::signal_add('redir modelist sync', 'sig_modelist_sync');
440 Irssi::signal_add('message irc mode', 'sig_message_irc_mode');
441 Irssi::signal_add_last('ban new', 'sb_update');
442 Irssi::signal_add_last('ban remove', 'sb_update');
443 Irssi::signal_add_last('window changed', 'sb_update');
444 Irssi::signal_add_last('window item changed', 'sb_update');
445 Irssi::command_bind('si' => sub { cmd_modelist_show('I') });
446 Irssi::command_bind('se' => sub { cmd_modelist_show('e') });
447 Irssi::command_bind('sr' => sub { cmd_modelist_show('R') });
448 Irssi::command_bind('uninvite' => sub { cmd_modelist_del('I', $_[0]) });
449 Irssi::command_bind('unexcept' => sub { cmd_modelist_del('e', $_[0]) });
450 Irssi::command_bind('unreop' => sub { cmd_modelist_del('R', $_[0]) });
451
452 sb_update();
453
454 Irssi::statusbar_item_register('modelist', undef, 'sb_modelist');
455 Irssi::statusbars_recreate_items();
456
457 foreach my $server ( Irssi::servers )
458 {
459 foreach my $chanrec ( $server->channels )
460 {
461 create_channel($chanrec, 1);
462 }
463 }
464
465
466
467