--- trunk/bin/irc-logger.pl 2008/03/09 00:26:49 103 +++ trunk/bin/irc-logger.pl 2008/03/14 00:17:49 119 @@ -18,6 +18,8 @@ use Carp qw/confess/; use XML::Feed; use DateTime::Format::Flexible; +use IPC::DirQueue; +use File::Slurp; =head1 NAME @@ -47,26 +49,41 @@ ## CONFIG +my $debug = 0; + +my $irc_config = { + nick => 'irc-logger', + server => 'irc.freenode.net', + port => 6667, + ircname => 'Anna the bot: try /msg irc-logger help', +}; + +my $queue_dir = './queue'; + my $HOSTNAME = `hostname -f`; chomp($HOSTNAME); -my $NICK = 'irc-logger'; -$NICK .= '-dev' if ($HOSTNAME =~ m/llin/); -my $CONNECT = { - Server => 'irc.freenode.net', - Nick => $NICK, - Ircname => "try /msg $NICK help", -}; + my $CHANNEL = '#razmjenavjestina'; -$CHANNEL = '#irc-logger' if ($HOSTNAME =~ m/llin/); -my $IRC_ALIAS = "log"; -if ( $HOSTNAME =~ m/lugarin/ ) { - $CONNECT->{Server} = 'irc.carnet.hr'; +if ( $HOSTNAME =~ m/llin/ ) { + $irc_config->{nick} = 'irc-logger-llin'; +# $irc_config = { +# nick => 'irc-logger-llin', +# server => 'localhost', +# port => 6668, +# }; + $CHANNEL = '#irc-logger'; +} elsif ( $HOSTNAME =~ m/lugarin/ ) { + $irc_config->{server} = 'irc.carnet.hr'; $CHANNEL = '#riss'; } -warn dump( $HOSTNAME, $CONNECT ); +my @channels = ( $CHANNEL ); + +warn "# config = ", dump( $irc_config ), $/; + +my $NICK = $irc_config->{nick} or die "no nick?"; my $DSN = 'DBI:Pg:dbname=' . $NICK; @@ -95,18 +112,28 @@ GetOptions( 'import-dircproxy:s' => \$import_dircproxy, 'log:s' => \$log_path, + 'queue:s' => \$queue_dir, ); #$SIG{__DIE__} = sub { # confess "fatal error"; #}; -open(STDOUT, '>', $log_path) || warn "can't redirect log to $log_path: $!"; - sub _log { - print strftime($TIMESTAMP,localtime()) . ' ' . join(" ",@_) . $/; + print strftime($TIMESTAMP,localtime()) . ' ' . join(" ",map { ref($_) ? dump( $_ ) : $_ } @_) . $/; +} + +open(STDOUT, '>', $log_path) && warn "log to $log_path: $!\n"; + +# queue + +if ( ! -d $queue_dir ) { + warn "## creating queue directory $queue_dir"; + mkdir $queue_dir or die "can't create queue directory $queue_dir: $!"; } +my $dq = IPC::DirQueue->new({ dir => $queue_dir }); + # HTML formatters my %escape = ('<'=>'<', '>'=>'>', '&'=>'&', '"'=>'"'); @@ -149,6 +176,13 @@ }, }; +# POE IRC +my $poe_irc = POE::Component::IRC->spawn( %$irc_config ) or + die "can't start ", dump( $irc_config ), ": $!"; + +my $irc = $poe_irc->session_id(); +_log "IRC session_id $irc"; + my $dbh = DBI->connect($DSN,"","", { RaiseError => 1, AutoCommit => 1 }) || die $DBI::errstr; $dbh->do( qq{ set client_encoding = 'UTF-8' } ); @@ -465,6 +499,8 @@ # $row->{nick} = $nick; # } + $append = 0 if $row->{me}; + if ($last_row->{nick} ne $nick) { # obfu way to find format for me_nick if needed or fallback to default my $fmt = $row->{me} ? ( $args->{fmt}->{me_nick} || $args->{fmt}->{nick} ) : $args->{fmt}->{nick}; @@ -628,12 +664,13 @@ # RSS follow # -my $_rss; +my $_stat; sub rss_fetch { my ($args) = @_; + # how many messages to send out when feed is seen for the first time? my $send_rss_msgs = 1; @@ -645,12 +682,14 @@ return; } + $_stat->{rss}->{url2link}->{ $args->{url} } = $feed->link; + my ( $total, $updates ) = ( 0, 0 ); for my $entry ($feed->entries) { $total++; # seen allready? - next if $_rss->{$args->{channel}}->{$feed->link}->{$entry->id}++ > 0; + next if $_stat->{rss}->{seen}->{$args->{channel}}->{$feed->link}->{$entry->id}++ > 0; sub prefix { my ($txt,$var) = @_; @@ -676,14 +715,33 @@ $msg .= prefix( ' | ' , $entry->title ); $msg .= prefix( ' | ' , $link ); # $msg .= prefix( ' id ' , $entry->id ); + if ( my $tags = $entry->category ) { + $tags =~ s!^\s+!!; + $tags =~ s!\s*$! !; + $tags =~ s!,?\s+!// !g; + $msg .= prefix( ' ' , $tags ); + } if ( $args->{kernel} && $send_rss_msgs ) { $send_rss_msgs--; - $sth_insert_log->execute( $args->{channel}, 1, $NICK, $msg, 'now()' ); + if ( ! $args->{private} ) { + # FIXME bug! should be save_message + save_message( channel => $args->{channel}, me => 1, nick => $NICK, message => $msg ); +# $sth_insert_log->execute( $args->{channel}, 1, $NICK, $msg, 'now()' ); + } my ( $type, $to ) = ( 'notice', $args->{channel} ); ( $type, $to ) = ( 'privmsg', $args->{nick} ) if $args->{private}; - _log(">> $type $to |", $msg); - $args->{kernel}->post( $IRC_ALIAS => $type => $to, $msg ); + + _log(">> $type $to", $msg); +# $args->{kernel}->post( $irc => $type => $to, $msg ); + # XXX enqueue message to send later + sub enqueue_post { + my $post = dump( @_ ); + warn "## queue_post $post\n" if $debug; + $dq->enqueue_string( $post ); + } + enqueue_post( $type => $to => $msg ); + $updates++; } } @@ -721,34 +779,42 @@ sub rss_check_updates { my $kernel = shift; - $_rss->{last_poll} ||= time(); - my $dt = time() - $_rss->{last_poll}; - warn "## rss_check_updates $dt > $rss_min_delay\n"; + $_stat->{rss}->{last_poll} ||= time(); + my $dt = time() - $_stat->{rss}->{last_poll}; if ( $dt > $rss_min_delay ) { - $_rss->{last_poll} = time(); + warn "## rss_check_updates $dt > $rss_min_delay\n"; + $_stat->{rss}->{last_poll} = time(); _log rss_fetch_all( $kernel ); } + # XXX send queue messages + while ( my $job = $dq->pickup_queued_job() ) { + my $data = read_file( $job->get_data_path ) || die "can't load ", $job->get_data_path, ": $!"; +# $kernel->post( $irc => $type => $to, $msg ); + my @data = eval $data; + _log ">> post from queue ", $irc, @data; + $kernel->post( $irc => @data ); + $job->finish; + warn "## done queued job: ",dump( @data ) if $debug; + } } # seed rss seen cache so we won't send out all items on startup -_log rss_fetch_all; - -# -# POE handing part -# - -my $ping; # ping stats - -POE::Component::IRC->new($IRC_ALIAS); +_log rss_fetch_all if ! $debug; POE::Session->create( inline_states => { _start => sub { - $_[KERNEL]->post($IRC_ALIAS => register => 'all'); - $_[KERNEL]->post($IRC_ALIAS => connect => $CONNECT); + $_[KERNEL]->post( $irc => register => 'all' ); + $_[KERNEL]->post( $irc => connect => {} ); }, + irc_001 => sub { + my ($kernel,$sender) = @_[KERNEL,SENDER]; + my $poco_object = $sender->get_heap(); + _log "connected to",$poco_object->server_name(); + $kernel->post( $sender => join => $_ ) for @channels; + undef; + }, irc_255 => sub { # server is done blabbing - $_[KERNEL]->post($IRC_ALIAS => join => $CHANNEL); - $_[KERNEL]->post( $IRC_ALIAS => privmsg => 'nickserv', "IDENTIFY $NICK" ); + $_[KERNEL]->post( $irc => join => $CHANNEL); }, irc_public => sub { my $kernel = $_[KERNEL]; @@ -780,7 +846,7 @@ }, irc_ping => sub { _log( "pong ", $_[ARG0] ); - $ping->{ $_[ARG0] }++; + $_stat->{ping}->{ $_[ARG0] }++; rss_check_updates( $_[KERNEL] ); }, irc_invite => sub { @@ -790,8 +856,8 @@ _log "invited to $channel by $nick"; - $_[KERNEL]->post( $IRC_ALIAS => privmsg => $nick, "how nice of you to invite me to $channel, I'll be right there..." ); - $_[KERNEL]->post($IRC_ALIAS => join => $channel); + $_[KERNEL]->post( $irc => privmsg => $nick, "how nice of you to invite me to $channel, I'll be right there..." ); + $_[KERNEL]->post( $irc => 'join' => $channel ); }, irc_msg => sub { @@ -799,6 +865,7 @@ my $nick = (split /!/, $_[ARG0])[0]; my $msg = $_[ARG2]; my $channel = $_[ARG1]->[0]; + warn "# ARG = ",dump( @_[ARG0,ARG1,ARG2] ) if $debug; my $res = "unknown command '$msg', try /msg $NICK help!"; my @out; @@ -809,10 +876,10 @@ $res = "usage: /msg $NICK comand | commands: stat - user/message stat | last - show backtrace | grep foobar - find foobar"; - } elsif ($msg =~ m/^msg\s+(\S+)\s+(.*)$/i) { + } elsif ($msg =~ m/^(privmsg|notice)\s+(\S+)\s+(.*)$/i) { - _log ">> /msg $1 $2"; - $_[KERNEL]->post( $IRC_ALIAS => privmsg => $1, $2 ); + _log ">> /$1 $2 $3"; + $_[KERNEL]->post( $irc => $1 => $2, $3 ); $res = ''; } elsif ($msg =~ m/^stat.*?\s*(\d*)/i) { @@ -842,7 +909,7 @@ foreach my $res (get_from_log( limit => $limit )) { _log "last: $res"; - $_[KERNEL]->post( $IRC_ALIAS => privmsg => $nick, $res ); + $_[KERNEL]->post( $irc => privmsg => $nick, $res ); } $res = ''; @@ -856,7 +923,7 @@ search => $what, )) { _log "search [$what]: $res"; - $_[KERNEL]->post( $IRC_ALIAS => privmsg => $nick, $res ); + $_[KERNEL]->post( $irc => privmsg => $nick, $res ); } $res = ''; @@ -891,10 +958,10 @@ " : " . ( $stat->{vote}->{'-'} || 0 ) . " --" . " from " . ( join(", ", @nicks) || 'nobody' ); - $_[KERNEL]->post( $IRC_ALIAS => notice => $nick, $res ); + $_[KERNEL]->post( $irc => notice => $nick, $res ); } elsif ($msg =~ m/^ping/) { - $res = "ping = " . dump( $ping ); + $res = "ping = " . dump( $_stat->{ping} ); } elsif ($msg =~ m/^conf(?:ig)*\s*(last-size|twitter)*\s*(.*)/) { if ( ! defined( $1 ) ) { my $sth = $dbh->prepare(qq{ select name,value,changed from meta where nick = ? and channel = ? }); @@ -927,18 +994,14 @@ } } elsif ($msg =~ m/^rss-update/) { $res = rss_fetch_all( $_[KERNEL] ); - } elsif ($msg =~ m/^rss-clean/) { - $_rss = undef; - $dbh->do( qq{ update feeds set last_update = now() - delay } ); - $res = "OK, cleaned RSS cache"; } elsif ($msg =~ m/^rss-list/) { my $sth = $dbh->prepare(qq{ select url,name,last_update,active,channel,nick,private from feeds }); $sth->execute; while (my @row = $sth->fetchrow_array) { - $_[KERNEL]->post( $IRC_ALIAS => privmsg => $nick, join(' | ',@row) ); + $_[KERNEL]->post( $irc => privmsg => $nick, join(' | ',@row) ); } $res = ''; - } elsif ($msg =~ m!^rss-(add|remove|stop|start)(?:-(private))?\s+(http://\S+)\s*(.*)!) { + } elsif ($msg =~ m!^rss-(add|remove|stop|start|clean)(?:-(private))?\s+(http://\S+)\s*(.*)!) { my ( $command, $sub, $url, $arg ) = ( $1,$2,$3,$4 ); my $channel = $1 if ( $arg =~ s/\s*(#\S+)\s*// ); @@ -949,6 +1012,7 @@ # remove => qq{ delete from feeds where url = ? and name = ? }, start => qq{ update feeds set active = true where url = ? }, stop => qq{ update feeds set active = false where url = ? }, + clean => qq{ update feeds set last_update = now() - delay where url = ? }, }; if ( $command eq 'add' && ! $channel ) { @@ -964,55 +1028,96 @@ if ($@) { $res = "ERROR: $@"; } else { - $res = "OK, RSS [$command|$sub|$url|$arg]"; + $res = "OK, RSS executed $command " . ( $sub ? "-$sub" : '' ) ."on $channel url $url"; + if ( $command eq 'clean' ) { + my $seen = $_stat->{rss}->{seen} || die "no seen?"; + my $want_link = $_stat->{rss}->{url2link}->{$url} || warn "no url2link($url)"; + foreach my $c ( keys %$seen ) { + my $c_hash = $seen->{$c} || die "no seen->{$c}"; + die "not HASH with rss links but ", dump($c_hash) unless ref($c_hash) eq 'HASH'; + foreach my $link ( keys %$c_hash ) { + next unless $link eq $want_link; + _log "RSS removed seen $c $url $link"; + } + } + } } } else { $res = "ERROR: don't know what to do with: $msg"; } + } elsif ($msg =~ m/^rss-clean/) { + # this makes sense because we didn't catch rss-clean http://... before! + $_stat->{rss} = undef; + $dbh->do( qq{ update feeds set last_update = now() - delay } ); + $res = "OK, cleaned RSS cache"; } if ($res) { _log ">> [$nick] $res"; - $_[KERNEL]->post( $IRC_ALIAS => privmsg => $nick, $res ); + $_[KERNEL]->post( $irc => privmsg => $nick, $res ); } rss_check_updates( $_[KERNEL] ); }, + irc_372 => sub { + _log "<< motd",$_[ARG0],$_[ARG1]; + }, + irc_375 => sub { + _log "<< motd", $_[ARG0], "start"; + }, + irc_376 => sub { + _log "<< motd", $_[ARG0], "end"; + }, +# irc_433 => sub { +# print "# irc_433: ",$_[ARG1], "\n"; +# warn "## indetify $NICK\n"; +# $_[KERNEL]->post( $irc => privmsg => 'nickserv', "IDENTIFY $NICK" ); +# }, +# irc_451 # please register irc_477 => sub { _log "<< irc_477: ",$_[ARG1]; - $_[KERNEL]->post( $IRC_ALIAS => privmsg => 'nickserv', "register $NICK" ); + _log ">> IDENTIFY $NICK"; + $_[KERNEL]->post( $irc => privmsg => 'NickServ', "IDENTIFY $NICK" ); }, irc_505 => sub { _log "<< irc_505: ",$_[ARG1]; - $_[KERNEL]->post( $IRC_ALIAS => privmsg => 'nickserv', "register $NICK" ); -# $_[KERNEL]->post( $IRC_ALIAS => privmsg => 'nickserv', "set hide email on" ); -# $_[KERNEL]->post( $IRC_ALIAS => privmsg => 'nickserv', "set email dpavlin\@rot13.org" ); + _log ">> register $NICK"; + $_[KERNEL]->post( $irc => privmsg => 'NickServ', "register $NICK" ); +# $_[KERNEL]->post( $irc => privmsg => 'nickserv', "IDENTIFY $NICK" ); +# $_[KERNEL]->post( $irc => privmsg => 'nickserv', "set hide email on" ); +# $_[KERNEL]->post( $irc => privmsg => 'nickserv', "set email dpavlin\@rot13.org" ); }, irc_registered => sub { - _log "## registrated $NICK, /msg nickserv IDENTIFY $NICK"; - $_[KERNEL]->post( $IRC_ALIAS => privmsg => 'nickserv', "IDENTIFY $NICK" ); + _log "<< registered $NICK"; }, irc_disconnected => sub { _log "## disconnected.. sleeping for $sleep_on_error seconds and reconnecting again"; sleep($sleep_on_error); - $_[KERNEL]->post( $IRC_ALIAS => connect => $CONNECT); + $_[KERNEL]->post( $irc => connect => {} ); }, irc_socketerr => sub { _log "## socket error... sleeping for $sleep_on_error seconds and retry"; sleep($sleep_on_error); - $_[KERNEL]->post( $IRC_ALIAS => connect => $CONNECT); + $_[KERNEL]->post( $irc => connect => {} ); + }, + irc_notice => sub { + _log "<< notice from ", $_[ARG0], $_[ARG1], $_[ARG2]; + my $m = $_[ARG2]; + if ( $m =~ m!/msg.*(NickServ).*(IDENTIFY)!i ) { + _log ">> suggested to $1 $2"; + $_[KERNEL]->post( $irc => privmsg => $1, "$2 $NICK" ); + } elsif ( $m =~ m!\Q$NICK\E.*registered!i ) { + _log ">> registreted, so IDENTIFY"; + $_[KERNEL]->post( $irc => privmsg => 'nickserv', "IDENTIFY $NICK" ); + } else { + warn "## ignore $m\n" if $debug; + } }, -# irc_433 => sub { -# print "# irc_433: ",$_[ARG1], "\n"; -# warn "## indetify $NICK\n"; -# $_[KERNEL]->post( $IRC_ALIAS => privmsg => 'nickserv', "IDENTIFY $NICK" ); -# }, -# irc_451 # please register irc_snotice => sub { - _log "<< snotice",$_[ARG0]; + _log "<< snotice", $_[ARG0]; #dump( $_[ARG0],$_[ARG1], $_[ARG2] ); if ( $_[ARG0] =~ m!/(QUOTE)\s+(PASS\s+\d+)!i ) { warn ">> $1 | $2\n"; - $_[KERNEL]->post( $IRC_ALIAS => lc($1) => $2); + $_[KERNEL]->post( $irc => lc($1) => $2); } }, _child => sub {}, @@ -1029,6 +1134,8 @@ # http server +_log "WEB archive at $url"; + my $httpd = POE::Component::Server::HTTP->new( Port => $http_port, PreHandler => { @@ -1075,7 +1182,7 @@ $style .= ".col-${max_color} { background: $c }\n"; $max_color++; } -warn "defined $max_color colors for users...\n"; +_log "WEB defined $max_color colors for users..."; sub root_handler { my ($request, $response) = @_; @@ -1097,8 +1204,12 @@ } my $search = $q->param('search') || $q->param('grep') || ''; + my $r_url = $request->url; + + my @commands = qw( tags last-tag follow stat ); + my $commands_re = join('|',@commands); - if ($request->url =~ m#/rss(?:/(tags|last-tag|follow.*)\w*(?:=(\d+))?)?#i) { + if ($r_url =~ m#/rss(?:/($commands_re.*)\w*(?:=(\d+))?)?#i) { my $show = lc($1); my $nr = $2; @@ -1112,6 +1223,8 @@ my $feed = XML::Feed->new( $type ); $feed->link( $url ); + my $rc = RC_OK; + if ( $show eq 'tags' ) { $nr ||= 50; $feed->title( "tags from $CHANNEL" ); @@ -1181,20 +1294,29 @@ $feed->add_entry( $feed_entry ); } + } elsif ( $show =~ m/^stat/ ) { + my $feed_entry = XML::Feed::Entry->new($type); $feed_entry->title( "Internal stats" ); $feed_entry->content( - '' . dump( $_rss ) . ']]>' + '' . dump( $_stat ) . ']]>' ); $feed->add_entry( $feed_entry ); } else { - _log "unknown rss request ",$request->url; - return RC_DENY; + _log "WEB unknown rss request $r_url"; + $feed->title( "unknown $r_url" ); + foreach my $c ( @commands ) { + my $feed_entry = XML::Feed::Entry->new($type); + $feed_entry->title( "rss/$c" ); + $feed_entry->link( "$url/rss/$c" ); + $feed->add_entry( $feed_entry ); + } + $rc = RC_DENY; } $response->content( $feed->as_xml ); - return RC_OK; + return $rc; } if ( $@ ) {