]> git.donarmstrong.com Git - infobot.git/blob - src/Modules/Factoids.pl
used \* instead of / for days, founded by fooz
[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 $noreply;
20     }
21
22     my $i = 0;
23     my %factinfo;
24     my @factinfo = &getFactInfo($faqtoid,"*");
25     foreach ( &dbGetRowInfo("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 $noreply;
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 $noreply;
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 $noreply;
136     }
137
138     &performStrictReply("$factinfo{'factoid_key'} -- ". join("; ", @array) .".");
139     return $noreply;
140 }
141
142 sub CmdFactStats {
143     my ($type) = @_;
144
145     if ($type =~ /^author$/i) {
146         my %hash = &dbGetCol("factoids", "factoid_key","created_by");
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");
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 =~ /^deadredir$/i) {
208         my @list = &searchTable("factoids", "factoid_key",
209                         "factoid_value", "^<REPLY> see ");
210         my %redir;
211         my $f;
212
213         for (@list) {
214             my $factoid = $_;
215             my $val = &getFactInfo($factoid, "factoid_value");
216             if ($val =~ /^<REPLY> see( also)? (.*?)\.?$/i) {
217                 my $redirf = lc $2;
218                 my $redir = &getFactInfo($redirf, "factoid_value");
219                 next if (defined $redir);
220                 next if (length $val > 50);
221
222                 $redir{$redirf}{$factoid} = 1;
223             }
224         }
225
226         my @newlist;
227         foreach $f (keys %redir) {
228             my @sublist = keys %{$redir{$f}};
229             for (@sublist) {
230                 s/([\,\;]+)/\037$1\037/g;
231             }
232
233             push(@newlist, join(', ', @sublist)." => $f");
234         }
235
236         # parse the results.
237         my $prefix = "Loose link (dead) redirections in factoids ";
238         return &formListReply(1, $prefix, @newlist);
239
240     } elsif ($type =~ /^dup(licate|e)$/i) {
241         &status("factstats(dupe): starting...");
242         my $start_time  = &timeget();
243         my %hash        = &dbGetCol("factoids", "factoid_key", "factoid_value", 1);
244         my $refs        = 0;
245         my @list;
246         my $v;
247
248         foreach $v (keys %hash) {
249             my $count = scalar(keys %{$hash{$v}});
250             next if ($count == 1);
251
252             my @sublist;
253             foreach (keys %{$hash{$v}}) {
254                 if ($v =~ /^<REPLY> see /i) {
255                     $refs++;
256                     next;
257                 }
258
259                 s/([\,\;]+)/\037$1\037/g;
260                 if ($_ eq "") {
261                     &WARN("dupe: _ = NULL. should never happen!.");
262                     next;
263                 }
264                 push(@sublist, $_);
265             }
266
267             next unless (scalar @sublist);
268
269             push(@list, join(", ", @sublist));
270         }
271
272         &status("factstats(dupe): (good) dupe refs: $refs.");
273         my $delta_time  = &timedelta($start_time);
274         &status(sprintf("factstats(dupe): %.02f sec to complete", $delta_time)) if ($delta_time > 0);
275
276         # bail out on no results.
277         if (scalar @list == 0) {
278             return "no duplicate factoids... woohoo.";
279         }
280
281         # parse the results.
282         my $prefix = "dupe factoid ";
283         return &formListReply(1, $prefix, @list);
284
285     } elsif ($type =~ /^nullfactoids$/i) {
286         my $query = "SELECT factoid_key,factoid_value FROM factoids WHERE factoid_value=''";
287         my $sth = $dbh->prepare($query);
288         &ERROR("factstats(null): => '$query'.") unless $sth->execute;
289
290         my @list;
291         while (my @row = $sth->fetchrow_array) {
292             if ($row[1] ne "") {
293                 &DEBUG("row[1] != NULL for $row[0].");
294                 next;
295             }
296
297             &DEBUG("row[0] => '$row[0]'.");
298             push(@list, $row[0]);
299         }
300         $sth->finish;
301
302         # parse the results.
303         my $prefix = "NULL factoids (not deleted yet) ";
304         return &formListReply(1, $prefix, @list);
305
306     } elsif ($type =~ /^(2|too)short$/i) {
307         # Custom select statement.
308         my $query = "SELECT factoid_key,factoid_value FROM factoids WHERE length(factoid_value) <= 40";
309         my $sth = $dbh->prepare($query);
310         &ERROR("factstats(lame): => '$query'.") unless $sth->execute;
311
312         my @list;
313         while (my @row = $sth->fetchrow_array) {
314             my($key,$val) = ($row[0], $row[1]);
315             my $match = 0;
316             $match++ if ($val =~ /\s{3,}/);
317             next unless ($match);
318
319             $key =~ s/\,/\037\,\037/g;
320             push(@list, $key);
321         }
322         $sth->finish;
323
324         # parse the results.
325         my $prefix = "Lame factoids ";
326         return &formListReply(1, $prefix, @list);
327
328     } elsif ($type =~ /^listfix$/i) {
329         # Custom select statement.
330         my $query = "SELECT factoid_key,factoid_value FROM factoids";
331         my $sth = $dbh->prepare($query);
332         &ERROR("factstats(listfix): => '$query'.") unless $sth->execute;
333
334         my @list;
335         while (my @row = $sth->fetchrow_array) {
336             my($key,$val) = ($row[0], $row[1]);
337             my $match = 0;
338             $match++ if ($val =~ /\S+,? or \S+,? or \S+,? or \S+,?/);
339             next unless ($match);
340
341             $key =~ s/\,/\037\,\037/g;
342             push(@list, $key);
343             $val =~ s/,? or /, /g;
344             &DEBUG("fixed: => $val.");
345             &setFactInfo($key,"factoid_value", $val);
346         }
347         $sth->finish;
348
349         # parse the results.
350         my $prefix = "Inefficient lists fixed ";
351         return &formListReply(1, $prefix, @list);
352
353     } elsif ($type =~ /^locked$/i) {
354         my %hash = &dbGetCol("factoids", "factoid_key","locked_by");
355         my @list = keys %hash;
356
357         for (@list) {
358             s/([\,\;]+)/\037$1\037/g;
359         }
360
361         my $prefix = "factoid statistics on $type ";
362         return &formListReply(0, $prefix, @list);
363
364     } elsif ($type =~ /^new$/i) {
365         my %hash = &dbGetCol("factoids", "factoid_key","created_time");
366         my %age;
367
368         foreach (keys %hash) {
369             my $created_time = $hash{$_};
370             my $delta_time   = time() - $created_time;
371             next if ($delta_time >= 60*60*24);
372
373             $age{$delta_time}{$_} = 1;
374         }
375
376         if (scalar keys %age == 0) {
377             return "sorry, no new factoids.";
378         }
379
380         my @list;
381         foreach (sort {$a <=> $b} keys %age) {
382             push(@list, join(",", keys %{$age{$_}}));
383         }
384
385         my $prefix = "new factoids in the last 24hours ";
386         return &formListReply(0, $prefix, @list);
387
388     } elsif ($type =~ /^part(ial)?dupe$/i) {
389         ### requires "custom" select statement... oh well...
390         my $start_time  = &timeget();
391
392         # form length|key and key=length hash list.
393         &status("factstats(partdupe): forming length hash list.");
394         my $query = "SELECT factoid_key,factoid_value,length(factoid_value) AS length FROM factoids WHERE length(factoid_value) >= 192 ORDER BY length";
395         my $sth = $dbh->prepare($query);
396         &ERROR("factstats(partdupe): => '$query'.") unless $sth->execute;
397
398         my (@key, @list);
399         my (%key, %length);
400         while (my @row = $sth->fetchrow_array) {
401             $length{$row[2]}{$row[0]} = 1;      # length(value)|key.
402             $key{$row[0]} = $row[1];            # key=value.
403             push(@key, $row[0]);
404         }
405         $sth->finish;
406         &status("factstats(partdupe): total keys => '". scalar(@key) ."'.");
407         &status("factstats(partdupe): now deciphering data gathered");
408
409         my @length = sort { $a <=> $b } keys %length;
410         my $key;
411
412         foreach $key (@key) {
413             shift @length if (length $key{$key} == $length[0]);
414
415             my $val = quotemeta $key{$key};
416             my @sublist;
417             my $length;
418             foreach $length (@length) {
419                 foreach (keys %{$length{$length}}) {
420                     if ($key{$_} =~ /^$val/i) {
421                         s/([\,\;]+)/\037$1\037/g;
422                         s/( and|and )/\037$1\037/g;
423                         push(@sublist,$key." and ".$_);
424                     }
425                 }
426             }
427             push(@list, join(" ,",@sublist)) if (scalar @sublist);
428         }
429
430         my $delta_time = sprintf("%.02fs", &timedelta($start_time) );
431         &status("factstats(partdupe): $delta_time sec to complete.") if ($delta_time > 0);
432
433         # bail out on no results.
434         if (scalar @list == 0) {
435             return "no initial partial duplicate factoids... woohoo.";
436         }
437
438         # parse the results.
439         my $prefix = "initial partial dupe factoid ";
440         return &formListReply(1, $prefix, @list);
441
442     } elsif ($type =~ /^profanity$/i) {
443         my %data = &dbGetCol("factoids", "factoid_key","factoid_value");
444         my @list;
445
446         foreach (keys %data) {
447             push(@list, $_) if (&hasProfanity($_." ".$data{$_}));
448         }
449
450         # parse the results.
451         my $prefix = "Profanity in factoids ";
452         return &formListReply(1, $prefix, @list);
453
454     } elsif ($type =~ /^redir(ection)?$/i) {
455         my @list = &searchTable("factoids", "factoid_key",
456                         "factoid_value", "^<REPLY> see ");
457         my %redir;
458         my $f;
459
460         for (@list) {
461             my $factoid = $_;
462             my $val = &getFactInfo($factoid, "factoid_value");
463             if ($val =~ /^<REPLY> see( also)? (.*?)\.?$/i) {
464                 my $redir       = lc $2;
465                 my $redirval    = &getFactInfo($redir, "factoid_value");
466                 if (defined $redirval) {
467                     $redir{$redir}{$factoid} = 1;
468                 } else {
469                     &WARN("factstats(redir): '$factoid' has loose link => '$redir'.");
470                 }
471             }
472         }
473
474         my @newlist;
475         foreach $f (keys %redir) {
476             my @sublist = keys %{$redir{$f}};
477             for (@sublist) {
478                 s/([\,\;]+)/\037$1\037/g;
479             }
480
481             push(@newlist, "$f => ". join(', ', @sublist));
482         }
483
484         # parse the results.
485         my $prefix = "Redirections in factoids ";
486         return &formListReply(1, $prefix, @newlist);
487
488     } elsif ($type =~ /^request(ed)?$/i) {
489         my %hash = &dbGetCol("factoids", "factoid_key", "requested_count",1);
490
491         if (!scalar keys %hash) {
492             return 'sorry, no factoids have been questioned.';
493         }
494
495         my $count;
496         my @list;
497         foreach $count (sort {$b <=> $a} keys %hash) {
498             my @faqtoids = sort keys %{$hash{$count}};
499
500             for (@faqtoids) {
501                 s/([\,\;]+)/\037$1\037/g;
502             }
503
504             push(@list, "$count - ". join(", ", @faqtoids));
505         }
506
507         my $prefix = "factoid statistics on $type ";
508         return &formListReply(0, $prefix, @list);
509
510     } elsif ($type =~ /^requesters?$/i) {
511         my %hash = &dbGetCol("factoids", "factoid_key","requested_by");
512         my %requester;
513
514         foreach (keys %hash) {
515             my $thisnuh = $hash{$_};
516
517             $thisnuh =~ /^(\S+)!\S+@\S+$/;
518             $requester{lc $1}++;
519         }
520
521         if (!scalar keys %requester) {
522             return 'sorry, no factoids with requested_by field.';
523         }
524
525         # work-around.
526         my %count;
527         foreach (keys %requester) {
528             $count{$requester{$_}}{$_} = 1;
529         }
530         undef %requester;
531
532         my $count;
533         my @list;
534         foreach $count (sort { $b <=> $a } keys %count) {
535             my $requester = join(", ", sort keys %{$count{$count}});
536             push(@list, "$count by $requester");
537         }
538
539         my $prefix = "rank of top factoid requesters: ";
540         return &formListReply(0, $prefix, @list);
541
542     } elsif ($type =~ /^seefix$/i) {
543         my @list = &searchTable("factoids", "factoid_key",
544                         "factoid_value", "^see ");
545         my @newlist;
546         my $fixed = 0;
547         my %loop;
548         my $f;
549
550         for (@list) {
551             my $factoid = $_;
552             my $val = &getFactInfo($factoid, "factoid_value");
553             if ($val =~ /^see( also)? (.*?)\.?$/i) {
554                 my $redirf = lc $2;
555                 my $redir = &getFactInfo($redirf, "factoid_value");
556
557                 if ($redirf =~ /^\Q$factoid\W$/i) {
558                     &delFactoid($factoid);
559                     $loop{$factoid} = 1;
560                 }
561
562                 if (defined $redir) {   # good.
563                     &setFactInfo($factoid,"factoid_value","<REPLY> see $redir");
564                     $fixed++;
565                 } else {
566                     push(@newlist, $redirf);
567                 }
568             }
569         }
570
571         # parse the results.
572         &msg($who, "Fixed $fixed factoids.");
573         &msg($who, "Self looped factoids removed: ".
574                 sort(keys %loop) ) if (scalar keys %loop);
575
576         my $prefix = "Loose link (dead) redirections in factoids ";
577         return &formListReply(1, $prefix, @newlist);
578
579     } elsif ($type =~ /^(2|too)long$/i) {
580         my @list;
581
582         # factoid_key.
583         $query = "SELECT factoid_key FROM factoids WHERE length(factoid_key) >= $param{'maxKeySize'}";
584         my $sth = $dbh->prepare($query);
585         $sth->execute;
586         while (my @row = $sth->fetchrow_array) {
587             push(@list,$row[0]);
588         }
589
590         # factoid_value.
591         my $query = "SELECT factoid_key,factoid_value FROM factoids WHERE length(factoid_value) >= $param{'maxDataSize'}";
592         $sth = $dbh->prepare($query);
593         $sth->execute;
594         while (my @row = $sth->fetchrow_array) {
595             push(@list,sprintf("\002%s\002 - %s", length($row[1]), $row[0]));
596         }
597
598         if (scalar @list == 0) {
599             return "good. no factoids exceed length.";
600         }
601
602         # parse the results.
603         my $prefix = "factoid key||value exceeding length ";
604         return &formListReply(1, $prefix, @list);
605
606     } elsif ($type =~ /^unrequest(ed)?$/i) {
607         my @list = &dbRawReturn("SELECT factoid_key FROM factoids WHERE requested_count IS NULL");
608
609         for (@list) {
610             s/([\,\;]+)/\037$1\037/g;
611         }
612
613         my $prefix = "Unrequested factoids ";
614         return &formListReply(0, $prefix, @list);
615     }
616
617     return "error: invalid type => '$type'.";
618 }
619
620 sub CmdListAuth {
621     my ($query) = @_;
622     my @list = &searchTable("factoids","factoid_key", "created_by", "^$query!");
623
624     my $prefix = "factoid author list by '$query' ";
625     &performStrictReply( &formListReply(1, $prefix, @list) );
626 }
627
628 1;