]> git.donarmstrong.com Git - infobot.git/blob - src/Misc.pl
tiny changes that I've missed
[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 ($str eq "has") {
253         $str = "have"   if ($int > 1);
254     } elsif ($str eq "is") {
255         $str = "are"    if ($int > 1);
256     } elsif ($str eq "was") {
257         $str = "were"   if ($int > 1);
258     } elsif ($str eq "this") {
259         $str = "these"  if ($int > 1);
260     } elsif ($str =~ /y$/) {
261         if ($int > 1) {
262             if ($str =~ /ey$/) {
263                 $str .= "s";    # eg: "money" => "moneys".
264             } else {
265                 $str =~ s/y$/ies/;
266             }
267         }
268     } else {
269         $str .= "s"     if ($int != 1);
270     }
271
272     return $str;
273 }
274
275
276
277 ##########
278 ### get commands.
279 ###
280
281 sub getRandomLineFromFile {
282     my($file) = @_;
283
284     if (! -f $file) {
285         &WARN("gRLfF: file '$file' does not exist.");
286         return;
287     }
288
289     if (open(IN,$file)) {
290         my @lines = <IN>;
291
292         if (!scalar @lines) {
293             &ERROR("GRLF: nothing loaded?");
294             return;
295         }
296
297         while (my $line = &getRandom(@lines)) {
298             chop $line;
299
300             next if ($line =~ /^\#/);
301             next if ($line =~ /^\s*$/);
302
303             return $line;
304         }
305     } else {
306         &WARN("gRLfF: could not open file '$file'.");
307         return;
308     }
309 }
310
311 sub getLineFromFile {
312     my($file,$lineno) = @_;
313
314     if (! -f $file) {
315         &ERROR("getLineFromFile: file '$file' does not exist.");
316         return 0;
317     }
318
319     if (open(IN,$file)) {
320         my @lines = <IN>;
321         close IN;
322
323         if ($lineno > scalar @lines) {
324             &ERROR("getLineFromFile: lineno exceeds line count from file.");
325             return 0;
326         }
327
328         my $line = $lines[$lineno-1];
329         chop $line;
330         return $line;
331     } else {
332         &ERROR("getLineFromFile: could not open file '$file'.");
333         return 0;
334     }
335 }
336
337 # Usage: &getRandom(@array);
338 sub getRandom {
339     my @array = @_;
340
341     srand();
342     return $array[int(rand(scalar @array))];
343 }
344
345 # Usage: &getRandomInt("30-60");
346 sub getRandomInt {
347     my $str = $_[0];
348
349     if (!defined $str) {
350         &WARN("gRI: str == NULL.");
351         return;
352     }
353
354     srand();
355
356     if ($str =~ /^(\d+)$/) {
357         my $i = $1;
358         my $fuzzy = int(rand 5);
359         if ($i < 10) {
360             return $i*60;
361         }
362         if (rand > 0.5) {
363             return ($i - $fuzzy)*60;
364         } else {
365             return ($i + $fuzzy)*60;
366         }
367     } elsif ($str =~ /^(\d+)-(\d+)$/) {
368         return ($2 - $1)*int(rand $1)*60;
369     } else {
370         return $str;    # hope we're safe.
371     }
372
373     &ERROR("getRandomInt: invalid arg '$str'.");
374     return 1800;
375 }
376
377 ##########
378 ### Is commands.
379 ###
380
381 sub iseq {
382     my ($left,$right) = @_;
383     return 0 unless defined $right;
384     return 0 unless defined $left;
385     return 1 if ($left =~ /^\Q$right$/i);
386 }
387
388 sub isne {
389     my $retval = &iseq(@_);
390     return 1 unless ($retval);
391     return 0;
392 }
393
394 # Usage: &IsHostMatch($nuh);
395 sub IsHostMatch {
396     my ($thisnuh) = @_;
397     my (%this,%local);
398
399     if ($nuh =~ /^(\S+)!(\S+)@(\S+)/) {
400         $local{'nick'} = lc $1;
401         $local{'user'} = lc $2;
402         $local{'host'} = &makeHostMask(lc $3);
403     }
404
405     if ($thisnuh =~ /^(\S+)!(\S+)@(\S+)/) {
406         $this{'nick'} = lc $1;
407         $this{'user'} = lc $2;
408         $this{'host'} = &makeHostMask(lc $3);
409     } else {
410         &WARN("IHM: thisnuh is invalid '$thisnuh'.");
411         return 1 if ($thisnuh eq "");
412         return 0;
413     }
414
415     # auth if 1) user and host match 2) user and nick match.
416     # this may change in the future.
417
418     if ($this{'user'} =~ /^\Q$local{'user'}\E$/i) {
419         return 2 if ($this{'host'} eq $local{'host'});
420         return 1 if ($this{'nick'} eq $local{'nick'});
421     }
422     return 0;
423 }
424
425 ####
426 # Usage: &isStale($file, $age);
427 sub isStale {
428     my ($file, $age) = @_;
429
430     if (!defined $age) {
431         &WARN("isStale: age == NULL.");
432         return 1;
433     }
434
435     return 1 unless ( -f $file);
436     return 1 if (time() - (stat($file))[9] > $age*60*60*24);
437     my $delta = time() - (stat($file))[9];
438     my $hage  = $age*60*60*24;
439     return 0;
440 }
441
442 ##########
443 ### make commands.
444 ###
445
446 # Usage: &makeHostMask($host);
447 sub makeHostMask {
448     my ($host) = @_;
449
450     if ($host =~ /^$mask{ip}$/) {
451         return "$1.$2.$3.*";
452     }
453
454     my @array = split(/\./, $host);
455     return $host if (scalar @array <= 3);
456     return "*.".join('.',@{array}[1..$#array]);
457 }
458
459 # Usage: &makeRandom(int);
460 sub makeRandom {
461     my ($max) = @_;
462     my @retval;
463     my %done;
464
465     if ($max =~ /^\D+$/) {
466         &ERROR("makeRandom: arg ($max) is not integer.");
467         return 0;
468     }
469
470     if ($max < 1) {
471         &ERROR("makeRandom: arg ($max) is not positive.");
472         return 0;
473     }
474
475     srand();
476     while (scalar keys %done < $max) {
477         my $rand = int(rand $max);
478         next if (exists $done{$rand});
479
480         push(@retval,$rand);
481         $done{$rand} = 1;
482     }
483
484     return @retval;
485 }
486
487 sub checkMsgType {
488     my ($reply) = @_;
489     return unless (&IsParam("minLengthBeforePrivate"));
490     return if ($force_public_reply);
491
492     if (length $reply > $param{'minLengthBeforePrivate'}) {
493         &status("Reply: len reply > minLBP ($param{'minLengthBeforePrivate'}); msgType now private.");
494         $msgType = 'private';
495     }
496 }
497
498 ###
499 ### Valid.
500 ###
501
502 # Usage: &validExec($string);
503 sub validExec {
504     my ($str) = @_;
505
506     if ($str =~ /[\'\"\|]/) {   # invalid.
507         return 0;
508     } else {                    # valid.
509         return 1;
510     }
511 }
512
513 # Usage: &validFactoid($lhs,$rhs);
514 sub validFactoid {
515     my ($lhs,$rhs) = @_;
516     my $valid = 0;
517
518     for (lc $lhs) {
519         # allow the following only if they have been made on purpose.
520         if ($rhs ne "" and $rhs !~ /^</) {
521             / \Q$ident$/i and last;     # someone said i'm something.
522             /^i('m)? / and last;
523             /^(it|that|there|what)('s)?(\s+|$)/ and last;
524             /^you('re)?(\s+|$)/ and last;
525
526             /^(where|who|why|when|how)(\s+|$)/ and last;
527             /^(this|that|these|those|they)(\s+|$)/ and last;
528             /^(every(one|body)|we) / and last;
529
530             /^say / and last;
531         }
532
533         # uncaught commands.
534         /^add topic / and last;         # topic management.
535         /( add$| add |^add )/ and last; # borked teach statement.
536         /^learn / and last;             # teach. damn morons.
537         /^tell (\S+) about / and last;  # tell.
538         /\=\~/ and last;                # substituition.
539         /^\S+ to \S+ \S+/ and last;     # babelfish.
540
541         /^\=/ and last;                 # botnick = heh is.
542         /wants you to know/ and last;
543
544         # symbols.
545         /(\"\*)/ and last;
546         /, / and last;
547         /^\'/ and last;
548
549         # delimiters.
550         /\=\>/ and last;                # '=>'.
551         /\;\;/ and last;                # ';;'.
552         /\|\|/ and last;                # '||'.
553
554         /^\Q$ident\E[\'\,\: ]/ and last;# dupe addressed.
555         /^[\-\, ]/ and last;
556         /\\$/ and last;                 # forgot shift for '?'.
557         /^all / and last;
558         /^also / and last;
559         / also$/ and last;
560         / and$/ and last;
561         /^because / and last;
562         /^gives / and last;
563         /^h(is|er) / and last;
564         /^if / and last;
565         / is,/ and last;
566         / it$/ and last;
567         / says$/ and last;
568         /^should / and last;
569         /^so / and last;
570         /^supposedly/ and last;
571         /^to / and last;
572         /^was / and last;
573         / which$/ and last;
574
575         # nasty bug I introduced _somehow_, probably by fixMySQLBug().
576         /\\\%/ and last;
577         /\\\_/ and last;
578
579         # weird/special stuff. also old blootbot or stock infobot bugs.
580         $rhs =~ /( \Q$ident\E's|\Q$ident\E's )/i and last; # ownership.
581
582         # duplication.
583         $rhs =~ /^\Q$lhs /i and last;
584         last if ($rhs =~ /^is /i and / is$/);
585
586         $valid++;
587     }
588
589     return $valid;
590 }
591
592 # Usage: &hasProfanity($string);
593 sub hasProfanity {
594     my ($string) = @_;
595     my $profanity = 1;
596
597     for (lc $string) {
598         /fuck/ and last;
599         /dick|dildo/ and last;
600         /shit|turd|crap/ and last;
601         /pussy|[ck]unt/ and last;
602         /wh[0o]re|bitch|slut/ and last;
603
604         $profanity = 0;
605     }
606
607     return $profanity;
608 }
609
610 ### rename to hasChanConf() ?
611 sub hasParam {
612     my ($param) = @_;
613
614     if (&IsChanConf($param)) {
615         return 1;
616     } else {
617         ### TODO: specific reason why it failed.
618         &msg($who, "unfortunately, \002$param\002 is disabled in my configuration") unless ($addrchar);
619         return 0;
620     }
621 }
622
623 sub Forker {
624     my ($label, $code) = @_;
625     my $pid;
626
627     &shmFlush();
628     &VERB("double fork detected; not forking.",2) if ($$ != $bot_pid);
629
630     if (&IsParam("forking") and $$ == $bot_pid) {
631         return unless &addForked($label);
632
633         $SIG{CHLD} = 'IGNORE';
634         $pid = eval { fork() };
635         return if $pid;         # parent does nothing
636
637         select(undef, undef, undef, 0.2);
638 #       &status("fork starting for '$label', PID == $$.");
639         &status("--- fork starting for '$label', PID == $$ ---");
640         &shmWrite($shm,"SET FORKPID $label $$");
641
642         sleep 1;
643     }
644
645     ### TODO: use AUTOLOAD
646     ### very lame hack.
647     if ($label !~ /-/ and !&loadMyModule($myModules{$label})) {
648         &DEBUG("Forker: failed?");
649         &delForked($label);
650     }
651
652     if (defined $code) {
653         $code->();                      # weird, hey?
654     } else {
655         &WARN("Forker: code not defined!");
656     }
657
658     &delForked($label);
659 }
660
661 sub closePID {
662     return 1 unless (exists $file{PID});
663     return 1 unless ( -f $file{PID});
664     return 1 if (unlink $file{PID});
665     return 0 if ( -f $file{PID});
666 }
667
668 1;