2 # Misc.pl: Miscellaneous stuff.
5 # NOTE: Based on code by Kevin Lenzo & Patrick Cole (c) 1997
8 if (&IsParam("useStrict")) { use strict; }
12 my $file = $bot_misc_dir."/blootbot.help";
15 # crude hack for pSReply() to work as expected.
16 $msgType = "private" if ($msgType eq "public");
18 if (!open(FILE, $file)) {
19 &ERROR("FAILED loadHelp ($file): $!");
23 while (defined(my $help = <FILE>)) {
24 $help =~ s/^[\# ].*//;
27 my ($key, $val) = split(/:/, $help, 2);
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:/;
38 $help{$key} = "" if (!exists $help{$key});
39 $help{$key} .= $val."\n";
43 if (!defined $topic or $topic eq "") {
44 &msg($who, $help{'main'});
48 my $count = scalar(keys %help);
50 foreach (sort keys %help) {
52 $reply = scalar(@array) ." topics: ".
53 join("\002,\002 ", @array);
56 if (length $reply > 400 or $count == $i) {
65 $topic = &fixString(lc $topic);
67 if (exists $help{$topic}) {
68 foreach (split /\n/, $help{$topic}) {
69 &performStrictReply($_);
72 &pSReply("no help on $topic. Use 'help' without arguments.");
81 ### TODO: gotta hate an if statement.
82 if ($pathnfile =~ /(.*)\/(.*?)$/) {
90 if ($no_timehires) { # fallback.
92 } else { # the real thing.
93 return [gettimeofday()];
98 my($start_time) = shift;
100 if ($no_timehires) { # fallback.
101 return time() - $start_time;
102 } else { # the real thing.
103 return tv_interval ($start_time);
112 # Usage; &formListReply($rand, $prefix, @list);
114 my($rand, $prefix, @list) = @_;
115 my $total = scalar @list;
116 my $maxshow = $param{'maxListReplyCount'} || 10;
117 my $maxlen = $param{'maxListReplyLength'} || 400;
121 return $prefix ."returned no results." unless ($total);
126 foreach (&makeRandom($total)) {
127 push(@rand, $list[$_]);
128 last if (scalar @rand == $maxshow);
131 } elsif ($total > $maxshow) {
132 &status("formListReply: truncating list.");
134 @list = @list[0..$maxshow-1];
139 $reply = $prefix ."(\002". scalar(@list). "\002 shown";
140 $reply .= "; \002$total\002 total" if ($total != scalar @list);
141 $reply .= "): ". join(" \002;;\002 ",@list) .".";
143 last if (length($reply) < $maxlen and scalar(@list) <= $maxshow);
144 last if (scalar(@list) == 1);
152 ### Intelligence joining of arrays.
153 # Usage: &IJoin(@array);
157 } elsif (scalar @_ == 1) {
160 return join(', ',@{_}[0..$#_-1]) . " and $_[$#_]";
165 # Usage: &Time2String(seconds);
170 return("NULL s") if (!defined $time or $time !~ /\d+/);
178 my $s = int($time) % 60;
179 my $m = int($time / 60) % 60;
180 my $h = int($time / 3600) % 24;
181 my $d = int($time / 86400);
184 push(@data, sprintf("\002%d\002d", $d)) if ($d != 0);
185 push(@data, sprintf("\002%d\002h", $h)) if ($h != 0);
186 push(@data, sprintf("\002%d\002m", $m)) if ($m != 0);
187 push(@data, sprintf("\002%d\002s", $s)) if ($s != 0 or !@data);
189 return $prefix.join(' ', @data);
196 # Usage: &fixFileList(@files);
201 # generate a hash list.
203 if (/^(.*\/)(.*?)$/) {
207 @files = (); # reuse the array.
209 # sort the hash list appropriately.
210 foreach (sort keys %files) {
212 my @keys = sort keys %{ $files{$file} };
213 my $i = scalar(@keys);
215 if (scalar @keys > 3) {
216 pop @keys while (scalar @keys > 3);
221 $file .= "\002{\002". join("\002|\002", @keys) ."\002}\002";
232 # Usage: &fixString($str);
234 my ($str, $level) = @_;
236 &WARN("fixString: str == NULL.");
241 s/^\s+//; # remove start whitespaces.
242 s/\s+$//; # remove end whitespaces.
243 s/\s+/ /g; # remove excessive whitespaces.
245 next unless (defined $level);
246 if (s/[\cA-\c_]//ig) { # remove control characters.
247 &DEBUG("stripped control chars");
254 # Usage: &fixPlural($str,$int);
259 &WARN("fixPlural: str == NULL.");
263 if (!defined $int or $int =~ /^\D+$/) {
264 &WARN("fixPlural: int != defined or int");
269 $str = "have" if ($int > 1);
270 } elsif ($str eq "is") {
271 $str = "are" if ($int > 1);
272 } elsif ($str eq "was") {
273 $str = "were" if ($int > 1);
274 } elsif ($str eq "this") {
275 $str = "these" if ($int > 1);
276 } elsif ($str =~ /y$/) {
279 $str .= "s"; # eg: "money" => "moneys".
285 $str .= "s" if ($int != 1);
295 sub getRandomLineFromFile {
299 &WARN("gRLfF: file '$file' does not exist.");
303 if (open(IN,$file)) {
306 if (!scalar @lines) {
307 &ERROR("GRLF: nothing loaded?");
311 while (my $line = &getRandom(@lines)) {
314 next if ($line =~ /^\#/);
315 next if ($line =~ /^\s*$/);
320 &WARN("gRLfF: could not open file '$file'.");
325 sub getLineFromFile {
326 my($file,$lineno) = @_;
329 &ERROR("getLineFromFile: file '$file' does not exist.");
333 if (open(IN,$file)) {
337 if ($lineno > scalar @lines) {
338 &ERROR("getLineFromFile: lineno exceeds line count from file.");
342 my $line = $lines[$lineno-1];
346 &ERROR("getLineFromFile: could not open file '$file'.");
351 # Usage: &getRandom(@array);
356 return $array[int(rand(scalar @array))];
359 # Usage: &getRandomInt("30-60");
364 &WARN("gRI: str == NULL.");
370 if ($str =~ /^(\d+(\.\d+)?)$/) {
372 my $fuzzy = int(rand 5);
377 return ($i - $fuzzy)*60;
379 return ($i + $fuzzy)*60;
381 } elsif ($str =~ /^(\d+)-(\d+)$/) {
382 return ($2 - $1)*int(rand $1)*60;
384 return $str; # hope we're safe.
387 &ERROR("getRandomInt: invalid arg '$str'.");
396 my ($left,$right) = @_;
397 return 0 unless defined $right;
398 return 0 unless defined $left;
399 return 1 if ($left =~ /^\Q$right$/i);
403 my $retval = &iseq(@_);
404 return 1 unless ($retval);
408 # Usage: &IsHostMatch($nuh);
413 if ($nuh =~ /^(\S+)!(\S+)@(\S+)/) {
414 $local{'nick'} = lc $1;
415 $local{'user'} = lc $2;
416 $local{'host'} = &makeHostMask(lc $3);
419 if ($thisnuh =~ /^(\S+)!(\S+)@(\S+)/) {
420 $this{'nick'} = lc $1;
421 $this{'user'} = lc $2;
422 $this{'host'} = &makeHostMask(lc $3);
424 &WARN("IHM: thisnuh is invalid '$thisnuh'.");
425 return 1 if ($thisnuh eq "");
429 # auth if 1) user and host match 2) user and nick match.
430 # this may change in the future.
432 if ($this{'user'} =~ /^\Q$local{'user'}\E$/i) {
433 return 2 if ($this{'host'} eq $local{'host'});
434 return 1 if ($this{'nick'} eq $local{'nick'});
440 # Usage: &isStale($file, $age);
442 my ($file, $age) = @_;
445 &WARN("isStale: age == NULL.");
449 if (!defined $file) {
450 &WARN("isStale: file == NULL.");
454 &DEBUG("!exist $file") if (! -f $file);
456 return 1 unless ( -f $file);
457 if ($file =~ /idx/) {
458 my $age2 = time() - (stat($file))[9];
459 &VERB("stale: $age2. (". &Time2String($age2) .")",2);
461 $age *= 60*60*24 if ($age >= 0 and $age < 30);
463 return 1 if (time() - (stat($file))[9] > $age);
471 # Usage: &makeHostMask($host);
476 if ($host =~ s/^(\S+!\S+\@)//) {
477 &DEBUG("mHM: detected nick!user\@ for host arg; fixing");
481 if ($host =~ /^$mask{ip}$/) {
482 return $nu."$1.$2.$3.*";
485 my @array = split(/\./, $host);
486 return $nu.$host if (scalar @array <= 3);
487 return $nu."*.".join('.',@{array}[1..$#array]);
490 # Usage: &makeRandom(int);
496 if ($max =~ /^\D+$/) {
497 &ERROR("makeRandom: arg ($max) is not integer.");
502 &ERROR("makeRandom: arg ($max) is not positive.");
507 while (scalar keys %done < $max) {
508 my $rand = int(rand $max);
509 next if (exists $done{$rand});
520 return unless (&IsParam("minLengthBeforePrivate"));
521 return if ($force_public_reply);
523 if (length $reply > $param{'minLengthBeforePrivate'}) {
524 &status("Reply: len reply > minLBP ($param{'minLengthBeforePrivate'}); msgType now private.");
525 $msgType = 'private';
533 # Usage: &validExec($string);
537 if ($str =~ /[\'\"\|]/) { # invalid.
544 # Usage: &hasProfanity($string);
551 /dick|dildo/ and last;
552 /shit|turd|crap/ and last;
553 /pussy|[ck]unt/ and last;
554 /wh[0o]re|bitch|slut/ and last;
565 if (&IsChanConf($param) or &IsParam($param)) {
568 ### TODO: specific reason why it failed.
569 &msg($who, "unfortunately, \002$param\002 is disabled in my configuration") unless ($addrchar);
575 my ($label, $code) = @_;
579 &VERB("double fork detected; not forking.",2) if ($$ != $bot_pid);
581 if (&IsParam("forking") and $$ == $bot_pid) {
582 return unless &addForked($label);
584 $SIG{CHLD} = 'IGNORE';
585 $pid = eval { fork() };
586 return if $pid; # parent does nothing
588 select(undef, undef, undef, 0.2);
589 # &status("fork starting for '$label', PID == $$.");
590 &status("--- fork starting for '$label', PID == $$ ---");
591 &shmWrite($shm,"SET FORKPID $label $$");
596 ### TODO: use AUTOLOAD
598 if ($label !~ /-/ and !&loadMyModule($myModules{$label})) {
599 &DEBUG("Forker: failed?");
604 $code->(); # weird, hey?
606 &WARN("Forker: code not defined!");
613 return 1 unless (exists $file{PID});
614 return 1 unless ( -f $file{PID});
615 return 1 if (unlink $file{PID});
616 return 0 if ( -f $file{PID});
621 my $salt = join '',('.','/',0..9,'A'..'Z','a'..'z')[rand 64, rand 64];
623 return crypt($str, $salt);
627 return unless (&getChanConfList("ircTextCounters"));
629 foreach (keys %cmdstats) {
631 my $i = &dbGet("stats", "counter", "nick=".&dbQuote($type).
632 " AND type='cmdstats'");
636 $i += $cmdstats{$type};
643 $hash{time} = time() if ($z);
645 &dbReplace("stats", %hash);