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