]> git.donarmstrong.com Git - infobot.git/blob - src/Modules/Factoids.pl
factstats author
[infobot.git] / src / Modules / Factoids.pl
1 #
2 #  Factoids.pl: Helpers for generating factoids statistics.
3 #       Author: dms
4 #      Version: v0.1 (20000514)
5 #     Splitted: SQLExtras.pl
6 #
7
8 use strict;
9
10 use vars qw($dbh $who);
11 use vars qw(%param);
12
13 ###
14 # Usage: &CmdFactInfo($faqtoid, $query);
15 sub CmdFactInfo {
16     my ($faqtoid, $query) = (lc $_[0], $_[1]);
17     my @array;
18     my $string = "";
19
20     if ($faqtoid eq "") {
21         &help("factinfo");
22         return;
23     }
24
25     my %factinfo = &sqlSelectRowHash("factoids", "*",
26         { factoid_key => $faqtoid }
27     );
28
29     # factoid does not exist.
30     if (scalar (keys %factinfo) <= 1) {
31         &performReply("there's no such factoid as \002$faqtoid\002");
32         return;
33     }
34
35     # fix for problem observed by asuffield.
36     # why did it happen though?
37     if (!$factinfo{'factoid_value'}) {
38         &performReply("there's no such factoid as \002$faqtoid\002; deleted because we don't have factoid_value!");
39         foreach (keys %factinfo) {
40             &DEBUG("factinfo{$_} => '$factinfo{$_}'.");
41         }
42 ###     &delFactoid($faqtoid);
43         return;
44     }
45
46     # created:
47     if ($factinfo{'created_by'}) {
48
49         $factinfo{'created_by'} =~ s/\!/ </;
50         $factinfo{'created_by'} .= ">";
51         $string  = "created by $factinfo{'created_by'}";
52
53         my $time = $factinfo{'created_time'};
54         if ($time) {
55             if (time() - $time > 60*60*24*7) {
56                 my $days = int( (time() - $time)/60/60/24 );
57                 $string .= " at \037". scalar(gmtime $time). "\037" .
58                                 " ($days days)";
59             } else {
60                 $string .= " ".&Time2String(time() - $time)." ago";
61             }
62         }
63
64         push(@array,$string);
65     }
66
67     # modified:
68     if (0 && $factinfo{'modified_by'}) {
69         $string = "last modified";
70
71         my $time = $factinfo{'modified_time'};
72         if ($time) {
73             if (time() - $time > 60*60*24*7) {
74                 $string .= " at \037". scalar(gmtime $time). "\037";
75             } else {
76                 $string .= " ".&Time2String(time() - $time)." ago ";
77             }
78         }
79
80         $string .= " by ".(split ",", $factinfo{'modified_by'})[0];
81
82         push(@array,$string);
83     }
84
85     # requested:
86     if ($factinfo{'requested_by'}) {
87         my $requested_count = $factinfo{'requested_count'};
88
89         if ($requested_count) {
90             $string  = "it has been requested ";
91             if ($requested_count == 1) {
92                 $string .= "\002once\002";
93             } else {
94                 $string .= "\002". $requested_count. "\002 ".
95                         &fixPlural("time", $requested_count);
96             }
97
98             my $requested_by = $factinfo{'requested_by'};
99             $requested_by =~ /\!/;
100             $string .= ", last by $`";
101
102             my $requested_time = $factinfo{'requested_time'};
103             if ($requested_time) {
104                 if (time() - $requested_time > 60*60*24*7) {
105                     $string .= " at \037". scalar(localtime $requested_time). "\037";
106                 } else {
107                     $string .= ", ".&Time2String(time() - $requested_time)." ago";
108                 }
109             }
110         } else {
111             $string  = "has not been requested yet";
112         }
113
114         push(@array, $string);
115     }
116
117     # locked:
118     if ($factinfo{'locked_by'}) {
119         $factinfo{'locked_by'} =~ /\!/;
120         $string = "it has been locked by $`";
121
122         push(@array, $string);
123     }
124
125     # factoid was inserted not through the bot.
126     if (!scalar @array) {
127         &performReply("no extra info on \002$faqtoid\002");
128         return;
129     }
130
131     &pSReply("$factinfo{'factoid_key'} -- ". join("; ", @array) .".");
132     return;
133 }
134
135 sub CmdFactStats {
136     my ($type) = @_;
137
138     if ($type =~ /^author$/i) {
139         my %hash = &sqlSelectColHash("factoids",
140                 "factoid_key,created_by", { },
141                 "created_by IS NOT NULL"
142         );
143         my %author;
144
145         foreach my $factoid (keys %hash) {
146             my $thisnuh = $hash{$factoid};
147
148             $thisnuh =~ /^(\S+)!\S+@\S+$/;
149             $author{lc $1}++;
150         }
151
152         if (!scalar keys %author) {
153             return 'sorry, no factoids with created_by field.';
154         }
155
156         # work-around.
157         my %count;
158         foreach (keys %author) {
159             $count{ $author{$_} }{$_} = 1;
160         }
161         undef %author;
162
163         my $count;
164         my @list;
165         foreach $count (sort { $b <=> $a } keys %count) {
166             my $author = join(", ", sort keys %{ $count{$count} });
167             push(@list, "$count by $author");
168         }
169
170         my $prefix = "factoid statistics by author: ";
171         return &formListReply(0, $prefix, @list);
172
173     } elsif ($type =~ /^vandalism$/i) {
174         &status("factstats(vandalism): starting...");
175         my $start_time  = &timeget();
176         my %data        = &sqlSelectColHash("factoids",
177                 "factoid_key,factoid_value", { },
178                 "factoid_value IS NOT NULL"
179         );
180         my @list;
181
182         my $delta_time  = &timedelta($start_time);
183         &status(sprintf("factstats(vandalismbroken): %.02f sec to retreive all factoids.", $delta_time)) if ($delta_time > 0);
184         $start_time     = &timeget();
185
186         # parse the factoids.
187         foreach (keys %data) {
188             if (&validFactoid($_, $data{$_}) == 0) {
189                 s/([\,\;]+)/\037$1\037/g;       # highlight chars.
190                 push(@list, $_);                # push it.
191             }
192         }
193
194         $delta_time     = &timedelta($start_time);
195         &status(sprintf("factstats(vandalism): %.02f sec to complete.", $delta_time)) if ($delta_time > 0);
196
197         # bail out on no results.
198         if (scalar @list == 0) {
199             return 'no vandalised factoids... wooohoo.';
200         }
201
202         # parse the results.
203         my $prefix = "Vandalised factoid ";
204         return &formListReply(1, $prefix, @list);
205
206     } elsif ($type =~ /^total$/i) {
207         &status("factstats(total): starting...");
208         my $start_time  = &timeget();
209         my @list;
210         my $str;
211         my($i,$j);
212         my %hash;
213
214         ### lets do it.
215         # total factoids requests.
216         $i = &sumKey("factoids", "requested_count");
217         push(@list, "total requests - $i");
218
219         # total factoids modified.
220         $str = &countKeys("factoids", "modified_by");
221         push(@list, "total modified - $str");
222
223         # total factoids modified.
224         $j      = &countKeys("factoids", "requested_count");
225         $str    = &countKeys("factoids", "factoid_key");
226         push(@list, "total non-requested - ".($str - $i));
227
228         # average request/factoid.
229         # i/j == total(requested_count)/count(requested_count)
230         $str = sprintf("%.01f", $i/$j);
231         push(@list, "average requested per factoid - $str");
232
233         # total prepared for deletion.
234         $str    = scalar( &searchTable("factoids", "factoid_key", "factoid_value", " #DEL") );
235         push(@list, "total prepared for deletion - $str");
236
237         # total unique authors.
238         # todo: convert to sqlSelectColHash ? (or ColArray?)
239         foreach ( &sqlRawReturn("SELECT created_by FROM factoids WHERE created_by IS NOT NULL") ) {
240             /^(\S+)!/;
241             my $nick = lc $1;
242             $hash{$nick}++;
243         }
244         push(@list, "total unique authors - ".(scalar keys %hash) );
245         undef %hash;
246
247         # total unique requesters.
248         foreach ( &sqlRawReturn("SELECT requested_by FROM factoids WHERE requested_by IS NOT NULL") ) {
249             /^(\S+)!/;
250             my $nick = lc $1;
251             $hash{$nick}++;
252         }
253         push(@list, "total unique requesters - ".(scalar keys %hash) );
254         undef %hash;
255
256         ### end of "job".
257
258         my $delta_time  = &timedelta($start_time);
259         &status(sprintf("factstats(broken): %.02f sec to retreive all factoids.", $delta_time)) if ($delta_time > 0);
260         $start_time     = &timeget();
261
262         # bail out on no results.
263         if (scalar @list == 0) {
264             return 'no broken factoids... wooohoo.';
265         }
266
267         # parse the results.
268         my $prefix = "General factoid stiatistics ";
269         return &formListReply(1, $prefix, @list);
270
271     } elsif ($type =~ /^deadredir$/i) {
272         my @list = &searchTable("factoids", "factoid_key",
273                         "factoid_value", "^<REPLY> see ");
274         my %redir;
275         my $f;
276
277         for (@list) {
278             my $factoid = $_;
279             my $val = &getFactInfo($factoid, "factoid_value");
280             if ($val =~ /^<REPLY> ?see( also)? (.*?)\.?$/i) {
281                 my $redirf = lc $2;
282                 my $redir = &getFactInfo($redirf, "factoid_value");
283                 next if (defined $redir);
284                 next if (length $val > 50);
285
286                 $redir{$redirf}{$factoid} = 1;
287             }
288         }
289
290         my @newlist;
291         foreach $f (keys %redir) {
292             my @sublist = keys %{ $redir{$f} };
293             for (@sublist) {
294                 s/([\,\;]+)/\037$1\037/g;
295             }
296
297             push(@newlist, join(', ', @sublist)." => $f");
298         }
299
300         # parse the results.
301         my $prefix = "Loose link (dead) redirections in factoids ";
302         return &formListReply(1, $prefix, @newlist);
303
304     } elsif ($type =~ /^dup(licate|e)$/i) {
305         &status("factstats(dupe): starting...");
306         my $start_time  = &timeget();
307         my %hash        = &sqlSelectColHash("factoids", 
308                 "factoid_key,factoid_value", { },
309                 "factoid_value IS NOT NULL", 1
310         );
311         my $refs        = 0;
312         my @list;
313         my $v;
314
315         foreach $v (keys %hash) {
316             my $count = scalar(keys %{ $hash{$v} });
317             next if ($count == 1);
318
319             my @sublist;
320             foreach (keys %{ $hash{$v} }) {
321                 if ($v =~ /^<REPLY> see /i) {
322                     $refs++;
323                     next;
324                 }
325
326                 s/([\,\;]+)/\037$1\037/g;
327                 if ($_ eq "") {
328                     &WARN("dupe: _ = NULL. should never happen!.");
329                     next;
330                 }
331                 push(@sublist, $_);
332             }
333
334             next unless (scalar @sublist);
335
336             push(@list, join(", ", @sublist));
337         }
338
339         &status("factstats(dupe): (good) dupe refs: $refs.");
340         my $delta_time  = &timedelta($start_time);
341         &status(sprintf("factstats(dupe): %.02f sec to complete", $delta_time)) if ($delta_time > 0);
342
343         # bail out on no results.
344         if (scalar @list == 0) {
345             return "no duplicate factoids... woohoo.";
346         }
347
348         # parse the results.
349         my $prefix = "dupe factoid ";
350         return &formListReply(1, $prefix, @list);
351
352     } elsif ($type =~ /^nullfactoids$/i) {
353         my $query = "SELECT factoid_key,factoid_value FROM factoids WHERE factoid_value=''";
354         my $sth = $dbh->prepare($query);
355         &ERROR("factstats(null): => '$query'.") unless $sth->execute;
356
357         my @list;
358         while (my @row = $sth->fetchrow_array) {
359             if ($row[1] ne "") {
360                 &DEBUG("row[1] != NULL for $row[0].");
361                 next;
362             }
363
364             &DEBUG("row[0] => '$row[0]'.");
365             push(@list, $row[0]);
366         }
367         $sth->finish;
368
369         # parse the results.
370         my $prefix = "NULL factoids (not deleted yet) ";
371         return &formListReply(1, $prefix, @list);
372
373     } elsif ($type =~ /^(2|too)short$/i) {
374         # Custom select statement.
375         my $query = "SELECT factoid_key,factoid_value FROM factoids WHERE length(factoid_value) <= 40";
376         my $sth = $dbh->prepare($query);
377         &ERROR("factstats(lame): => '$query'.") unless $sth->execute;
378
379         my @list;
380         while (my @row = $sth->fetchrow_array) {
381             my($key,$val) = ($row[0], $row[1]);
382             my $match = 0;
383             $match++ if ($val =~ /\s{3,}/);
384             next unless ($match);
385
386             my $v = &getFactoid($val);
387             if (defined $v) {
388                 &DEBUG("key $key => $val => $v");
389             }
390
391             $key =~ s/\,/\037\,\037/g;
392             push(@list, $key);
393         }
394         $sth->finish;
395
396         # parse the results.
397         my $prefix = "Lame factoids ";
398         return &formListReply(1, $prefix, @list);
399
400     } elsif ($type =~ /^listfix$/i) {
401         # Custom select statement.
402         my $query = "SELECT factoid_key,factoid_value FROM factoids";
403         my $sth = $dbh->prepare($query);
404         &ERROR("factstats(listfix): => '$query'.") unless $sth->execute;
405
406         my @list;
407         while (my @row = $sth->fetchrow_array) {
408             my($key,$val) = ($row[0], $row[1]);
409             my $match = 0;
410             $match++ if ($val =~ /\S+,? or \S+,? or \S+,? or \S+,?/);
411             next unless ($match);
412
413             $key =~ s/\,/\037\,\037/g;
414             push(@list, $key);
415             $val =~ s/,? or /, /g;
416             &DEBUG("fixed: => $val.");
417             &setFactInfo($key,"factoid_value", $val);
418         }
419         $sth->finish;
420
421         # parse the results.
422         my $prefix = "Inefficient lists fixed ";
423         return &formListReply(1, $prefix, @list);
424
425     } elsif ($type =~ /^locked$/i) {
426         my %hash = &sqlSelectColhash("factoids", 
427                 "factoid_key,locked_by", { },
428                 "locked_by IS NOT NULL"
429         );
430         my @list = keys %hash;
431
432         for (@list) {
433             s/([\,\;]+)/\037$1\037/g;
434         }
435
436         my $prefix = "factoid statistics on $type ";
437         return &formListReply(0, $prefix, @list);
438
439     } elsif ($type =~ /^new$/i) {
440         my %hash = &sqlSelectColHash("factoids",
441                 "factoid_key,created_time", { },
442                 "created_time IS NOT NULL"
443         );
444         my %age;
445
446         foreach (keys %hash) {
447             my $created_time = $hash{$_};
448             my $delta_time   = time() - $created_time;
449             next if ($delta_time >= 60*60*24);
450
451             $age{$delta_time}{$_} = 1;
452         }
453
454         if (scalar keys %age == 0) {
455             return "sorry, no new factoids.";
456         }
457
458         my @list;
459         foreach (sort {$a <=> $b} keys %age) {
460             push(@list, join(",", keys %{ $age{$_} }));
461         }
462
463         my $prefix = "new factoids in the last 24hours ";
464         return &formListReply(0, $prefix, @list);
465
466     } elsif ($type =~ /^part(ial)?dupe$/i) {
467         ### requires "custom" select statement... oh well...
468         my $start_time  = &timeget();
469
470         # form length|key and key=length hash list.
471         &status("factstats(partdupe): forming length hash list.");
472         my $query = "SELECT factoid_key,factoid_value,length(factoid_value) AS length FROM factoids WHERE length(factoid_value) >= 192 ORDER BY length";
473         my $sth = $dbh->prepare($query);
474         &ERROR("factstats(partdupe): => '$query'.") unless $sth->execute;
475
476         my (@key, @list);
477         my (%key, %length);
478         while (my @row = $sth->fetchrow_array) {
479             $length{$row[2]}{$row[0]} = 1;      # length(value)|key.
480             $key{$row[0]} = $row[1];            # key=value.
481             push(@key, $row[0]);
482         }
483         $sth->finish;
484         &status("factstats(partdupe): total keys => '". scalar(@key) ."'.");
485         &status("factstats(partdupe): now deciphering data gathered");
486
487         my @length = sort { $a <=> $b } keys %length;
488         my $key;
489
490         foreach $key (@key) {
491             shift @length if (length $key{$key} == $length[0]);
492
493             my $val = quotemeta $key{$key};
494             my @sublist;
495             my $length;
496             foreach $length (@length) {
497                 foreach (keys %{ $length{$length} }) {
498                     if ($key{$_} =~ /^$val/i) {
499                         s/([\,\;]+)/\037$1\037/g;
500                         s/( and|and )/\037$1\037/g;
501                         push(@sublist,$key." and ".$_);
502                     }
503                 }
504             }
505             push(@list, join(" ,",@sublist)) if (scalar @sublist);
506         }
507
508         my $delta_time = sprintf("%.02fs", &timedelta($start_time) );
509         &status("factstats(partdupe): $delta_time sec to complete.") if ($delta_time > 0);
510
511         # bail out on no results.
512         if (scalar @list == 0) {
513             return "no initial partial duplicate factoids... woohoo.";
514         }
515
516         # parse the results.
517         my $prefix = "initial partial dupe factoid ";
518         return &formListReply(1, $prefix, @list);
519
520     } elsif ($type =~ /^profanity$/i) {
521         my %data = &sqlSelectColHash("factoids",
522                 "factoid_key,factoid_value", { },
523                 "factoid_value IS NOT NULL"
524         );
525         my @list;
526
527         foreach (keys %data) {
528             push(@list, $_) if (&hasProfanity($_." ".$data{$_}));
529         }
530
531         # parse the results.
532         my $prefix = "Profanity in factoids ";
533         return &formListReply(1, $prefix, @list);
534
535     } elsif ($type =~ /^redir(ection)?$/i) {
536         my @list = &searchTable("factoids", "factoid_key",
537                         "factoid_value", "^<REPLY> see ");
538         my %redir;
539         my $f;
540
541         for (@list) {
542             my $factoid = $_;
543             my $val = &getFactInfo($factoid, "factoid_value");
544             if ($val =~ /^<REPLY> see( also)? (.*?)\.?$/i) {
545                 my $redir       = lc $2;
546                 my $redirval    = &getFactInfo($redir, "factoid_value");
547                 if (defined $redirval) {
548                     $redir{$redir}{$factoid} = 1;
549                 } else {
550                     &WARN("factstats(redir): '$factoid' has loose link => '$redir'.");
551                 }
552             }
553         }
554
555         my @newlist;
556         foreach $f (keys %redir) {
557             my @sublist = keys %{ $redir{$f} };
558             for (@sublist) {
559                 s/([\,\;]+)/\037$1\037/g;
560             }
561
562             push(@newlist, "$f => ". join(', ', @sublist));
563         }
564
565         # parse the results.
566         my $prefix = "Redirections in factoids ";
567         return &formListReply(1, $prefix, @newlist);
568
569     } elsif ($type =~ /^request(ed)?$/i) {
570         my %hash = &sqlSelectColHash("factoids",
571                 "factoid_key,requested_count", { },
572                 "requested_count IS NOT NULL", 1
573         );
574
575         if (!scalar keys %hash) {
576             return 'sorry, no factoids have been questioned.';
577         }
578
579         my $count;
580         my @list;
581         my $total       = 0;
582         foreach $count (sort {$b <=> $a} keys %hash) {
583             my @faqtoids = sort keys %{ $hash{$count} };
584
585             for (@faqtoids) {
586                 s/([\,\;]+)/\037$1\037/g;
587             }
588             $total      += $count * scalar(@faqtoids);
589
590             push(@list, "$count - ". join(", ", @faqtoids));
591         }
592         unshift(@list, "\037$total - TOTAL\037");
593
594         my $prefix = "factoid statistics on $type ";
595         return &formListReply(0, $prefix, @list);
596
597     } elsif ($type =~ /^reqrate$/i) {
598         my %hash = &sqlSelectColHash("factoids",
599                 "factoid_key,(unix_timestamp() - created_time)/requested_count as rate", { },
600                 "requested_by IS NOT NULL and created_time IS NOT NULL ORDER BY rate LIMIT 15", 1
601         );
602
603         my $rate;
604         my @list;
605         my $total       = 0;
606         my $users       = 0;
607         foreach $rate (sort { $b <=> $a } keys %hash) {
608             my $f       = join(", ", sort keys %{ $hash{$rate} });
609             my $str     = "$f - ".&Time2String($rate);
610             $str        =~ s/\002//g;
611             push(@list, $str);
612         }
613
614         my $prefix = "Rank of top factoid rate (time/req): ";
615         return &formListReply(0, $prefix, @list);
616
617     } elsif ($type =~ /^requesters?$/i) {
618         my %hash = &sqlSelectColHash("factoids",
619                 "factoid_key,requested_by", { },
620                 "requested_by IS NOT NULL"
621         );
622         my %requester;
623
624         foreach (keys %hash) {
625             my $thisnuh = $hash{$_};
626
627             $thisnuh =~ /^(\S+)!\S+@\S+$/;
628             $requester{lc $1}++;
629         }
630
631         if (!scalar keys %requester) {
632             return 'sorry, no factoids with requested_by field.';
633         }
634
635         # work-around.
636         my %count;
637         foreach (keys %requester) {
638             $count{ $requester{$_} }{$_} = 1;
639         }
640         undef %requester;
641
642         my $count;
643         my @list;
644         my $total       = 0;
645         my $users       = 0;
646         foreach $count (sort { $b <=> $a } keys %count) {
647             my $requester = join(", ", sort keys %{ $count{$count} });
648             $total      += $count * scalar(keys %{ $count{$count} });
649             $users      += scalar(keys %{ $count{$count} });
650             push(@list, "$count by $requester");
651         }
652         unshift(@list, "\037$total TOTAL REQUESTS; $users UNIQUE REQUESTERS\037");
653         # should not the above value be the same as collected by
654         # 'requested'? soemthing weird is going on!
655
656         my $prefix = "rank of top factoid requesters: ";
657         return &formListReply(0, $prefix, @list);
658
659     } elsif ($type =~ /^seefix$/i) {
660         my @list = &searchTable("factoids", "factoid_key",
661                         "factoid_value", "^see ");
662         my @newlist;
663         my $fixed = 0;
664         my %loop;
665         my $f;
666
667         for (@list) {
668             my $factoid = $_;
669             my $val = &getFactInfo($factoid, "factoid_value");
670         
671             next unless ($val =~ /^see( also)? (.*?)\.?$/i);
672
673             my $redirf  = lc $2;
674             my $redir   = &getFactInfo($redirf, "factoid_value");
675
676             if ($redirf =~ /^\Q$factoid\W$/i) {
677                 &delFactoid($factoid);
678                 $loop{$factoid} = 1;
679             }
680
681             if (defined $redir) {       # good.
682                 &setFactInfo($factoid,"factoid_value","<REPLY> see $redir");
683                 $fixed++;
684             } else {
685                 push(@newlist, $redirf);
686             }
687         }
688
689         # parse the results.
690         &msg($who, "Fixed $fixed factoids.");
691         &msg($who, "Self looped factoids removed: ".
692                 sort(keys %loop) ) if (scalar keys %loop);
693
694         my $prefix = "Loose link (dead) redirections in factoids ";
695         return &formListReply(1, $prefix, @newlist);
696
697     } elsif ($type =~ /^(2|too)long$/i) {
698         my @list;
699         my $query;
700
701         # factoid_key.
702         $query = "SELECT factoid_key FROM factoids WHERE length(factoid_key) >= $param{'maxKeySize'}";
703         my $sth = $dbh->prepare($query);
704         $sth->execute;
705         while (my @row = $sth->fetchrow_array) {
706             push(@list,$row[0]);
707         }
708         $sth->finish;
709
710         # factoid_value.
711         $query = "SELECT factoid_key,factoid_value FROM factoids WHERE length(factoid_value) >= $param{'maxDataSize'}";
712         $sth = $dbh->prepare($query);
713         $sth->execute;
714         while (my @row = $sth->fetchrow_array) {
715             push(@list,sprintf("\002%s\002 - %s", length($row[1]), $row[0]));
716         }
717         $sth->finish;
718
719         if (scalar @list == 0) {
720             return "good. no factoids exceed length.";
721         }
722
723         # parse the results.
724         my $prefix = "factoid key||value exceeding length ";
725         return &formListReply(1, $prefix, @list);
726
727     } elsif ($type =~ /^unrequest(ed)?$/i) {
728         # todo: use sqlSelect()
729         my @list = &sqlRawReturn("SELECT factoid_key FROM factoids WHERE requested_count IS NULL");
730
731         for (@list) {
732             s/([\,\;]+)/\037$1\037/g;
733         }
734
735         my $prefix = "Unrequested factoids ";
736         return &formListReply(0, $prefix, @list);
737     }
738
739     return "error: invalid type => '$type'.";
740 }
741
742 sub CmdListAuth {
743     my ($query) = @_;
744     my @list = &searchTable("factoids","factoid_key", "created_by", "^$query!");
745
746     my $prefix = "factoid author list by '$query' ";
747     &performStrictReply( &formListReply(1, $prefix, @list) );
748 }
749
750 1;