1 # ho_lusercount.pl
2 #
3 # $Id: ho_lusercount.pl,v 1.3 2004/08/15 08:53:05 jvunder REL_0_3 $
4 #
5 # Part of the Hybrid Oper Script Collection.
6 #
7 # This provides a statusbar item containing the users on the server.
8 # It uses /lusers for that, plus usermode +c to increase/decrease the
9 # number of users whenever a client (dis)connects.
10
11 # Known bugs:
12 # * Doing /lusers <other_server> <other_server> may confuse the script.
13 # * You need to do a manual /lusers for every network after loading.
14 # * The "I have <num> clients" server notice gets eaten. It is resent,
15 # but not with optimal formatting.
16
17 # Todo:
18 # Add different formats for increase/decrease history lines with different
19 # colours.
20
21 # /lusers output
22 # >> :irc.efnet.nl 255 Garion :I have 10970 clients and 2 servers
23
24 use strict;
25 use vars qw($VERSION %IRSSI $SCRIPT_NAME);
26
27 # Why doesn't use constant work in my irssi? *confused*
28 #use constant MIN_HISTORY_DELTA_TIME => 10; # seconds
29 my $MIN_HISTORY_DELTA_TIME = 10;
30
31 use Irssi qw(
32 settings_get_int settings_get_str settings_get_bool
33 settings_set_int settings_set_str settings_set_bool
34 settings_add_int settings_add_str settings_add_bool
35 );
36 use Irssi::TextUI; # for statusbar
37 use HOSC::again;
38 use HOSC::again 'HOSC::Base';
39 use HOSC::again 'HOSC::Tools';
40
41 # ---------------------------------------------------------------------
42
43 ($VERSION) = '$Revision: 1.3 $' =~ / (\d+\.\d+) /;
44 %IRSSI = (
45 authors => 'Garion',
46 contact => 'garion@efnet.nl',
47 name => 'ho_lusercount',
48 description => 'Statusbar item with number of clients, and client history graph.',
49 license => 'Public Domain',
50 url => 'http://www.garion.org/irssi/hosc.php',
51 changed => '24 April 2004 22:24:08',
52 );
53 $SCRIPT_NAME = 'Lusercount';
54
55 # ---------------------------------------------------------------------
56 #
57 # Thanks to:
58 #
59 # - mofo, for suggestions
60
61 # ---------------------------------------------------------------------
62
63 # Hash that holds a {$network}->{variable} substructure.
64 my %luserinfo;
65
66 # Statusbar timer refresh handle
67 my $delta_handle_statusbar;
68
69 # Statusbar timer refresh time
70 my $delta_statusbar;
71
72 # History timer refresh handle
73 my $delta_handle_history;
74
75 # History timer refresh time
76 my $delta_history;
77
78 # ---------------------------------------------------------------------
79 # >> :irc.efnet.nl 255 Garion :I have 10970 clients and 2 servers
80
81 sub event_lusers_output {
82 my ($server, $msg) = @_;
83 if ($msg =~ /I have ([0-9]+) clients/o) {
84 for my $n (split /\s+/,
85 lc(settings_get_str('ho_lusercount_networks'))
86 ) {
87 if (lc($n) eq lc($server->{tag})) {
88 $luserinfo{$n}->{numclients} = $1;
89 }
90 }
91
92 # Re-send the message. Needs proper formatting..?
93 $msg =~ s/^[^:]+://;
94 Irssi::print($msg, MSGLEVEL_CRAP);
95
96 Irssi::statusbar_items_redraw('lusercount');
97 }
98 }
99
100 # ---------------------------------------------------------------------
101
102 # A Server Event has occurred. Check if it is a server NOTICE;
103 # if so, process it.
104
105 sub event_serverevent {
106 my ($server, $msg, $nick, $hostmask) = @_;
107 my ($nickname, $username, $hostname);
108
109 # If it is not a NOTICE, we don't want to have anything to do with it.
110 return if $msg !~ /^NOTICE/o;
111
112 # If the hostmask is set, it is not a server NOTICE, so we'll ignore it
113 # as well.
114 return if length($hostmask) > 0;
115
116 # Check whether the server notice is on one of the networks we are
117 # monitoring.
118 my $watch_this_network = 0;
119 foreach my $network (split /\s+/,
120 lc(settings_get_str('ho_lusercount_networks'))
121 ) {
122 if ($network eq lc($server->{tag})) {
123 $watch_this_network = 1;
124 last;
125 }
126 }
127 return unless $watch_this_network;
128
129 my $ownnick = $server->{'nick'};
130
131 # Remove the NOTICE part from the message
132 # NOTE: this is probably unnecessary.
133 $msg =~ s/^NOTICE $ownnick ://;
134
135 # Remove the server prefix
136 # NOTE: this is probably unnecessary.
137 #$msg =~ s/^$prefix//;
138
139 process_event($server, $msg);
140 }
141
142 # ---------------------------------------------------------------------
143
144 # This function takes a server notice and matches it with a few regular
145 # expressions to see if any special action needs to be taken.
146
147 sub process_event {
148 my ($server, $msg) = @_;
149
150 # Client connect: nick, user, host, ip, class, realname
151 if (index($msg ,"tice -- Client connecting: ") >= 0) {
152 client_add(lc($server->{tag}));
153 if (settings_get_bool('ho_lusercount_suppress_snotices')) {
154 Irssi::signal_stop();
155 }
156 return;
157 }
158
159 # Client exit: nick, user, host, reason, ip
160 if (index($msg, "tice -- Client exiting: ") >= 0) {
161 client_remove(lc($server->{tag}));
162 if (settings_get_bool('ho_lusercount_suppress_snotices')) {
163 Irssi::signal_stop();
164 }
165 return;
166 }
167 }
168
169 # ---------------------------------------------------------------------
170 # Refreshes the number of clients connected in the past N seconds,
171 # where N is the setting ho_lusercount_delta_statusbar.
172
173 sub delta_statusbar {
174 foreach my $n (split /\s+/,
175 lc(settings_get_str('ho_lusercount_networks'))
176 ) {
177 my $l = $luserinfo{$n};
178 $l->{deltaclients} = $l->{deltaclients_temp};
179 $l->{deltaclients_temp} = 0;
180 $l->{connectedclients} = $l->{connectedclients_temp};
181 $l->{connectedclients_temp} = 0;
182 $l->{disconnectedclients} = $l->{disconnectedclients_temp};
183 $l->{disconnectedclients_temp} = 0;
184 }
185
186 my $time = settings_get_int("ho_lusercount_delta_statusbar") * 1000;
187 $time = 5000 if $time < 1000;
188
189 if ($time != $delta_statusbar) {
190 Irssi::print("Changing statusbar timer.");
191 Irssi::timeout_remove($delta_handle_statusbar)
192 if $delta_handle_statusbar;
193 $delta_statusbar = $time;
194 Irssi::timeout_add($delta_statusbar, 'delta_statusbar', undef);
195 }
196 }
197
198 # ---------------------------------------------------------------------
199
200 sub delta_history {
201 my $now = time();
202 foreach my $n (split /\s+/,
203 lc(settings_get_str('ho_lusercount_networks'))
204 ) {
205 $luserinfo{$n}->{client_history}->{$now} = $luserinfo{$n}->{numclients};
206 }
207
208 clean_delta_history();
209
210 my $time = settings_get_int("ho_lusercount_delta_history") * 1000;
211 $time = $MIN_HISTORY_DELTA_TIME if $time < $MIN_HISTORY_DELTA_TIME;
212
213 if ($time != $delta_history) {
214 ho_print("lusercount - Changing history timer.");
215 if ($delta_handle_history) {
216 Irssi::timeout_remove($delta_handle_history);
217 }
218 $delta_history = $time;
219 Irssi::timeout_add($delta_history, 'delta_history', undef);
220 }
221 }
222
223 # ---------------------------------------------------------------------
224
225 sub clean_delta_history {
226 my $keeptime = settings_get_int("ho_lusercount_history_time");
227 my $now = time();
228
229 for my $n (split /\s+/,
230 lc(settings_get_str('ho_lusercount_networks'))
231 ) {
232 next unless defined $luserinfo{$n};
233
234 for my $time (sort {$a <=> $b} keys %{ $luserinfo{$n}->{client_history} }) {
235 if ($now - $time > $keeptime) {
236 delete($luserinfo{$n}->{client_history}->{$time});
237 } else {
238 last;
239 }
240 }
241 }
242 }
243
244 # ---------------------------------------------------------------------
245 # Adds a client object to the client array, and an entry in the client
246 # hashtable pointing to this object.
247
248 sub client_add {
249 my ($n) = @_;
250
251 # Increase the number of clients.
252 $luserinfo{$n}->{numclients}++;
253 $luserinfo{$n}->{deltaclients_temp}++;
254 $luserinfo{$n}->{connectedclients_temp}++;
255
256 # Redraw the statusbar item.
257 Irssi::statusbar_items_redraw("lusercount");
258 }
259
260 # ---------------------------------------------------------------------
261 # Removes a client from the client array and from the client hash.
262 # Argument is the nickname of the client to be removed.
263 # If this nickname is not in the client array, nothing happens.
264
265 sub client_remove {
266 my ($n) = @_;
267
268 # Decrease the number of clients.
269 $luserinfo{$n}->{numclients}--;
270 $luserinfo{$n}->{deltaclients_temp}--;
271 $luserinfo{$n}->{disconnectedclients_temp}++;
272
273 # Redraw the statusbar item.
274 Irssi::statusbar_items_redraw("lusercount");
275 }
276
277 # ---------------------------------------------------------------------
278
279 sub cmd_lusercount {
280 my ($data, $server, $item) = @_;
281 if ($data =~ /^[(history)|(help)]/i ) {
282 Irssi::command_runsub ('lusercount', $data, $server, $item);
283 } else {
284 ho_print("Use '/lusercount history <network>' to show the ".
285 "history of that network or '/lusercount help' for help.")
286 }
287 }
288
289 # ---------------------------------------------------------------------
290
291 sub cmd_lusercount_help {
292 print_help();
293 }
294
295 # ---------------------------------------------------------------------
296 # Shows the history of connected clients.
297
298 sub cmd_lusercount_history {
299 my ($data, $server, $item) = @_;
300
301 if ($data =~ /^\s*$/) {
302 ho_print("Use /LUSERCOUNT HISTORY <network> [time in minutes].");
303 return;
304 }
305
306 my ($tag, $time);
307 if ($data =~ /^(\S+)\s+(\d+)/) {
308 ($tag, $time) = ($1, int($2));
309 } else {
310 $tag = lc($data);
311 $time = 0;
312 }
313
314 if (!defined $luserinfo{$tag}) {
315 ho_print("No lusercount history available for network '$tag'.");
316 return;
317 }
318
319 # Determine the begin time of the history to display.
320 my $begin_time = 0;
321 $begin_time = int(time - 60 * $time) if $time > 0;
322
323 Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'ho_lusercount_history_begin',
324 $tag);
325 my $previous_time = undef;
326 my $diff = 0;
327
328 my $max_diff = get_max_diff($tag, $begin_time);
329
330 my $graph_width = settings_get_int('ho_lusercount_graph_width');
331 $graph_width++ unless $graph_width % 2; # make sure width is odd
332
333 for my $time (sort {$a <=> $b}
334 keys %{ $luserinfo{$tag}->{client_history} }
335 ) {
336 next if $time < $begin_time;
337 my $humantime = sprintf("%02d:%02d",
338 (localtime($time))[2], (localtime($time))[1]);
339 my $diff_col = '%n';
340
341 if (defined $previous_time) {
342 $diff =
343 $luserinfo{$tag}->{client_history}->{$time} -
344 $luserinfo{$tag}->{client_history}->{$previous_time};
345
346 if ($diff > 0.6 * $max_diff) {
347 $diff_col = '%G';
348 } elsif ($diff < - 0.6 * $max_diff) {
349 $diff_col = '%R';
350 }
351 }
352
353 Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'ho_lusercount_history_line',
354 $humantime,
355 sprintf("%4d", $luserinfo{$tag}->{client_history}->{$time}),
356 sprintf("%3d", $diff),
357 get_graph_line($diff, $max_diff, $graph_width)
358 );
359 $previous_time = $time;
360 }
361 Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'ho_lusercount_history_end',
362 $tag);
363 }
364
365 # ---------------------------------------------------------------------
366 # Returns the maximum difference between 2 consecutive timestamps in
367 # the client history of $network_tag.
368
369 sub get_max_diff {
370 my ($network_tag, $begin_time) = @_;
371 my $previous_time = undef;
372 my $diff = 0;
373 my $maxdiff = 0;
374 $begin_time = 0 unless defined $begin_time;
375
376 for my $time (
377 sort {$a <=> $b}
378 keys %{ $luserinfo{$network_tag}->{client_history} }
379 ) {
380 next if $time < $begin_time;
381 if (defined $previous_time) {
382 $diff = abs(
383 $luserinfo{$network_tag}->{client_history}->{$time} -
384 $luserinfo{$network_tag}->{client_history}->{$previous_time}
385 );
386 }
387
388 $maxdiff = $diff if $diff > $maxdiff;
389
390 $previous_time = $time;
391 }
392
393 return $maxdiff;
394 }
395
396 # ---------------------------------------------------------------------
397
398 sub get_graph_line {
399 my ($diff, $max_diff, $width) = @_;
400
401 my $half_width = int(0.5 * $width);
402 my $left = " " x $half_width;
403 my $right = $left;
404
405 # Don't bother if there is no difference.
406 return $left . '|' . $right if $max_diff == 0;
407
408 my $num_blocks = int( abs( $half_width * ($diff / $max_diff) ) );
409
410 if ($diff > 0) {
411 $right = '*' x $num_blocks . ' ' x ($half_width - $num_blocks);
412 } elsif ($diff < 0) {
413 $left = ' ' x ($half_width - $num_blocks) . '*' x $num_blocks;
414 }
415
416 return $left . '|' . $right;
417 }
418
419 # ---------------------------------------------------------------------
420 # Statusbar item determination function.
421
422 sub lusercount_sb {
423 my ($item, $get_size_only) = @_;
424
425 my $txt = "{sb ";
426 for my $n (split /\s+/, lc(settings_get_str('ho_lusercount_networks'))) {
427 if (defined($luserinfo{$n})) {
428 my $info = settings_get_str('ho_lusercount_format');
429 $info =~ s/\$n/$n/;
430 $info =~ s/\$c/$luserinfo{$n}->{numclients}/;
431 $info =~ s/\$D/$luserinfo{$n}->{deltaclients}/;
432 $info =~ s/\$i/$luserinfo{$n}->{connectedclients}/;
433 $info =~ s/\$d/$luserinfo{$n}->{disconnectedclients}/;
434 $txt .= $info . " ";
435 }
436 }
437
438 $txt =~ s/ $/}/;
439 $item->default_handler($get_size_only, "$txt", undef, 1);
440 }
441
442 # ---------------------------------------------------------------------
443
444 ho_print_init_begin($SCRIPT_NAME);
445
446 Irssi::signal_add_first('server event', 'event_serverevent');
447 Irssi::signal_add_first('event 255', 'event_lusers_output');
448
449 Irssi::command_bind('lusercount', 'cmd_lusercount');
450 Irssi::command_bind('lusercount help', 'cmd_lusercount_help');
451 Irssi::command_bind('lusercount history', 'cmd_lusercount_history');
452
453 settings_add_int('ho', 'ho_lusercount_delta_statusbar', 10);
454 settings_add_int('ho', 'ho_lusercount_delta_history', 300);
455 settings_add_int('ho', 'ho_lusercount_history_time', 86400);
456 settings_add_str('ho', 'ho_lusercount_networks', "");
457 settings_add_bool('ho', 'ho_lusercount_suppress_snotices', 0);
458 settings_add_str('ho', 'ho_lusercount_format', '$n: $c/$D ($i:$d)');
459 settings_add_int('ho', 'ho_lusercount_graph_width', 40);
460
461 Irssi::theme_register( [
462 'ho_lusercount_history_begin',
463 '%Y+-%n Lusercount history for $0:',
464
465 'ho_lusercount_history_line',
466 '%Y|%n $0 - $1 ($2) $3',
467
468 'ho_lusercount_history_end',
469 '%Y+-%n End of lusercount history for $0.',
470 ]);
471
472 Irssi::statusbar_item_register('lusercount', '{sb $1-}', 'lusercount_sb');
473
474 Irssi::statusbar_items_redraw("lusercount");
475
476 # Add the statusbar update timer
477 $delta_statusbar = settings_get_int("ho_lusercount_delta_statusbar") * 1000;
478 $delta_statusbar = 5000 if $delta_statusbar < 1000;
479 $delta_handle_statusbar =
480 Irssi::timeout_add($delta_statusbar, 'delta_statusbar', undef);
481
482 # Add the history update timer
483 $delta_history = settings_get_int("ho_lusercount_delta_history") * 1000;
484 $delta_history = $MIN_HISTORY_DELTA_TIME
485 if $delta_history < $MIN_HISTORY_DELTA_TIME;
486 $delta_handle_history =
487 Irssi::timeout_add($delta_history, 'delta_history', undef);
488
489 if (length(settings_get_str('ho_lusercount_networks')) == 0) {
490 ho_print("Use /SET ho_lusercount_networks <networks> to set ".
491 "the list of network tags the lusercount must be tracked on. This is ".
492 "a space separated list.");
493 }
494
495 ho_print_init_end($SCRIPT_NAME);
496 ho_print('Use /LUSERCOUNT HELP for help.');
497
498 # ---------------------------------------------------------------------
499
500 sub print_help {
501 ho_print_help('head', $SCRIPT_NAME);
502
503 ho_print_help('section', 'Syntax');
504 ho_print_help('syntax', 'LUSERCOUNT HISTORY <tag> [<time>]');
505
506 ho_print_help('section', 'Description');
507 ho_print_help("This script provides a statusbar item named ".
508 "'lusercount' which keeps track of the number of users on any ".
509 "number of servers by means of client connect and exit server ".
510 "notices.");
511 ho_print_help("The appearance of this statusbar item is determined ".
512 "by the ho_lusercount_format setting. Use that to tweak the ".
513 "appearance to your liking. There are 5 special variables in this ".
514 "setting, which will be replaced by their value:");
515 ho_print_help(' $n - the network tag');
516 ho_print_help(' $c - the number of clients');
517 ho_print_help(' $D - the delta (change) in clients in the past time unit');
518 ho_print_help(' $i - the increase in clients in the past time unit');
519 ho_print_help(' $d - the decrease in clients in the past time unit');
520 ho_print_help('$D is $i plus $d.' . "\n");
521
522 ho_print_help('The other feature of this script is that it keeps track '.
523 'of the history of the number of clients on servers. That history '.
524 "can be displayed via the 'lusercount history' command. The first ".
525 'argument is the network tag to display, and the second (optional) '.
526 "argument indicates that only the last N minutes should be shown.\n");
527
528 ho_print_help('section', 'Settings');
529 ho_print_help('setting', 'ho_lusercount_format',
530 'Appearance of the statusbar.');
531 ho_print_help('setting', 'ho_lusercount_network_tags',
532 'Tags of the networks the lusercount must be tracked.');
533 ho_print_help('setting', 'ho_lusercount_delta_statusbar',
534 'Time between two consecutive statusbar updates, in seconds.');
535 ho_print_help('setting', 'ho_lusercount_delta_history',
536 'Time between two consecutive history snapshots, in seconds.');
537 ho_print_help('setting', 'ho_lusercount_history_time',
538 'How long to keep usercount history, in seconds.');
539 ho_print_help('setting', 'ho_lusercount_graph_width',
540 'Width of the lusercount history graph.');
541 ho_print_help('setting', 'ho_lusercount_suppress_snotices',
542 'Whether this script should block the client connect/exit '.
543 'server notices after having processed them.');
544 }
545
546 # ---------------------------------------------------------------------
syntax highlighted by Code2HTML, v. 0.9.1