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