1# 2# usage: /sync-check [channel (servers)|-stop] 3# examples: 4# /sync-check *.de 5# /sync-check 6# /sync-check #irssi 7# /sync-check poznan.irc.pl 8# /sync-check #irssi poznan.irc.pl *.de 9# /sync-check -stop 10# usage: /SET synccheck_show_all_errors [On/Off] 11# 12 13use strict; 14use Irssi 20020313 (); 15 16use vars qw($VERSION %IRSSI); 17$VERSION = "0.4.9.1"; 18%IRSSI = ( 19 authors => 'Marcin Rozycki', 20 contact => 'derwan@irssi.pl', 21 name => 'sync-check', 22 description => 'Script checking channel synchronization. Usage: /sync-check [channel (servers)|-stop]', 23 license => 'GNU GPL v2', 24 url => 'http://derwan.irssi.pl', 25 changed => 'Fri Aug 9 23:00:00 CEST 2002' 26); 27 28my $synccheck = undef; 29 30sub _print ($$$) 31{ 32 my ($server, $level, $msg) = @_; 33 if (defined $synccheck and $server and my $win = $server->channel_find($synccheck->{name})) { 34 $win->print($msg, $level); 35 } 36} 37 38sub _endof 39{ 40 %$synccheck = (), undef $synccheck if (defined $synccheck); 41 Irssi::print(shift) if (@_); 42} 43 44sub _new ($) 45{ 46 _endof; my $server = shift; 47 return 0 unless ($server and $server->{type} eq 'SERVER' and $server->{connected}); 48 49 $synccheck = {}; 50 $synccheck->{time} = time; 51 $synccheck->{server} = $server->{address}; 52 $synccheck->{tag} = $server->{tag}; 53 $synccheck->{_error} = 0; 54 $synccheck->{_tested} = 0; 55 $synccheck->{_info} = 0; 56 57 return $synccheck; 58} 59 60sub _setchan ($) 61{ 62 if (defined $synccheck) { 63 $synccheck->{name} = shift; 64 $synccheck->{channel} = lc($synccheck->{name}); 65 } 66} 67 68sub _addlink ($) 69{ 70 my $link = shift; 71 if (defined $synccheck and $link and $link ne $synccheck->{server}) { 72 push (@{$synccheck->{links}}, $link); 73 } 74} 75 76sub _register 77{ 78 my $server = shift; my $nick = lc(shift); my $sig = shift; 79 %{$synccheck->{names}->{$server}->{$nick}} = ( 80 NULL => 1, 81 op => 0, 82 voice => 0, 83 $sig => 1, 84 ) if (defined $synccheck); 85} 86 87sub _isregister ($$) 88{ 89 my $server = shift; my $nick = lc(shift); 90 return ((defined $synccheck and defined $synccheck->{names}->{$server}->{$nick}->{NULL}) ? 1 : 0); 91} 92 93sub _isop ($$) 94{ 95 my $server = shift; my $nick = lc(shift); 96 return ((_isregister($server, $nick) and $synccheck->{names}->{$server}->{$nick}->{op}) ? 1 : 0); 97} 98 99sub _isvoice ($$) 100{ 101 my $server = shift; my $nick = lc(shift); 102 return ((_isregister($server, $nick) and $synccheck->{names}->{$server}->{$nick}->{voice}) ? 1 : 0); 103} 104 105sub _rec2mod ($) 106{ 107 my $hash = shift; 108 my $mod = ($hash->{voice}) ? '+' : undef; $mod .= ($hash->{op}) ? '@' : undef; 109 return $mod; 110} 111 112sub _reg2mod ($$) 113{ 114 my $server = shift; my $nick = lc(shift); my $mod = undef; 115 if (_isregister($server, $nick)) { 116 $mod .= ($synccheck->{names}->{$server}->{$nick}->{voice}) ? '+' : ($synccheck->{names}->{$server}->{$nick}->{op}) ? '@' : ''; 117 } 118 return $mod; 119} 120 121sub _errorregister ($$) { 122 my ($nick, $sig) = @_; my $retval = 1; 123 unless (Irssi::settings_get_bool("synccheck_show_all_errors")) { 124 $retval = ($synccheck->{registered_errors}->{$nick}->{$sig}) ? 0 : 1; 125 } 126 $synccheck->{registered_errors}->{$nick}->{$sig}++; 127 return $retval; 128} 129 130sub _adderror ($$$$) 131{ 132 my ($nick, $sig, $server, $error) = @_; 133 if (_errorregister $nick, $sig) { 134 push @{$synccheck->{errors}->{$server}}, $error; 135 } 136} 137 138sub _flusherrors ($$) 139{ 140 my ($local, $remote) = @_; 141 if ($#{$synccheck->{errors}->{$remote}} >= 0) { 142 _print($local, MSGLEVEL_CLIENTCRAP, "(error in synchronization will be shown only once for the first server where the error exists, but it can exists on more servers; if you want to show all errors use /set synccheck_show_all_errors On)") 143 if (!Irssi::settings_get_bool("synccheck_show_all_errors") and !$synccheck->{_info}++); 144 _print($local, MSGLEVEL_CLIENTCRAP, "%RPossible channel %n%_$synccheck->{name}%_%R desynced%n%_ $synccheck->{server} <-> $remote%_:"); 145 for (@{$synccheck->{errors}->{$remote}}) 146 { 147 _print($local, MSGLEVEL_CLIENTCRAP, "%_".sprintf("%03d", ++$synccheck->{_error}).".%_ $_"); 148 } 149 delete $synccheck->{errors}->{$remote}; 150 } 151} 152 153sub _addnames 154{ 155 my $remote = shift; return unless ($remote and defined $synccheck); 156 for (@_) 157 { 158 /^\@/ and substr($_, 0, 1) = "", _register($remote, $_, "op"), next; 159 /^\+/ and substr($_, 0, 1) = "", _register($remote, $_, "voice"), next; 160 _register($remote, $_, "NULL"); 161 } 162} 163 164sub _numlinks 165{ 166 return ((defined $synccheck and defined $synccheck->{links}) ? scalar(@{$synccheck->{links}}) : 0); 167} 168 169sub tdiff ($) 170{ 171 my $end = time(); my $start = shift; 172 return (($start and $start =~ /^\d+$/ and $start <= $end) ? ($end - $start) : 0); 173} 174 175sub _synccheck ($) 176{ 177 my $local = shift; my $remote = ${$synccheck->{links}}[$synccheck->{_tested}++]; 178 179 _endof("End of sync-check (canceled)"), return 180 if (!$synccheck or !$local->channel_find($synccheck->{name})); 181 182 unless ($remote) { 183 _print($local, MSGLEVEL_CLIENTCRAP, "%_Sync-check%_ in $synccheck->{name} ($synccheck->{tag}) %_finished in ".tdiff($synccheck->{time})." secs%_"); 184 _endof; return; 185 } 186 187 _print($local, MSGLEVEL_CLIENTCRAP, "%K->%n checking $synccheck->{name}: $synccheck->{server} %_<-> $remote%_ %K[%n$synccheck->{_tested}/"._numlinks."%K]%n"); 188 189 $local->redirect_event("names", 0, '', 1, undef, { 190 'event 353' => 'redir names line', 191 'event 366' => 'redir names done', 192 'event 402', => 'redir names split', 193 '' => 'event empty' }); 194 195 $local->send_raw("NAMES $synccheck->{channel} :$remote"); 196} 197 198sub _test 199{ 200 my ($local, $remote) = @_; 201 202 unless (_isregister $remote, $local->{nick}) { 203 _adderror($local->{nick}, "notexsit", $remote, "%_you\'re%_ not in channel $synccheck->{name} on $remote"); 204 _flusherrors($local, $remote); 205 delete $synccheck->{names}->{$remote}; 206 return; 207 } 208 209 my $channel = $local->channel_find($synccheck->{name}); 210 _endof, return unless $channel; 211 212 my %orig = (); map($orig{lc($_->{nick})} = $_, $channel->nicks()); 213 214 foreach my $nick (keys %{$synccheck->{names}->{$remote}}) 215 { 216 if (!$orig{$nick}) { 217 _adderror($nick, "notexist", $remote, "%_*notexist%_($synccheck->{server}) %_!= "._reg2mod($remote, $nick)."$nick%_($remote)"); 218 $orig{$nick} = 0; next; 219 } 220 221 my $op = _isop $remote, $nick; my $voice = _isvoice $remote, $nick; 222 if ($orig{$nick}->{op} != $op) { 223 my $mod1 = _rec2mod($orig{$nick}); my $mod2 = _reg2mod($remote, $nick); 224 _adderror($nick, "op", $remote, "%_$mod1%_$nick($synccheck->{server}) %_!= $mod2%_$nick($remote)"); 225 226 } elsif (!$op and $orig{$nick}->{voice} != $voice) { 227 my $mod1 = _rec2mod($orig{$nick}); my $mod2 = _reg2mod($remote, $nick); 228 _adderror($nick, "voice", $remote, "%_$mod1%_$nick($synccheck->{server}) %_!= $mod2%_$nick($remote)"); 229 } 230 $orig{$nick} = 0; 231 } 232 delete $synccheck->{names}->{$remote}; 233 234 foreach my $nick (keys %orig) 235 { 236 next unless $orig{$nick}; 237 _adderror($nick, "notexist", $remote, _rec2mod($orig{$nick})."%_$nick%_($synccheck->{server}) %_!= *notexist%_($remote)"); 238 } 239 240 _flusherrors($local, $remote); 241 _synccheck $local; 242} 243 244Irssi::command_bind 'sync-check' => sub 245{ 246 my $usage = "/%_sync-check%_ [%_channel%_ (%_servers%_)|%_-stop%_]"; 247 248 unless ($_[1] and $_[1]->{type} eq 'SERVER' and $_[1]->{connected}) { 249 Irssi::print("Not connected to server"); 250 return; 251 } 252 253 if (defined $synccheck) { 254 if ($_[0] !~ /^-stop/) { 255 Irssi::print("Sync-check already running " . tdiff($synccheck->{time}) . " secs ago for channel %_$synccheck->{name}%_, wait..."); 256 } else { 257 _endof("%_Stopping%_ sync-checker for channel %_$synccheck->{name}%_ in $synccheck->{tag}") 258 } 259 return; 260 } 261 262 return unless _new($_[1]); 263 264 foreach (split / +/, $_[0]) 265 { 266 /^-yes/i and $synccheck->{_yes} = 1, next; 267 /^-stop$/ and _endof("Not running any sync-checker"), return; 268 /^-/ and _endof("Unknown argument: %_$_%_, usage: $usage"), return; 269 if ($_[1]->ischannel($_)) { _setchan $_; } else { _addlink $_; } 270 } 271 272 if ($synccheck->{channel} and !$_[1]->channel_find($synccheck->{channel})) { 273 _endof("You\'re not in channel %_$synccheck->{name}%_"); return; 274 } elsif (!$synccheck->{channel}) { 275 if ($_[2] and $_[2]->{type} eq 'CHANNEL') { 276 _setchan $_[2]->{name}; 277 } else { 278 _endof("Not joined to any channel"); return; 279 } 280 } 281 282 if (!_numlinks) { 283 _endof("Doing this is not a good idea. Add -YES option to command if you really mean it"), return unless ($synccheck->{_yes}); 284 285 _print($_[1], MSGLEVEL_CLIENTCRAP, "Checking for %_links%_ from %_$synccheck->{server}%_ in %_$synccheck->{tag}%_, wait..."); 286 $_[1]->redirect_event('links', 0, '', 1, undef, { 287 'event 364' => 'redir links line', 288 'event 365' => 'redir links done', 289 '' => 'event empty' }); 290 $_[1]->send_raw('LINKS :*'); 291 292 } else { 293 if (_numlinks) { 294 _print($_[1], MSGLEVEL_CLIENTCRAP, "%_Checking channel $synccheck->{name} synchronization%_ in: $synccheck->{server} %_<->%_ @{$synccheck->{links}}. This will take a while.."); 295 _synccheck $_[1]; 296 } 297 } 298}; 299 300Irssi::Irc::Server::redirect_register( 301 "links", 0, 0, 302 { "event 364" => 1, }, 303 { "event 402" => 1, "event 263" => 1, "event 365" => 1, }, 304 undef, 305); 306 307Irssi::Irc::Server::redirect_register( 308 "names", 0, 0, 309 { "event 353" => 1, }, 310 { "event 366" => 1, 311 "event 402" => 1, }, 312 undef, 313); 314 315Irssi::signal_add 'redir links line' => sub { 316 $_[1] =~ /(.*) (.*) (.*) :(.*)/; 317 _addlink $2; 318}; 319 320Irssi::signal_add 'redir links done' => sub { 321 if (_numlinks) { 322 _print($_[0], MSGLEVEL_CLIENTCRAP, "%_Checking channel $synccheck->{name} synchronization%_ in: $synccheck->{server} %_<->%_ @{$synccheck->{links}}. This will take a while.."); 323 _synccheck $_[0]; 324 } 325}; 326 327Irssi::signal_add 'redir names line' => sub { 328 $_[1] =~ /(.*) (.*) :(.*)/; 329 _addnames($_[2], split(" ", $3)) if (defined $synccheck and lc($2) eq $synccheck->{channel}); 330}; 331 332Irssi::signal_add 'redir names done' => sub 333{ 334 $_[1] =~ /(.*) (.*) :(.*)/; 335 _test($_[0], $_[2]) if (defined $synccheck and lc($2) eq $synccheck->{channel});; 336}; 337 338Irssi::signal_add 'redir names split' => sub 339{ 340 $_[1] =~ /(.*) (.*) :(.*)/; 341 _print($_[0], MSGLEVEL_CLIENTCRAP, "%K->%n%_ $2%_: cannot find link (".lc($3)."), skipping"); 342 _synccheck $_[0]; 343}; 344 345Irssi::settings_add_bool('misc', 'synccheck_show_all_errors', 0); 346 347