]> git.donarmstrong.com Git - infobot.git/blob - src/Modules/Factoids.pl
- added top3 irctextcounter stats
[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 if (&IsParam("useStrict")) { use strict; }
9
10 ###
11 # Usage: &CmdFactInfo($faqtoid, $query);
12 sub CmdFactInfo {
13     my ($faqtoid, $query) = (lc $_[0], $_[1]);
14     my @array;
15     my $string = "";
16
17     if ($faqtoid eq "") {
18         &help("factinfo");
19         return;
20     }
21
22     my $i = 0;
23     my %factinfo;
24     my @factinfo = &getFactInfo($faqtoid,"*");
25     foreach ( &dbGetColInfo("factoids") ) {
26         $factinfo{$_} = $factinfo[$i] || '';
27         $i++;
28     }
29
30     # factoid does not exist.
31     if (scalar @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(localtime $time). "\037" .
59                                 " ($days days)";
60             } else {
61                 $string .= " ".&Time2String(time() - $time)." ago";
62             }
63         }
64
65         push(@array,$string);
66     }
67
68     # modified:
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(localtime $time). "\037";
76 #           } else {
77 #               $string .= " ".&Time2String(time() - $time)." ago";
78 #           }
79 #       }
80 #
81 #       my @x;
82 #       foreach (split ",", $factinfo{'modified_by'}) {
83 #           /\!/;
84 #           push(@x, $`);
85 #       }
86 #       $string .= "by ".&IJoin(@x);
87 #
88 #       $i++;
89 #       push(@array,$string);
90 #    }
91
92     # requested:
93     if ($factinfo{'requested_by'}) {
94         my $requested_count = $factinfo{'requested_count'};
95
96         if ($requested_count) {
97             $string  = "it has been requested ";
98             if ($requested_count == 1) {
99                 $string .= "\002once\002";
100             } else {
101                 $string .= "\002". $requested_count. "\002 ".
102                         &fixPlural("time", $requested_count);
103             }
104         }
105
106         $string .= ", " if ($string ne "");
107
108         my $requested_by = $factinfo{'requested_by'};
109         $requested_by =~ /\!/;
110         $string .= "last by $`";
111
112         my $requested_time = $factinfo{'requested_time'};
113         if ($requested_time) {
114             if (time() - $requested_time > 60*60*24*7) {
115                 $string .= " at \037". scalar(localtime $requested_time). "\037";
116             } else {
117                 $string .= ", ".&Time2String(time() - $requested_time)." ago";
118             }
119         }
120
121         push(@array,$string);
122     }
123
124     # locked:
125     if ($factinfo{'locked_by'}) {
126         $factinfo{'locked_by'} =~ /\!/;
127         $string = "it has been locked by $`";
128
129         push(@array, $string);
130     }
131
132     # factoid was inserted not through the bot.
133     if (!scalar @array) {
134         &performReply("no extra info on \002$faqtoid\002");
135         return;
136     }
137
138     &performStrictReply("$factinfo{'factoid_key'} -- ". join("; ", @array) .".");
139     return;
140 }
141
142 sub CmdFactStats {
143     my ($type) = @_;
144
145     if ($type =~ /^author$/i) {
146         my %hash = &dbGetCol("factoids", "factoid_key,created_by", "created_by IS NOT NULL");
147         my %author;
148
149         foreach (keys %hash) {
150             my $thisnuh = $hash{$_};
151
152             $thisnuh =~ /^(\S+)!\S+@\S+$/;
153             $author{lc $1}++;
154         }
155
156         if (!scalar keys %author) {
157             return 'sorry, no factoids with created_by field.';
158         }
159
160         # work-around.
161         my %count;
162         foreach (keys %author) {
163             $count{ $author{$_} }{$_} = 1;
164         }
165         undef %author;
166
167         my $count;
168         my @list;
169         foreach $count (sort { $b <=> $a } keys %count) {
170             my $author = join(", ", sort keys %{ $count{$count} });
171             push(@list, "$count by $author");
172         }
173
174         my $prefix = "factoid statistics by author: ";
175         return &formListReply(0, $prefix, @list);
176
177     } elsif ($type =~ /^broken$/i) {
178         &status("factstats(broken): starting...");
179         my $start_time  = &timeget();
180         my %data        = &dbGetCol("factoids", "factoid_key,factoid_value", "factoid_value IS NOT NULL");
181         my @list;
182
183         my $delta_time  = &timedelta($start_time);
184         &status(sprintf("factstats(broken): %.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(broken): %.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 broken factoids... wooohoo.';
201         }
202
203         # parse the results.
204         my $prefix = "broken 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         foreach ( &dbRawReturn("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 ( &dbRawReturn("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        = &dbGetCol("factoids", "factoid_key,factoid_value", "factoid_value IS NOT NULL", 1);
308         my $refs        = 0;
309         my @list;
310         my $v;
311
312         foreach $v (keys %hash) {
313             my $count = scalar(keys %{ $hash{$v} });
314             next if ($count == 1);
315
316             my @sublist;
317             foreach (keys %{ $hash{$v} }) {
318                 if ($v =~ /^<REPLY> see /i) {
319                     $refs++;
320                     next;
321                 }
322
323                 s/([\,\;]+)/\037$1\037/g;
324                 if ($_ eq "") {
325                     &WARN("dupe: _ = NULL. should never happen!.");
326                     next;
327                 }
328                 push(@sublist, $_);
329             }
330
331             next unless (scalar @sublist);
332
333             push(@list, join(", ", @sublist));
334         }
335
336         &status("factstats(dupe): (good) dupe refs: $refs.");
337         my $delta_time  = &timedelta($start_time);
338         &status(sprintf("factstats(dupe): %.02f sec to complete", $delta_time)) if ($delta_time > 0);
339
340         # bail out on no results.
341         if (scalar @list == 0) {
342             return "no duplicate factoids... woohoo.";
343         }
344
345         # parse the results.
346         my $prefix = "dupe factoid ";
347         return &formListReply(1, $prefix, @list);
348
349     } elsif ($type =~ /^nullfactoids$/i) {
350         my $query = "SELECT factoid_key,factoid_value FROM factoids WHERE factoid_value=''";
351         my $sth = $dbh->prepare($query);
352         &ERROR("factstats(null): => '$query'.") unless $sth->execute;
353
354         my @list;
355         while (my @row = $sth->fetchrow_array) {
356             if ($row[1] ne "") {
357                 &DEBUG("row[1] != NULL for $row[0].");
358                 next;
359             }
360
361             &DEBUG("row[0] => '$row[0]'.");
362             push(@list, $row[0]);
363         }
364         $sth->finish;
365
366         # parse the results.
367         my $prefix = "NULL factoids (not deleted yet) ";
368         return &formListReply(1, $prefix, @list);
369
370     } elsif ($type =~ /^(2|too)short$/i) {
371         # Custom select statement.
372         my $query = "SELECT factoid_key,factoid_value FROM factoids WHERE length(factoid_value) <= 40";
373         my $sth = $dbh->prepare($query);
374         &ERROR("factstats(lame): => '$query'.") unless $sth->execute;
375
376         my @list;
377         while (my @row = $sth->fetchrow_array) {
378             my($key,$val) = ($row[0], $row[1]);
379             my $match = 0;
380             $match++ if ($val =~ /\s{3,}/);
381             next unless ($match);
382
383             my $v = &getFactoid($val);
384             if (defined $v) {
385                 &DEBUG("key $key => $val => $v");
386             }
387
388             $key =~ s/\,/\037\,\037/g;
389             push(@list, $key);
390         }
391         $sth->finish;
392
393         # parse the results.
394         my $prefix = "Lame factoids ";
395         return &formListReply(1, $prefix, @list);
396
397     } elsif ($type =~ /^listfix$/i) {
398         # Custom select statement.
399         my $query = "SELECT factoid_key,factoid_value FROM factoids";
400         my $sth = $dbh->prepare($query);
401         &ERROR("factstats(listfix): => '$query'.") unless $sth->execute;
402
403         my @list;
404         while (my @row = $sth->fetchrow_array) {
405             my($key,$val) = ($row[0], $row[1]);
406             my $match = 0;
407             $match++ if ($val =~ /\S+,? or \S+,? or \S+,? or \S+,?/);
408             next unless ($match);
409
410             $key =~ s/\,/\037\,\037/g;
411             push(@list, $key);
412             $val =~ s/,? or /, /g;
413             &DEBUG("fixed: => $val.");
414             &setFactInfo($key,"factoid_value", $val);
415         }
416         $sth->finish;
417
418         # parse the results.
419         my $prefix = "Inefficient lists fixed ";
420         return &formListReply(1, $prefix, @list);
421
422     } elsif ($type =~ /^locked$/i) {
423         my %hash = &dbGetCol("factoids", "factoid_key,locked_by", "locked_by IS NOT NULL");
424         my @list = keys %hash;
425
426         for (@list) {
427             s/([\,\;]+)/\037$1\037/g;
428         }
429
430         my $prefix = "factoid statistics on $type ";
431         return &formListReply(0, $prefix, @list);
432
433     } elsif ($type =~ /^new$/i) {
434         my %hash = &dbGetCol("factoids", "factoid_key,created_time", "created_time IS NOT NULL");
435         my %age;
436
437         foreach (keys %hash) {
438             my $created_time = $hash{$_};
439             my $delta_time   = time() - $created_time;
440             next if ($delta_time >= 60*60*24);
441
442             $age{$delta_time}{$_} = 1;
443         }
444
445         if (scalar keys %age == 0) {
446             return "sorry, no new factoids.";
447         }
448
449         my @list;
450         foreach (sort {$a <=> $b} keys %age) {
451             push(@list, join(",", keys %{ $age{$_} }));
452         }
453
454         my $prefix = "new factoids in the last 24hours ";
455         return &formListReply(0, $prefix, @list);
456
457     } elsif ($type =~ /^part(ial)?dupe$/i) {
458         ### requires "custom" select statement... oh well...
459         my $start_time  = &timeget();
460
461         # form length|key and key=length hash list.
462         &status("factstats(partdupe): forming length hash list.");
463         my $query = "SELECT factoid_key,factoid_value,length(factoid_value) AS length FROM factoids WHERE length(factoid_value) >= 192 ORDER BY length";
464         my $sth = $dbh->prepare($query);
465         &ERROR("factstats(partdupe): => '$query'.") unless $sth->execute;
466
467         my (@key, @list);
468         my (%key, %length);
469         while (my @row = $sth->fetchrow_array) {
470             $length{$row[2]}{$row[0]} = 1;      # length(value)|key.
471             $key{$row[0]} = $row[1];            # key=value.
472             push(@key, $row[0]);
473         }
474         $sth->finish;
475         &status("factstats(partdupe): total keys => '". scalar(@key) ."'.");
476         &status("factstats(partdupe): now deciphering data gathered");
477
478         my @length = sort { $a <=> $b } keys %length;
479         my $key;
480
481         foreach $key (@key) {
482             shift @length if (length $key{$key} == $length[0]);
483
484             my $val = quotemeta $key{$key};
485             my @sublist;
486             my $length;
487             foreach $length (@length) {
488                 foreach (keys %{ $length{$length} }) {
489                     if ($key{$_} =~ /^$val/i) {
490                         s/([\,\;]+)/\037$1\037/g;
491                         s/( and|and )/\037$1\037/g;
492                         push(@sublist,$key." and ".$_);
493                     }
494                 }
495             }
496             push(@list, join(" ,",@sublist)) if (scalar @sublist);
497         }
498
499         my $delta_time = sprintf("%.02fs", &timedelta($start_time) );
500         &status("factstats(partdupe): $delta_time sec to complete.") if ($delta_time > 0);
501
502         # bail out on no results.
503         if (scalar @list == 0) {
504             return "no initial partial duplicate factoids... woohoo.";
505         }
506
507         # parse the results.
508         my $prefix = "initial partial dupe factoid ";
509         return &formListReply(1, $prefix, @list);
510
511     } elsif ($type =~ /^profanity$/i) {
512         my %data = &dbGetCol("factoids", "factoid_key,factoid_value", "factoid_value IS NOT NULL");
513         my @list;
514
515         foreach (keys %data) {
516             push(@list, $_) if (&hasProfanity($_." ".$data{$_}));
517         }
518
519         # parse the results.
520         my $prefix = "Profanity in factoids ";
521         return &formListReply(1, $prefix, @list);
522
523     } elsif ($type =~ /^redir(ection)?$/i) {
524         my @list = &searchTable("factoids", "factoid_key",
525                         "factoid_value", "^<REPLY> see ");
526         my %redir;
527         my $f;
528
529         for (@list) {
530             my $factoid = $_;
531             my $val = &getFactInfo($factoid, "factoid_value");
532             if ($val =~ /^<REPLY> see( also)? (.*?)\.?$/i) {
533                 my $redir       = lc $2;
534                 my $redirval    = &getFactInfo($redir, "factoid_value");
535                 if (defined $redirval) {
536                     $redir{$redir}{$factoid} = 1;
537                 } else {
538                     &WARN("factstats(redir): '$factoid' has loose link => '$redir'.");
539                 }
540             }
541         }
542
543         my @newlist;
544         foreach $f (keys %redir) {
545             my @sublist = keys %{ $redir{$f} };
546             for (@sublist) {
547                 s/([\,\;]+)/\037$1\037/g;
548             }
549
550             push(@newlist, "$f => ". join(', ', @sublist));
551         }
552
553         # parse the results.
554         my $prefix = "Redirections in factoids ";
555         return &formListReply(1, $prefix, @newlist);
556
557     } elsif ($type =~ /^request(ed)?$/i) {
558         my %hash = &dbGetCol("factoids", "factoid_key,requested_count", "requested_count IS NOT NULL", 1);
559
560         if (!scalar keys %hash) {
561             return 'sorry, no factoids have been questioned.';
562         }
563
564         my $count;
565         my @list;
566         my $total       = 0;
567         foreach $count (sort {$b <=> $a} keys %hash) {
568             my @faqtoids = sort keys %{ $hash{$count} };
569
570             for (@faqtoids) {
571                 s/([\,\;]+)/\037$1\037/g;
572             }
573             $total      += $count * scalar(@faqtoids);
574
575             push(@list, "$count - ". join(", ", @faqtoids));
576         }
577         unshift(@list, "\037$total - TOTAL\037");
578
579         my $prefix = "factoid statistics on $type ";
580         return &formListReply(0, $prefix, @list);
581
582     } elsif ($type =~ /^reqrate$/i) {
583         my %hash = &dbGetCol("factoids",
584                 "factoid_key,(unix_timestamp() - created_time)/requested_count as rate",
585                 "requested_by IS NOT NULL and created_time IS NOT NULL ORDER BY rate LIMIT 15", 1);
586
587         my $rate;
588         my @list;
589         my $total       = 0;
590         my $users       = 0;
591         foreach $rate (sort { $b <=> $a } keys %hash) {
592             my $f       = join(", ", sort keys %{ $hash{$rate} });
593             push(@list, "$f - ".&Time2String($rate));
594         }
595
596         my $prefix = "Rank of top factoid rate (time/req): ";
597         return &formListReply(0, $prefix, @list);
598
599     } elsif ($type =~ /^requesters?$/i) {
600         my %hash = &dbGetCol("factoids", "factoid_key,requested_by", "requested_by IS NOT NULL");
601         my %requester;
602
603         foreach (keys %hash) {
604             my $thisnuh = $hash{$_};
605
606             $thisnuh =~ /^(\S+)!\S+@\S+$/;
607             $requester{lc $1}++;
608         }
609
610         if (!scalar keys %requester) {
611             return 'sorry, no factoids with requested_by field.';
612         }
613
614         # work-around.
615         my %count;
616         foreach (keys %requester) {
617             $count{ $requester{$_} }{$_} = 1;
618         }
619         undef %requester;
620
621         my $count;
622         my @list;
623         my $total       = 0;
624         my $users       = 0;
625         foreach $count (sort { $b <=> $a } keys %count) {
626             my $requester = join(", ", sort keys %{ $count{$count} });
627             $total      += $count * scalar(keys %{ $count{$count} });
628             $users      += scalar(keys %{ $count{$count} });
629             push(@list, "$count by $requester");
630         }
631         unshift(@list, "\037$total TOTAL REQUESTS; $users UNIQUE REQUESTERS\037");
632         # should not the above value be the same as collected by
633         # 'requested'? soemthing weird is going on!
634
635         my $prefix = "rank of top factoid requesters: ";
636         return &formListReply(0, $prefix, @list);
637
638     } elsif ($type =~ /^seefix$/i) {
639         my @list = &searchTable("factoids", "factoid_key",
640                         "factoid_value", "^see ");
641         my @newlist;
642         my $fixed = 0;
643         my %loop;
644         my $f;
645
646         for (@list) {
647             my $factoid = $_;
648             my $val = &getFactInfo($factoid, "factoid_value");
649         
650             next unless ($val =~ /^see( also)? (.*?)\.?$/i);
651
652             my $redirf  = lc $2;
653             my $redir   = &getFactInfo($redirf, "factoid_value");
654
655             if ($redirf =~ /^\Q$factoid\W$/i) {
656                 &delFactoid($factoid);
657                 $loop{$factoid} = 1;
658             }
659
660             if (defined $redir) {       # good.
661                 &setFactInfo($factoid,"factoid_value","<REPLY> see $redir");
662                 $fixed++;
663             } else {
664                 push(@newlist, $redirf);
665             }
666         }
667
668         # parse the results.
669         &msg($who, "Fixed $fixed factoids.");
670         &msg($who, "Self looped factoids removed: ".
671                 sort(keys %loop) ) if (scalar keys %loop);
672
673         my $prefix = "Loose link (dead) redirections in factoids ";
674         return &formListReply(1, $prefix, @newlist);
675
676     } elsif ($type =~ /^(2|too)long$/i) {
677         my @list;
678
679         # factoid_key.
680         $query = "SELECT factoid_key FROM factoids WHERE length(factoid_key) >= $param{'maxKeySize'}";
681         my $sth = $dbh->prepare($query);
682         $sth->execute;
683         while (my @row = $sth->fetchrow_array) {
684             push(@list,$row[0]);
685         }
686
687         # factoid_value.
688         my $query = "SELECT factoid_key,factoid_value FROM factoids WHERE length(factoid_value) >= $param{'maxDataSize'}";
689         $sth = $dbh->prepare($query);
690         $sth->execute;
691         while (my @row = $sth->fetchrow_array) {
692             push(@list,sprintf("\002%s\002 - %s", length($row[1]), $row[0]));
693         }
694
695         if (scalar @list == 0) {
696             return "good. no factoids exceed length.";
697         }
698
699         # parse the results.
700         my $prefix = "factoid key||value exceeding length ";
701         return &formListReply(1, $prefix, @list);
702
703     } elsif ($type =~ /^unrequest(ed)?$/i) {
704         my @list = &dbRawReturn("SELECT factoid_key FROM factoids WHERE requested_count IS NULL");
705
706         for (@list) {
707             s/([\,\;]+)/\037$1\037/g;
708         }
709
710         my $prefix = "Unrequested factoids ";
711         return &formListReply(0, $prefix, @list);
712     }
713
714     return "error: invalid type => '$type'.";
715 }
716
717 sub CmdListAuth {
718     my ($query) = @_;
719     my @list = &searchTable("factoids","factoid_key", "created_by", "^$query!");
720
721     my $prefix = "factoid author list by '$query' ";
722     &performStrictReply( &formListReply(1, $prefix, @list) );
723 }
724
725 1;