]> git.donarmstrong.com Git - infobot.git/blob - src/UserExtra.pl
Crypt patch, fixes the crypt function
[infobot.git] / src / UserExtra.pl
1 #
2 # UserExtra.pl: User Commands, Public.
3 #       Author: dms
4 #
5
6 use strict;
7 use vars qw($message $arg $qWord $verb $lobotomized $who $result $chan
8         $conn $msgType $query $talkchannel $ident $memusage);
9 use vars qw(%channels %chanstats %cmdstats %count %ircstats %param
10         %cache %mask %userstats);
11
12 ### hooks get added in CommandHooks.pl.
13
14 ###
15 ### Start of commands for hooks.
16 ###
17
18 sub chaninfo {
19     my $chan = lc shift(@_);
20     my $mode;
21
22     if ($chan eq "") {          # all channels.
23         my $i           = keys %channels;
24         my $reply       = "I'm on \002$i\002 ".&fixPlural("channel",$i);
25         my $tucount     = 0;    # total user count.
26         my $uucount     = 0;    # unique user count.
27         my @array;
28
29         ### line 1.
30         foreach (sort keys %channels) {
31             if ( /^\s*$/ or / / ) {
32                 &status("chanstats: fe channels: chan == NULL.");
33                 #&ircCheck();
34                 next;
35             }
36             next if (/^_default$/);
37
38             my $str = sprintf("%s/%d", $_, scalar(keys %{ $channels{$_}{''} }));
39             push(@array, $str);
40         }
41         &performStrictReply($reply.": ".join(', ', @array));
42
43         ### total user count.
44         foreach $chan (keys %channels) {
45             $tucount += scalar(keys %{ $channels{$chan}{''} });
46         }
47
48         ### unique user count.
49         my %nicks = ();
50         foreach $chan (keys %channels) {
51             my $nick;
52             foreach $nick (keys %{ $channels{$chan}{''} }) {
53                 $nicks{$nick}++;
54             }
55         }
56         $uucount = scalar(keys %nicks);
57
58         my $chans = scalar(keys %channels);
59         &performStrictReply(
60             "i've cached \002$tucount\002 ". &fixPlural("user",$tucount).
61             ", \002$uucount\002 unique ". &fixPlural("user",$uucount).
62             ", distributed over \002$chans\002 ".
63             &fixPlural("channel", $chans)."."
64         );
65         &ircCheck();
66
67         return;
68     }
69
70     # channel specific.
71
72     if (&validChan($chan) == 0) {
73         &msg($who,"error: invalid channel \002$chan\002");
74         return;
75     }
76
77     # Step 1:
78     my @array;
79     foreach (sort keys %{ $chanstats{$chan} }) {
80         my $int = $chanstats{$chan}{$_};
81         next unless ($int);
82
83         push(@array, "\002$int\002 ". &fixPlural($_,$int));
84     }
85     my $reply = "On \002$chan\002, there ".
86                 &fixPlural("has",scalar(@array)). " been ".
87                 &IJoin(@array);
88
89     # Step 1b: check channel inconstencies.
90     $chanstats{$chan}{'Join'}           ||= 0;
91     $chanstats{$chan}{'SignOff'}        ||= 0;
92     $chanstats{$chan}{'Part'}           ||= 0;
93
94     my $delta_stats = $chanstats{$chan}{'Join'}
95                 - $chanstats{$chan}{'SignOff'}
96                 - $chanstats{$chan}{'Part'};
97
98     if ($delta_stats) {
99         my $total = scalar(keys %{ $channels{$chan}{''} });
100         &status("chaninfo: join ~= signoff + part (drift of $delta_stats < $total).");
101
102         if ($delta_stats > $total) {
103             &ERROR("chaninfo: delta_stats exceeds total users.");
104         }
105     }
106
107     # Step 2:
108     undef @array;
109     my $type;
110     foreach ("v","o","") {
111         my $int = scalar(keys %{ $channels{$chan}{$_} });
112         next unless ($int);
113
114         $type = "Voice" if ($_ eq "v");
115         $type = "Opped" if ($_ eq "o");
116         $type = "Total" if ($_ eq "");
117
118         push(@array,"\002$int\002 $type");
119     }
120     $reply .= ".  At the moment, ". &IJoin(@array);
121
122     # Step 3:
123     my %new;
124     foreach (keys %userstats) {
125         next unless (exists $userstats{$_}{'Count'});
126         if ($userstats{$_}{'Count'} =~ /^\D+$/) {
127             &WARN("userstats{$_}{Count} is non-digit.");
128             next;
129         }
130
131         $new{$_} = $userstats{$_}{'Count'};
132     }
133
134     # TODO: show top 3 with percentages?
135     my($count) = (sort { $new{$b} <=> $new{$a} } keys %new)[0];
136     if ($count) {
137         $reply .= ".  \002$count\002 has said the most with a total of \002$new{$count}\002 messages";
138     }
139     &performStrictReply("$reply.");
140 }
141
142 # Command statistics.
143 sub cmdstats {
144     my @array;
145
146     if (!scalar(keys %cmdstats)) {
147         &performReply("no-one has run any commands yet");
148         return;
149     }
150
151     my %countstats;
152     foreach (keys %cmdstats) {
153         $countstats{ $cmdstats{$_} }{$_} = 1;
154     }
155
156     foreach (sort {$b <=> $a} keys %countstats) {
157         my $int = $_;
158         next unless ($int);
159
160         foreach (keys %{ $countstats{$int} }) {
161             push(@array, "\002$int\002 of $_");
162         }
163     }
164     &performStrictReply("command usage include ". &IJoin(@array).".");
165 }
166
167 # Factoid extension info. xk++
168 sub factinfo {
169     my $faqtoid = lc shift(@_);
170     my $query   = "";
171
172     if ($faqtoid =~ /^\-(\S+)(\s+(.*))$/) {
173         &msg($who,"error: individual factoid info queries not supported as yet.");
174         &msg($who,"it's possible that the factoid mistakenly begins with '-'.");
175         return;
176
177         $query   = lc $1;
178         $faqtoid = lc $3;
179     }
180
181     &CmdFactInfo($faqtoid, $query);
182 }
183
184 sub factstats {
185     my $type = shift(@_);
186
187     &Forker("Factoids", sub {
188         &performStrictReply( &CmdFactStats($type) );
189     } );
190 }
191
192 sub karma {
193     my $target  = lc( shift || $who );
194     my $karma   = &sqlSelect("stats", "counter",
195         { nick => $target, type => "karma" }) || 0;
196
197     if ($karma != 0) {
198         &performStrictReply("$target has karma of $karma");
199     } else {
200         &performStrictReply("$target has neutral karma");
201     }
202 }
203
204 sub tell {
205     my $args = shift;
206     my ($target, $tell_obj) = ('','');
207     my $dont_tell_me    = 0;
208     my $reply;
209
210     ### is this fixed elsewhere?
211     $args =~ s/\s+/ /g;         # fix up spaces.
212     $args =~ s/^\s+|\s+$//g;    # again.
213
214     # this one catches most of them
215     if ($args =~ /^(\S+) (-?)about (.*)$/i) {
216         $target         = $1;
217         $tell_obj       = $3;
218         $dont_tell_me   = ($2) ? 1 : 0;
219
220         $tell_obj       = $who  if ($tell_obj =~ /^(me|myself)$/i);
221         $query          = $tell_obj;
222     } elsif ($args =~ /^(\S+) where (\S+) can (\S+) (.*)$/i) {
223         # i'm sure this could all be nicely collapsed
224         $target         = $1;
225         $tell_obj       = $4;
226         $query          = $tell_obj;
227
228     } elsif ($args =~ /^(\S+) (what|where) (.*?) (is|are)[.?!]*$/i) {
229         $target         = $1;
230         $qWord          = $2;
231         $tell_obj       = $3;
232         $verb           = $4;
233         $query          = "$qWord $verb $tell_obj";
234
235     } elsif ($args =~ /^(.*?) to (\S+)$/i) {
236         $target         = $3;
237         $tell_obj       = $2;
238         $query          = $tell_obj;
239     }
240
241     # check target type. Deny channel targets.
242     if ($target !~ /^$mask{nick}$/ or $target =~ /^$mask{chan}$/) {
243         &msg($who,"No, $who, I won't. (target invalid?)");
244         return;
245     }
246
247     $target     = $talkchannel  if ($target =~ /^us$/i);
248     $target     = $who          if ($target =~ /^(me|myself)$/i);
249
250     &status("tell: target = $target, query = $query");
251
252     # "intrusive".
253 #    if ($target !~ /^$mask{chan}$/ and !&IsNickInAnyChan($target)) {
254 #       &msg($who, "No, $target is not in any of my chans.");
255 #       return;
256 #    }
257
258     # self.
259     if ($target =~  /^\Q$ident\E$/i) {
260         &msg($who, "Isn't that a bit silly?");
261         return;
262     }
263
264     my $oldwho          = $who;
265     my $oldmtype        = $msgType;
266     $who                = $target;
267     my $result = &doQuestion($tell_obj);
268         # ^ returns '0' if nothing was found.
269     $who                = $oldwho;
270
271     # no such factoid.
272     if (!defined $result || $result =~ /^0?$/) {
273         $who            = $target;
274         $msgType        = "private";
275
276         # support command redirection.
277         # recursive cmdHooks aswell :)
278         my $done = 0;
279         $done++ if &parseCmdHook($tell_obj);
280         $message        = $tell_obj;
281         $done++ unless (&Modules());
282
283         &VERB("tell: setting old values of who and msgType.",2);
284         $who            = $oldwho;
285         $msgType        = $oldmtype;
286
287         if ($done) {
288             &msg($who, "told $target about CMD '$tell_obj'");
289         } else {
290             &msg($who, "i dunno what is '$tell_obj'.");
291         }
292
293         return;
294     }
295
296     # success.
297     &status("tell: <$who> telling $target about $tell_obj.");
298     if ($who ne $target) {
299         if ($dont_tell_me) {
300             &msg($who, "told $target about $tell_obj.");
301         } else {
302             &msg($who, "told $target about $tell_obj ($result)");
303         }
304
305         $reply = "$who wants you to know: $result";
306     } else {
307         $reply = "telling yourself: $result";
308     }
309
310     &msg($target, $reply);
311 }
312
313 sub countryStats {
314     if (exists $cache{countryStats}) {
315         &msg($who,"countrystats is already running!");
316         return;
317     }
318
319     if ($chan eq "") {
320         $chan = $_[0];
321     }
322
323     if ($chan eq "") {
324         &help("countrystats");
325         return;
326     }
327
328     $conn->who($chan);
329     $cache{countryStats}{chan}  = $chan;
330     $cache{countryStats}{mtype} = $msgType;
331     $cache{countryStats}{who}   = $who;
332     $cache{on_who_Hack}         = 1;
333 }
334
335 sub do_countrystats {
336     $chan       = $cache{countryStats}{chan};
337     $msgType    = $cache{countryStats}{mtype};
338     $who        = $cache{countryStats}{who};
339
340     my $total   = 0;
341     my %cstats;
342     foreach (keys %{ $cache{nuhInfo} }) {
343         my $h = $cache{nuhInfo}{$_}{Host};
344
345         if ($h =~ /^.*\.(\D+)$/) {      # host
346             $cstats{$1}++;
347         } else {                        # ip
348             $cstats{unresolve}++;
349         }
350         $total++;
351     }
352     my %count;
353     foreach (keys %cstats) {
354         $count{ $cstats{$_} }{$_} = 1;
355     }
356
357     my @list;
358     foreach (sort {$b <=> $a} keys %count) {
359         my $str = join(", ", sort keys %{ $count{$_} });
360 #       push(@list, "$str ($_)");
361         my $perc        = sprintf("%.01f", 100 * $_ / $total);
362         $perc           =~ s/\.0+$//;
363         push(@list, "$str ($_, $perc %)");
364     }
365
366     # TODO: move this into a scheduler
367     $msgType    = "private";
368     &performStrictReply( &formListReply(0, "Country Stats ", @list) );
369
370     delete $cache{countryStats};
371     delete $cache{on_who_Hack};
372 }
373
374 ###
375 ### amalgamated commands.
376 ###
377
378 sub userCommands {
379     # conversion: ascii.
380     if ($message =~ /^(asci*|chr) (\d+)$/) {
381         &DEBUG("ascii/chr called ...");
382         return unless (&IsChanConfOrWarn("allowConv"));
383
384         &DEBUG("ascii/chr called");
385
386         $arg    = $2;
387         $result = chr($arg);
388         $result = "NULL"        if ($arg == 0);
389
390         &performReply( sprintf("ascii %s is '%s'", $arg, $result) );
391
392         return;
393     }
394
395     # conversion: ord.
396     if ($message =~ /^ord(\s+(.*))$/) {
397         return unless (&IsChanConfOrWarn("allowConv"));
398
399         $arg = $2;
400
401         if (!defined $arg or length $arg != 1) {
402             &help("ord");
403             return;
404         }
405
406         if (ord($arg) < 32) {
407             $arg = chr(ord($arg) + 64);
408             if ($arg eq chr(64)) {
409                 $arg = 'NULL';
410             } else {
411                 $arg = '^'.$arg;
412             }
413         }
414
415         &performReply( sprintf("'%s' is ascii %s", $arg, ord $arg) );
416         return;
417     }
418
419     # hex.
420     if ($message =~ /^hex(\s+(.*))?$/i) {
421         return unless (&IsChanConfOrWarn("allowConv"));
422         my $arg = $2;
423
424         if (!defined $arg) {
425             &help("hex");
426             return;
427         }
428
429         if (length $arg > 80) {
430             &msg($who, "Too long.");
431             return;
432         }
433
434         my $retval;
435         foreach (split //, $arg) {
436             $retval .= sprintf(" %X", ord($_));
437         }
438
439         &performStrictReply("$arg is$retval");
440
441         return;
442     }
443
444     # crypt.
445     if ($message =~ /^crypt(\s+(.*))?$/i) {
446         # Sorry, its messy, but it seems to work now. Added $salt and $ciphertext -- troubled
447         my $salt;
448         my $ciphertext;
449         ($salt, $ciphertext) = split /\s+(.*)$/, $2;
450         my @args        = split /\s+/, $2;
451
452         # Original was > 2 and thus only allowed 1 word to be passed to crypt.
453         # Although now it will crypt to random salt if only 1 param -- troubled
454         if (!scalar @args or scalar @args < 1) {
455             &help("crypt");
456             return;
457         }
458
459         if (scalar @args >= 2) {
460 # disable cause $1$ will use md5
461 #           if (length $args[0] != 2) {
462 #               &msg($who, "invalid format...");
463 #               return;
464 #           }
465
466             &performStrictReply( crypt($ciphertext, $salt) );
467         } else {
468         # When does this get called now since above commented out? -- troubled
469             &performStrictReply( &mkcrypt($args[0]) );
470         }
471
472         return;
473     }
474
475     # cycle.
476     if ($message =~ /^(cycle)(\s+(\S+))?$/i) {
477         return unless (&hasFlag("o"));
478         my $chan = lc $3;
479
480         if ($chan eq "") {
481             if ($msgType =~ /public/) {
482                 $chan = $talkchannel;
483                 &DEBUG("cycle: setting chan to '$chan'.");
484             } else {
485                 &help("cycle");
486                 return;
487             }
488         }
489
490         if (&validChan($chan) == 0) {
491             &msg($who,"error: invalid channel \002$chan\002");
492             return;
493         }
494
495         &msg($chan, "I'm coming back. (courtesy of $who)");
496         &part($chan);
497 ###     &ScheduleThis(5, "getNickInUse") if (@_);
498         &status("Schedule rejoin in 5secs to $chan by $who.");
499         $conn->schedule(5, sub { &joinchan($chan); });
500
501         return;
502     }
503
504     # reload.
505     if ($message =~ /^reload$/i) {
506         return unless (&hasFlag("n"));
507
508         &status("USER reload $who");
509         &performStrictReply("reloading...");
510         my $modules = &reloadAllModules();
511         &performStrictReply("reloaded:$modules");
512         return;
513     }
514
515     # redir.
516     if ($message =~ /^redir(\s+(.*))?/i) {
517         return unless (&hasFlag("o"));
518         my $factoid = $2;
519
520         if (!defined $factoid) {
521             &help("redir");
522             return;
523         }
524
525         my $val  = &getFactInfo($factoid, "factoid_value");
526         if (!defined $val or $val eq "") {
527             &msg($who, "error: '$factoid' does not exist.");
528             return;
529         }
530         &DEBUG("val => '$val'.");
531         my @list = &searchTable("factoids", "factoid_key",
532                                         "factoid_value", "^$val\$");
533
534         if (scalar @list == 1) {
535             &msg($who, "hrm... '$factoid' is unique.");
536             return;
537         }
538         if (scalar @list > 5) {
539             &msg($who, "A bit too many factoids to be redirected, hey?");
540             return;
541         }
542
543         my @redir;
544         &status("Redirect '$factoid' (". ($#list) .")...");
545         for (@list) {
546             my $x = $_;
547             next if (/^\Q$factoid\E$/i);
548
549             &status("  Redirecting '$_'.");
550             my $was = &getFactoid($_);
551             if ($was =~ /<REPLY> see/i) {
552                 &status("warn: not redirecting a redirection.");
553                 next;
554             }
555
556             &DEBUG("  was '$was'.");
557             push(@redir,$x);
558             &setFactInfo($x, "factoid_value", "<REPLY> see $factoid");
559         }
560         &status("Done.");
561
562         &msg($who, &formListReply(0, "'$factoid' is redirected to by '", @redir));
563
564         return;
565     }
566
567     # rot13 it.
568     if ($message =~ /^rot([0-9]*)(\s+(.*))?/i) {
569         my $reply = $3;
570
571         if (!defined $reply) {
572             &help("rot13");
573             return;
574         }
575         my $num = $1 % 26;
576         my $upper="ABCDEFGHIJKLMNOPQRSTUVWXYZ";
577         my $lower="abcdefghijklmnopqrstuvwxyz";
578         my $to=substr($upper,$num).substr($upper,0,$num).substr($lower,$num).substr($lower,0,$num);
579         eval "\$reply =~ tr/$upper$lower/$to/;";
580
581         #$reply =~ y/A-Za-z/N-ZA-Mn-za-m/;
582         &performStrictReply($reply);
583
584         return;
585     }
586
587     # cpustats.
588     if ($message =~ /^cpustats$/i) {
589         if ($^O !~ /linux/) {
590             &ERROR("cpustats: your OS is not supported yet.");
591             return;
592         }
593
594         ### poor method to get info out of file, please fix.
595         open(STAT,"/proc/$$/stat");
596         my $line = <STAT>;
597         chop $line;
598         my @data = split(/ /, $line);
599         close STAT;
600
601         # utime(13) + stime(14).
602         my $cpu_usage   = sprintf("%.01f", ($data[13]+$data[14]) / 100 );
603         # cutime(15) + cstime (16).
604         my $cpu_usage2  = sprintf("%.01f", ($data[15]+$data[16]) / 100 );
605         my $time        = time() - $^T;
606         my $raw_perc    = $cpu_usage*100/$time;
607         my $raw_perc2   = $cpu_usage2*100/$time;
608         my $perc;
609         my $perc2;
610         my $total;
611         my $ratio;
612
613         if ($raw_perc > 1) {
614             $perc       = sprintf("%.01f", $raw_perc);
615             $perc2      = sprintf("%.01f", $raw_perc2);
616             $total      = sprintf("%.01f", $raw_perc+$raw_perc2);
617         } elsif ($raw_perc > 0.1) {
618             $perc       = sprintf("%.02f", $raw_perc);
619             $perc2      = sprintf("%.02f", $raw_perc2);
620             $total      = sprintf("%.02f", $raw_perc+$raw_perc2);
621         } else {                        # <=0.1
622             $perc       = sprintf("%.03f", $raw_perc);
623             $perc2      = sprintf("%.03f", $raw_perc2);
624             $total      = sprintf("%.03f", $raw_perc+$raw_perc2);
625         }
626         $ratio  = sprintf("%.01f", 100*$perc/($perc+$perc2) );
627
628         &performStrictReply("Total CPU usage: \002$cpu_usage\002 s ... ".
629                 "Total used: \002$total\002 % ".
630                 "(parent/child ratio: $ratio %)"
631         );
632
633         return;
634     }
635
636     # ircstats.
637     if ($message =~ /^ircstats?$/i) {
638         $ircstats{'TotalTime'}  ||= 0;
639         $ircstats{'OffTime'}    ||= 0;
640
641         my $count       = $ircstats{'ConnectCount'};
642         my $format_time = &Time2String(time() - $ircstats{'ConnectTime'});
643         my $total_time  = time() - $ircstats{'ConnectTime'} +
644                                 $ircstats{'TotalTime'};
645         my $reply;
646
647         my $connectivity = 100 * ($total_time - $ircstats{'OffTime'}) /
648                                 $total_time;
649         my $p = sprintf("%.03f", $connectivity);
650         $p =~ s/(\.\d*)0+$/$1/;
651         if ($p =~ s/\.0$//) {
652             # this should not happen... but why...
653         } else {
654             $p =~ s/\.$//
655         }
656
657         if ($total_time != (time() - $ircstats{'ConnectTime'}) ) {
658             my $tt_format = &Time2String($total_time);
659             &DEBUG("tt_format => $tt_format");
660         }
661
662         ### RECONNECT COUNT.
663         if ($count == 1) {      # good.
664             $reply = "I'm connected to $ircstats{'Server'} and have been so".
665                 " for $format_time";
666         } else {
667             $reply = "Currently I'm hooked up to $ircstats{'Server'} but only".
668                 " for $format_time.  ".
669                 "I had to reconnect \002$count\002 times.".
670                 "   Connectivity: $p %";
671         }
672
673         ### REASON.
674         my $reason = $ircstats{'DisconnectReason'};
675         if (defined $reason) {
676             $reply .= ".  I was last disconnected for '$reason'.";
677         }
678
679         &performStrictReply($reply);
680
681         return;
682     }
683
684     # status.
685     if ($message =~ /^statu?s$/i) {
686         my $startString = scalar(gmtime $^T);
687         my $upString    = &Time2String(time() - $^T);
688         my ($puser,$psystem,$cuser,$csystem) = times;
689         my $factoids    = &countKeys("factoids");
690         my $forks = 0;
691         foreach (keys %forked) {
692             $forks += scalar keys %{ $forked{$_} };
693         }
694         $forks /= 2;
695         $count{'Commands'}      = 0;
696         foreach (keys %cmdstats) {
697             $count{'Commands'} += $cmdstats{$_};
698         }
699
700         &performStrictReply(
701         "Since $startString, there have been".
702           " \002$count{'Update'}\002 ".
703                 &fixPlural("modification", $count{'Update'}).
704           ", \002$count{'Question'}\002 ".
705                 &fixPlural("question",$count{'Question'}).
706           ", \002$count{'Dunno'}\002 ".
707                 &fixPlural("dunno",$count{'Dunno'}).
708           ", \002$count{'Moron'}\002 ".
709                 &fixPlural("moron",$count{'Moron'}).
710           " and \002$count{'Commands'}\002 ".
711                 &fixPlural("command",$count{'Commands'}).
712           ".  I have been awake for $upString this session, and ".
713           "currently reference \002$factoids\002 factoids.  ".
714           "I'm using about \002$memusage\002 ".
715           "kB of memory. With \002$forks\002 active ".
716                 &fixPlural("fork",$forks).
717           ". Process time user/system $puser/$psystem child $cuser/$csystem"
718         );
719
720         return;
721     }
722
723     # wantNick. xk++
724     # FIXME does not try to get nick "back", just switches nicks
725     if ($message =~ /^wantNick\s(.*)?$/i) {
726         return unless (&hasFlag("o"));
727         my $wantnick = lc $1;
728         my $mynick = $conn->nick();
729
730         if ($mynick eq $wantnick) {
731             &msg($who, "I hope you're right. I'll try anyway (mynick=$mynick, wantnick=$wantnick).");
732         }
733
734         # fallback check, I guess.  needed?
735         if (! &IsNickInAnyChan( $wantnick ) ) {
736             my $str = "attempting to change nick from $mynick to $wantnick";
737             &status($str);
738             &msg($who, $str);
739             &nick($wantnick);
740             return;
741         }
742
743         # idea from dondelecarlo :)
744         # TODO: use cache{nickserv}
745         if ($param{'nickServ_pass'}) {
746             my $str = "someone is using nick $wantnick; GHOSTing";
747             &status($str);
748             &msg($who, $str);
749             &msg("NickServ", "GHOST $wantnick $param{'nickServ_pass'}");
750
751             $conn->schedule(5, sub {
752                 &status("going to change nick from $mynick to $wantnick after GHOST.");
753                 &nick($wantnick);
754             } );
755
756             return;
757         }
758
759         return;
760     }
761
762     return "CONTINUE";
763 }
764
765 1;