PageRenderTime 49ms CodeModel.GetById 18ms RepoModel.GetById 1ms app.codeStats 0ms

/branches/don/dpkg/src/Modules/News.pl

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