]> git.donarmstrong.com Git - infobot.git/blob - src/Misc.pl
added debian-specific debug to isStale
[infobot.git] / src / Misc.pl
1 #
2 #   Misc.pl: Miscellaneous stuff.
3 #    Author: dms
4 #   Version: 20000124
5 #      NOTE: Based on code by Kevin Lenzo & Patrick Cole  (c) 1997
6 #
7
8 if (&IsParam("useStrict")) { use strict; }
9
10 sub help {
11     my $topic = shift;
12     my $file  = $bot_misc_dir."/blootbot.help";
13     my %help  = ();
14
15     # crude hack for pSReply() to work as expected.
16     $msgType = "private" if ($msgType eq "public");
17
18     if (!open(FILE, $file)) {
19         &ERROR("FAILED loadHelp ($file): $!");
20         return;
21     }
22
23     while (defined(my $help = <FILE>)) {
24         $help =~ s/^[\# ].*//;
25         chomp $help;
26         next unless $help;
27         my ($key, $val) = split(/:/, $help, 2);
28
29         $val =~ s/^\s+//;
30         $val =~ s/^D:/\002   Desc\002:/;
31         $val =~ s/^E:/\002Example\002:/;
32         $val =~ s/^N:/\002   NOTE\002:/;
33         $val =~ s/^U:/\002  Usage\002:/;
34         $val =~ s/##/$key/;
35         $val =~ s/__/\037/g;
36         $val =~ s/==/        /;
37
38         $help{$key}  = ""                if (!exists $help{$key});
39         $help{$key} .= $val."\n";
40     }
41     close FILE;
42
43     if (!defined $topic or $topic eq "") {
44         &msg($who, $help{'main'});
45
46         my $i = 0;
47         my @array;
48         my $count = scalar(keys %help);
49         my $reply;
50         foreach (sort keys %help) {
51             push(@array,$_);
52             $reply = scalar(@array) ." topics: ".
53                         join("\002,\002 ", @array);
54             $i++;
55
56             if (length $reply > 400 or $count == $i) {
57                 &msg($who,$reply);
58                 undef @array;
59             }
60         }
61
62         return '';
63     }
64
65     $topic = &fixString(lc $topic);
66
67     if (exists $help{$topic}) {
68         foreach (split /\n/, $help{$topic}) {
69             &performStrictReply($_);
70         }
71     } else {
72         &pSReply("no help on $topic.  Use 'help' without arguments.");
73     }
74
75     return '';
76 }
77
78 sub getPath {
79     my ($pathnfile) = @_;
80
81     ### TODO: gotta hate an if statement.
82     if ($pathnfile =~ /(.*)\/(.*?)$/) {
83         return $1;
84     } else {
85         return ".";
86     }
87 }
88
89 sub timeget {
90     if ($no_timehires) {        # fallback.
91         return time();
92     } else {                    # the real thing.
93         return [gettimeofday()];
94     }
95 }    
96
97 sub timedelta {
98     my($start_time) = shift;
99
100     if ($no_timehires) {        # fallback.
101         return time() - $start_time;
102     } else {                    # the real thing.
103         return tv_interval ($start_time);
104     }
105 }
106
107 ###
108 ### FORM Functions.
109 ###
110
111 ###
112 # Usage; &formListReply($rand, $prefix, @list);
113 sub formListReply {
114     my($rand, $prefix, @list) = @_;
115     my $total   = scalar @list;
116     my $maxshow = $param{'maxListReplyCount'}  || 10;
117     my $maxlen  = $param{'maxListReplyLength'} || 400;
118     my $reply;
119
120     # no results.
121     return $prefix ."returned no results." unless ($total);
122
123     # random.
124     if ($rand) {
125         my @rand;
126         foreach (&makeRandom($total)) {
127             push(@rand, $list[$_]);
128             last if (scalar @rand == $maxshow);
129         }
130         @list = @rand;
131     } elsif ($total > $maxshow) {
132         &status("formListReply: truncating list.");
133
134         @list = @list[0..$maxshow-1];
135     }
136
137     # form the reply.
138     while () {
139         $reply  = $prefix ."(\002". scalar(@list). "\002 shown";
140         $reply .= "; \002$total\002 total" if ($total != scalar @list);
141         $reply .= "): ". join(" \002;;\002 ",@list) .".";
142
143         last if (length($reply) < $maxlen and scalar(@list) <= $maxshow);
144         last if (scalar(@list) == 1);
145
146         pop @list;
147     }
148
149     return $reply;
150 }
151
152 ### Intelligence joining of arrays.
153 # Usage: &IJoin(@array);
154 sub IJoin {
155     if (!scalar @_) {
156         return "NULL";
157     } elsif (scalar @_ == 1) {
158         return $_[0];
159     } else {
160         return join(', ',@{_}[0..$#_-1]) . " and $_[$#_]";
161     }
162 }
163
164 #####
165 # Usage: &Time2String(seconds);
166 sub Time2String {
167     my $time = shift;
168     my $retval;
169
170     return("0s")
171                 if (!defined $time or $time !~ /\d+/ or $time <= 0);
172
173     my $s = int($time) % 60;
174     my $m = int($time / 60) % 60;
175     my $h = int($time / 3600) % 24;
176     my $d = int($time / 86400);
177
178     $retval .= sprintf(" \002%d\002d", $d) if ($d != 0);
179     $retval .= sprintf(" \002%d\002h", $h) if ($h != 0);
180     $retval .= sprintf(" \002%d\002m", $m) if ($m != 0);
181     $retval .= sprintf(" \002%d\002s", $s) if ($s != 0);
182
183     return substr($retval, 1);
184 }
185
186 ###
187 ### FIX Functions.
188 ###
189
190 # Usage: &fixFileList(@files);
191 sub fixFileList {
192     my @files = @_;
193     my %files;
194
195     # generate a hash list.
196     foreach (@files) {
197         if (/^(.*\/)(.*?)$/) {
198             $files{$1}{$2} = 1;
199         }
200     }
201     @files = ();        # reuse the array.
202
203     # sort the hash list appropriately.
204     foreach (sort keys %files) {
205         my $file = $_;
206         my @keys = sort keys %{$files{$file}};
207         my $i    = scalar(@keys);
208
209         if ($i > 1) {
210             $file .= "\002{\002". join("\002|\002", @keys) ."\002}\002";
211         } else {
212             $file .= $keys[0];
213         }
214
215         push(@files,$file);
216     }
217
218     return @files;
219 }
220
221 # Usage: &fixString($str);
222 sub fixString {
223     my ($str, $level) = @_;
224     if (!defined $str) {
225         &WARN("fixString: str == NULL.");
226         return '';
227     }
228
229     for ($str) {
230         s/^\s+//;               # remove start whitespaces.
231         s/\s+$//;               # remove end whitespaces.
232         s/\s+/ /g;              # remove excessive whitespaces.
233
234         next unless (defined $level);
235         if (s/[\cA-\c_]//ig) {          # remove control characters.
236             &DEBUG("stripped control chars");
237         }
238     }
239
240     return $str;
241 }
242
243 # Usage: &fixPlural($str,$int);
244 sub fixPlural {
245     my ($str,$int) = @_;
246
247     if (!defined $str) {
248         &WARN("fixPlural: str == NULL.");
249         return;
250     }
251
252     if (!defined $int or $int =~ /^\D+$/) {
253         &WARN("fixPlural: int != defined or int");
254         return $str;
255     }
256
257     if ($str eq "has") {
258         $str = "have"   if ($int > 1);
259     } elsif ($str eq "is") {
260         $str = "are"    if ($int > 1);
261     } elsif ($str eq "was") {
262         $str = "were"   if ($int > 1);
263     } elsif ($str eq "this") {
264         $str = "these"  if ($int > 1);
265     } elsif ($str =~ /y$/) {
266         if ($int > 1) {
267             if ($str =~ /ey$/) {
268                 $str .= "s";    # eg: "money" => "moneys".
269             } else {
270                 $str =~ s/y$/ies/;
271             }
272         }
273     } else {
274         $str .= "s"     if ($int != 1);
275     }
276
277     return $str;
278 }
279
280
281
282 ##########
283 ### get commands.
284 ###
285
286 sub getRandomLineFromFile {
287     my($file) = @_;
288
289     if (! -f $file) {
290         &WARN("gRLfF: file '$file' does not exist.");
291         return;
292     }
293
294     if (open(IN,$file)) {
295         my @lines = <IN>;
296
297         if (!scalar @lines) {
298             &ERROR("GRLF: nothing loaded?");
299             return;
300         }
301
302         while (my $line = &getRandom(@lines)) {
303             chop $line;
304
305             next if ($line =~ /^\#/);
306             next if ($line =~ /^\s*$/);
307
308             return $line;
309         }
310     } else {
311         &WARN("gRLfF: could not open file '$file'.");
312         return;
313     }
314 }
315
316 sub getLineFromFile {
317     my($file,$lineno) = @_;
318
319     if (! -f $file) {
320         &ERROR("getLineFromFile: file '$file' does not exist.");
321         return 0;
322     }
323
324     if (open(IN,$file)) {
325         my @lines = <IN>;
326         close IN;
327
328         if ($lineno > scalar @lines) {
329             &ERROR("getLineFromFile: lineno exceeds line count from file.");
330             return 0;
331         }
332
333         my $line = $lines[$lineno-1];
334         chop $line;
335         return $line;
336     } else {
337         &ERROR("getLineFromFile: could not open file '$file'.");
338         return 0;
339     }
340 }
341
342 # Usage: &getRandom(@array);
343 sub getRandom {
344     my @array = @_;
345
346     srand();
347     return $array[int(rand(scalar @array))];
348 }
349
350 # Usage: &getRandomInt("30-60");
351 sub getRandomInt {
352     my $str = $_[0];
353
354     if (!defined $str) {
355         &WARN("gRI: str == NULL.");
356         return;
357     }
358
359     srand();
360
361     if ($str =~ /^(\d+(\.\d+)?)$/) {
362         my $i = $1;
363         my $fuzzy = int(rand 5);
364         if ($i < 10) {
365             return $i*60;
366         }
367         if (rand > 0.5) {
368             return ($i - $fuzzy)*60;
369         } else {
370             return ($i + $fuzzy)*60;
371         }
372     } elsif ($str =~ /^(\d+)-(\d+)$/) {
373         return ($2 - $1)*int(rand $1)*60;
374     } else {
375         return $str;    # hope we're safe.
376     }
377
378     &ERROR("getRandomInt: invalid arg '$str'.");
379     return 1800;
380 }
381
382 ##########
383 ### Is commands.
384 ###
385
386 sub iseq {
387     my ($left,$right) = @_;
388     return 0 unless defined $right;
389     return 0 unless defined $left;
390     return 1 if ($left =~ /^\Q$right$/i);
391 }
392
393 sub isne {
394     my $retval = &iseq(@_);
395     return 1 unless ($retval);
396     return 0;
397 }
398
399 # Usage: &IsHostMatch($nuh);
400 sub IsHostMatch {
401     my ($thisnuh) = @_;
402     my (%this,%local);
403
404     if ($nuh =~ /^(\S+)!(\S+)@(\S+)/) {
405         $local{'nick'} = lc $1;
406         $local{'user'} = lc $2;
407         $local{'host'} = &makeHostMask(lc $3);
408     }
409
410     if ($thisnuh =~ /^(\S+)!(\S+)@(\S+)/) {
411         $this{'nick'} = lc $1;
412         $this{'user'} = lc $2;
413         $this{'host'} = &makeHostMask(lc $3);
414     } else {
415         &WARN("IHM: thisnuh is invalid '$thisnuh'.");
416         return 1 if ($thisnuh eq "");
417         return 0;
418     }
419
420     # auth if 1) user and host match 2) user and nick match.
421     # this may change in the future.
422
423     if ($this{'user'} =~ /^\Q$local{'user'}\E$/i) {
424         return 2 if ($this{'host'} eq $local{'host'});
425         return 1 if ($this{'nick'} eq $local{'nick'});
426     }
427     return 0;
428 }
429
430 ####
431 # Usage: &isStale($file, $age);
432 sub isStale {
433     my ($file, $age) = @_;
434
435     if (!defined $age) {
436         &WARN("isStale: age == NULL.");
437         return 1;
438     }
439
440     if (!defined $file) {
441         &WARN("isStale: file == NULL.");
442         return 1;
443     }
444
445     return 1 unless ( -f $file);
446     if ($file =~ /idx/) {
447         my $age = time() - (stat($file))[9];
448         &DEBUG("stale: $age. (". &Time2String($age) .")");
449     }
450     return 1 if (time() - (stat($file))[9] > $age*60*60*24);
451     return 0;
452 }
453
454 ##########
455 ### make commands.
456 ###
457
458 # Usage: &makeHostMask($host);
459 sub makeHostMask {
460     my ($host) = @_;
461
462     if ($host =~ /^$mask{ip}$/) {
463         return "$1.$2.$3.*";
464     }
465
466     my @array = split(/\./, $host);
467     return $host if (scalar @array <= 3);
468     return "*.".join('.',@{array}[1..$#array]);
469 }
470
471 # Usage: &makeRandom(int);
472 sub makeRandom {
473     my ($max) = @_;
474     my @retval;
475     my %done;
476
477     if ($max =~ /^\D+$/) {
478         &ERROR("makeRandom: arg ($max) is not integer.");
479         return 0;
480     }
481
482     if ($max < 1) {
483         &ERROR("makeRandom: arg ($max) is not positive.");
484         return 0;
485     }
486
487     srand();
488     while (scalar keys %done < $max) {
489         my $rand = int(rand $max);
490         next if (exists $done{$rand});
491
492         push(@retval,$rand);
493         $done{$rand} = 1;
494     }
495
496     return @retval;
497 }
498
499 sub checkMsgType {
500     my ($reply) = @_;
501     return unless (&IsParam("minLengthBeforePrivate"));
502     return if ($force_public_reply);
503
504     if (length $reply > $param{'minLengthBeforePrivate'}) {
505         &status("Reply: len reply > minLBP ($param{'minLengthBeforePrivate'}); msgType now private.");
506         $msgType = 'private';
507     }
508 }
509
510 ###
511 ### Valid.
512 ###
513
514 # Usage: &validExec($string);
515 sub validExec {
516     my ($str) = @_;
517
518     if ($str =~ /[\'\"\|]/) {   # invalid.
519         return 0;
520     } else {                    # valid.
521         return 1;
522     }
523 }
524
525 # Usage: &validFactoid($lhs,$rhs);
526 sub validFactoid {
527     my ($lhs,$rhs) = @_;
528     my $valid = 0;
529
530     for (lc $lhs) {
531         # allow the following only if they have been made on purpose.
532         if ($rhs ne "" and $rhs !~ /^</) {
533             / \Q$ident$/i and last;     # someone said i'm something.
534             /^i('m)? / and last;
535             /^(it|that|there|what)('s)?(\s+|$)/ and last;
536             /^you('re)?(\s+|$)/ and last;
537
538             /^(where|who|why|when|how)(\s+|$)/ and last;
539             /^(this|that|these|those|they)(\s+|$)/ and last;
540             /^(every(one|body)|we) / and last;
541
542             /^say / and last;
543         }
544
545         # uncaught commands.
546         /^add topic / and last;         # topic management.
547         /( add$| add |^add )/ and last; # borked teach statement.
548         /^learn / and last;             # teach. damn morons.
549         /^tell (\S+) about / and last;  # tell.
550         /\=\~/ and last;                # substituition.
551         /^\S+ to \S+ \S+/ and last;     # babelfish.
552
553         /^\=/ and last;                 # botnick = heh is.
554         /wants you to know/ and last;
555
556         # symbols.
557         /(\"\*)/ and last;
558         /, / and last;
559         /^\'/ and last;
560
561         # delimiters.
562         /\=\>/ and last;                # '=>'.
563         /\;\;/ and last;                # ';;'.
564         /\|\|/ and last;                # '||'.
565
566         /^\Q$ident\E[\'\,\: ]/ and last;# dupe addressed.
567         /^[\-\, ]/ and last;
568         /\\$/ and last;                 # forgot shift for '?'.
569         /^all / and last;
570         /^also / and last;
571         / also$/ and last;
572         / and$/ and last;
573         /^because / and last;
574         /^gives / and last;
575         /^h(is|er) / and last;
576         /^if / and last;
577         / is,/ and last;
578         / it$/ and last;
579         / says$/ and last;
580         /^should / and last;
581         /^so / and last;
582         /^supposedly/ and last;
583         /^to / and last;
584         /^was / and last;
585         / which$/ and last;
586
587         # nasty bug I introduced _somehow_, probably by fixMySQLBug().
588         /\\\%/ and last;
589         /\\\_/ and last;
590
591         # weird/special stuff. also old blootbot or stock infobot bugs.
592         $rhs =~ /( \Q$ident\E's|\Q$ident\E's )/i and last; # ownership.
593
594         # duplication.
595         $rhs =~ /^\Q$lhs /i and last;
596         last if ($rhs =~ /^is /i and / is$/);
597
598         $valid++;
599     }
600
601     return $valid;
602 }
603
604 # Usage: &hasProfanity($string);
605 sub hasProfanity {
606     my ($string) = @_;
607     my $profanity = 1;
608
609     for (lc $string) {
610         /fuck/ and last;
611         /dick|dildo/ and last;
612         /shit|turd|crap/ and last;
613         /pussy|[ck]unt/ and last;
614         /wh[0o]re|bitch|slut/ and last;
615
616         $profanity = 0;
617     }
618
619     return $profanity;
620 }
621
622 sub hasParam {
623     my ($param) = @_;
624
625     if (&IsChanConf($param) or &IsParam($param)) {
626         return 1;
627     } else {
628         ### TODO: specific reason why it failed.
629         &msg($who, "unfortunately, \002$param\002 is disabled in my configuration") unless ($addrchar);
630         return 0;
631     }
632 }
633
634 sub Forker {
635     my ($label, $code) = @_;
636     my $pid;
637
638     &shmFlush();
639     &VERB("double fork detected; not forking.",2) if ($$ != $bot_pid);
640
641     if (&IsParam("forking") and $$ == $bot_pid) {
642         return unless &addForked($label);
643
644         $SIG{CHLD} = 'IGNORE';
645         $pid = eval { fork() };
646         return if $pid;         # parent does nothing
647
648         select(undef, undef, undef, 0.2);
649 #       &status("fork starting for '$label', PID == $$.");
650         &status("--- fork starting for '$label', PID == $$ ---");
651         &shmWrite($shm,"SET FORKPID $label $$");
652
653         sleep 1;
654     }
655
656     ### TODO: use AUTOLOAD
657     ### very lame hack.
658     if ($label !~ /-/ and !&loadMyModule($myModules{$label})) {
659         &DEBUG("Forker: failed?");
660         &delForked($label);
661     }
662
663     if (defined $code) {
664         $code->();                      # weird, hey?
665     } else {
666         &WARN("Forker: code not defined!");
667     }
668
669     &delForked($label);
670 }
671
672 sub closePID {
673     return 1 unless (exists $file{PID});
674     return 1 unless ( -f $file{PID});
675     return 1 if (unlink $file{PID});
676     return 0 if ( -f $file{PID});
677 }
678
679 1;