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