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