2 # Misc.pl: Miscellaneous stuff.
5 # NOTE: Based on code by Kevin Lenzo & Patrick Cole (c) 1997
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);
16 my $file = $bot_data_dir."/infobot.help";
19 # crude hack for performStrictReply() to work as expected.
20 $msgType = 'private' if ($msgType eq 'public');
22 if (!open(FILE, $file)) {
23 &ERROR("Failed reading help file ($file): $!");
27 while (defined(my $help = <FILE>)) {
28 $help =~ s/^[\# ].*//;
31 my ($key, $val) = split(/:/, $help, 2);
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:/;
42 $help{$key} = '' if (!exists $help{$key});
43 $help{$key} .= $val."\n";
47 if (!defined $topic or $topic eq '') {
48 &msg($who, $help{'main'});
52 my $count = scalar(keys %help);
54 foreach (sort keys %help) {
56 $reply = scalar(@array) ." topics: ".
57 join("\002,\002 ", @array);
60 if (length $reply > 400 or $count == $i) {
69 $topic = &fixString(lc $topic);
71 if (exists $help{$topic}) {
72 foreach (split /\n/, $help{$topic}) {
73 &performStrictReply($_);
76 &performStrictReply("no help on $topic. Use 'help' without arguments.");
85 ### TODO: gotta hate an if statement.
86 if ($pathnfile =~ /(.*)\/(.*?)$/) {
94 if ($no_timehires) { # fallback.
96 } else { # the real thing.
97 return [gettimeofday()];
102 my($start_time) = shift;
104 if ($no_timehires) { # fallback.
105 return time() - $start_time;
106 } else { # the real thing.
107 return tv_interval ($start_time);
116 # Usage; &formListReply($rand, $prefix, @list);
118 my($rand, $prefix, @list) = @_;
119 my $total = scalar @list;
120 my $maxshow = &getChanConfDefault('maxListReplyCount', 15, $chan);
121 my $maxlen = &getChanConfDefault('maxListReplyLength', 400, $chan);
124 # remove irc overhead
128 return $prefix ."returned no results." unless ($total);
133 foreach (&makeRandom($total)) {
134 push(@rand, $list[$_]);
135 last if (scalar @rand == $maxshow);
137 if ($total > $maxshow) {
142 } elsif ($total > $maxshow) {
143 &status("formListReply: truncating list.");
145 @list = @list[0..$maxshow-1];
149 # FIXME: should grow and exit when full, not discard any that are oversize
151 $reply = $prefix ."(\002". scalar(@list). "\002";
152 $reply .= " of \002$total\002" if ($total != scalar @list);
153 $reply .= "): " . join(" \002;;\002 ", @list) .".";
155 last if (length($reply) < $maxlen and scalar(@list) <= $maxshow);
156 last if (scalar(@list) == 1);
164 ### Intelligence joining of arrays.
165 # Usage: &IJoin(@array);
169 } elsif (scalar @_ == 1) {
172 return join(', ',@{_}[0..$#_-1]) . " and $_[$#_]";
177 # Usage: &Time2String(seconds);
183 return 'NULL' if (!defined $time);
184 return $time if ($time !~ /\d+/);
191 $t[0] = int($time) % 60;
192 $t[1] = int($time / 60) % 60;
193 $t[2] = int($time / 3600) % 24;
194 $t[3] = int($time / 86400);
196 push(@s, "$t[3]d") if ($t[3] != 0);
197 push(@s, "$t[2]h") if ($t[2] != 0);
198 push(@s, "$t[1]m") if ($t[1] != 0);
199 push(@s, "$t[0]s") if ($t[0] != 0 or !@s);
201 my $retval = $prefix.join(' ', @s);
202 $retval =~ s/(\d+)/\002$1\002/g;
210 # Usage: &fixFileList(@files);
215 # generate a hash list.
217 next unless /^(.*\/)(.*?)$/;
221 @files = (); # reuse the array.
223 # sort the hash list appropriately.
224 foreach (sort keys %files) {
226 my @keys = sort keys %{ $files{$file} };
227 my $i = scalar(@keys);
229 if (scalar @keys > 3) {
230 pop @keys while (scalar @keys > 3);
235 $file .= "\002{\002". join("\002|\002", @keys) ."\002}\002";
246 # Usage: &fixString($str);
248 my ($str, $level) = @_;
250 &WARN("fixString: str == NULL.");
255 s/^\s+//; # remove start whitespaces.
256 s/\s+$//; # remove end whitespaces.
257 s/\s+/ /g; # remove excessive whitespaces.
259 next unless (defined $level);
260 if (s/[\cA-\c_]//ig) { # remove control characters.
261 &DEBUG("stripped control chars");
268 # Usage: &fixPlural($str,$int);
273 &WARN("fixPlural: str == NULL.");
277 if (!defined $int or $int =~ /^\D+$/) {
278 &WARN("fixPlural: int != defined or int");
283 $str = 'have' if ($int > 1);
284 } elsif ($str eq 'is') {
285 $str = 'are' if ($int > 1);
286 } elsif ($str eq 'was') {
287 $str = 'were' if ($int > 1);
288 } elsif ($str eq 'this') {
289 $str = 'these' if ($int > 1);
290 } elsif ($str =~ /y$/) {
293 $str .= 's'; # eg: 'money' => 'moneys'.
299 $str .= 's' if ($int != 1);
309 sub getRandomLineFromFile {
312 if (!open(IN, $file)) {
313 &WARN("gRLfF: could not open ($file): $!");
320 if (!scalar @lines) {
321 &ERROR("GRLF: nothing loaded?");
325 # could we use the filehandler instead and put it through getRandom?
326 while (my $line = &getRandom(@lines)) {
329 next if ($line =~ /^\#/);
330 next if ($line =~ /^\s*$/);
336 sub getLineFromFile {
337 my($file,$lineno) = @_;
340 &ERROR("getLineFromFile: file '$file' does not exist.");
344 if (open(IN,$file)) {
348 if ($lineno > scalar @lines) {
349 &ERROR("getLineFromFile: lineno exceeds line count from file.");
353 my $line = $lines[$lineno-1];
357 &ERROR("gLFF: Could not open file ($file): $!");
362 # Usage: &getRandom(@array);
367 return $array[int(rand(scalar @array))];
370 # Usage: &getRandomInt("30-60"); &getRandomInt(5);
371 # Desc : Returns a randomn integer between "X-Y" or 1 and the value passed
375 if ( !defined $str ) {
376 &WARN("getRandomInt: str == NULL.");
380 if ( $str =~ /^(\d+(\.\d+)?)$/ ) {
381 return int( rand $str ) + 1;
382 } elsif ( $str =~ /^(\d+)-(\d+)$/ ) {
383 return $1 if $1 == $2;
384 my $min = $1 < $2 ? $1 : $2; # Swap is backwords
385 my $max = $2 > $1 ? $2 : $1;
386 return int( rand( $max - $min + 1 ) ) + $min;
389 # &ERROR("getRandomInt: invalid arg '$str'.");
399 my ($left,$right) = @_;
400 return 0 unless defined $right;
401 return 0 unless defined $left;
402 return 1 if ($left =~ /^\Q$right$/i);
406 my $retval = &iseq(@_);
407 return 1 unless ($retval);
411 # Usage: &IsHostMatch($nuh);
416 if ($nuh =~ /^(\S+)!(\S+)@(\S+)/) {
417 $local{'nick'} = lc $1;
418 $local{'user'} = lc $2;
419 $local{'host'} = &makeHostMask(lc $3);
422 if (!defined $thisnuh) {
423 &WARN("IHM: thisnuh == NULL.");
425 } elsif ($thisnuh =~ /^(\S+)!(\S+)@(\S+)/) {
426 $this{'nick'} = lc $1;
427 $this{'user'} = lc $2;
428 $this{'host'} = &makeHostMask(lc $3);
430 &WARN("IHM: thisnuh is invalid '$thisnuh'.");
431 return 1 if ($thisnuh eq '');
435 # auth if 1) user and host match 2) user and nick match.
436 # this may change in the future.
438 if ($this{'user'} =~ /^\Q$local{'user'}\E$/i) {
439 return 2 if ($this{'host'} eq $local{'host'});
440 return 1 if ($this{'nick'} eq $local{'nick'});
446 # Usage: &isStale($file, $age);
448 my ($file, $age) = @_;
451 &WARN("isStale: age == NULL.");
455 if (!defined $file) {
456 &WARN("isStale: file == NULL.");
460 &DEBUG("!exist $file") if (! -f $file);
462 return 1 unless ( -f $file);
463 if ($file =~ /idx/) {
464 my $age2 = time() - (stat($file))[9];
465 &VERB("stale: $age2. (". &Time2String($age2) .")",2);
467 $age *= 60*60*24 if ($age >= 0 and $age < 30);
469 return 1 if (time() - (stat($file))[9] > $age);
474 my ($file, $time) = @_;
480 my $time_file = (stat $file)[9];
482 if ($time <= $time_file) {
493 # Usage: &makeHostMask($host);
498 if ($host =~ s/^(\S+!\S+\@)//) {
499 &DEBUG("mHM: detected nick!user\@ for host arg; fixing");
504 if ($host =~ /^$mask{ip}$/) {
505 return $nu."$1.$2.$3.*";
508 my @array = split(/\./, $host);
509 return $nu.$host if (scalar @array <= 3);
510 return $nu."*.".join('.',@{array}[1..$#array]);
513 # Usage: &makeRandom(int);
519 if ($max =~ /^\D+$/) {
520 &ERROR("makeRandom: arg ($max) is not integer.");
525 &ERROR("makeRandom: arg ($max) is not positive.");
530 while (scalar keys %done < $max) {
531 my $rand = int(rand $max);
532 next if (exists $done{$rand});
543 return unless (&IsParam('minLengthBeforePrivate'));
544 return if ($force_public_reply);
546 if (length $reply > $param{'minLengthBeforePrivate'}) {
547 &status("Reply: len reply > minLBP ($param{'minLengthBeforePrivate'}); msgType now private.");
548 $msgType = 'private';
556 # Usage: &validExec($string);
560 if ($str =~ /[\`\'\"\|]/) { # invalid.
567 # Usage: &hasProfanity($string);
574 /dick|dildo/ and last;
576 /pussy|[ck]unt/ and last;
577 /wh[0o]re|bitch|slut/ and last;
585 sub IsChanConfOrWarn {
588 if (&IsChanConf($param) > 0) {
591 ### TODO: specific reason why it failed.
592 &msg($who, "unfortunately, \002$param\002 is disabled in my configuration") unless ($addrchar);
598 my ($label, $code) = @_;
602 &VERB("double fork detected; not forking.",2) if ($$ != $bot_pid);
604 if (&IsParam('forking') and $$ == $bot_pid) {
605 return unless &addForked($label);
607 $SIG{CHLD} = 'IGNORE';
608 $pid = eval { fork() };
609 return if $pid; # parent does nothing
611 select(undef, undef, undef, 0.2);
612 # &status("fork starting for '$label', PID == $$.");
613 &status("--- fork starting for '$label', PID == $$, bot_pid == $bot_pid ---");
614 &shmWrite($shm,"SET FORKPID $label $$");
619 ### TODO: use AUTOLOAD
621 if ($label !~ /-/ and !&loadMyModule($label)) {
622 &DEBUG("Forker: failed?");
627 $code->(); # weird, hey?
629 &WARN("Forker: code not defined!");
636 return 1 unless (exists $file{PID});
637 return 1 unless ( -f $file{PID});
638 return 1 if (unlink $file{PID});
639 return 0 if ( -f $file{PID});
644 my $salt = join '',('.','/',0..9,'A'..'Z','a'..'z')[rand 64, rand 64];
646 return crypt($str, $salt);
650 return unless (&getChanConfList('ircTextCounters'));
652 foreach (keys %cmdstats) {
654 my $i = &sqlSelect('stats', 'counter', {
661 $i += $cmdstats{$type};
664 &sqlSet('stats', {'nick' => $type}, {
674 # vim:ts=4:sw=4:expandtab:tw=80