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