]> git.donarmstrong.com Git - infobot.git/blob - src/Modules/News.pl
- show total commands used in "status"
[infobot.git] / src / Modules / News.pl
1 #
2 # News.pl: Advanced news management
3 #   Author: dms
4 #  Version: v0.3 (20014012)
5 #  Created: 20010326
6 #    Notes: Testing done by greycat, kudos!
7 #
8 ### structure:
9 # news{ channel }{ string } { item }
10 # newsuser{ channel }{ user } = time()
11 ### where item is:
12 #       Time    - when it was added (used for sorting)
13 #       Author  - Who by.
14 #       Expire  - Time to expire.
15 #       Text    - Actual text.
16 ###
17
18 package News;
19
20 sub Parse {
21     my($what)   = @_;
22     $chan       = undef;
23
24     if (!keys %::news) {
25         if (!exists $::cache{newsFirst}) {
26             &::DEBUG("looks like we enabled news option just then; loading up news file just in case.");
27             $::cache{newsFirst} = 1;
28         }
29
30         &readNews();
31     }
32
33     if ($::msgType ne "private") {
34         $chan = $::chan;
35     }
36
37     if (defined $what and $what =~ s/^($::mask{chan})\s*//) {
38         # todo: check if the channel exists aswell.
39         $chan   = lc $1;
40
41         if (!&::IsNickInChan($::who, $chan)) {
42             &::notice($::who, "sorry but you're not on $chan.");
43             return;
44         }
45     }
46
47     if (!defined $chan) {
48         my @chans = &::getNickInChans($::who);
49
50         if (scalar @chans > 1) {
51             &::notice($::who, "error: I dunno which channel you are referring to since you're on more than one. Try 'news #chan ...' instead");
52             return;
53         }
54
55         if (scalar @chans == 0) {
56             &::notice($::who, "error: I couldn't find you on any chan. This must be a bug!");
57             return;
58         }
59
60         $chan   = $chans[0];
61         &::VERB("Guessed $::who being on chan $chan",2);
62         $::chan = $chan;        # hack for IsChanConf().
63     }
64
65     if (!defined $what or $what =~ /^\s*$/) {
66         &list();
67         return;
68     }
69
70     if ($what =~ /^add(\s+(.*))?$/i) {
71         &add($2);
72
73     } elsif ($what =~ /^del(\s+(.*))?$/i) {
74         &del($2);
75
76     } elsif ($what =~ /^mod(\s+(.*))?$/i) {
77         &mod($2);
78
79     } elsif ($what =~ /^set(\s+(.*))?$/i) {
80         &set($2);
81
82     } elsif ($what =~ /^(\d+)$/i) {
83         &::VERB("News: read shortcut called.",2);
84         &read($1);
85
86     } elsif ($what =~ /^read(\s+(.*))?$/i) {
87         &read($2);
88
89     } elsif ($what =~ /^(latest|new)(\s+(.*))?$/i) {
90         &latest($3 || $chan, 1);
91
92     } elsif ($what =~ /^list$/i) {
93         &list();
94
95     } elsif ($what =~ /^(expire|text|desc)(\s+(.*))?$/i) {
96         # shortcut/link.
97         # nice hack.
98         my($arg1,$arg2) = split(/\s+/, $3, 2);
99         &set("$arg1 $1 $arg2");
100
101     } elsif ($what =~ /^help(\s+(.*))?$/i) {
102         &::help("news$1");
103
104     } elsif ($what =~ /^newsflush$/i) {
105         &::msg($::who, "newsflush called... check out the logs!");
106         &::newsFlush();
107
108     } elsif ($what =~ /^(un)?notify$/i) {
109         my $state = ($1) ? 0 : 1;
110
111         # todo: don't notify even if "news" is called.
112         if (!&::IsChanConf("newsNotifyAll")) {
113             &::DEBUG("chan => $chan, ::chan => $::chan.");
114             &::notice($::who, "not available for this channel or disabled altogether.");
115             return;
116         }
117
118         my $t = $::newsuser{$chan}{$::who};
119         if ($state) {   # state = 1
120             if (defined $t and ($t == 0 or $t == -1)) {
121                 &::notice($::who, "enabled notify.");
122                 delete $::newsuser{$chan}{$::who};
123                 return;
124             }
125             &::notice($::who, "already enabled.");
126
127         } else {                # state = 0
128             my $x = $::newsuser{$chan}{$::who};
129             if (defined $x and ($x == 0 or $x == -1)) {
130                 &::notice($::who, "notify already disabled");
131                 return;
132             }
133             $::newsuser{$chan}{$::who} = -1;
134             &::notice($::who, "notify is now disabled.");
135         }
136
137     } else {
138         &::DEBUG("could not parse '$what'");
139         &::notice($::who, "unknown command: $what");
140     }
141 }
142
143 sub readNews {
144     my $file = "$::bot_base_dir/blootbot-news.txt";
145     if (! -f $file or -z $file) {
146         return;
147     }
148
149     if (fileno NEWS) {
150         &::DEBUG("readNews: fileno exists, should never happen.");
151         return;
152     }
153
154     my($item,$chan);
155     my($ci,$cu) = (0,0);
156
157     open(NEWS, $file);
158     while (<NEWS>) {
159         chop;
160
161         # todo: allow commands.
162
163         if (/^[\s\t]+(\S+):[\s\t]+(.*)$/) {
164             if (!defined $item) {
165                 &::DEBUG("!defined item, never happen!");
166                 next;
167             }
168
169             $::news{$chan}{$item}{$1} = $2;
170             next;
171         }
172
173         # U <chan> <nick> <time>
174         if (/^U\s+(\S+)\s+(\S+)\s+(\d+)$/) {
175             $::newsuser{$1}{$2} = $3;
176             $cu++;
177             next;
178         }
179
180         if (/^(\S+)[\s\t]+(.*)$/) {
181             $chan = $1;
182             $item = $2;
183             $ci++;
184         }
185     }
186     close NEWS;
187
188     my $cn = scalar(keys %::news);
189     &::status("News: Read ".
190         $ci. &::fixPlural(" item", $ci). " for ".
191         $cn. &::fixPlural(" chan", $cn). ", ".
192         $cu. &::fixPlural(" user", $cu), " cache"
193     ) if ($ci or $cn or $cu);
194 }
195
196 sub writeNews {
197     if (!scalar keys %::news and !scalar keys %::newsuser) {
198         &::VERB("wN: nothing to write.",2);
199         return;
200     }
201
202     my $file = "$::bot_base_dir/blootbot-news.txt";
203
204     if (fileno NEWS) {
205         &::ERROR("fileno NEWS exists, should never happen.");
206         return;
207     }
208
209     # todo: add commands to output file.
210     my $c = 0;
211     my($cc,$ci,$cu) = (0,0,0);
212
213     open(NEWS, ">$file");
214     foreach $chan (sort keys %::news) {
215         $c = scalar keys %{ $::news{$chan} };
216         next unless ($c);
217         $cc++;
218
219         foreach $item (sort keys %{ $::news{$chan} }) {
220             $c = scalar keys %{ $::news{$chan}{$item} };
221             next unless ($c);
222             $ci++;
223
224             print NEWS "$chan $item\n";
225             foreach $what (sort keys %{ $::news{$chan}{$item} }) {
226                 print NEWS "    $what: $::news{$chan}{$item}{$what}\n";
227             }
228             print NEWS "\n";
229         }
230     }
231
232     # todo: show how many users we wrote down.
233     if (&::getChanConfList("newsKeepRead")) {
234         # old users are removed in newsFlush(), perhaps it should be
235         # done here.
236
237         foreach $chan (sort keys %::newsuser) {
238
239             foreach (sort keys %{ $::newsuser{$chan} }) {
240                 print NEWS "U $chan $_ $::newsuser{$chan}{$_}\n";
241                 $cu++;
242             }
243         }
244     }
245
246     close NEWS;
247
248     &::status("News: Wrote $ci items for $cc chans, $cu user cache.");
249 }
250
251 sub add {
252     my($str) = @_;
253
254     if (!defined $chan or !defined $str or $str =~ /^\s*$/) {
255         &::help("news add");
256         return;
257     }
258
259     if (length $str > 64) {
260         &::notice($::who, "That's not really an item (>64chars)");
261         return;
262     }
263
264     if (exists $::news{$chan}{$str}{Time}) {
265         &::notice($::who, "'$str' for $chan already exists!");
266         return;
267     }
268
269     $::news{$chan}{$str}{Time}  = time();
270     my $expire = &::getChanConfDefault("newsDefaultExpire",7);
271     $::news{$chan}{$str}{Expire}        = time() + $expire*60*60*24;
272     $::news{$chan}{$str}{Author}        = $::who;
273
274     my $agestr  = &::Time2String($::news{$chan}{$str}{Expire} - time() );
275     my $item    = &newsS2N($str);
276     &::notice($::who, "Added '\037$str\037' at [".localtime(time).
277                 "] by \002$::who\002 for item #\002$item\002.");
278     &::notice($::who, "Now do 'news text $item <your_description>'");
279     &::notice($::who, "This item will expire at \002".
280         localtime($::news{$chan}{$str}{Expire})."\002 [$agestr from now] "
281     );
282
283     &writeNews();
284 }
285
286 sub del {
287     my($what)   = @_;
288     my $item    = 0;
289
290     if (!defined $what) {
291         &::help("news del");
292         return;
293     }
294
295     if ($what =~ /^\d+$/) {
296         my $count = scalar keys %{ $::news{$chan} };
297         if (!$count) {
298             &::notice($::who, "No news for $chan.");
299             return;
300         }
301
302         if ($what > $count or $what < 0) {
303             &::notice($::who, "$what is out of range (max $count)");
304             return;
305         }
306
307         $item   = &getNewsItem($what);
308         $what   = $item;                # hack hack hack.
309
310     } else {
311         $_      = &getNewsItem($what);  # hack hack hack.
312         $what   = $_ if (defined $_);
313
314         if (!exists $::news{$chan}{$what}) {
315             my @found;
316             foreach (keys %{ $::news{$chan} }) {
317                 next unless (/\Q$what\E/);
318                 push(@found, $_);
319             }
320
321             if (!scalar @found) {
322                 &::notice($::who, "could not find $what.");
323                 return;
324             }
325
326             if (scalar @found > 1) {
327                 &::notice($::who, "too many matches for $what.");
328                 return;
329             }
330
331             $what       = $found[0];
332             &::DEBUG("del: str: guessed what => $what");
333         }
334     }
335
336     if (exists $::news{$chan}{$what}) {
337         my $auth = 0;
338         $auth++ if ($::who eq $::news{$chan}{$what}{Author});
339         $auth++ if (&::IsFlag("o"));
340
341         if (!$auth) {
342             # todo: show when it'll expire.
343             &::notice($::who, "Sorry, you cannot remove items; just let them expire on their own.");
344             return;
345         }
346
347         &::notice($::who, "ok, deleted '$what' from \002$chan\002...");
348         delete $::news{$chan}{$what};
349     } else {
350         &::notice($::who, "error: not found $what in news for $chan.");
351     }
352 }
353
354 sub list {
355     if (!scalar keys %{ $::news{$chan} }) {
356         &::notice($::who, "No News for \002$chan\002.");
357         return;
358     }
359
360     if (&::IsChanConf("newsKeepRead")) {
361         my $x = $::newsuser{$chan}{$::who};
362
363         if (defined $x and ($x == 0 or $x == -1)) {
364             &::DEBUG("not updating time for $::who.");
365         } else {
366             $::newsuser{$chan}{$::who} = time();
367         }
368     }
369
370     my $count = scalar keys %{ $::news{$chan} };
371     &::notice($::who, "|==== News for \002$chan\002: ($count items)");
372     my $newest  = 0;
373     foreach (keys %{ $::news{$chan} }) {
374         my $t   = $::news{$chan}{$_}{Time};
375         $newest = $t if ($t > $newest);
376     }
377     my $timestr = &::Time2String(time() - $newest);
378     &::notice($::who, "|= Last updated $timestr ago.");
379     &::notice($::who, " \037Num\037 \037Item ".(" "x40)." \037");
380
381     my $i = 1;
382     foreach ( &getNewsAll() ) {
383         my $subtopic    = $_;
384         my $setby       = $::news{$chan}{$subtopic}{Author};
385
386         if (!defined $subtopic) {
387             &::DEBUG("warn: subtopic == undef.");
388             next;
389         }
390
391         # todo: show request stats aswell.
392         &::notice($::who, sprintf("\002[\002%2d\002]\002 %s",
393                                 $i, $subtopic));
394         $i++;
395     }
396
397     &::notice($::who, "|= End of News.");
398     &::notice($::who, "use 'news read <#>' or 'news read <keyword>'");
399 }
400
401 sub read {
402     my($str) = @_;
403
404     if (!defined $chan or !defined $str or $str =~ /^\s*$/) {
405         &::help("news read");
406         return;
407     }
408
409     if (!scalar keys %{ $::news{$chan} }) {
410         &::notice($::who, "No News for \002$chan\002.");
411         return;
412     }
413
414     my $item    = &getNewsItem($str);
415     if (!defined $item or !scalar keys %{ $::news{$chan}{$item} }) {
416         &::notice($::who, "No news item called '$str'");
417         return;
418     }
419
420     if (!exists $::news{$chan}{$item}{Text}) {
421         &::notice($::who, "Someone forgot to add info to this news item");
422         return;
423     }
424
425     my $t       = localtime( $::news{$chan}{$item}{Time} );
426     my $a       = $::news{$chan}{$item}{Author};
427     my $text    = $::news{$chan}{$item}{Text};
428     my $num     = &newsS2N($item);
429     my $rwho    = $::news{$chan}{$item}{Request_By} || $::who;
430     my $rcount  = $::news{$chan}{$item}{Request_Count} || 0;
431
432     if (length $text < $::param{maxKeySize}) {
433         &::VERB("NEWS: Possible news->factoid redirection.",2);
434         my $f   = &::getFactoid($text);
435
436         if (defined $f) {
437             &::VERB("NEWS: ok, $text is factoid redirection.",2);
438             $f =~ s/^<REPLY>\s*//i;     # anything else?
439             $text = $f;
440         }
441     }
442
443     $_ = $::news{$chan}{$item}{'Expire'};
444     my $e;
445     if ($_) {
446         $e = sprintf("\037%s\037  [%s from now]",
447                 scalar(localtime($_)),
448                 &::Time2String($_ - time())
449         );
450     }
451
452     &::notice($::who, "+- News \002$chan\002 #$num: $item");
453     &::notice($::who, "| Added by $a at \037$t\037");
454     &::notice($::who, "| Expire: $e") if (defined $e);
455     &::notice($::who, $text);
456     &::notice($::who, "| Requested \002$rcount\002 times, last by \002$rwho\002") if ($rcount and $rwho);
457
458     $::news{$chan}{$item}{'Request_By'}   = $::who;
459     $::news{$chan}{$item}{'Request_Time'} = time();
460     $::news{$chan}{$item}{'Request_Count'}++;
461 }
462
463 sub mod {
464     my($item, $str) = split /\s+/, $_[0], 2;
465
466     if (!defined $item or $item eq "" or $str =~ /^\s*$/) {
467         &::help("news mod");
468         return;
469     }
470
471     my $news = &getNewsItem($item);
472
473     if (!defined $news) {
474         &::DEBUG("error: mod: news == undefined.");
475         return;
476     }
477     my $nnews = $::news{$chan}{$news}{Text};
478     my $mod_news  = $news;
479     my $mod_nnews = $nnews;
480
481     # SAR patch. mu++
482     if ($str =~ m|^\s*s([/,#\|])(.+?)\1(.*?)\1([a-z]*);?\s*$|) {
483         my ($delim, $op, $np, $flags) = ($1,$2,$3,$4);
484
485         if ($flags !~ /^(g)?$/) {
486             &::notice($::who, "error: Invalid flags to regex.");
487             return;
488         }
489
490         ### TODO: use m### to make code safe!
491         # todo: make code safer.
492         my $done = 0;
493         # todo: use eval to deal with flags easily.
494         if ($flags eq "") {
495             $done++ if (!$done and $mod_news  =~ s/\Q$op\E/$np/);
496             $done++ if (!$done and $mod_nnews =~ s/\Q$op\E/$np/);
497         } elsif ($flags eq "g") {
498             $done++ if ($mod_news  =~ s/\Q$op\E/$np/g);
499             $done++ if ($mod_nnews =~ s/\Q$op\E/$np/g);
500         }
501
502         if (!$done) {
503             &::notice($::who, "warning: regex not found in news.");
504             return;
505         }
506
507         if ($mod_news ne $news) { # news item.
508             if (exists $::news{$chan}{$mod_news}) {
509                 &::notice($::who, "item '$mod_news' already exists.");
510                 return;
511             }
512
513             &::notice($::who, "Moving item '$news' to '$mod_news' with SAR s/$op/$np/.");
514             foreach (keys %{ $::news{$chan}{$news} }) {
515                 $::news{$chan}{$mod_news}{$_} = $::news{$chan}{$news}{$_};
516                 delete $::news{$chan}{$news}{$_};
517             }
518             # needed?
519             delete $::news{$chan}{$news};
520         }
521
522         if ($mod_nnews ne $nnews) { # news Text/Description.
523             &::notice($::who, "Changing text for '$news' SAR s/$op/$np/.");
524             if ($mod_news ne $news) {
525                 $::news{$chan}{$mod_news}{Text} = $mod_nnews;
526             } else {
527                 $::news{$chan}{$news}{Text}     = $mod_nnews;
528             }
529         }
530
531         return;
532     } else {
533         &::notice($::who, "error: that regex failed ;(");
534         return;
535     }
536
537     &::notice($::who, "error: Invalid regex. Try s/1/2/, s#3#4#...");
538 }
539
540 sub set {
541     my($args) = @_;
542     $args =~ /^(\S+)\s+(\S+)\s+(.*)$/;
543     my($item, $what, $value) = ($1,$2,$3);
544
545     &::DEBUG("set called.");
546
547     if ($item eq "") {
548         &::help("news set");
549         return;
550     }
551
552     &::DEBUG("item => '$item'.");
553     my $news = &getNewsItem($item);
554
555     if (!defined $news) {
556         &::notice($::who, "Could not find item '$item' substring or # in news list.");
557         return;
558     }
559
560     # list all values for chan.
561     if (!defined $what) {
562         &::DEBUG("set: 1");
563         return;
564     }
565
566     my $ok = 0;
567     my @elements = ("Expire","Text");
568     foreach (@elements) {
569         next unless ($what =~ /^$_$/i);
570         $what = $_;
571         $ok++;
572         last;
573     }
574
575     if (!$ok) {
576         &::notice($::who, "Invalid set.  Try: @elements");
577         return;
578     }
579
580     # show (read) what.
581     if (!defined $value) {
582         &::DEBUG("set: 2");
583         return;
584     }
585
586     if (!exists $::news{$chan}{$news}) {
587         &::notice($::who, "news '$news' does not exist");
588         return;
589     }
590
591     if ($what eq "Expire") {
592         # todo: use do_set().
593
594         my $time = 0;
595         my $plus = ($value =~ s/^\+//g);
596         while ($value =~ s/^(\d+)(\S*)\s*//) {
597             my($int,$unit) = ($1,$2);
598             $time += $int       if ($unit =~ /^s(ecs?)?$/i);
599             $time += $int*60    if ($unit =~ /^m(in(utes?)?)?$/i);
600             $time += $int*60*60 if ($unit =~ /^h(ours?)?$/i);
601             $time += $int*60*60*24 if (!$unit or $unit =~ /^d(ays?)?$/i);
602             $time += $int*60*60*24*7 if ($unit =~ /^w(eeks?)?$/i);
603             $time += $int*60*60*24*30 if ($unit =~ /^mon(th)?$/i);
604         }
605
606         if ($value =~ s/^never$//i) {
607             # never.
608             $time = -1;
609         } elsif ($plus) {
610             # from now.
611             $time += time();
612         } else {
613             # from creation of item.
614             $time += $::news{$chan}{$news}{Time};
615         }
616
617         if (!$time or ($value and $value !~ /^never$/i)) {
618             &::DEBUG("set: Expire... need to parse.");
619             return;
620         }
621
622         if ($time == -1) {
623             &::notice($::who, "Set never expire for \002$item\002." );
624         } elsif ($time < -1) {
625             &::DEBUG("time should never be negative ($time).");
626             return;
627         } else {
628             &::notice($::who, "Set expire for \002$item\002, to ".
629                 localtime($time) ." [".&::Time2String($time - time())."]" );
630
631             if (time() > $time) {
632                 &::DEBUG("hrm... time() > $time, should expire.");
633             }
634         }
635
636
637         $::news{$chan}{$news}{Expire} = $time;
638
639         return;
640     }
641
642     my $auth = 0;
643     &::DEBUG("who => '$::who'");
644     my $author = $::news{$chan}{$news}{Author};
645     $auth++ if ($::who eq $author);
646     $auth++ if (&::IsFlag("o"));
647     if (!defined $author) {
648         &::DEBUG("news{$chan}{$news}{Author} is not defined! auth'd anyway");
649         $::news{$chan}{$news}{Author} = $::who;
650         $author = $::who;
651         $auth++;
652     }
653
654     if (!$auth) {
655         # todo: show when it'll expire.
656         &::notice($::who, "Sorry, you cannot set items. (author $author owns it)");
657         return;
658     }
659
660     # todo: clean this up.
661     my $old = $::news{$chan}{$news}{$what};
662     if (defined $old) {
663         &::DEBUG("old => $old.");
664     }
665     $::news{$chan}{$news}{$what} = $value;
666     &::notice($::who, "Setting [$chan]/{$news}/<$what> to '$value'.");
667 }
668
669 sub latest {
670     my($tchan, $flag) = @_;
671
672     $chan ||= $tchan;   # hack hack hack.
673
674     # todo: if chan = undefined, guess.
675     if (!exists $::news{$chan}) {
676         &::notice($::who, "invalid chan $chan") if ($flag);
677         return;
678     }
679
680     my $t = $::newsuser{$chan}{$::who};
681     if (defined $t and ($t == 0 or $t == -1)) {
682         if ($flag) {
683             &::notice($::who, "if you want to read news, try /msg $::ident news or /msg $::ident news notify");
684         } else {
685             &::DEBUG("not displaying any new news for $::who");
686             return;
687         }
688     }
689
690     my $x = &::IsChanConf("newsNotifyAll");
691     if (&::IsChanConf("newsNotifyAll") and !defined $t) {
692         $t = 1;
693     }
694
695     if (!defined $t) {
696         &::DEBUG("something went really wrong.");
697         &::DEBUG("chan => $chan, ::chan => $::chan");
698         &::notice($::who, "something went really wrong.");
699         return;
700     }
701
702     my @new;
703     foreach (keys %{ $::news{$chan} }) {
704         next if (!defined $t);
705         next if ($t > $::news{$chan}{$_}{Time});
706
707         # don't list new items if they don't have Text.
708         if (!exists $::news{$chan}{$_}{Text}) {
709             if (time() - $::news{$chan}{$_}{Time} > 60*60*24*3) {
710                 &::DEBUG("deleting news{$chan}{$_} because it was too old and had no text info.");
711                 delete $::news{$chan}{$_};
712             }
713
714             next;
715         }
716
717         push(@new, $_);
718     }
719
720     # !scalar @new, $flag
721     if (!scalar @new and $flag) {
722         &::notice($::who, "no new news for $chan.");
723         return;
724     }
725
726     # scalar @new, !$flag
727     my $unread  = scalar @new;
728     my $total   = scalar keys %{ $::news{$chan} };
729     if (!$flag) {
730         return unless ($unread);
731
732         my $reply = "There are unread news in $chan ($unread unread, $total total). /msg $::ident news latest";
733         $reply   .= "  If you don't want further news notification, /msg $::ident news unnotify" if ($unread == $total);
734         &::notice($::who, $reply);
735
736         return;
737     }
738
739     # scalar @new, $flag
740     if (scalar @new) {
741         &::notice($::who, "+==== New news for \002$chan\002 ($unread new; $total total):");
742
743         my $t = $::newsuser{$chan}{$::who};
744         if (defined $t and $t > 1) {
745             my $timestr = &::Time2String( time() - $t );
746             &::notice($::who, "|= Last time read $timestr ago");
747         }
748
749         my @sorted;
750         foreach (@new) {
751             my $i   = &newsS2N($_);
752             $sorted[$i] = $_;
753         }
754
755         for (my $i=0; $i<=scalar(@sorted); $i++) {
756             my $news = $sorted[$i];
757             next unless (defined $news);
758
759             my $age = time() - $::news{$chan}{$news}{Time};
760             &::notice($::who, sprintf("\002[\002%2d\002]\002 %s",
761                 $i, $news) );
762 #               $i, $_, &::Time2String($age) ) );
763         }
764
765         &::notice($::who, "|= to read, do 'news read <#>' or 'news read <keyword>'");
766
767         # lame hack to prevent dupes if we just ignore it.
768         my $x = $::newsuser{$chan}{$::who};
769         if (defined $x and ($x == 0 or $x == -1)) {
770             &::DEBUG("not updating time for $::who. (2)");
771         } else {
772             $::newsuser{$chan}{$::who} = time();
773         }
774     }
775 }
776
777 ###
778 ### helpers...
779 ###
780
781 sub getNewsAll {
782     my %time;
783     foreach (keys %{ $::news{$chan} }) {
784         $time{ $::news{$chan}{$_}{Time} } = $_;
785     }
786
787     my @items;
788     foreach (sort { $a <=> $b } keys %time) {
789         push(@items, $time{$_});
790     }
791
792     return @items;
793 }
794
795 sub newsS2N {
796     my($what)   = @_;
797     my $item    = 0;
798     my @items;
799     my $no;
800
801     my %time;
802     foreach (keys %{ $::news{$chan} }) {
803         my $t = $::news{$chan}{$_}{Time};
804
805         if (!defined $t or $t !~ /^\d+$/) {
806             &::DEBUG("warn: t is undefined for news{$chan}{$_}{Time}; removing item.");
807             delete $::news{$chan}{$_};
808             next;
809         }
810
811         $time{$t} = $_;
812     }
813
814     foreach (sort { $a <=> $b } keys %time) {
815         $item++;
816         return $item if ($time{$_} eq $what);
817     }
818
819     &::DEBUG("newsS2N($what): failed...");
820 }
821
822 sub getNewsItem {
823     my($what)   = @_;
824     my $item    = 0;
825
826     my %time;
827     foreach (keys %{ $::news{$chan} }) {
828         my $t = $::news{$chan}{$_}{Time};
829
830         if (!defined $t or $t !~ /^\d+$/) {
831             &::DEBUG("warn: t is undefined for news{$chan}{$_}{Time}; removing item.");
832             delete $::news{$chan}{$_};
833             next;
834         }
835
836         $time{$t} = $_;
837     }
838
839     # number to string resolution.
840     if ($what =~ /^\d+$/) {
841         foreach (sort { $a <=> $b } keys %time) {
842             $item++;
843             return $time{$_} if ($item == $what);
844         }
845
846     } else {
847         # partial string to full string resolution
848         # in some cases, string->number resolution.
849
850         my @items;
851         my $no;
852         foreach (sort { $a <=> $b } keys %time) {
853             $item++;
854 #           $no = $item if ($time{$_} eq $what);
855             if ($time{$_} eq $what) {
856                 $no = $item;
857                 next;
858             }
859
860             push(@items, $time{$_}) if ($time{$_} =~ /\Q$what\E/i);
861         }
862
863         if (defined $no and !@items) {
864             &::DEBUG("string->number resolution: $what->$no.");
865             return $no;
866         }
867
868         if (scalar @items > 1) {
869             &::DEBUG("Multiple matches, not guessing.");
870             &::notice($::who, "Multiple matches, not guessing.");
871             return;
872         }
873
874         if (@items) {
875             &::DEBUG("gNI: part_string->full_string: $what->$items[0]");
876             return $items[0];
877         } else {
878             &::DEBUG("gNI: No match for '$what'");
879             return;
880         }
881     }
882
883     &::ERROR("getNewsItem: Should not happen (what = $what)");
884     return;
885 }
886
887 sub do_set {
888     my($what,$value) = @_;
889
890     if (!defined $chan) {
891         &::DEBUG("do_set: chan not defined.");
892         return;
893     }
894
895     if (!defined $what or $what =~ /^\s*$/) {
896         &::DEBUG("what $what is not defined.");
897         return;
898     }
899     if (!defined $value or $value =~ /^\s*$/) {
900         &::DEBUG("value $value is not defined.");
901         return;
902     }
903
904     &::DEBUG("do_set: TODO...");
905 }
906
907 1;