]> git.donarmstrong.com Git - infobot.git/blob - src/CommandStubs.pl
- unified for global command hooks
[infobot.git] / src / CommandStubs.pl
1 #
2 # User Command Extension Stubs
3 #
4
5 if (&IsParam("useStrict")) { use strict; }
6
7 $babel::lang_regex = "";        # lame fix.
8
9 ### PROPOSED COMMAND HOOK IMPLEMENTATION.
10 # addCmdHook("SECTION", 'TEXT_HOOK',
11 #       (CODEREF        => 'Blah', 
12 #       Forker          => 1,
13 #       CheckModule     => 1,                   # ???
14 #       Module          => 'blah.pl'            # preload module.
15 #       Identifier      => 'config_label',      # change to Config?
16 #       Help            => 'help_label',
17 #       Cmdstats        => 'text_label',)
18 #}
19 ###
20
21 sub addCmdHook {
22     my ($hashname, $ident, %hash) = @_;
23
24     &VERB("aCH: added $ident",2);       # use $hash{'Identifier'}?
25     ### hrm... prevent warnings?
26     ${"hooks_$hashname"}{$ident} = \%hash;
27 }
28
29 # RUN IF ADDRESSED.
30 sub parseCmdHook {
31     my ($hashname, $line) = @_;
32     my @args    = split(' ', $line);
33     my $cmd     = shift(@args);
34
35     &shmFlush();
36
37 ### DOES NOT WORK?
38 #    if (!exists %{"hooks_$hashname"}) {
39 #       &WARN("cmd hooks \%$hashname does not exist.");
40 #       return 0;
41 #    }
42
43     foreach (keys %{"hooks_$hashname"}) {
44         my $ident = $_;
45
46         next unless ($cmd =~ /^$ident$/i);
47
48         &DEBUG("pCH(hooks_$hashname): $cmd matched $ident");
49         my %hash = %{ ${"hooks_$hashname"}{$ident} };
50
51         if (!exists $hash{CODEREF}) {
52             &ERROR("CODEREF undefined for $cmd or $ident.");
53             return 1;
54         }
55
56         ### DEBUG.
57         foreach (keys %hash) {
58             &DEBUG(" $cmd->$_ => '$hash{$_}'.");
59         }
60
61         ### HELP.
62         if (exists $hash{'Help'} and !scalar(@args)) {
63             &help( $hash{'Help'} );
64             return 1;
65         }
66
67         ### IDENTIFIER.
68         if (exists $hash{'Identifier'}) {
69             return $noreply unless (&hasParam($hash{'Identifier'}));
70         }
71
72         ### FORKER,IDENTIFIER,CODEREF.
73         if (exists $hash{'Forker'}) {
74             &Forker($hash{'Identifier'}, sub { \&{$hash{'CODEREF'}}(@args) } );
75         } else {
76             if (exists $hash{'Module'}) {
77                 &loadMyModule($myModules{ $hash{'Module'} });
78             }
79
80             ### TODO: check if CODEREF exists.
81
82 ### ANY PROBLEMS WITH THIS? if so, add option to do either.
83 ###         &{$hash{'CODEREF'}}(@args);
84             &{$hash{'CODEREF'}}(join ' ', @args);
85         }
86
87         ### CMDSTATS.
88         if (exists $hash{'Cmdstats'}) {
89             ${"hooks_$hashname"}{$hash{'Cmdstats'}}++;
90         }
91
92         &DEBUG("pCH: ended.");
93
94         return 1;
95     }
96
97     return 0;
98 }
99
100 ###
101 ### START ADDING HOOKS.
102 ###
103 &addCmdHook("extra", 'd?bugs', ('CODEREF' => 'debianBugs',
104         'Forker' => 1, 'Identifier' => 'debianExtra',
105         'Cmdstats' => 'Debian Bugs') );
106 &addCmdHook("extra", 'dauthor', ('CODEREF' => 'Debian::searchAuthor',
107         'Forker' => 1, 'Identifier' => 'debian',
108         'Cmdstats' => 'Debian Author Search', 'Help' => "dauthor" ) );
109 &addCmdHook("extra", '(d|search)desc', ('CODEREF' => 'Debian::searchDesc',
110         'Forker' => 1, 'Identifier' => 'debian',
111         'Cmdstats' => 'Debian Desc Search', 'Help' => "ddesc" ) );
112 &addCmdHook("extra", 'dnew', ('CODEREF' => 'DebianNew',
113         'Identifier' => 'debian' ) );
114 &addCmdHook("extra", 'dincoming', ('CODEREF' => 'Debian::generateIncoming',
115         'Forker' => 1, 'Identifier' => 'debian' ) );
116 &addCmdHook("extra", 'dstats', ('CODEREF' => 'Debian::infoStats',
117         'Forker' => 1, 'Identifier' => 'debian',
118         'Cmdstats' => 'Debian Statistics' ) );
119 &addCmdHook("extra", 'd?contents', ('CODEREF' => 'Debian::searchContents',
120         'Forker' => 1, 'Identifier' => 'debian',
121         'Cmdstats' => 'Debian Contents Search', 'Help' => "contents" ) );
122 &addCmdHook("extra", 'd?find', ('CODEREF' => 'Debian::DebianFind',
123         'Forker' => 1, 'Identifier' => 'debian',
124         'Cmdstats' => 'Debian Search', 'Help' => "find" ) );
125 &addCmdHook("extra", 'insult', ('CODEREF' => 'Insult::Insult',
126         'Forker' => 1, 'Identifier' => 'insult', 'Help' => "insult" ) );
127 &addCmdHook("extra", 'kernel', ('CODEREF' => 'Kernel::Kernel',
128         'Forker' => 1, 'Identifier' => 'kernel',
129         'Cmdstats' => 'Kernel') );
130 &addCmdHook("extra", 'listauth', ('CODEREF' => 'CmdListAuth',
131         'Identifier' => 'search', Module => 'factoids', 
132         'Help' => 'listauth') );
133 &addCmdHook("extra", 'quote', ('CODEREF' => 'Quote::Quote',
134         'Forker' => 1, 'Identifier' => 'quote',
135         'Help' => 'quote', 'Cmdstats' => 'Quote') );
136 &addCmdHook("extra", 'countdown', ('CODEREF' => 'Countdown',
137         'Module' => 'countdown', 'Identifier' => 'countdown',
138         'Cmdstats' => 'Countdown') );
139 &addCmdHook("extra", 'lart', ('CODEREF' => 'lart',
140         'Identifier' => 'lart', 'Help' => 'lart') );
141 &addCmdHook("extra", 'convert', ('CODEREF' => 'convert',
142         'Forker' => 1, 'Identifier' => 'units',
143         'Help' => 'convert') );
144 &addCmdHook("extra", '(cookie|random)', ('CODEREF' => 'cookie',
145         'Forker' => 1, 'Identifier' => 'factoids') );
146 &addCmdHook("extra", 'u(ser)?info', ('CODEREF' => 'userinfo',
147         'Identifier' => 'userinfo', 'Help' => 'userinfo',
148         'Module' => 'userinfo') );
149 &addCmdHook("extra", 'rootWarn', ('CODEREF' => 'CmdrootWarn',
150         'Identifier' => 'rootWarn', 'Module' => 'rootwarn') );
151 &addCmdHook("extra", 'seen', ('CODEREF' => 'seen', 'Identifier' =>
152         'seen') );
153 &addCmdHook("extra", 'dict', ('CODEREF' => 'Dict::Dict',
154         'Identifier' => 'dict', 'Help' => 'dict',
155         'Forker' => 1, 'Cmdstats' => 'Dict') );
156 &addCmdHook("extra", 'slashdot', ('CODEREF' => 'Slashdot::Slashdot',
157         'Identifier' => 'slashdot', 'Forker' => 1,
158         'Cmdstats' => 'Slashdot') );
159 &addCmdHook("extra", 'uptime', ('CODEREF' => 'uptime', 'Identifier' => 'uptime',
160         'Cmdstats' => 'Uptime') );
161 &addCmdHook("extra", 'nullski', ('CODEREF' => 'nullski', ) );
162 &addCmdHook("extra", 'crash', ('CODEREF' => 'crash' ) );
163 sub nullski { my ($arg) = @_; foreach (`$arg`) { &msg($who,$_); } }
164 &addCmdHook("extra", '(fm|freshmeat)', ('CODEREF' => 'Freshmeat::Freshmeat',
165         'Identifier' => 'freshmeat', 'Cmdstats' => 'Freshmeat',
166         'Forker' => 1, 'Help' => 'freshmeat') );
167 ###
168 ### END OF ADDING HOOKS.
169 ###
170 &status("CMD: loaded ".scalar(keys %hooks_extra)." EXTRA command hooks.");
171
172 sub Modules {
173     if (!defined $message) {
174         &WARN("Modules: message is undefined. should never happen.");
175         return;
176     }
177
178     # babel bot: Jonathan Feinberg++
179     if (&IsParam("babelfish") and $message =~ m{
180                 ^\s*
181                 (?:babel(?:fish)?|x|xlate|translate)
182                 \s+
183                 (to|from)               # direction of translation (through)
184                 \s+
185                 ($babel::lang_regex)\w* # which language?
186                 \s*
187                 (.+)                    # The phrase to be translated
188         }xoi) {
189
190         &Forker("babelfish", sub { &babel::babelfish(lc $1, lc $2, $3); } );
191
192         $cmdstats{'BabelFish'}++;
193         return $noreply;
194     }
195
196     if (&IsParam("debian")) {
197         my $debiancmd    = 'conflicts?|depends?|desc|file|info|provides?';
198         $debiancmd      .= '|recommends?|suggests?|maint|maintainer';
199         if ($message =~ /^($debiancmd)(\s+(.*))?$/i) {
200             my $package = lc $3;
201
202             if (defined $package) {
203                 &Forker("debian", sub { &Debian::infoPackages($1, $package); } );
204             } else {
205                 &help($1);
206             }
207
208             return $noreply;
209         }
210     }
211
212     # google searching. Simon++
213     if (&IsParam("wwwsearch") and $message =~ /^(?:search\s+)?(\S+)\s+for\s+['"]?(.*?)['"]?\s*\?*$/i) {
214         return $noreply unless (&hasParam("wwwsearch"));
215
216         &Forker("wwwsearch", sub { &W3Search::W3Search($1,$2); } );
217
218         $cmdstats{'WWWSearch'}++;
219         return $noreply;
220     }
221
222     # list{keys|values}. xk++. Idea taken from #linuxwarez@EFNET
223     if ($message =~ /^list(\S+)( (.*))?$/i) {
224         return $noreply unless (&hasParam("search"));
225
226         my $thiscmd     = lc($1);
227         my $args        = $3;
228
229         $thiscmd =~ s/^vals$/values/;
230         return $noreply if ($thiscmd ne "keys" && $thiscmd ne "values");
231
232         # Usage:
233         if (!defined $args) {
234             &help("list". $thiscmd);
235             return $noreply;
236         }
237
238         if (length $args == 1) {
239             &msg($who,"search string is too short.");
240             return $noreply;
241         }
242
243         &Forker("search", sub { &Search::Search($thiscmd, $args); } );
244
245         $cmdstats{'Factoid Search'}++;
246         return $noreply;
247     }
248
249     # Nickometer. Adam Spiers++
250     if ($message =~ /^(?:lame|nick)ometer(?: for)? (\S+)/i) {
251         return $noreply unless (&hasParam("nickometer"));
252
253         my $term = (lc $1 eq 'me') ? $who : $1;
254         $term =~ s/\?+\s*//;
255
256         &loadMyModule($myModules{'nickometer'});
257         my $percentage = &nickometer($term);
258
259         if ($percentage =~ /NaN/) {
260             $percentage = "off the scale";
261         } else {
262             $percentage = sprintf("%0.4f", $percentage);
263             $percentage =~ s/\.?0+$//;
264             $percentage .= '%';
265         }
266
267         if ($msgType eq 'public') {
268             &say("'$term' is $percentage lame, $who");
269         } else {
270             &msg($who, "the 'lame nick-o-meter' reading for $term is $percentage, $who");
271         }
272
273         return $noreply;
274     }
275
276     # Topic management. xk++
277     # may want to add a flag(??) for topic in the near future. -xk
278     if ($message =~ /^topic(\s+(.*))?$/i) {
279         return $noreply unless (&hasParam("topic"));
280
281         my $chan        = $talkchannel;
282         my @args        = split(/ /, $2);
283
284         if (!scalar @args) {
285             &msg($who,"Try 'help topic'");
286             return $noreply;
287         }
288
289         $chan           = lc(shift @args) if ($msgType eq 'private');
290         my $thiscmd     = shift @args;
291
292         # topic over public:
293         if ($msgType eq 'public' && $thiscmd =~ /^#/) {
294             &msg($who, "error: channel argument is not required.");
295             &msg($who, "\002Usage\002: topic <CMD>");
296             return $noreply;
297         }
298
299         # topic over private:
300         if ($msgType eq 'private' && $chan !~ /^#/) {
301             &msg($who, "error: channel argument is required.");
302             &msg($who, "\002Usage\002: topic #channel <CMD>");
303             return $noreply;
304         }
305
306         if (&validChan($chan) == 0) {
307             &msg($who,"error: invalid channel \002$chan\002");
308             return $noreply;
309         }
310
311         # for semi-outsiders.
312         if (!&IsNickInChan($who,$chan)) {
313             &msg($who, "Failed. You ($who) are not in $chan, hey?");
314             return $noreply;
315         }
316
317         # now lets do it.
318         &loadMyModule($myModules{'topic'});
319         &Topic($chan, $thiscmd, join(' ', @args));
320         $cmdstats{'Topic'}++;
321         return $noreply;
322     }
323
324     # wingate.
325     if ($message =~ /^wingate$/i) {
326         return $noreply unless (&hasParam("wingate"));
327
328         my $reply = "Wingate statistics: scanned \002"
329                         .scalar(keys %wingate)."\002 hosts";
330         my $queue = scalar(keys %wingateToDo);
331         if ($queue) {
332             $reply .= ".  I have \002$queue\002 hosts in the queue";
333             $reply .= ".  Started the scan ".&Time2String(time() - $wingaterun)." ago";
334         }
335
336         &performStrictReply("$reply.");
337
338         return $noreply;
339     }
340
341     # do nothing and let the other routines have a go
342     return '';
343 }
344
345 # Freshmeat. xk++
346 sub freshmeat {
347     my ($query) = @_;
348
349     if (!defined $query) {
350         &help("freshmeat");
351         &msg($who, "I have \002".&countKeys("freshmeat")."\002 entries.");
352         return $noreply;
353     }
354
355     &Freshmeat::Freshmeat($query);
356 }
357
358 # Uptime. xk++
359 sub uptime {
360     my $count = 1;
361     &msg($who, "- Uptime for $ident -");
362     &msg($who, "Now: ". &Time2String(&uptimeNow()) ." running $bot_version");
363
364     foreach (&uptimeGetInfo()) {
365         /^(\d+)\.\d+ (.*)/;
366         my $time = &Time2String($1);
367         my $info = $2;
368
369         &msg($who, "$count: $time $2");
370         $count++;
371     }
372 }
373
374 # seen.
375 sub seen {
376     my($person) = @_;
377
378     if (!defined $person) {
379         &help("seen");
380
381         my $i = &countKeys("seen");
382         &msg($who,"there ". &fixPlural("is",$i) ." \002$i\002 ".
383                 "seen ". &fixPlural("entry",$i) ." that I know of.");
384
385         return $noreply;
386     }
387
388     my @seen;
389     $person =~ s/\?*$//;
390
391     &seenFlush();       # very evil hack. oh well, better safe than sorry.
392
393     ### TODO: Support &dbGetRowInfo(); like in &FactInfo();
394     my $select = "nick,time,channel,host,message";
395     if ($person eq "random") {
396         @seen = &randKey("seen", $select);
397     } else {
398         @seen = &dbGet("seen", "nick", $person, $select);
399     }
400
401     if (scalar @seen < 2) {
402         foreach (@seen) {
403             &DEBUG("seen: _ => '$_'.");
404         }
405         &performReply("i haven't seen '$person'");
406         return $noreply;
407     }
408
409     # valid seen.
410     my $reply;
411     ### TODO: multi channel support. may require &IsNick() to return
412     ### all channels or something.
413     my @chans = &GetNickInChans($seen[0]);
414     if (scalar @chans) {
415         $reply = "$seen[0] is currently on";
416
417         foreach (@chans) {
418             $reply .= " ".$_;
419             next unless (exists $userstats{lc $seen[0]}{'Join'});
420             $reply .= " (".&Time2String(time() - $userstats{lc $seen[0]}{'Join'}).")";
421         }
422
423         if (&IsParam("seenStats")) {
424             my $i;
425             $i = $userstats{lc $seen[0]}{'Count'};
426             $reply .= ".  Has said a total of \002$i\002 messages" if (defined $i);
427             $i = $userstats{lc $seen[0]}{'Time'};
428             $reply .= ".  Is idling for ".&Time2String(time() - $i) if (defined $i);
429         }
430     } else {
431         my $howlong = &Time2String(time() - $seen[1]);
432         $reply = "$seen[0] <$seen[3]> was last seen on IRC ".
433                  "in channel $seen[2], $howlong ago, ".
434                  "saying\002:\002 '$seen[4]'.";
435     }
436
437     &performStrictReply($reply);
438     return $noreply;
439 }
440
441 # User Information Services. requested by Flugh.
442 sub userinfo {
443     my ($arg) = join(' ',@_);
444
445     if ($arg =~ /^set(\s+(.*))?$/i) {
446         $arg = $2;
447         if (!defined $arg) {
448             &help("userinfo set");
449             return $noreply;
450         }
451
452         &UserInfoSet(split /\s+/, $arg, 2);
453     } elsif ($arg =~ /^unset(\s+(.*))?$/i) {
454         $arg = $2;
455         if (!defined $arg) {
456             &help("userinfo unset");
457             return $noreply;
458         }
459
460         &UserInfoSet($arg, "");
461     } else {
462         &UserInfoGet($arg);
463     }
464 }
465
466 # cookie (random). xk++
467 sub cookie {
468     my ($arg) = @_;
469
470     # lets find that secret cookie.
471     my $target          = ($msgType ne 'public') ? $who : $talkchannel;
472     my $cookiemsg       = &getRandom(keys %{$lang{'cookie'}});
473     my ($key,$value);
474
475     ### WILL CHEW TONS OF MEM.
476     ### TODO: convert this to a Forker function!
477     if ($arg) {
478         my @list = &searchTable("factoids", "factoid_key", "factoid_value", $arg);
479         $key  = &getRandom(@list);
480         $val  = &getFactInfo("factoids", $key, "factoid_value");
481     } else {
482         ($key,$value) = &randKey("factoids","factoid_key,factoid_value");
483     }
484
485     for ($cookiemsg) {
486         s/##KEY/\002$key\002/;
487         s/##VALUE/$value/;
488         s/##WHO/$who/;
489         s/\$who/$who/;  # cheap fix.
490         s/(\S+)?\s*<\S+>/$1 /;
491         s/\s+/ /g;
492     }
493
494     if ($cookiemsg =~ s/^ACTION //i) {
495         &action($target, $cookiemsg);
496     } else {
497         &msg($target, $cookiemsg);
498     }
499 }
500
501 sub convert {
502     my $arg = join(' ',@_);
503     my ($from,$to) = ('','');
504
505     ($from,$to) = ($1,$2) if ($arg =~ /^(.*?) to (.*)$/i);
506     ($from,$to) = ($2,$1) if ($arg =~ /^(.*?) from (.*)$/i);
507
508     if (!$to or !$from) {
509         &msg($who, "Invalid format!");
510         &help("convert");
511         return $noreply;
512     }
513
514     &Units::convertUnits($from, $to);
515
516     return $noreply;
517 }
518
519 sub lart {
520     my ($target) = &fixString($_[0]);
521     my $extra   = 0;
522     my $chan    = $talkchannel;
523
524     if ($msgType eq 'private') {
525         if ($target =~ /^($mask{chan})\s+(.*)$/) {
526             $chan       = $1;
527             $target     = $2;
528             $extra      = 1;
529         } else {
530             &msg($who, "error: invalid format or missing arguments.");
531             &help("lart");
532             return $noreply;
533         }
534     }
535
536     my $line = &getRandomLineFromFile($bot_misc_dir. "/blootbot.lart");
537     if (defined $line) {
538         if ($target =~ /^(me|you|itself|\Q$ident\E)$/i) {
539             $line =~ s/WHO/$who/g;
540         } else {
541             $line =~ s/WHO/$target/g;
542         }
543         $line .= ", courtesy of $who" if ($extra);
544
545         &action($chan, $line);
546     } else {
547         &status("lart: error reading file?");
548     }
549 }
550
551 sub DebianNew {
552     my $idx   = "debian/Packages-woody.idx";
553     my $error = 0;
554     my %pkg;
555     my @new;
556
557     $error++ unless ( -e $idx);
558     $error++ unless ( -e "$idx-old");
559
560     if ($error) {
561         $error = "no woody/woody-old index file found.";
562         &ERROR("Debian: $error");
563         &msg($who, $error);
564         return;
565     }
566
567     open(IDX1, $idx);
568     open(IDX2, "$idx-old");
569
570     while (<IDX2>) {
571         chop;
572         next if (/^\*/);
573
574         $pkg{$_} = 1;
575     }
576     close IDX2;
577
578     open(IDX1,$idx);
579     while (<IDX1>) {
580         chop;
581         next if (/^\*/);
582         next if (exists $pkg{$_});
583
584         push(@new);
585     }
586     close IDX1;
587
588     &main::performStrictReply( &main::formListReply(0, "New debian packages:", @new) );
589 }
590
591 1;