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