2 # News.pl: Advanced news management
4 # Version: v0.2 (20010326)
6 # Notes: Testing done by greycat, kudos!
9 # news{ channel }{ string } { items }
10 # newsuser{ channel }{ user } = time()
12 # Time - when it was added (used for sorting)
14 # Expire - Time to expire.
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;
33 if ($::msgType eq "private") {
38 if (defined $what and $what =~ s/^($::mask{chan})\s*//) {
39 # todo: check if the channel exists aswell.
44 my @chans = &::GetNickInChans($::who);
46 if (scalar @chans > 1) {
47 &::msg($::who, "error: I dunno which channel you are referring to since you're on more than one.");
51 if (scalar @chans == 0) {
52 &::msg($::who, "error: I couldn't find you on any chan. This must be a bug!");
57 &::DEBUG("Guessed $::who being on chan $chan");
60 if (!defined $what or $what =~ /^\s*$/) {
65 if ($what =~ /^add(\s+(.*))?$/i) {
67 } elsif ($what =~ /^del(\s+(.*))?$/i) {
69 } elsif ($what =~ /^mod(\s+(.*))?$/i) {
71 } elsif ($what =~ /^set(\s+(.*))?$/i) {
73 } elsif ($what =~ /^(\d)$/i) {
74 &::DEBUG("read shortcut called.");
76 } elsif ($what =~ /^read(\s+(.*))?$/i) {
78 } elsif ($what =~ /^list$/i) {
79 &::DEBUG("list longcut called.");
81 } elsif ($what =~ /^(expire|text|desc)(\s+(.*))?$/i) {
84 my($arg1,$arg2) = split(/\s+/, $3, 2);
85 &set("$arg1 $1 $arg2");
86 } elsif ($what =~ /^help(\s+(.*))?$/i) {
89 &::DEBUG("could not parse '$what'.");
90 &::msg($::who, "unknown command: $what");
95 my $file = "$::bot_base_dir/blootbot-news.txt";
101 &::DEBUG("readNews: fileno exists, should never happen.");
112 # todo: allow commands.
114 if (/^[\s\t]+(\S+):[\s\t]+(.*)$/) {
115 if (!defined $item) {
116 &::DEBUG("!defined item, never happen!");
119 $::news{$chan}{$item}{$1} = $2;
123 # U <chan> <nick> <time>
124 if (/^U\s+(\S+)\s+(\S+)\s+(\d+)$/) {
125 $::newsuser{$1}{$2} = $3;
130 if (/^(\S+)[\s\t]+(.*)$/) {
138 &::status("News: Read $ci items for ".scalar(keys %::news)
139 ." chans, $cu users cache");
143 if (!scalar keys %::news) {
144 &::DEBUG("wN: nothing to write.");
148 my $file = "$::bot_base_dir/blootbot-news.txt";
151 &::ERROR("fileno NEWS exists, should never happen.");
155 # todo: add commands to output file.
157 my($cc,$ci,$cu) = (0,0,0);
159 open(NEWS, ">$file");
160 foreach $chan (sort keys %::news) {
161 $c = scalar keys %{ $::news{$chan} };
165 foreach $item (sort keys %{ $::news{$chan} }) {
166 $c = scalar keys %{ $::news{$chan}{$item} };
170 print NEWS "$chan $item\n";
171 foreach $what (sort keys %{ $::news{$chan}{$item} }) {
172 print NEWS " $what: $::news{$chan}{$item}{$what}\n";
178 # todo: show how many users we wrote down.
179 if (&::getChanConfList("newsKeepRead")) {
180 # old users are removed in newsFlush(), perhaps it should be
183 foreach $chan (sort keys %::newsuser) {
185 foreach (sort keys %{ $::newsuser{$chan} }) {
186 print NEWS "U $chan $_ $::newsuser{$chan}{$_}\n";
194 &::status("News: Wrote $ci items for $cc chans, $cu user cache.");
200 if (!defined $chan or !defined $str or $str =~ /^\s*$/) {
205 if (length $str > 64) {
206 &::msg($::who, "That's not really an item (>64chars)");
210 if (exists $::news{$chan}{$str}{Time}) {
211 &::msg($::who, "'$str' for $chan already exists!");
215 $::news{$chan}{$str}{Time} = time();
216 my $expire = &::getChanConfDefault("newsDefaultExpire",7);
217 $::news{$chan}{$str}{Expire} = time() + $expire*60*60*24;
218 $::news{$chan}{$str}{Author} = $::who;
220 my $agestr = &::Time2String($::news{$chan}{$str}{Expire} - time() );
221 my $item = &getNewsItem($str);
223 &::DEBUG("item eq str ($item): should never happen.");
225 &::msg($::who, "Added '\037$str\037' at [".localtime(time).
226 "] by \002$::who\002 for item #\002$item\002.");
227 &::msg($::who, "Now do 'news text $item <your_description>'");
228 &::msg($::who, "This item will expire at \002".
229 localtime($::news{$chan}{$str}{Expire})."\002 [$agestr from now] "
237 if (!defined $what) {
242 if ($what =~ /^\d+$/) {
243 my $count = scalar keys %{ $::news{$chan} };
245 &::msg($::who, "No news for $chan.");
249 if ($what > $count or $what < 0) {
250 &::msg($::who, "$what is out of range (max $count)");
254 $item = &getNewsItem($what);
255 &::DEBUG("del: num: item => $item ($what)");
256 $what = $item; # hack hack hack.
259 $_ = &getNewsItem($what); # hack hack hack.
260 $what = $_ if (defined $_);
262 if (!exists $::news{$chan}{$what}) {
264 foreach (keys %{ $::news{$chan} }) {
265 next unless (/\Q$what\E/);
269 if (!scalar @found) {
270 &::msg($::who, "could not find $what.");
274 if (scalar @found > 1) {
275 &::msg($::who, "too many matches for $what.");
280 &::DEBUG("del: str: guessed what => $what");
284 if (exists $::news{$chan}{$what}) {
286 $auth++ if ($::who eq $::news{$chan}{$what}{Author});
287 $auth++ if (&::IsFlag("o"));
290 # todo: show when it'll expire.
291 &::msg($::who, "Sorry, you cannot remove items; just let them expire on their own.");
295 &::msg($::who, "ok, deleted '$what' from \002$chan\002...");
296 delete $::news{$chan}{$what};
298 &::msg($::who, "error: not found $what in news for $chan.");
303 if (!scalar keys %{ $::news{$chan} }) {
304 &::msg($::who, "No News for \002$chan\002.");
308 if (&::IsChanConf("newsKeepRead")) {
309 $::newsuser{$chan}{$::who} = time();
312 &::msg($::who, "|==== News for \002$chan\002:");
314 foreach (keys %{ $::news{$chan} }) {
315 my $t = $::news{$chan}{$_}{Time};
316 $newest = $t if ($t > $newest);
318 my $timestr = &::Time2String(time() - $newest);
319 &::msg($::who, "|= Last updated $timestr ago.");
320 &::msg($::who, " \037Num\037 \037Item ".(" "x40)." \037");
323 foreach ( &getNewsAll() ) {
325 my $setby = $::news{$chan}{$subtopic}{Author};
327 if (!defined $subtopic) {
328 &::DEBUG("warn: subtopic == undef.");
332 # todo: show request stats aswell.
333 &::msg($::who, sprintf("\002[\002%2d\002]\002 %s",
338 &::msg($::who, "|= End of News.");
339 &::msg($::who, "use 'news read <#>' or 'news read <keyword>'");
345 if (!defined $chan or !defined $str or $str =~ /^\s*$/) {
346 &::help("news read");
350 if (!scalar keys %{ $::news{$chan} }) {
351 &::msg($::who, "No News for \002$chan\002.");
355 # my $item = (exists $::news{$chan}{$str}) ? $str : &getNewsItem($str);
356 my $item = &getNewsItem($str);
357 if (!defined $item or !scalar keys %{ $::news{$chan}{$item} }) {
358 &::msg($::who, "No news item called '$str'");
362 if (!exists $::news{$chan}{$item}{Text}) {
363 &::msg($::who, "Someone forgot to add info to this news item");
367 # todo: show item number.
368 # todo: show ago-time aswell?
369 # todo: show request stats aswell.
370 my $t = localtime($::news{$chan}{$item}{Time});
371 my $a = $::news{$chan}{$item}{Author};
372 &::msg($::who, "+- News \002$chan\002 ##, item '\037$item\037':");
373 &::msg($::who, "| Added by $a at $t");
374 &::msg($::who, $::news{$chan}{$item}{Text});
376 $::news{$chan}{$item}{'Request_By'} = $::who;
377 $::news{$chan}{$item}{'Request_Time'} = time();
378 $::news{$chan}{$item}{'Request_Count'}++;
382 my($item, $str) = split /\s+/, $_[0], 2;
384 if (!defined $item or $item eq "" or $str =~ /^\s*$/) {
389 my $news = &getNewsItem($item);
391 if (!defined $news) {
392 &::DEBUG("error: mod: news == undefined.");
395 my $nnews = $::news{$chan}{$news}{Text};
396 my $mod_news = $news;
397 my $mod_nnews = $nnews;
400 if ($str =~ m|^\s*s([/,#\|])(.+?)\1(.*?)\1([a-z]*);?\s*$|) {
401 my ($delim, $op, $np, $flags) = ($1,$2,$3,$4);
403 if ($flags !~ /^(g)?$/) {
404 &::msg($::who, "error: Invalid flags to regex.");
408 ### TODO: use m### to make code safe!
409 # todo: make code safer.
411 # todo: use eval to deal with flags easily.
413 $done++ if (!$done and $mod_news =~ s/\Q$op\E/$np/);
414 $done++ if (!$done and $mod_nnews =~ s/\Q$op\E/$np/);
415 } elsif ($flags eq "g") {
416 $done++ if ($mod_news =~ s/\Q$op\E/$np/g);
417 $done++ if ($mod_nnews =~ s/\Q$op\E/$np/g);
421 &::msg($::who, "warning: regex not found in news.");
425 if ($mod_news ne $news) { # news item.
426 if (exists $::news{$chan}{$mod_news}) {
427 &::msg($::who, "item '$mod_news' already exists.");
431 &::msg($::who, "Moving item '$news' to '$mod_news' with SAR s/$op/$np/.");
432 foreach (keys %{ $::news{$chan}{$news} }) {
433 $::news{$chan}{$mod_news}{$_} = $::news{$chan}{$news}{$_};
434 delete $::news{$chan}{$news}{$_};
437 delete $::news{$chan}{$news};
440 if ($mod_nnews ne $nnews) { # news Text/Description.
441 &::msg($::who, "Changing text for '$news' SAR s/$op/$np/.");
442 if ($mod_news ne $news) {
443 $::news{$chan}{$mod_news}{Text} = $mod_nnews;
445 $::news{$chan}{$news}{Text} = $mod_nnews;
451 &::msg($::who, "error: that regex failed ;(");
455 &::msg($::who, "error: Invalid regex. Try s/1/2/, s#3#4#...");
460 $args =~ /^(\S+)\s+(\S+)\s+(.*)$/;
461 my($item, $what, $value) = ($1,$2,$3);
463 &::DEBUG("set called.");
470 &::DEBUG("item => '$item'.");
471 my $news = &getNewsItem($item);
472 &::DEBUG("news => '$news'");
474 if (!defined $news) {
475 &::msg($::who, "Could not find item '$item' substring or # in news list.");
479 # list all values for chan.
480 if (!defined $what) {
486 my @elements = ("Expire","Text");
487 foreach (@elements) {
488 next unless ($what =~ /^$_$/i);
495 &::msg($::who, "Invalid set. Try: @elements");
500 if (!defined $value) {
505 if (!exists $::news{$chan}{$news}) {
506 &::msg($::who, "news '$news' does not exist");
510 if ($what eq "Expire") {
511 # todo: use do_set().
514 my $plus = ($value =~ s/^\+//g);
515 while ($value =~ s/^(\d+)(\S*)\s*//) {
516 my($int,$unit) = ($1,$2);
517 $time += $int if ($unit =~ /^s(ecs?)?$/i);
518 $time += $int*60 if ($unit =~ /^m(in(utes?)?)?$/i);
519 $time += $int*60*60 if ($unit =~ /^h(ours?)?$/i);
520 $time += $int*60*60*24 if (!$unit or $unit =~ /^d(ays?)?$/i);
521 $time += $int*60*60*24*7 if ($unit =~ /^w(eeks?)?$/i);
522 $time += $int*60*60*24*30 if ($unit =~ /^mon(th)?$/i);
525 if ($value =~ s/^never$//i) {
532 # from creation of item.
533 $time += $::news{$chan}{$news}{Time};
536 if (!$time or ($value and $value !~ /^never$/i)) {
537 &::DEBUG("set: Expire... need to parse.");
542 &::msg($::who, "Set never expire for \002$item\002." );
543 } elsif ($time < -1) {
544 &::DEBUG("time should never be negative ($time).");
547 &::msg($::who, "Set expire for \002$item\002, to ".
548 localtime($time) ." [".&::Time2String($time - time())."]" );
550 if (time() > $time) {
551 &::DEBUG("hrm... time() > $time, should expire.");
556 $::news{$chan}{$news}{Expire} = $time;
562 &::DEBUG("who => '$::who'");
563 my $author = $::news{$chan}{$news}{Author};
564 $auth++ if ($::who eq $author);
565 $auth++ if (&::IsFlag("o"));
566 if (!defined $author) {
567 &::DEBUG("news{$chan}{$news}{Author} is not defined! auth'd anyway");
568 $::news{$chan}{$news}{Author} = $::who;
574 # todo: show when it'll expire.
575 &::msg($::who, "Sorry, you cannot set items. (author $author owns it)");
579 my $old = $::news{$chan}{$news}{$what};
581 &::DEBUG("old => $old.");
583 $::news{$chan}{$news}{$what} = $value;
584 &::msg($::who, "Setting [$chan]/{$news}/<$what> to '$value'.");
593 foreach (keys %{ $::news{$chan} }) {
594 $time{ $::news{$chan}{$_}{Time} } = $_;
598 foreach (sort { $a <=> $b } keys %time) {
599 push(@items, $time{$_});
610 foreach (keys %{ $::news{$chan} }) {
611 my $t = $::news{$chan}{$_}{Time};
613 if (!defined $t or $t !~ /^\d+$/) {
614 &::DEBUG("warn: t is undefined for news{$chan}{$_}{Time}; removing item.");
615 delete $::news{$chan}{$_};
622 # number to string resolution.
623 if ($what =~ /^\d+$/) {
624 foreach (sort { $a <=> $b } keys %time) {
626 return $time{$_} if ($item == $what);
630 # partial string to full string resolution
634 foreach (sort { $a <=> $b } keys %time) {
636 $no = $item if ($time{$_} eq $what);
637 push(@items, $time{$_}) if ($time{$_} =~ /\Q$what\E/i);
640 # since we have so much built into this function, there is so
641 # many guesses we can make.
642 # todo: split this command in the future into:
643 # full_string->number and number->string
644 # partial_string->full_string
645 if (defined $no and !@items) {
646 &::DEBUG("string->number resolution.");
650 if (scalar @items > 1) {
651 &::DEBUG("Multiple matches, not guessing.");
652 &::msg($::who, "Multiple matches, not guessing.");
657 &::DEBUG("gNI: Guessed '$items[0]'.");
660 &::DEBUG("gNI: No match.");
665 &::ERROR("getNewsItem: Should not happen (what = $what)");
670 my($what,$value) = @_;
672 if (!defined $chan) {
673 &::DEBUG("do_set: chan not defined.");
677 if (!defined $what or $what =~ /^\s*$/) {
678 &::DEBUG("what $what is not defined.");
681 if (!defined $value or $value =~ /^\s*$/) {
682 &::DEBUG("value $value is not defined.");
686 &::DEBUG("do_set: TODO...");