X-Git-Url: https://git.donarmstrong.com/?a=blobdiff_plain;f=src%2FCommandStubs.pl;h=1a55669aef5b19ad323f1480aa82a45997506e3f;hb=c68ac9b2d4b88535c1a2cfa695f6c4f683f0373c;hp=f11394a3e831c1368b9c7dbdb912ce2f69272c89;hpb=fd084d8f177c41acb25035cb7e7a0737862620c8;p=infobot.git diff --git a/src/CommandStubs.pl b/src/CommandStubs.pl index f11394a..1a55669 100644 --- a/src/CommandStubs.pl +++ b/src/CommandStubs.pl @@ -1,81 +1,225 @@ # # User Command Extension Stubs +# WARN: this file does not reload on HUP. # if (&IsParam("useStrict")) { use strict; } -use vars qw(@W3Search_engines $W3Search_regex); -@W3Search_engines = qw(AltaVista Dejanews Excite Gopher HotBot Infoseek - Lycos Magellan PLweb SFgate Simple Verity Google); -$W3Search_regex = join '|', @W3Search_engines; -$babel::lang_regex = ""; # lame fix. - -### PROPOSED COMMAND HOOK IMPLEMENTATION. -# addCmdHook('TEXT_HOOK', $code_ref, -# (Forker => 1, -# CheckModule => 1, -# Identifier => 'config_label', +$babel_lang_regex = "fr|sp|po|pt|it|ge|de|gr|en"; + +### COMMAND HOOK IMPLEMENTATION. +# addCmdHook("SECTION", 'TEXT_HOOK', +# (CODEREF => 'Blah', +# Forker => 1, +# CheckModule => 1, # ??? +# Module => 'blah.pl' # preload module. +# Identifier => 'config_label', # change to Config? # Help => 'help_label', # Cmdstats => 'text_label',) #} -### EXAMPLE -# addCmdHook('d?find', ( -# CODEREF => \&debianFind(), -# CheckModule => 1, -# Forker => 1, # if simple function. -# Identifier => "debian", -# Help => "dfind", -# Cmdstats => "Debian Search",) ); -### NOTES: -# * viable solution? ### sub addCmdHook { - my ($ident, %hash) = @_; + my ($hashname, $ident, %hash) = @_; - &DEBUG("aCH: added $ident to command hooks."); - $cmdhooks{$ident} = \%hash; + if (exists ${"hooks_$hashname"}{$ident}) { +### &WARN("aCH: cmd hooks \%$hashname{$ident} already exists."); + return; + } + + &VERB("aCH: added $ident",2); # use $hash{'Identifier'}? + ### hrm... prevent warnings? + ${"hooks_$hashname"}{$ident} = \%hash; } # RUN IF ADDRESSED. sub parseCmdHook { - my @args = split(' ', $message); + my ($hashname, $line) = @_; + $line =~ /^(\S+)( (.*))?$/; + my $cmd = $1; # command name is whitespaceless. + my $flatarg = $3; + my @args = split(/\s+/, $flatarg || ''); + my $done = 0; + + &shmFlush(); + + if (!defined %{"hooks_$hashname"}) { + &WARN("cmd hooks \%$hashname does not exist."); + return 0; + } - foreach (keys %cmdhooks) { + if (!defined $cmd) { + &WARN("cstubs: cmd == NULL."); + return 0; + } + + foreach (keys %{"hooks_$hashname"}) { + # rename to something else! like $id or $label? my $ident = $_; - &DEBUG("cmdhooks{$ident} => ..."); - next unless ($args[0] =~ /^$ident$/i); + next unless ($cmd =~ /^$ident$/i); + + if ($done) { + &WARN("pCH: Multiple hook match: $ident"); + next; + } + + &status("hooks($hashname): $cmd matched '$ident'"); + my %hash = %{ ${"hooks_$hashname"}{$ident} }; - &DEBUG("pCH: MATCHED!"); - my %hash = %{ $cmdhooks{$ident} }; + if (!scalar keys %hash) { + &WARN("CmdHook: hash is NULL?"); + return 1; + } + + if ($hash{NoArgs} and $flatarg) { + &DEBUG("cmd $ident does not take args ('$flatarg'); skipping."); + next; + } + + if (!exists $hash{CODEREF}) { + &ERROR("CODEREF undefined for $cmd or $ident."); + return 1; + } ### DEBUG. foreach (keys %hash) { - &DEBUG(" $ident->$_ => '$hash{$_}'."); + &VERB(" $cmd->$_ => '$hash{$_}'.",2); + } + + ### HELP. + if (exists $hash{'Help'} and !scalar(@args)) { + &help( $hash{'Help'} ); + return 1; } ### IDENTIFIER. if (exists $hash{'Identifier'}) { - return $noreply unless (&hasParam($hash{'Identifier'})); + return 1 unless (&hasParam($hash{'Identifier'})); + } + + ### USER FLAGS. + if (exists $hash{'UserFlag'}) { + return 1 unless (&hasFlag($hash{'UserFlag'})); } ### FORKER,IDENTIFIER,CODEREF. if (exists $hash{'Forker'}) { - &Forker($hash{'Identifier'}, \&{$hash{'CODEREF'}}); + $hash{'Identifier'} .= "-" if ($hash{'Forker'} eq "NULL"); + + if (exists $hash{'ArrayArgs'}) { + &Forker($hash{'Identifier'}, sub { \&{ $hash{'CODEREF'} }(@args) } ); + } else { + &Forker($hash{'Identifier'}, sub { \&{ $hash{'CODEREF'} }($flatarg) } ); + } + + } else { + if (exists $hash{'Module'}) { + &loadMyModule($myModules{ $hash{'Module'} }); + } + + # check if CODEREF exists. + if (!defined &{ $hash{'CODEREF'} }) { + &WARN("coderef $hash{'CODEREF'} don't exist."); + if (defined $who) { + &msg($who, "coderef does not exist for $ident."); + } + + return 1; + } + + if (exists $hash{'ArrayArgs'}) { + &{ $hash{'CODEREF'} }(@args); + } else { + &{ $hash{'CODEREF'} }($flatarg); + } } ### CMDSTATS. if (exists $hash{'Cmdstats'}) { - $cmdstats{$hash{'Cmdstats'}}++; + $cmdstats{ $hash{'Cmdstats'} }++; } + + &VERB("hooks: End of command.",2); + + $done = 1; } - &DEBUG("pCH: ended."); + return 1 if ($done); + return 0; } -&addCmdHook('d?bugs', ('CODEREF' => 'debianBugs', - 'Forker' => 1, 'Identifier' => 'debianExtra', 'Cmdstats' => 1) ); +### +### START ADDING HOOKS. +### +&addCmdHook("extra", 'd?bugs', ('CODEREF' => 'DBugs::Parse', + 'Forker' => 1, 'Identifier' => 'debianExtra', + 'Cmdstats' => 'Debian Bugs') ); +&addCmdHook("extra", 'dauthor', ('CODEREF' => 'Debian::searchAuthor', + 'Forker' => 1, 'Identifier' => 'debian', + 'Cmdstats' => 'Debian Author Search', 'Help' => "dauthor" ) ); +&addCmdHook("extra", '(d|search)desc', ('CODEREF' => 'Debian::searchDescFE', + 'Forker' => 1, 'Identifier' => 'debian', + 'Cmdstats' => 'Debian Desc Search', 'Help' => "ddesc" ) ); +&addCmdHook("extra", 'dnew', ('CODEREF' => 'DebianNew', + 'Identifier' => 'debian' ) ); +&addCmdHook("extra", 'dincoming', ('CODEREF' => 'Debian::generateIncoming', + 'Forker' => 1, 'Identifier' => 'debian' ) ); +&addCmdHook("extra", 'dstats', ('CODEREF' => 'Debian::infoStats', + 'Forker' => 1, 'Identifier' => 'debian', + 'Cmdstats' => 'Debian Statistics' ) ); +&addCmdHook("extra", 'd?contents', ('CODEREF' => 'Debian::searchContents', + 'Forker' => 1, 'Identifier' => 'debian', + 'Cmdstats' => 'Debian Contents Search', 'Help' => "contents" ) ); +&addCmdHook("extra", 'd?find', ('CODEREF' => 'Debian::DebianFind', + 'Forker' => 1, 'Identifier' => 'debian', + 'Cmdstats' => 'Debian Search', 'Help' => "find" ) ); +&addCmdHook("extra", 'insult', ('CODEREF' => 'Insult::Insult', + 'Forker' => 1, 'Identifier' => 'insult', 'Help' => "insult" ) ); +&addCmdHook("extra", 'kernel', ('CODEREF' => 'Kernel::Kernel', + 'Forker' => 1, 'Identifier' => 'kernel', + 'Cmdstats' => 'Kernel', 'NoArgs' => 1) ); +&addCmdHook("extra", 'listauth', ('CODEREF' => 'CmdListAuth', + 'Identifier' => 'search', Module => 'factoids', + 'Help' => 'listauth') ); +&addCmdHook("extra", 'quote', ('CODEREF' => 'Quote::Quote', + 'Forker' => 1, 'Identifier' => 'quote', + 'Help' => 'quote', 'Cmdstats' => 'Quote') ); +&addCmdHook("extra", 'countdown', ('CODEREF' => 'Countdown', + 'Module' => 'countdown', 'Identifier' => 'countdown', + 'Cmdstats' => 'Countdown') ); +&addCmdHook("extra", 'lart', ('CODEREF' => 'lart', + 'Identifier' => 'lart', 'Help' => 'lart') ); +&addCmdHook("extra", 'convert', ('CODEREF' => 'convert', + 'Forker' => 1, 'Identifier' => 'units', + 'Help' => 'convert') ); +&addCmdHook("extra", '(cookie|random)', ('CODEREF' => 'cookie', + 'Forker' => 1, 'Identifier' => 'factoids') ); +&addCmdHook("extra", 'u(ser)?info', ('CODEREF' => 'userinfo', + 'Identifier' => 'userinfo', 'Help' => 'userinfo', + 'Module' => 'userinfo') ); +&addCmdHook("extra", 'rootWarn', ('CODEREF' => 'CmdrootWarn', + 'Identifier' => 'rootWarn', 'Module' => 'rootwarn') ); +&addCmdHook("extra", 'seen', ('CODEREF' => 'seen', 'Identifier' => + 'seen') ); +&addCmdHook("extra", 'dict', ('CODEREF' => 'Dict::Dict', + 'Identifier' => 'dict', 'Help' => 'dict', + 'Forker' => 1, 'Cmdstats' => 'Dict') ); +&addCmdHook("extra", 'slashdot', ('CODEREF' => 'Slashdot::Slashdot', + 'Identifier' => 'slashdot', 'Forker' => 1, + 'Cmdstats' => 'Slashdot') ); +&addCmdHook("extra", 'uptime', ('CODEREF' => 'uptime', 'Identifier' => 'uptime', + 'Cmdstats' => 'Uptime') ); +&addCmdHook("extra", 'nullski', ('CODEREF' => 'nullski', ) ); +&addCmdHook("extra", '(fm|freshmeat)', ('CODEREF' => 'Freshmeat::Freshmeat', + 'Identifier' => 'freshmeat', 'Cmdstats' => 'Freshmeat', + 'Forker' => 1, 'Help' => 'freshmeat') ); +&addCmdHook("extra", 'verstats', ('CODEREF' => 'do_verstats' ) ); + +### +### END OF ADDING HOOKS. +### +&status("CMD: loaded ".scalar(keys %hooks_extra)." EXTRA command hooks."); sub Modules { if (!defined $message) { @@ -84,623 +228,740 @@ sub Modules { } # babel bot: Jonathan Feinberg++ - if (&IsParam("babelfish") and $message =~ m{ + if ($message =~ m{ ^\s* (?:babel(?:fish)?|x|xlate|translate) \s+ (to|from) # direction of translation (through) \s+ - ($babel::lang_regex)\w* # which language? + ($babel_lang_regex)\w* # which language? \s* (.+) # The phrase to be translated - }xoi) { + }xoi) { + return unless (&hasParam("babelfish")); &Forker("babelfish", sub { &babel::babelfish(lc $1, lc $2, $3); } ); $cmdstats{'BabelFish'}++; - return $noreply; + return; } - # cookie (random). xk++ - if ($message =~ /^(cookie|random)(\s+(.*))?$/i) { - return $noreply unless (&hasParam("cookie")); + my $debiancmd = 'conflicts?|depends?|desc|file|info|provides?'; + $debiancmd .= '|recommends?|suggests?|maint|maintainer'; - my $arg = $3; + if ($message =~ /^($debiancmd)(\s+(.*))?$/i) { + return unless (&hasParam("debian")); + my $package = lc $3; - # lets find that secret cookie. - my $target = $talkchannel; - $target = $who if ($msgType ne 'public'); - - my $cookiemsg = &getRandom(keys %{$lang{'cookie'}}); - my ($key,$value); - ### WILL CHEW TONS OF MEM. - ### TODO: convert this to a Forker function! - if ($arg) { - my @list = &searchTable("factoids", "factoid_key", "factoid_value", $arg); - $key = &getRandom(@list); - $val = &getFactInfo("factoids", $key, "factoid_value"); + if (defined $package) { + &Forker("debian", sub { &Debian::infoPackages($1, $package); } ); } else { - ($key,$value) = &randKey("factoids","factoid_key,factoid_value"); + &help($1); } - $cookiemsg =~ s/##KEY/\002$key\002/; - $cookiemsg =~ s/##VALUE/$value/; - $cookiemsg =~ s/##WHO/$who/; - $cookiemsg =~ s/\$who/$who/; # cheap fix. - $cookiemsg =~ s/(\S+)?\s*<\S+>/$1 /; - $cookiemsg =~ s/\s+/ /g; - - if ($cookiemsg =~ s/^ACTION //i) { - &action($target, $cookiemsg); - } else { - &msg($target, $cookiemsg); - } - - $cmdstats{'Random Cookie'}++; - return $noreply; + return; } - if ($message =~ /^d?bugs$/i) { - return $noreply unless (&hasParam("debianExtra")); + # google searching. Simon++ + if ($message =~ /^(?:search\s+)?(\S+)\s+for\s+['"]?(.*?)["']?\s*\?*$/i) { + return unless (&hasParam("wwwsearch")); - &Forker("debianExtra", sub { &debianBugs(); } ); + &Forker("wwwsearch", sub { &W3Search::W3Search($1,$2); } ); - $cmdstats{'Debian Bugs'}++; - return $noreply; + $cmdstats{'WWWSearch'}++; + return; } - # Debian Author Search. - if ($message =~ /^dauthor(\s+(.*))?$/i) { - return $noreply unless (&hasParam("debian")); + # text counters. (eg: hehstats) + my $itc; + $itc = &getChanConf("ircTextCounters"); + $itc = &findChanConf("ircTextCounters") unless ($itc); + if ($itc) { + $itc =~ s/([^\w\s])/\\$1/g; + my $z = join '|', split ' ', $itc; - my $query = $2; - if (!defined $query) { - &help("dauthor"); - return $noreply; + if ($msgType eq "privmsg" and $message =~ / ($mask{chan})$/) { + &DEBUG("ircTC: privmsg detected; chan = $1"); + $chan = $1; } - &Forker("debian", sub { &Debian::searchAuthor($query); } ); - - $cmdstats{'Debian Author Search'}++; - return $noreply; - } - - # Debian Author Search. - if ($message =~ /^(d|search)desc(\s+(.*))?$/i) { - return $noreply unless (&hasParam("debian")); - - my $query = $2; - if (!defined $query) { - &help("ddesc"); - return $noreply; + if ($message =~ /^_stats(\s+(\S+))$/i) { + &textstats_main($2); + return; } - &Forker("debian", sub { &Debian::searchDesc($query); } ); + if ($message =~ /^($z)stats(\s+(\S+))?$/i) { + my $type = $1; + my $arg = $3; - $cmdstats{'Debian Desc Search'}++; - return $noreply; - } + # even more uglier with channel/time arguments. + my $c = $chan; +# my $c = $chan || "PRIVATE"; + my $where = "type=".&dbQuote($type); + $where .= " AND channel=".&dbQuote($c) if (defined $c); + &DEBUG("not using chan arg") if (!defined $c); + my $sum = (&dbRawReturn("SELECT SUM(counter) FROM stats" + ." WHERE ".$where ))[0]; - # Debian Incoming Search. - if ($message =~ /^dincoming$/i) { - return $noreply unless (&hasParam("debian")); + if (!defined $arg or $arg =~ /^\s*$/) { + # this is way fucking ugly. - &Forker("debian", sub { &Debian::generateIncoming(); } ); + my %hash = &dbGetCol("stats", "nick,counter", + $where." ORDER BY counter DESC LIMIT 3", 1); + my $i; + my @top; + + # unfortunately we have to sort it again! + # todo: make dbGetCol return hash and array? too much effort. + my $tp = 0; + foreach $i (sort { $b <=> $a } keys %hash) { + foreach (keys %{ $hash{$i} }) { + my $p = sprintf("%.01f", 100*$i/$sum); + $tp += $p; + push(@top, "\002$_\002 -- $i ($p%)"); + } + } + my $topstr = ""; + &DEBUG("*stats: tp => $tp"); + if (scalar @top) { + $topstr = ". Top ".scalar(@top).": ".join(', ', @top); + } + + if (defined $sum) { + &pSReply("total count of \037$type\037 on \002$c\002: $sum$topstr"); + } else { + &pSReply("zero counter for \037$type\037."); + } + } else { + my $x = (&dbRawReturn("SELECT SUM(counter) FROM stats". + " WHERE $where AND nick=".&dbQuote($arg) ))[0]; + + if (!defined $x) { # !defined. + &pSReply("$arg has not said $type yet."); + return; + } + + # defined. + my @array = &dbGet("stats", "nick", + $where." ORDER BY counter", 1); + my $good = 0; + my $i = 0; + for($i=0; $i $a} keys %nickometer) { + my $str = join(", ", sort keys %{ $nickometer{$_} }); + push(@list, "$str ($_%)"); + } - my $query = $2; - $query =~ s/^[\s\t]+//; - $query =~ s/[\s\t]+$//; - $query =~ s/[\s\t]+/ /; + &pSReply( &formListReply(0, "Nickometer list for $term ", @list) ); + &DEBUG("test."); - if (!defined $query) { - &help("dict"); - return $noreply; + return; } - if (length $query > 30) { - &msg($who,"dictionary word is too long."); - return $noreply; + my $percentage = &nickometer($term); + + if ($percentage =~ /NaN/) { + $percentage = "off the scale"; + } else { + $percentage = sprintf("%0.4f", $percentage); + $percentage =~ s/\.?0+$//; + $percentage .= '%'; } - &Forker("dict", sub { &Dict::Dict($query); } ); + if ($msgType eq 'public') { + &say("'$term' is $percentage lame, $who"); + } else { + &msg($who, "the 'lame nick-o-meter' reading for $term is $percentage, $who"); + } - $cmdstats{'Dict'}++; - return $noreply; + return; } - # Freshmeat. xk++ - if ($message =~ /^(fm|freshmeat)(\s+(.*))?$/i) { - return $noreply unless (&hasParam("freshmeat")); + # Topic management. xk++ + # may want to add a userflags for topic. -xk + if ($message =~ /^topic(\s+(.*))?$/i) { + return unless (&hasParam("topic")); - my $query = $3; + my $chan = $talkchannel; + my @args = split / /, $2 || ""; - if (!defined $query) { - &help("freshmeat"); - &msg($who, "I have \002".&countKeys("freshmeat")."\002 entries."); - return $noreply; + if (!scalar @args) { + &msg($who,"Try 'help topic'"); + return; } - &loadMyModule($myModules{'freshmeat'}); - &Freshmeat::Freshmeat($query); + $chan = lc(shift @args) if ($msgType eq 'private'); + my $thiscmd = shift @args; - $cmdstats{'Freshmeat'}++; - return $noreply; - } + # topic over public: + if ($msgType eq 'public' && $thiscmd =~ /^#/) { + &msg($who, "error: channel argument is not required."); + &msg($who, "\002Usage\002: topic "); + return; + } - # google searching. Simon++ - if (&IsParam("wwwsearch") and $message =~ /^(?:search\s+)?($W3Search_regex)\s+for\s+['"]?(.*?)['"]?\s*\?*$/i) { - return $noreply unless (&hasParam("wwwsearch")); + # topic over private: + if ($msgType eq 'private' && $chan !~ /^#/) { + &msg($who, "error: channel argument is required."); + &msg($who, "\002Usage\002: topic #channel "); + return; + } - &Forker("wwwsearch", sub { &W3Search::W3Search($1,$2,$param{'wwwsearch'}); } ); + if (&validChan($chan) == 0) { + &msg($who,"error: invalid channel \002$chan\002"); + return; + } - $cmdstats{'WWWSearch'}++; - return $noreply; + # for semi-outsiders. + if (!&IsNickInChan($who,$chan)) { + &msg($who, "Failed. You ($who) are not in $chan, hey?"); + return; + } + + # now lets do it. + &loadMyModule($myModules{'topic'}); + &Topic($chan, $thiscmd, join(' ', @args)); + $cmdstats{'Topic'}++; + return; } - # insult server. patch thanks to michael@limit.org - if ($message =~ /^insult(\s+(\S+))?$/) { - return $noreply unless (&hasParam("insult")); + # wingate. + if ($message =~ /^wingate$/i) { + return unless (&hasParam("wingate")); - my $person = $2; - if (!defined $person) { - &help("insult"); - return $noreply; + my $reply = "Wingate statistics: scanned \002" + .scalar(keys %wingate)."\002 hosts"; + my $queue = scalar(keys %wingateToDo); + if ($queue) { + $reply .= ". I have \002$queue\002 hosts in the queue"; + $reply .= ". Started the scan ".&Time2String(time() - $wingaterun)." ago"; } - &Forker("insult", sub { &Insult::Insult($person); } ); + &performStrictReply("$reply."); - return $noreply; + return; } - # Kernel. xk++ - if ($message =~ /^kernel$/i) { - return $noreply unless (&hasParam("kernel")); + # do nothing and let the other routines have a go + return "CONTINUE"; +} - &Forker("kernel", sub { &Kernel::Kernel(); } ); +# Freshmeat. xk++ +sub freshmeat { + my ($query) = @_; - $cmdstats{'Kernel'}++; - return $noreply; + if (!defined $query) { + &help("freshmeat"); + &msg($who, "I have \002".&countKeys("freshmeat")."\002 entries."); + return; } - # LART. originally by larne/cerb. - if ($message =~ /^lart(\s+(.*))?$/i) { - return $noreply unless (&hasParam("lart")); - my ($target) = &fixString($2); - - if (!defined $target) { - &help("lart"); - return $noreply; - } - my $extra = 0; - - my $chan = $talkchannel; - if ($msgType eq 'private') { - if ($target =~ /^($mask{chan})\s+(.*)$/) { - $chan = $1; - $target = $2; - $extra = 1; - } else { - &msg($who, "error: invalid format or missing arguments."); - &help("lart"); - return $noreply; - } - } + &Freshmeat::Freshmeat($query); +} - my $line = &getRandomLineFromFile($bot_misc_dir. "/blootbot.lart"); - if (defined $line) { - if ($target =~ /^(me|you|itself|\Q$ident\E)$/i) { - $line =~ s/WHO/$who/g; - } else { - $line =~ s/WHO/$target/g; - } - $line .= ", courtesy of $who" if ($extra); +# Uptime. xk++ +sub uptime { + my $count = 1; + &msg($who, "- Uptime for $ident -"); + &msg($who, "Now: ". &Time2String(&uptimeNow()) ." running $bot_version"); - &action($chan, $line); - } else { - &status("lart: error reading file?"); - } + foreach (&uptimeGetInfo()) { + /^(\d+)\.\d+ (.*)/; + my $time = &Time2String($1); + my $info = $2; - return $noreply; + &msg($who, "$count: $time $2"); + $count++; } +} - # Search factoid extensions by 'author'. xk++ - if ($message =~ /^listauth(\s+(\S+))?$/i) { - return $noreply unless (&hasParam("search")); +# seen. +sub seen { + my($person) = lc shift; + $person =~ s/\?*$//; - my $query = $2; + if (!defined $person or $person =~ /^$/) { + &help("seen"); - if (!defined $query) { - &help("listauth"); - return $noreply; - } + my $i = &countKeys("seen"); + &msg($who,"there ". &fixPlural("is",$i) ." \002$i\002 ". + "seen ". &fixPlural("entry",$i) ." that I know of."); - &loadMyModule($myModules{'factoids'}); - &performStrictReply( &CmdListAuth($query) ); - return $noreply; + return; } - # list{keys|values}. xk++. Idea taken from #linuxwarez@EFNET - if ($message =~ /^list(\S+)( (.*))?$/i) { - return $noreply unless (&hasParam("search")); - - my $thiscmd = lc($1); - my $args = $3; + my @seen; - $thiscmd =~ s/^vals$/values/; - return $noreply if ($thiscmd ne "keys" && $thiscmd ne "values"); + &seenFlush(); # very evil hack. oh well, better safe than sorry. - # Usage: - if (!defined $args) { - &help("list". $thiscmd); - return $noreply; - } + ### TODO: Support &dbGetColInfo(); like in &FactInfo(); + my $select = "nick,time,channel,host,message"; + if ($person eq "random") { + @seen = &randKey("seen", $select); + } else { + @seen = &dbGet("seen", $select, "nick=".&dbQuote($person) ); + } - if (length $args == 1) { - &msg($who,"search string is too short."); - return $noreply; + if (scalar @seen < 2) { + foreach (@seen) { + &DEBUG("seen: _ => '$_'."); } + &performReply("i haven't seen '$person'"); + return; + } - &Forker("search", sub { &Search::Search($thiscmd, $args); } ); - - $cmdstats{'Factoid Search'}++; - return $noreply; + # valid seen. + my $reply; + ### TODO: multi channel support. may require &IsNick() to return + ### all channels or something. + my @chans = &getNickInChans($seen[0]); + if (scalar @chans) { + $reply = "$seen[0] is currently on"; + + foreach (@chans) { + $reply .= " ".$_; + next unless (exists $userstats{lc $seen[0]}{'Join'}); + $reply .= " (".&Time2String(time() - $userstats{lc $seen[0]}{'Join'}).")"; + } + + if (&IsParam("seenStats")) { + my $i; + $i = $userstats{lc $seen[0]}{'Count'}; + $reply .= ". Has said a total of \002$i\002 messages" if (defined $i); + $i = $userstats{lc $seen[0]}{'Time'}; + $reply .= ". Is idling for ".&Time2String(time() - $i) if (defined $i); + } + } else { + my $howlong = &Time2String(time() - $seen[1]); + $reply = "$seen[0] <$seen[3]> was last seen on IRC ". + "in channel $seen[2], $howlong ago, ". + "saying\002:\002 '$seen[4]'."; } - # Nickometer. Adam Spiers++ - if ($message =~ /^(?:lame|nick)ometer(?: for)? (\S+)/i) { - return $noreply unless (&hasParam("nickometer")); + &pSReply($reply); + return; +} - my $term = (lc $1 eq 'me') ? $who : $1; - $term =~ s/\?+\s*//; +# User Information Services. requested by Flugh. +sub userinfo { + my ($arg) = join(' ',@_); - &loadMyModule($myModules{'nickometer'}); - my $percentage = &nickometer($term); - - if ($percentage =~ /NaN/) { - $percentage = "off the scale"; - } else { - $percentage = sprintf("%0.4f", $percentage); - $percentage =~ s/\.?0+$//; - $percentage .= '%'; + if ($arg =~ /^set(\s+(.*))?$/i) { + $arg = $2; + if (!defined $arg) { + &help("userinfo set"); + return; } - if ($msgType eq 'public') { - &say("'$term' is $percentage lame, $who"); - } else { - &msg($who, "the 'lame nick-o-meter' reading for $term is $percentage, $who"); + &UserInfoSet(split /\s+/, $arg, 2); + } elsif ($arg =~ /^unset(\s+(.*))?$/i) { + $arg = $2; + if (!defined $arg) { + &help("userinfo unset"); + return; } - return $noreply; + &UserInfoSet($arg, ""); + } else { + &UserInfoGet($arg); } +} - # Quotes. mu++ - if ($message =~ /^quote(\s+(\S+))?$/i) { - return $noreply unless (&hasParam("quote")); - - my $query = $2; - - if ($query eq "") { - &help("quote"); - return $noreply; - } - - &Forker("quote", sub { &Quote::Quote($query); } ); - - $cmdstats{'Quote'}++; - return $noreply; +# cookie (random). xk++ +sub cookie { + my ($arg) = @_; + + # lets find that secret cookie. + my $target = ($msgType ne 'public') ? $who : $talkchannel; + my $cookiemsg = &getRandom(keys %{ $lang{'cookie'} }); + my ($key,$value); + + ### WILL CHEW TONS OF MEM. + ### TODO: convert this to a Forker function! + if ($arg) { + my @list = &searchTable("factoids", "factoid_key", "factoid_value", $arg); + $key = &getRandom(@list); + $val = &getFactInfo("factoids", $key, "factoid_value"); + } else { + ($key,$value) = &randKey("factoids","factoid_key,factoid_value"); } - # rootWarn. xk++ - if ($message =~ /^rootWarn$/i) { - return $noreply unless (&hasParam("rootWarn")); + for ($cookiemsg) { + s/##KEY/\002$key\002/; + s/##VALUE/$value/; + s/##WHO/$who/; + s/\$who/$who/; # cheap fix. + s/(\S+)?\s*<\S+>/$1 /; + s/\s+/ /g; + } - &loadMyModule($myModules{'rootwarn'}); - &performStrictReply( &CmdrootWarn() ); - return $noreply; + if ($cookiemsg =~ s/^ACTION //i) { + &action($target, $cookiemsg); + } else { + &msg($target, $cookiemsg); } +} - # seen. - if ($message =~ /^seen(\s+(\S+))?$/) { - return $noreply unless (&hasParam("seen")); +sub convert { + my $arg = join(' ',@_); + my ($from,$to) = ('',''); - my $person = $2; - if (!defined $person) { - &help("seen"); + ($from,$to) = ($1,$2) if ($arg =~ /^(.*?) to (.*)$/i); + ($from,$to) = ($2,$1) if ($arg =~ /^(.*?) from (.*)$/i); - my $i = &countKeys("seen"); - &msg($who,"there ". &fixPlural("is",$i) ." \002$i\002 ". - "seen ". &fixPlural("entry",$i) ." that I know of."); + if (!$to or !$from) { + &msg($who, "Invalid format!"); + &help("convert"); + return; + } - return $noreply; - } + &Units::convertUnits($from, $to); - my @seen; - $person =~ s/\?*$//; + return; +} - &seenFlush(); # very evil hack. oh well, better safe than sorry. +sub lart { + my ($target) = &fixString($_[0]); + my $extra = 0; + my $chan = $talkchannel; - ### TODO: Support &dbGetRowInfo(); like in &FactInfo(); - my $select = "nick,time,channel,host,message"; - if ($person eq "random") { - @seen = &randKey("seen", $select); + if ($msgType eq 'private') { + if ($target =~ /^($mask{chan})\s+(.*)$/) { + $chan = $1; + $target = $2; + $extra = 1; } else { - @seen = &dbGet("seen", "nick", $person, $select); + &msg($who, "error: invalid format or missing arguments."); + &help("lart"); + return; } + } - if (scalar @seen < 2) { - foreach (@seen) { - &DEBUG("seen: _ => '$_'."); - } - &performReply("i haven't seen '$person'"); - return $noreply; - } - - # valid seen. - my $reply; - ### TODO: multi channel support. may require &IsNick() to return - ### all channels or something. - my @chans = &GetNickInChans($seen[0]); - if (scalar @chans) { - $reply = "$seen[0] is currently on"; - - foreach (@chans) { - $reply .= " ".$_; - next unless (exists $userstats{lc $seen[0]}{'Join'}); - $reply .= " (".&Time2String(time() - $userstats{lc $seen[0]}{'Join'}).")"; - } - - if (&IsParam("seenStats")) { - my $i; - $i = $userstats{lc $seen[0]}{'Count'}; - $reply .= ". Has said a total of \002$i\002 messages" if (defined $i); - $i = $userstats{lc $seen[0]}{'Time'}; - $reply .= ". Is idling for ".&Time2String(time() - $i) if (defined $i); - } + my $line = &getRandomLineFromFile($bot_data_dir. "/blootbot.lart"); + if (defined $line) { + if ($target =~ /^(me|you|itself|\Q$ident\E)$/i) { + $line =~ s/WHO/$who/g; } else { - my $howlong = &Time2String(time() - $seen[1]); - $reply = "$seen[0] <$seen[3]> was last seen on IRC ". - "in channel $seen[2], $howlong ago, ". - "saying\002:\002 '$seen[4]'."; + $line =~ s/WHO/$target/g; } + $line .= ", courtesy of $who" if ($extra); - &performStrictReply($reply); - return $noreply; + &action($chan, $line); + } else { + &status("lart: error reading file?"); } +} - # slashdot headlines: from Chris Tessone. - if ($message =~ /^slashdot$/i) { - return $noreply unless (&hasParam("slashdot")); +sub DebianNew { + my $idx = "debian/Packages-woody.idx"; + my $error = 0; + my %pkg; + my @new; - &Forker("slashdot", sub { &Slashdot::Slashdot() }); + $error++ unless ( -e $idx); + $error++ unless ( -e "$idx-old"); - $cmdstats{'Slashdot'}++; - return $noreply; + if ($error) { + $error = "no woody/woody-old index file found."; + &ERROR("Debian: $error"); + &msg($who, $error); + return; } - # Topic management. xk++ - # may want to add a flag(??) for topic in the near future. -xk - if ($message =~ /^topic(\s+(.*))?$/i) { - return $noreply unless (&hasParam("topic")); + open(IDX1, $idx); + open(IDX2, "$idx-old"); - my $chan = $talkchannel; - my @args = split(/ /, $2); + while () { + chop; + next if (/^\*/); - if (!scalar @args) { - &msg($who,"Try 'help topic'"); - return $noreply; - } + $pkg{$_} = 1; + } + close IDX2; - $chan = lc(shift @args) if ($msgType eq 'private'); - my $thiscmd = shift @args; + open(IDX1,$idx); + while () { + chop; + next if (/^\*/); + next if (exists $pkg{$_}); - # topic over public: - if ($msgType eq 'public' && $thiscmd =~ /^#/) { - &msg($who, "error: channel argument is not required."); - &msg($who, "\002Usage\002: topic "); - return $noreply; - } + push(@new); + } + close IDX1; - # topic over private: - if ($msgType eq 'private' && $chan !~ /^#/) { - &msg($who, "error: channel argument is required."); - &msg($who, "\002Usage\002: topic #channel "); - return $noreply; - } + &::performStrictReply( &::formListReply(0, "New debian packages:", @new) ); +} - if (&validChan($chan) == 0) { - &msg($who,"error: invalid channel \002$chan\002"); - return $noreply; - } +sub do_verstats { + my ($chan) = @_; - # for semi-outsiders. - if (!&IsNickInChan($who,$chan)) { - &msg($who, "Failed. You ($who) are not in $chan, hey?"); - return $noreply; - } + if (!defined $chan) { + &help("verstats"); + return; + } - # now lets do it. - &loadMyModule($myModules{'topic'}); - &Topic($chan, $thiscmd, join(' ', @args)); - $cmdstats{'Topic'}++; - return $noreply; + if (!&validChan($chan)) { + &msg($who, "chan $chan is invalid."); + return; } - # Countdown. - if ($message =~ /^countdown(\s+(\S+))?$/i) { - return $noreply unless (&hasParam("countdown")); + if (scalar @vernick > scalar(keys %{ $channels{lc $chan}{''} })/4) { + &msg($who, "verstats already in progress for someone else."); + return; + } - my $query = $2; + &msg($who, "Sending CTCP VERSION to #$chan..."); + $conn->ctcp("VERSION", $chan); + $cache{verstats}{chan} = $chan; + $cache{verstats}{who} = $who; + $cache{verstats}{msgType} = $msgType; - &loadMyModule($myModules{'countdown'}); - &Countdown($query); + $conn->schedule(30, sub { + my $c = lc $cache{verstats}{chan}; + @vernicktodo = (); - $cmdstats{'Countdown'}++; + foreach (keys %{ $channels{$c}{''} } ) { + next if (grep /^\Q$_\E$/i, @vernick); + push(@vernicktodo, $_); + } - return $noreply; - } + &verstats_flush(); + } ); - # User Information Services. requested by Flugh. - if ($message =~ /^u(ser)?info(\s+(.*))?$/i) { - return $noreply unless (&hasParam("userinfo")); - &loadMyModule($myModules{'userinfo'}); + $conn->schedule(60, sub { + my $vtotal = 0; + my $c = lc $cache{verstats}{chan}; + my $total = keys %{ $channels{$c}{''} }; + $chan = $c; + $who = $cache{verstats}{who}; + $msgType = $cache{verstats}{msgType}; + delete $cache{verstats}; # sufficient? - my $arg = $3; - if (!defined $arg or $arg eq "") { - &help("userinfo"); - return $noreply; + foreach (keys %ver) { + $vtotal += scalar keys %{ $ver{$_} }; } - if ($arg =~ /^set(\s+(.*))?$/i) { - $arg = $2; - if (!defined $arg) { - &help("userinfo set"); - return $noreply; - } + my %sorted; + my $unknown = $total - $vtotal; + my $perc = sprintf("%.1f", $unknown * 100 / $total); + $perc =~ s/.0$//; + $sorted{$perc}{"unknown/cloak"} = "$unknown ($perc%)" if ($unknown); - &UserInfoSet(split /\s+/, $arg, 2); - } elsif ($arg =~ /^unset(\s+(.*))?$/i) { - $arg = $2; - if (!defined $arg) { - &help("userinfo unset"); - return $noreply; - } + foreach (keys %ver) { + my $count = scalar keys %{ $ver{$_} }; + $perc = sprintf("%.01f", $count * 100 / $total); + $perc =~ s/.0$//; # lame compression. - &UserInfoSet($arg, ""); - } else { - &UserInfoGet($arg); + $sorted{$perc}{$_} = "$count ($perc%)"; } - $cmdstats{'UIS'}++; - return $noreply; - } + ### can be compressed to a map? + my @list; + foreach ( sort { $b <=> $a } keys %sorted ) { + my $perc = $_; + foreach (sort keys %{ $sorted{$perc} }) { + push(@list, "$_ - $sorted{$perc}{$_}"); + } + } - # Uptime. xk++ - if ($message =~ /^uptime$/i) { - return $noreply unless (&hasParam("uptime")); + &pSReply( &formListReply(0, "IRC Client versions for $c ", @list) ); - my $count = 1; - &msg($who, "- Uptime for $ident -"); - &msg($who, "Now: ". &Time2String(&uptimeNow()) ." running $bot_version"); - foreach (&uptimeGetInfo()) { - /^(\d+)\.\d+ (.*)/; - my $time = &Time2String($1); - my $info = $2; + # clean up not-needed data structures. + undef %ver; + undef @vernick; + } ); - &msg($who, "$count: $time $2"); - $count++; - } + return; +} + +sub verstats_flush { + for (1..5) { + last unless (scalar @vernicktodo); - $cmdstats{'Uptime'}++; - return $noreply; + my $n = shift(@vernicktodo); + $conn->ctcp("VERSION", $n); } - # wingate. - if ($message =~ /^wingate$/i) { - return $noreply unless (&hasParam("wingate")); + return unless (scalar @vernicktodo); - my $reply = "Wingate statistics: scanned \002" - .scalar(keys %wingate)."\002 hosts"; - my $queue = scalar(keys %wingateToDo); - if ($queue) { - $reply .= ". I have \002$queue\002 hosts in the queue"; - $reply .= ". Started the scan ".&Time2String(time() - $wingaterun)." ago"; + $conn->schedule(3, \&verstats_flush() ); +} + +sub textstats_main { + my($arg) = @_; + + # even more uglier with channel/time arguments. + my $c = $chan; +# my $c = $chan || "PRIVATE"; + my $where = "channel=".&dbQuote($c) if (defined $c); + &DEBUG("not using chan arg") if (!defined $c); + my $sum = (&dbRawReturn("SELECT SUM(counter) FROM stats" + ." WHERE ".$where ))[0]; + + if (!defined $arg or $arg =~ /^\s*$/) { + # this is way fucking ugly. + &DEBUG("_stats: !arg"); + + my %hash = &dbGetCol("stats", "nick,counter", + $where." ORDER BY counter DESC LIMIT 3", 1); + my $i; + my @top; + + # unfortunately we have to sort it again! + # todo: make dbGetCol return hash and array? too much effort. + my $tp = 0; + foreach $i (sort { $b <=> $a } keys %hash) { + foreach (keys %{ $hash{$i} }) { + my $p = sprintf("%.01f", 100*$i/$sum); + $tp += $p; + push(@top, "\002$_\002 -- $i ($p%)"); + } } - &performStrictReply("$reply."); + my $topstr = ""; + &DEBUG("*stats: tp => $tp"); + if (scalar @top) { + $topstr = ". Top ".scalar(@top).": ".join(', ', @top); + } - return $noreply; - } + if (defined $sum) { + &pSReply("total count of \037$type\037 on \002$c\002: $sum$topstr"); + } else { + &pSReply("zero counter for \037$type\037."); + } + } else { + my %hash = &dbGetCol("stats", "type,counter", + "$where AND nick=".&dbQuote($arg) ); - # convert. - if ($message =~ /^convert(\s+(.*))?$/i) { - return $noreply unless (&hasParam("units")); + foreach (keys %hash) { + &DEBUG("_stats: hash{$_} => $hash{$_}"); + # ranking. + my @array = &dbGet("stats", "nick", + $where." ORDER BY counter", 1); + my $good = 0; + my $i = 0; + for($i=0; $i $i, good => $good, total => $total"); } - my ($from,$to); - ($from,$to) = ($1,$2) if ($str =~ /^(.*) to (.*)$/); - ($from,$to) = ($2,$1) if ($str =~ /^(.*) from (.*)$/); - if (!defined $from or !defined $to or $to eq "" or $from eq "") { - &msg($who, "Invalid format!"); - &help("convert"); - return $noreply; + return; + + if (!defined $x) { # !defined. + &pSReply("$arg has not said $type yet."); + return; } - &Forker("units", sub { &Units::convertUnits($from, $to); } ); + my $xtra = ""; + if ($total and $good) { + my $pct = sprintf("%.01f", 100*(1+$total-$i)/$total); + $xtra = ", ranked $i\002/\002$total (percentile: \002$pct\002 %)"; + } - return $noreply; + my $pct1 = sprintf("%.01f", 100*$x/$sum); + &pSReply("\002$arg\002 has said \037$type\037 \002$x\002 times (\002$pct1\002 %)$xtra"); } - - # do nothing and let the other routines have a go - return ''; } +sub nullski { my ($arg) = @_; return unless (defined $arg); + foreach (`$arg`) { &msg($who,$_); } } + 1;