html/synccheck.pl


   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 
  13 use strict;
  14 use Irssi 20020313 ();
  15 
  16 use 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 
  28 my $synccheck = undef;
  29 
  30 sub _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 
  38 sub _endof
  39 {
  40 	%$synccheck = (), undef $synccheck if (defined $synccheck);
  41 	Irssi::print(shift) if (@_);
  42 }
  43 
  44 sub _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 
  60 sub _setchan ($)
  61 {
  62 	if (defined $synccheck) {
  63 		$synccheck->{name} = shift;
  64 		$synccheck->{channel} = lc($synccheck->{name});
  65 	}
  66 }
  67 
  68 sub _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 
  76 sub _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 
  87 sub _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 
  93 sub _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 
  99 sub _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 
 105 sub _rec2mod ($)
 106 {
 107 	my $hash = shift;
 108 	my $mod = ($hash->{voice}) ? '+' : undef; $mod .= ($hash->{op}) ? '@' : undef;
 109 	return $mod;
 110 }
 111 
 112 sub _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 
 121 sub _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 
 130 sub _adderror ($$$$)
 131 {
 132 	my ($nick, $sig, $server, $error) = @_;
 133 	if (_errorregister $nick, $sig) {
 134 		push @{$synccheck->{errors}->{$server}}, $error;
 135 	}
 136 }
 137 
 138 sub _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 
 153 sub _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 
 164 sub _numlinks
 165 {
 166 	return ((defined $synccheck and defined $synccheck->{links}) ? scalar(@{$synccheck->{links}}) : 0);
 167 }
 168 
 169 sub tdiff ($)
 170 {
 171 	my $end = time(); my $start = shift;
 172 	return (($start and $start =~ /^\d+$/ and $start <= $end) ? ($end - $start) : 0);
 173 }
 174 
 175 sub _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 
 198 sub _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 
 244 Irssi::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 
 300 Irssi::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 
 307 Irssi::Irc::Server::redirect_register(
 308 	"names", 0, 0,
 309 	{ "event 353" => 1, },
 310 	{ "event 366" => 1,
 311 	  "event 402" => 1, },
 312 	undef,
 313 );
 314 
 315 Irssi::signal_add 'redir links line' => sub {
 316 	$_[1] =~ /(.*) (.*) (.*) :(.*)/;
 317 	_addlink $2;
 318 };
 319 
 320 Irssi::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 
 327 Irssi::signal_add 'redir names line' => sub {
 328 	$_[1] =~ /(.*) (.*) :(.*)/;
 329 	_addnames($_[2], split(" ", $3)) if (defined $synccheck and lc($2) eq $synccheck->{channel});
 330 };
 331 
 332 Irssi::signal_add 'redir names done' => sub
 333 {
 334 	$_[1] =~ /(.*) (.*) :(.*)/;
 335 	_test($_[0], $_[2]) if (defined $synccheck and lc($2) eq $synccheck->{channel});;
 336 };
 337 
 338 Irssi::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 
 345 Irssi::settings_add_bool('misc', 'synccheck_show_all_errors', 0);
 346