use strict;
-# dynamic scalar. MUST BE REDUCED IN SIZE!!!
+# scalar. MUST BE REDUCED IN SIZE!!!
### TODO: reorder.
use vars qw(
- $answer $correction_plausible $loggingstatus $talkchannel
- $statcount $memusage $user $memusageOld $infobot_version $dbh
- $shm $host $msg $infobot_misc_dir $infobot_pid $infobot_base_dir
- $infobot_src_dir $conn $irc $learnok $nick $ident $no_syscall
+ $bot_misc_dir $bot_pid $bot_base_dir $bot_src_dir
+ $bot_data_dir $bot_config_dir $bot_state_dir $bot_run_dir
+ $answer $correction_plausible $talkchannel $bot_release
+ $statcount $memusage $user $memusageOld $bot_version $dbh
+ $shm $host $msg $noreply $conn $irc $learnok $nick $ident
$force_public_reply $addrchar $userHandle $addressedother
$floodwho $chan $msgtime $server $firsttime $wingaterun
+ $flag_quit $msgType $no_syscall
+ $utime_userfile $wtime_userfile $ucount_userfile
+ $utime_chanfile $wtime_chanfile $ucount_chanfile
+ $pubsize $pubcount $pubtime
+ $msgsize $msgcount $msgtime
+ $notsize $notcount $nottime
+ $running
);
-# dynamic hash.
-use vars qw(@joinchan @ircServers @wingateBad @wingateNow @wingateCache
+# array.
+use vars qw(@ircServers @wingateBad @wingateNow @wingateCache
);
-# dynamic hash. MUST BE REDUCED IN SIZE!!!
+### hash. MUST BE REDUCED IN SIZE!!!
+#
use vars qw(%count %netsplit %netsplitservers %flood %dcc %orig
- %nuh %talkWho %seen %floodwarn %param %dbh %ircPort %userList
- %jointime %topic %joinverb %moduleAge %last %time %mask %file
+ %nuh %talkWho %seen %floodwarn %param %dbh %ircPort
+ %topic %moduleAge %last %time %mask %file
+ %forked %chanconf %channels %cache
);
# Signals.
$SIG{'TERM'} = 'doExit'; # 15.
$SIG{'__WARN__'} = 'doWarn';
+# initialize variables.
$last{buflen} = 0;
-$last{say} = "";
-$last{msg} = "";
-$userHandle = "default";
-$msgtime = time();
+$last{say} = '';
+$last{msg} = '';
+$userHandle = "_default";
$wingaterun = time();
$firsttime = 1;
-$infobot_version = "blootbot 1.0.0 (20000725) -- $^O";
+$utime_userfile = 0;
+$wtime_userfile = 0;
+$ucount_userfile = 0;
+$utime_chanfile = 0;
+$wtime_chanfile = 0;
+$ucount_chanfile = 0;
+$running = 0;
+### more variables...
+# static scalar variables.
+$mask{ip} = '(\d+)\.(\d+)\.(\d+)\.(\d+)';
+$mask{host} = '[\d\w\_\-\/]+\.[\.\d\w\_\-\/]+';
+$mask{chan} = '[\#\&]\S*|_default';
+my $isnick1 = 'a-zA-Z\[\]\{\}\_\`\^\|\\\\';
+my $isnick2 = '0-9\-';
+$mask{nick} = "[$isnick1]{1}[$isnick1$isnick2]*";
+$mask{nuh} = '\S*!\S*\@\S*';
+$msgtime = time();
+$msgsize = 0;
+$msgcount = 0;
+$pubtime = 0;
+$pubsize = 0;
+$pubcount = 0;
+$nottime = 0;
+$notsize = 0;
+$notcount = 0;
+###
+open(VERSION, '<VERSION');
+$bot_release = <VERSION> || "(unknown version)";
+chomp($bot_release);
+close(VERSION);
+$bot_version = "infobot $bot_release -- $^O";
+$noreply = 'NOREPLY';
##########
### misc commands.
###
+sub whatInterface {
+ if (!&IsParam('Interface') or $param{'Interface'} =~ /IRC/) {
+ return 'IRC';
+ } else {
+ return 'CLI';
+ }
+}
+
sub doExit {
- my ($sig) = @_;
+ my ($sig) = @_;
+
+ if (defined $flag_quit) {
+ &WARN("doExit: quit already called.");
+ return;
+ }
+ $flag_quit = 1;
- if (!defined $infobot_pid) { # independent.
+ if (!defined $bot_pid) { # independent.
exit 0;
- } elsif ($infobot_pid == $$) { # parent.
+ } elsif ($bot_pid == $$) { # parent.
&status("parent caught SIG$sig (pid $$).") if (defined $sig);
- my $type;
- &closeDCC();
+ &status("--- Start of quit.");
+ $ident ||= 'infobot'; # lame hack.
+
+ &status("Memory Usage: $memusage KiB");
+
&closePID();
- &seenFlush();
- &quit($param{'quitMsg'}) if (&whatInterface() =~ /IRC/);
- &uptimeWriteFile();
- &closeDB();
+ &closeStats();
+ # shutdown IRC and related components.
+ if (&whatInterface() =~ /IRC/) {
+ &closeDCC();
+ &seenFlush();
+ &quit($param{'quitMsg'});
+ }
+ &writeUserFile();
+ &writeChanFile();
+ &uptimeWriteFile() if (&IsParam('Uptime'));
+ &sqlCloseDB();
&closeSHM($shm);
- &dumpallvars() if (&IsParam("dumpvarsAtExit"));
+
+ if (&IsParam('dumpvarsAtExit')) {
+ &loadMyModule('DumpVars');
+ &dumpallvars();
+ }
+ &symdumpAll() if (&IsParam('symdumpAtExit'));
&closeLog();
+ &closeSQLDebug() if (&IsParam('SQLDebug'));
+
+ &status("--- QUIT.");
} else { # child.
&status("child caught SIG$sig (pid $$).");
}
&WARN("PERL: $_");
}
- $SIG{__WARN__} = 'doWarn';
+ $SIG{__WARN__} = 'doWarn'; # ???
}
# Usage: &IsParam($param);
+# infobot.config specific.
sub IsParam {
my $param = $_[0];
return 1;
}
-sub showProc {
- my ($prefix) = $_[0] || "";
+#####
+# Usage: &ChanConfList($param)
+# About: gets channels with 'param' enabled. (!!!)
+# Return: array of channels
+sub ChanConfList {
+ my $param = $_[0];
+ return unless (defined $param);
+ my %chan = &getChanConfList($param);
+
+ if (exists $chan{_default}) {
+ return keys %chanconf;
+ } else {
+ return keys %chan;
+ }
+}
- if (!open(IN, "/proc/$$/status")) {
- &ERROR("cannot open '/proc/$$/status'.");
- return;
+#####
+# Usage: &getChanConfList($param)
+# About: gets channels with 'param' enabled, internal use only.
+# Return: hash of channels
+sub getChanConfList {
+ my $param = $_[0];
+ my %chan;
+
+ return unless (defined $param);
+
+ foreach (keys %chanconf) {
+ my $chan = $_;
+ my @array = grep /^$param$/, keys %{ $chanconf{$chan} };
+ #&DEBUG("gCCL param => $param, chan => $chan, keys => " . join(':',keys %{ $chanconf{$chan} }) . " array => " . join(':', @array)) if ($param eq 'whatever');
+
+ next unless (scalar @array);
+
+ if (scalar @array > 1) {
+ &WARN("multiple items found?");
+ }
+
+ if ($chanconf{$chan}{$param} eq '0') {
+ $chan{$chan} = -1;
+ } else {
+ $chan{$chan} = 1;
+ }
+ }
+
+ return %chan;
+}
+
+#####
+# Usage: &IsChanConf($param);
+# About: Check for 'param' on the basis of channel config.
+# Return: 1 for enabled, 0 for passive disable, -1 for active disable.
+sub IsChanConf {
+ my($param) = shift;
+
+ # knocked tons of bugs with this! :)
+ my $debug = 0; # 1 if ($param eq 'whatever');
+
+ if (!defined $param) {
+ &WARN("IsChanConf: param == NULL.");
+ return 0;
+ }
+
+ # these should get moved to your .chan file instead of the .config
+ # .config items overide any .chan entries
+ if (&IsParam($param)) {
+ &WARN("ICC: found '$param' option in main config file.");
+ return 1;
+ }
+
+ $chan ||= "_default";
+
+ my $old = $chan;
+ if ($chan =~ tr/A-Z/a-z/) {
+ &WARN("IsChanConf: lowercased chan. ($old)");
+ }
+
+ ### TODO: VERBOSITY on how chanconf returned 1 or 0 or -1.
+ my %chan = &getChanConfList($param);
+ my $nomatch = 0;
+ if (!defined $msgType) {
+ $nomatch++;
+ } else {
+ $nomatch++ if ($msgType eq '');
+ $nomatch++ unless ($msgType =~ /^(public|private)$/i);
+ }
+
+### debug purposes only.
+# if ($debug) {
+# &DEBUG("param => $param, msgType => $msgType.");
+# foreach (keys %chan) {
+# &DEBUG(" $_ => $chan{$_}");
+# }
+# }
+
+ if ($nomatch) {
+ if ($chan{$chan}) {
+ &DEBUG("ICC: other: $chan{$chan} (_default/$param)") if ($debug);
+ } elsif ($chan{_default}) {
+ &DEBUG("ICC: other: $chan{_default} (_default/$param)") if ($debug);
+ } else {
+ &DEBUG("ICC: other: 0 ($param)") if ($debug);
+ }
+ return $chan{$chan} || $chan{_default} || 0;
+ } elsif ($msgType =~ /^(public|private)$/i) {
+ if ($chan{$chan}) {
+ &DEBUG("ICC: $msgType: $chan{$chan} ($chan/$param)") if ($debug);
+ } elsif ($chan{_default}) {
+ &DEBUG("ICC: $msgType: $chan{_default} (_default/$param)") if ($debug);
+ } else {
+ &DEBUG("ICC: $msgType: 0 ($param)") if ($debug);
+ }
+ return $chan{$chan} || $chan{_default} || 0;
+ }
+
+ &DEBUG("ICC: no-match: 0/$param (msgType = $msgType)");
+
+ return 0;
+}
+
+#####
+# Usage: &getChanConf($param);
+# About: Retrieve value for 'param' value in current/default chan.
+# Return: scalar for success, undef for failure.
+sub getChanConf {
+ my($param,$c) = @_;
+
+ if (!defined $param) {
+ &WARN("gCC: param == NULL.");
+ return 0;
+ }
+
+ # this looks evil...
+ if (0 and !defined $chan) {
+ &DEBUG("gCC: ok !chan... doing _default instead.");
+ }
+
+ $c ||= $chan;
+ $c ||= "_default";
+ $c = "_default" if ($c eq "*"); # FIXME
+ my @c = grep /^\Q$c\E$/i, keys %chanconf;
+
+ if (@c) {
+ if (0 and $c[0] ne $c) {
+ &WARN("c ne chan ($c[0] ne $chan)");
+ }
+ if (!defined $chanconf{$c[0]}{$param} and ($c ne '_default')) {
+ return &getChanConf($param, '_default');
+ }
+ &DEBUG("gCC: $param,$c \"" . $chanconf{$c[0]}{$param} . '"');
+ return $chanconf{$c[0]}{$param};
}
- if ($^O eq "linux") {
+ #&DEBUG("gCC: returning _default... " . $chanconf{"_default"}{$param});
+ return $chanconf{"_default"}{$param};
+}
+
+sub getChanConfDefault {
+ my($what, $default, $chan) = @_;
+ $chan ||= "_default";
+
+ if (exists $param{$what}) {
+ if (!exists $cache{config}{$what}) {
+ &status("config ($chan): backward-compatible option: found param{$what} ($param{$what}) instead of chan option");
+ $cache{config}{$what} = 1;
+ }
+
+ return $param{$what};
+ }
+ my $val = &getChanConf($what, $chan);
+ return $val if (defined $val);
+
+ $param{$what} = $default;
+ &status("config ($chan): auto-setting param{$what} = $default");
+ $cache{config}{$what} = 1;
+ return $default;
+}
+
+
+#####
+# Usage: &findChanConf($param);
+# About: Retrieve value for 'param' value from any chan.
+# Return: scalar for success, undef for failure.
+sub findChanConf {
+ my($param) = @_;
+
+ if (!defined $param) {
+ &WARN("param == NULL.");
+ return 0;
+ }
+
+ my $c;
+ foreach $c (keys %chanconf) {
+ foreach (keys %{ $chanconf{$c} }) {
+ next unless (/^$param$/);
+
+ return $chanconf{$c}{$_};
+ }
+ }
+
+ return;
+}
+
+sub showProc {
+ my ($prefix) = $_[0] || '';
+
+ if ($^O eq 'linux') {
+ if (!open(IN, "/proc/$$/status")) {
+ &ERROR("cannot open '/proc/$$/status'.");
+ return;
+ }
+
while (<IN>) {
$memusage = $1 if (/^VmSize:\s+(\d+) kB/);
}
close IN;
- if (defined $memusageOld and &IsParam("DEBUG")) {
- # it's always going to be increase.
- my $delta = $memusage - $memusageOld;
- if ($delta > 500) {
- &status("MEM:$prefix increased by $delta kB. (total: $memusage kB)");
- } elsif ($delta > 0) {
- &status("MEM:$prefix increased by $delta kB.");
- } elsif ($delta < 0) {
- $delta = -$delta;
- # never knew RSS could decrease, probably Size can't?
- &status("MEM:$prefix decreased by $delta kB. YES YES YES");
- }
- }
- $memusageOld = $memusage;
+ } elsif ($^O eq 'netbsd') {
+ $memusage = int( (stat "/proc/$$/mem")[7]/1024 );
+
+ } elsif ($^O =~ /^(free|open)bsd$/) {
+ my @info = split /\s+/, `/bin/ps -l -p $$`;
+ $memusage = $info[20];
+
} else {
- $memusage = "UNKNOWN";
+ $memusage = 'UNKNOWN';
+ return;
+ }
+
+ if (defined $memusageOld and &IsParam('DEBUG')) {
+ # it's always going to be increase.
+ my $delta = $memusage - $memusageOld;
+ my $str;
+ if ($delta == 0) {
+ return;
+ } elsif ($delta > 500) {
+ $str = "MEM:$prefix increased by $delta KiB. (total: $memusage KiB)";
+ } elsif ($delta > 0) {
+ $str = "MEM:$prefix increased by $delta KiB";
+ } else { # delta < 0.
+ $delta = -$delta;
+ # never knew RSS could decrease, probably Size can't?
+ $str = "MEM:$prefix decreased by $delta KiB.";
+ }
+
+ &status($str);
}
- ### TODO: FreeBSD/*BSD support.
+ $memusageOld = $memusage;
}
######
sub setup {
&showProc(" (\&openLog before)");
&openLog(); # write, append.
+ &status("--- Started logging.");
# read.
- &loadIgnore($infobot_misc_dir."/infobot.ignore");
- &loadLang($infobot_misc_dir."/infobot.lang");
- &loadIRCServers($infobot_misc_dir."/ircII.servers");
- &loadUsers($infobot_misc_dir."/infobot.users");
+ &loadLang($bot_data_dir. "/infobot.lang");
+ &loadIRCServers();
+ &readUserFile();
+ &readChanFile();
+ &loadMyModulesNow(); # must be after chan file.
$shm = &openSHM();
- &openDB();
-
- &status("Setup: ". &countKeys("factoids") ." factoids.");
-
- &status("Initial memory usage: $memusage kB");
+ &openSQLDebug() if (&IsParam('SQLDebug'));
+ &sqlOpenDB($param{'DBName'}, $param{'DBType'}, $param{'SQLUser'},
+ $param{'SQLPass'});
+ &checkTables();
+
+ &status("Setup: ". &countKeys('factoids') ." factoids.");
+ &getChanConfDefault('sendPrivateLimitLines', 3, $chan);
+ &getChanConfDefault('sendPrivateLimitBytes', 1000, $chan);
+ &getChanConfDefault('sendPublicLimitLines', 3, $chan);
+ &getChanConfDefault('sendPublicLimitBytes', 1000, $chan);
+ &getChanConfDefault('sendNoticeLimitLines', 3, $chan);
+ &getChanConfDefault('sendNoticeLimitBytes', 1000, $chan);
+
+ $param{tempDir} =~ s#\~/#$ENV{HOME}/#;
+
+ &status("Initial memory usage: $memusage KiB");
+ &status("-------------------------------------------------------");
}
sub setupConfig {
- &loadConfig($infobot_misc_dir."/infobot.config");
+ $param{'VERBOSITY'} = 1;
+ &loadConfig($bot_config_dir."/infobot.config");
- foreach ("ircNick", "ircUser", "ircName", "DBType") {
+ foreach ( qw(ircNick ircUser ircName DBType tempDir) ) {
next if &IsParam($_);
&ERROR("Parameter $_ has not been defined.");
exit 1;
}
+ if ($param{tempDir} =~ s#\~/#$ENV{HOME}/#) {
+ &VERB("Fixing up tempDir.",2);
+ }
+
+ if ($param{tempDir} =~ /~/) {
+ &ERROR("parameter tempDir still contains tilde.");
+ exit 1;
+ }
+
+ if (! -d $param{tempDir}) {
+ &status("making $param{tempDir}...");
+ mkdir $param{tempDir}, 0755;
+ }
+
# static scalar variables.
- $file{utm} = "$infobot_base_dir/$param{'ircUser'}.uptime";
- $file{PID} = "$infobot_base_dir/$param{'ircUser'}.pid";
+ $file{utm} = "$bot_state_dir/$param{'ircUser'}.uptime";
+ $file{PID} = "$bot_run_dir/$param{'ircUser'}.pid";
}
sub startup {
- if (&IsParam("DEBUG")) {
+ if (&IsParam('DEBUG')) {
&status("enabling debug diagnostics.");
- ### I thought disabling this reduced memory usage by 1000 kB.
+ # I thought disabling this reduced memory usage by 1000 KiB.
use diagnostics;
}
$count{'Question'} = 0;
$count{'Update'} = 0;
$count{'Dunno'} = 0;
-
- &loadMyModulesNow();
+ $count{'Moron'} = 0;
}
sub shutdown {
+ my ($sig) = @_;
# reverse order of &setup().
- &closeDB();
- &closeSHM($shm); # aswell. TODO: use this in &doExit?
+ &status("--- shutdown called.");
+
+ # hack.
+ $ident ||= 'infobot';
+
+ if (!&isFileUpdated("$bot_state_dir/infobot.users", $wtime_userfile)) {
+ &writeUserFile()
+ }
+
+ if (!&isFileUpdated("$bot_state_dir/infobot.chan", $wtime_chanfile)) {
+ &writeChanFile();
+ }
+
+ &sqlCloseDB();
+ # aswell. TODO: use this in &doExit?
+ &closeSHM($shm);
&closeLog();
}
sub restart {
my ($sig) = @_;
- if ($$ == $infobot_pid) {
- &status("$sig called.");
+ if ($$ == $bot_pid) {
+ &status("--- $sig called.");
### crappy bug in Net::IRC?
- if (!$conn->connected and time - $msgtime > 900) {
- &status("reconnecting because of uncaught disconnect.");
-## $irc->start;
+ my $delta = time() - $msgtime;
+ &DEBUG("restart: dtime = $delta");
+ if (!$conn->connected or time() - $msgtime > 900) {
+ &status("reconnecting because of uncaught disconnect \@ ".scalar(gmtime) );
+### $irc->start;
+ &clearIRCVars();
$conn->connect();
- return;
+### return;
}
- &shutdown();
- &loadConfig($infobot_misc_dir."/infobot.config");
- &reloadModules() if (&IsParam("DEBUG"));
+ &ircCheck(); # heh, evil!
+
+ &DCCBroadcast("-HUP called.",'m');
+ &shutdown($sig);
+ &loadConfig($bot_config_dir."/infobot.config");
+ &reloadAllModules() if (&IsParam('DEBUG'));
&setup();
- &status("End of $sig.");
+ &status("--- End of $sig.");
} else {
&status("$sig called; ignoring restart.");
}
my ($file) = @_;
if (!open(FILE, $file)) {
- &ERROR("FAILED loadConfig ($file): $!");
- &status("Please copy files/sample.config to files/infobot.config");
- &status(" and edit files/infobot.config, modify to tastes.");
+ &ERROR("Failed to read configuration file ($file): $!");
+ &status("Please read the INSTALL file on how to install and setup this file.");
exit 0;
}
next unless /\S/;
my ($set,$key,$val) = split(/\s+/, $_, 3);
- if ($set ne "set") {
+ if ($set ne 'set') {
&status("loadConfig: invalid line '$_'.");
next;
}
}
1;
+
+# vim:ts=4:sw=4:expandtab:tw=80