]> git.donarmstrong.com Git - infobot.git/blob - src/Modules/Factoids.pl
fix count(*)
[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: (TimRiker asks "why do you keep turning this off?)
68     if ($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(vandalism): %.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         my $dangling = 0;
541
542         for (@list) {
543             my $factoid = $_;
544             my $val = &getFactInfo($factoid, "factoid_value");
545             if ($val =~ /^<REPLY> see( also)? (.*?)\.?$/i) {
546                 my $redir       = lc $2;
547                 my $redirval    = &getFactInfo($redir, "factoid_value");
548                 if (defined $redirval) {
549                     $redir{$redir}{$factoid} = 1;
550                 } else {
551                     &DEBUG("factstats(redir): '$factoid' has loose link => '$redir'.");
552                     $dangling++;
553                 }
554             }
555         }
556
557         my @newlist;
558         foreach $f (keys %redir) {
559             my @sublist = keys %{ $redir{$f} };
560             for (@sublist) {
561                 s/([\,\;]+)/\037$1\037/g;
562             }
563
564             push(@newlist, "$f => ". join(', ', @sublist));
565         }
566
567         # parse the results.
568         my $prefix = "Redirections in factoids, $dangling dangling ";
569         return &formListReply(1, $prefix, @newlist);
570
571     } elsif ($type =~ /^request(ed)?$/i) {
572         my %hash = &sqlSelectColHash("factoids",
573                 "factoid_key,requested_count", { },
574                 "requested_count IS NOT NULL", 1
575         );
576
577         if (!scalar keys %hash) {
578             return 'sorry, no factoids have been questioned.';
579         }
580
581         my $count;
582         my @list;
583         my $total       = 0;
584         foreach $count (sort {$b <=> $a} keys %hash) {
585             my @faqtoids = sort keys %{ $hash{$count} };
586
587             for (@faqtoids) {
588                 s/([\,\;]+)/\037$1\037/g;
589             }
590             $total      += $count * scalar(@faqtoids);
591
592             push(@list, "$count - ". join(", ", @faqtoids));
593         }
594         unshift(@list, "\037$total - TOTAL\037");
595
596         my $prefix = "factoid statistics on $type ";
597         return &formListReply(0, $prefix, @list);
598
599     } elsif ($type =~ /^reqrate$/i) {
600         my %hash = &sqlSelectColHash("factoids",
601                 "factoid_key,(unix_timestamp() - created_time)/requested_count as rate", { },
602                 "requested_by IS NOT NULL and created_time IS NOT NULL ORDER BY rate LIMIT 15", 1
603         );
604
605         my $rate;
606         my @list;
607         my $total       = 0;
608         my $users       = 0;
609         foreach $rate (sort { $b <=> $a } keys %hash) {
610             my $f       = join(", ", sort keys %{ $hash{$rate} });
611             my $str     = "$f - ".&Time2String($rate);
612             $str        =~ s/\002//g;
613             push(@list, $str);
614         }
615
616         my $prefix = "Rank of top factoid rate (time/req): ";
617         return &formListReply(0, $prefix, @list);
618
619     } elsif ($type =~ /^requesters?$/i) {
620         my %hash = &sqlSelectColHash("factoids",
621                 "factoid_key,requested_by", { },
622                 "requested_by IS NOT NULL"
623         );
624         my %requester;
625
626         foreach (keys %hash) {
627             my $thisnuh = $hash{$_};
628
629             $thisnuh =~ /^(\S+)!\S+@\S+$/;
630             $requester{lc $1}++;
631         }
632
633         if (!scalar keys %requester) {
634             return 'sorry, no factoids with requested_by field.';
635         }
636
637         # work-around.
638         my %count;
639         foreach (keys %requester) {
640             $count{ $requester{$_} }{$_} = 1;
641         }
642         undef %requester;
643
644         my $count;
645         my @list;
646         my $total       = 0;
647         my $users       = 0;
648         foreach $count (sort { $b <=> $a } keys %count) {
649             my $requester = join(", ", sort keys %{ $count{$count} });
650             $total      += $count * scalar(keys %{ $count{$count} });
651             $users      += scalar(keys %{ $count{$count} });
652             push(@list, "$count by $requester");
653         }
654         unshift(@list, "\037$total TOTAL REQUESTS; $users UNIQUE REQUESTERS\037");
655         # should not the above value be the same as collected by
656         # 'requested'? soemthing weird is going on!
657
658         my $prefix = "rank of top factoid requesters: ";
659         return &formListReply(0, $prefix, @list);
660
661     } elsif ($type =~ /^seefix$/i) {
662         my @list = &searchTable("factoids", "factoid_key",
663                         "factoid_value", "^see ");
664         my @newlist;
665         my $fixed = 0;
666         my %loop;
667         my $f;
668
669         for (@list) {
670             my $factoid = $_;
671             my $val = &getFactInfo($factoid, "factoid_value");
672
673             next unless ($val =~ /^see( also)? (.*?)\.?$/i);
674
675             my $redirf  = lc $2;
676             my $redir   = &getFactInfo($redirf, "factoid_value");
677
678             if ($redirf =~ /^\Q$factoid\W$/i) {
679                 &delFactoid($factoid);
680                 $loop{$factoid} = 1;
681             }
682
683             if (defined $redir) {       # good.
684                 &setFactInfo($factoid,"factoid_value","<REPLY> see $redir");
685                 $fixed++;
686             } else {
687                 push(@newlist, $redirf);
688             }
689         }
690
691         # parse the results.
692         &msg($who, "Fixed $fixed factoids.");
693         &msg($who, "Self looped factoids removed: ".
694                 sort(keys %loop) ) if (scalar keys %loop);
695
696         my $prefix = "Loose link (dead) redirections in factoids ";
697         return &formListReply(1, $prefix, @newlist);
698
699     } elsif ($type =~ /^(2|too)long$/i) {
700         my @list;
701         my $query;
702
703         # factoid_key.
704         $query = "SELECT factoid_key FROM factoids WHERE length(factoid_key) >= $param{'maxKeySize'}";
705         my $sth = $dbh->prepare($query);
706         $sth->execute;
707         while (my @row = $sth->fetchrow_array) {
708             push(@list,$row[0]);
709         }
710         $sth->finish;
711
712         # factoid_value.
713         $query = "SELECT factoid_key,factoid_value FROM factoids WHERE length(factoid_value) >= $param{'maxDataSize'}";
714         $sth = $dbh->prepare($query);
715         $sth->execute;
716         while (my @row = $sth->fetchrow_array) {
717             push(@list,sprintf("\002%s\002 - %s", length($row[1]), $row[0]));
718         }
719         $sth->finish;
720
721         if (scalar @list == 0) {
722             return "good. no factoids exceed length.";
723         }
724
725         # parse the results.
726         my $prefix = "factoid key||value exceeding length ";
727         return &formListReply(1, $prefix, @list);
728
729     } elsif ($type =~ /^unrequest(ed)?$/i) {
730         # todo: use sqlSelect()
731         my ($count) = &sqlRawReturn("SELECT COUNT(*) FROM factoids WHERE requested_count = '0'");
732
733         return "Unrequested factoids: $count";
734     }
735
736     return "error: invalid type => '$type'.";
737 }
738
739 sub CmdListAuth {
740     my ($query) = @_;
741     my @list = &searchTable("factoids","factoid_key", "created_by", "^$query!");
742
743     my $prefix = "factoid author list by '$query' ";
744     &performStrictReply( &formListReply(1, $prefix, @list) );
745 }
746
747 1;