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