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 ) {
57 scalar(@array) . ' topics: ' . 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($_);
78 "no help on $topic. Use 'help' without arguments.");
87 ### TODO: gotta hate an if statement.
88 if ( $pathnfile =~ /(.*)\/(.*?)$/ ) {
97 if ($no_timehires) { # fallback.
100 else { # the real thing.
101 return [ gettimeofday() ];
106 my ($start_time) = shift;
108 if ($no_timehires) { # fallback.
109 return time() - $start_time;
111 else { # the real thing.
112 return tv_interval($start_time);
121 # Usage; &formListReply($rand, $prefix, @list);
123 my ( $rand, $prefix, @list ) = @_;
124 my $total = scalar @list;
125 my $maxshow = &getChanConfDefault( 'maxListReplyCount', 15, $chan );
126 my $maxlen = &getChanConfDefault( 'maxListReplyLength', 400, $chan );
129 # remove irc overhead
133 return $prefix . 'returned no results.' unless ($total);
138 foreach ( &makeRandom($total) ) {
139 push( @rand, $list[$_] );
140 last if ( scalar @rand == $maxshow );
142 if ( $total > $maxshow ) {
149 elsif ( $total > $maxshow ) {
150 &status('formListReply: truncating list.');
152 @list = @list[ 0 .. $maxshow - 1 ];
156 # FIXME: should grow and exit when full, not discard any that are oversize
158 $reply = $prefix . "(\002" . scalar(@list) . "\002";
159 $reply .= " of \002$total\002" if ( $total != scalar @list );
160 $reply .= '): ' . join( " \002;;\002 ", @list ) . '.';
162 last if ( length($reply) < $maxlen and scalar(@list) <= $maxshow );
163 last if ( scalar(@list) == 1 );
171 ### Intelligence joining of arrays.
172 # Usage: &IJoin(@array);
177 elsif ( scalar @_ == 1 ) {
181 return join( ', ', @{_}[ 0 .. $#_ - 1 ] ) . " and $_[$#_]";
186 # Usage: &Time2String(seconds);
192 return 'NULL' if ( !defined $time );
193 return $time if ( $time !~ /\d+/ );
200 $t[0] = int($time) % 60;
201 $t[1] = int( $time / 60 ) % 60;
202 $t[2] = int( $time / 3600 ) % 24;
203 $t[3] = int( $time / 86400 );
205 push( @s, "$t[3]d" ) if ( $t[3] != 0 );
206 push( @s, "$t[2]h" ) if ( $t[2] != 0 );
207 push( @s, "$t[1]m" ) if ( $t[1] != 0 );
208 push( @s, "$t[0]s" ) if ( $t[0] != 0 or !@s );
210 my $retval = $prefix . join( ' ', @s );
211 $retval =~ s/(\d+)/\002$1\002/g;
219 # Usage: &fixFileList(@files);
224 # generate a hash list.
226 next unless /^(.*\/)(.*?)$/;
230 @files = (); # reuse the array.
232 # sort the hash list appropriately.
233 foreach ( sort keys %files ) {
235 my @keys = sort keys %{ $files{$file} };
236 my $i = scalar(@keys);
238 if ( scalar @keys > 3 ) {
239 pop @keys while ( scalar @keys > 3 );
240 push( @keys, '...' );
244 $file .= "\002{\002" . join( "\002|\002", @keys ) . "\002}\002";
250 push( @files, $file );
256 # Usage: &fixString($str);
258 my ( $str, $level ) = @_;
259 if ( !defined $str ) {
260 &WARN('fixString: str == NULL.');
265 s/^\s+//; # remove start whitespaces.
266 s/\s+$//; # remove end whitespaces.
267 s/\s+/ /g; # remove excessive whitespaces.
269 next unless ( defined $level );
270 if (s/[\cA-\c_]//ig) { # remove control characters.
271 &DEBUG('stripped control chars');
278 # Usage: &fixPlural($str,$int);
280 my ( $str, $int ) = @_;
282 if ( !defined $str ) {
283 &WARN('fixPlural: str == NULL.');
287 if ( !defined $int or $int =~ /^\D+$/ ) {
288 &WARN('fixPlural: int != defined or int');
292 if ( $str eq 'has' ) {
293 $str = 'have' if ( $int > 1 );
295 elsif ( $str eq 'is' ) {
296 $str = 'are' if ( $int > 1 );
298 elsif ( $str eq 'was' ) {
299 $str = 'were' if ( $int > 1 );
301 elsif ( $str eq 'this' ) {
302 $str = 'these' if ( $int > 1 );
304 elsif ( $str =~ /y$/ ) {
306 if ( $str =~ /ey$/ ) {
307 $str .= 's'; # eg: 'money' => 'moneys'.
315 $str .= 's' if ( $int != 1 );
325 sub getRandomLineFromFile {
328 if ( !open( IN, $file ) ) {
329 &WARN("gRLfF: could not open ($file): $!");
336 if ( !scalar @lines ) {
337 &ERROR('GRLF: nothing loaded?');
341 # could we use the filehandler instead and put it through getRandom?
342 while ( my $line = &getRandom(@lines) ) {
345 next if ( $line =~ /^\#/ );
346 next if ( $line =~ /^\s*$/ );
352 sub getLineFromFile {
353 my ( $file, $lineno ) = @_;
356 &ERROR("getLineFromFile: file '$file' does not exist.");
360 if ( open( IN, $file ) ) {
364 if ( $lineno > scalar @lines ) {
365 &ERROR('getLineFromFile: lineno exceeds line count from file.');
369 my $line = $lines[ $lineno - 1 ];
374 &ERROR("gLFF: Could not open file ($file): $!");
379 # Usage: &getRandom(@array);
384 return $array[ int( rand( scalar @array ) ) ];
387 # Usage: &getRandomInt('30-60'); &getRandomInt(5);
388 # Desc : Returns a randomn integer between 'X-Y' or 1 and the value passed
392 if ( !defined $str ) {
393 &WARN('getRandomInt: str == NULL.');
397 if ( $str =~ /^(\d+(\.\d+)?)$/ ) {
398 return int( rand $str ) + 1;
400 elsif ( $str =~ /^(\d+)-(\d+)$/ ) {
401 return $1 if $1 == $2;
402 my $min = $1 < $2 ? $1 : $2; # Swap is backwords
403 my $max = $2 > $1 ? $2 : $1;
404 return int( rand( $max - $min + 1 ) ) + $min;
408 # &ERROR("getRandomInt: invalid arg '$str'.");
418 my ( $left, $right ) = @_;
419 return 0 unless defined $right;
420 return 0 unless defined $left;
421 return 1 if ( $left =~ /^\Q$right$/i );
425 my $retval = &iseq(@_);
426 return 1 unless ($retval);
430 # Usage: &IsHostMatch($nuh);
433 my ( %this, %local );
435 if ( $nuh =~ /^(\S+)!(\S+)@(\S+)/ ) {
436 $local{'nick'} = lc $1;
437 $local{'user'} = lc $2;
438 $local{'host'} = &makeHostMask( lc $3 );
441 if ( !defined $thisnuh ) {
442 &WARN('IHM: thisnuh == NULL.');
445 elsif ( $thisnuh =~ /^(\S+)!(\S+)@(\S+)/ ) {
446 $this{'nick'} = lc $1;
447 $this{'user'} = lc $2;
448 $this{'host'} = &makeHostMask( lc $3 );
451 &WARN("IHM: thisnuh is invalid '$thisnuh'.");
452 return 1 if ( $thisnuh eq '' );
456 # auth if 1) user and host match 2) user and nick match.
457 # this may change in the future.
459 if ( $this{'user'} =~ /^\Q$local{'user'}\E$/i ) {
460 return 2 if ( $this{'host'} eq $local{'host'} );
461 return 1 if ( $this{'nick'} eq $local{'nick'} );
467 # Usage: &isStale($file, $age);
469 my ( $file, $age ) = @_;
471 if ( !defined $age ) {
472 &WARN('isStale: age == NULL.');
476 if ( !defined $file ) {
477 &WARN('isStale: file == NULL.');
481 &DEBUG("!exist $file") if ( !-f $file );
483 return 1 unless ( -f $file );
484 if ( $file =~ /idx/ ) {
485 my $age2 = time() - ( stat($file) )[9];
486 &VERB( "stale: $age2. (" . &Time2String($age2) . ')', 2 );
488 $age *= 60 * 60 * 24 if ( $age >= 0 and $age < 30 );
490 return 1 if ( time() - ( stat($file) )[9] > $age );
495 my ( $file, $time ) = @_;
501 my $time_file = ( stat $file )[9];
503 if ( $time <= $time_file ) {
515 # Usage: &makeHostMask($host);
520 if ( $host =~ s/^(\S+!\S+\@)// ) {
521 &DEBUG("mHM: detected nick!user\@ for host arg; fixing");
526 if ( $host =~ /^$mask{ip}$/ ) {
527 return $nu . "$1.$2.$3.*";
530 my @array = split( /\./, $host );
531 return $nu . $host if ( scalar @array <= 3 );
532 return $nu . '*.' . join( '.', @{array}[ 1 .. $#array ] );
535 # Usage: &makeRandom(int);
541 if ( $max =~ /^\D+$/ ) {
542 &ERROR("makeRandom: arg ($max) is not integer.");
547 &ERROR("makeRandom: arg ($max) is not positive.");
552 while ( scalar keys %done < $max ) {
553 my $rand = int( rand $max );
554 next if ( exists $done{$rand} );
556 push( @retval, $rand );
565 return unless ( &IsParam('minLengthBeforePrivate') );
566 return if ($force_public_reply);
568 if ( length $reply > $param{'minLengthBeforePrivate'} ) {
570 "Reply: len reply > minLBP ($param{'minLengthBeforePrivate'}); msgType now private."
572 $msgType = 'private';
580 # Usage: &validExec($string);
584 if ( $str =~ /[\`\'\"\|]/ ) { # invalid.
592 # Usage: &hasProfanity($string);
599 /dick|dildo/ and last;
601 /pussy|[ck]unt/ and last;
602 /wh[0o]re|bitch|slut/ and last;
610 sub IsChanConfOrWarn {
613 if ( &IsChanConf($param) > 0 ) {
617 ### TODO: specific reason why it failed.
619 "unfortunately, \002$param\002 is disabled in my configuration" )
626 my ( $label, $code ) = @_;
630 &VERB( 'double fork detected; not forking.', 2 ) if ( $$ != $bot_pid );
632 if ( &IsParam('forking') and $$ == $bot_pid ) {
633 return unless &addForked($label);
635 $SIG{CHLD} = 'IGNORE';
636 $pid = eval { fork() };
637 return if $pid; # parent does nothing
639 select( undef, undef, undef, 0.2 );
641 # &status("fork starting for '$label', PID == $$.");
643 "--- fork starting for '$label', PID == $$, bot_pid == $bot_pid ---"
645 &shmWrite( $shm, "SET FORKPID $label $$" );
650 ### TODO: use AUTOLOAD
652 if ( $label !~ /-/ and !&loadMyModule($label) ) {
653 &DEBUG('Forker: failed?');
657 if ( defined $code ) {
658 $code->(); # weird, hey?
661 &WARN('Forker: code not defined!');
668 return 1 unless ( exists $file{PID} );
669 return 1 unless ( -f $file{PID} );
670 return 1 if ( unlink $file{PID} );
671 return 0 if ( -f $file{PID} );
677 ( '.', '/', 0 .. 9, 'A' .. 'Z', 'a' .. 'z' )[ rand 64, rand 64 ];
679 return crypt( $str, $salt );
683 return unless ( &getChanConfList('ircTextCounters') );
685 foreach ( keys %cmdstats ) {
692 'type' => 'cmdstats',
698 $i += $cmdstats{$type};
704 'type' => 'cmdstats',
716 # vim:ts=4:sw=4:expandtab:tw=80