]> git.donarmstrong.com Git - infobot.git/blob - src/Misc.pl
- $nuh{} fix up.
[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     return 1 unless ( -f $file);
441     return 1 if (time() - (stat($file))[9] > $age*60*60*24);
442     my $delta = time() - (stat($file))[9];
443     my $hage  = $age*60*60*24;
444     return 0;
445 }
446
447 ##########
448 ### make commands.
449 ###
450
451 # Usage: &makeHostMask($host);
452 sub makeHostMask {
453     my ($host) = @_;
454
455     if ($host =~ /^$mask{ip}$/) {
456         return "$1.$2.$3.*";
457     }
458
459     my @array = split(/\./, $host);
460     return $host if (scalar @array <= 3);
461     return "*.".join('.',@{array}[1..$#array]);
462 }
463
464 # Usage: &makeRandom(int);
465 sub makeRandom {
466     my ($max) = @_;
467     my @retval;
468     my %done;
469
470     if ($max =~ /^\D+$/) {
471         &ERROR("makeRandom: arg ($max) is not integer.");
472         return 0;
473     }
474
475     if ($max < 1) {
476         &ERROR("makeRandom: arg ($max) is not positive.");
477         return 0;
478     }
479
480     srand();
481     while (scalar keys %done < $max) {
482         my $rand = int(rand $max);
483         next if (exists $done{$rand});
484
485         push(@retval,$rand);
486         $done{$rand} = 1;
487     }
488
489     return @retval;
490 }
491
492 sub checkMsgType {
493     my ($reply) = @_;
494     return unless (&IsParam("minLengthBeforePrivate"));
495     return if ($force_public_reply);
496
497     if (length $reply > $param{'minLengthBeforePrivate'}) {
498         &status("Reply: len reply > minLBP ($param{'minLengthBeforePrivate'}); msgType now private.");
499         $msgType = 'private';
500     }
501 }
502
503 ###
504 ### Valid.
505 ###
506
507 # Usage: &validExec($string);
508 sub validExec {
509     my ($str) = @_;
510
511     if ($str =~ /[\'\"\|]/) {   # invalid.
512         return 0;
513     } else {                    # valid.
514         return 1;
515     }
516 }
517
518 # Usage: &validFactoid($lhs,$rhs);
519 sub validFactoid {
520     my ($lhs,$rhs) = @_;
521     my $valid = 0;
522
523     for (lc $lhs) {
524         # allow the following only if they have been made on purpose.
525         if ($rhs ne "" and $rhs !~ /^</) {
526             / \Q$ident$/i and last;     # someone said i'm something.
527             /^i('m)? / and last;
528             /^(it|that|there|what)('s)?(\s+|$)/ and last;
529             /^you('re)?(\s+|$)/ and last;
530
531             /^(where|who|why|when|how)(\s+|$)/ and last;
532             /^(this|that|these|those|they)(\s+|$)/ and last;
533             /^(every(one|body)|we) / and last;
534
535             /^say / and last;
536         }
537
538         # uncaught commands.
539         /^add topic / and last;         # topic management.
540         /( add$| add |^add )/ and last; # borked teach statement.
541         /^learn / and last;             # teach. damn morons.
542         /^tell (\S+) about / and last;  # tell.
543         /\=\~/ and last;                # substituition.
544         /^\S+ to \S+ \S+/ and last;     # babelfish.
545
546         /^\=/ and last;                 # botnick = heh is.
547         /wants you to know/ and last;
548
549         # symbols.
550         /(\"\*)/ and last;
551         /, / and last;
552         /^\'/ and last;
553
554         # delimiters.
555         /\=\>/ and last;                # '=>'.
556         /\;\;/ and last;                # ';;'.
557         /\|\|/ and last;                # '||'.
558
559         /^\Q$ident\E[\'\,\: ]/ and last;# dupe addressed.
560         /^[\-\, ]/ and last;
561         /\\$/ and last;                 # forgot shift for '?'.
562         /^all / and last;
563         /^also / and last;
564         / also$/ and last;
565         / and$/ and last;
566         /^because / and last;
567         /^gives / and last;
568         /^h(is|er) / and last;
569         /^if / and last;
570         / is,/ and last;
571         / it$/ and last;
572         / says$/ and last;
573         /^should / and last;
574         /^so / and last;
575         /^supposedly/ and last;
576         /^to / and last;
577         /^was / and last;
578         / which$/ and last;
579
580         # nasty bug I introduced _somehow_, probably by fixMySQLBug().
581         /\\\%/ and last;
582         /\\\_/ and last;
583
584         # weird/special stuff. also old blootbot or stock infobot bugs.
585         $rhs =~ /( \Q$ident\E's|\Q$ident\E's )/i and last; # ownership.
586
587         # duplication.
588         $rhs =~ /^\Q$lhs /i and last;
589         last if ($rhs =~ /^is /i and / is$/);
590
591         $valid++;
592     }
593
594     return $valid;
595 }
596
597 # Usage: &hasProfanity($string);
598 sub hasProfanity {
599     my ($string) = @_;
600     my $profanity = 1;
601
602     for (lc $string) {
603         /fuck/ and last;
604         /dick|dildo/ and last;
605         /shit|turd|crap/ and last;
606         /pussy|[ck]unt/ and last;
607         /wh[0o]re|bitch|slut/ and last;
608
609         $profanity = 0;
610     }
611
612     return $profanity;
613 }
614
615 ### rename to hasChanConf() ?
616 sub hasParam {
617     my ($param) = @_;
618
619     if (&IsChanConf($param)) {
620         return 1;
621     } else {
622         ### TODO: specific reason why it failed.
623         &msg($who, "unfortunately, \002$param\002 is disabled in my configuration") unless ($addrchar);
624         return 0;
625     }
626 }
627
628 sub Forker {
629     my ($label, $code) = @_;
630     my $pid;
631
632     &shmFlush();
633     &VERB("double fork detected; not forking.",2) if ($$ != $bot_pid);
634
635     if (&IsParam("forking") and $$ == $bot_pid) {
636         return unless &addForked($label);
637
638         $SIG{CHLD} = 'IGNORE';
639         $pid = eval { fork() };
640         return if $pid;         # parent does nothing
641
642         select(undef, undef, undef, 0.2);
643 #       &status("fork starting for '$label', PID == $$.");
644         &status("--- fork starting for '$label', PID == $$ ---");
645         &shmWrite($shm,"SET FORKPID $label $$");
646
647         sleep 1;
648     }
649
650     ### TODO: use AUTOLOAD
651     ### very lame hack.
652     if ($label !~ /-/ and !&loadMyModule($myModules{$label})) {
653         &DEBUG("Forker: failed?");
654         &delForked($label);
655     }
656
657     if (defined $code) {
658         $code->();                      # weird, hey?
659     } else {
660         &WARN("Forker: code not defined!");
661     }
662
663     &delForked($label);
664 }
665
666 sub closePID {
667     return 1 unless (exists $file{PID});
668     return 1 unless ( -f $file{PID});
669     return 1 if (unlink $file{PID});
670     return 0 if ( -f $file{PID});
671 }
672
673 1;