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