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