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