--- trunk/bin/irc-logger.pl 2008/03/07 11:16:05 95 +++ trunk/bin/irc-logger.pl 2008/03/10 13:02:32 112 @@ -2,6 +2,23 @@ use strict; $|++; +use POE qw(Component::IRC Component::Server::HTTP); +use HTTP::Status; +use DBI; +use Regexp::Common qw /URI/; +use CGI::Simple; +use HTML::TagCloud; +use POSIX qw/strftime/; +use HTML::CalendarMonthSimple; +use Getopt::Long; +use DateTime; +use URI::Escape; +use Data::Dump qw/dump/; +use DateTime::Format::ISO8601; +use Carp qw/confess/; +use XML::Feed; +use DateTime::Format::Flexible; + =head1 NAME irc-logger.pl @@ -30,19 +47,37 @@ ## CONFIG +my $irc_config = { + nick => 'irc-logger', + server => 'irc.freenode.net', + port => 6667, + ircname => 'Anna the bot: try /msg irc-logger help', +}; + 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/llin/ ) { + $irc_config->{nick} = 'irc-logger-dev'; +# $irc_config = { +# nick => 'irc-logger-dev', +# server => 'localhost', +# port => 6668, +# }; + $CHANNEL = '#irc-logger'; +} elsif ( $HOSTNAME =~ m/lugarin/ ) { + $irc_config->{server} = 'irc.carnet.hr'; + $CHANNEL = '#riss'; +} + +my @channels = ( $CHANNEL ); + +warn "# config = ", dump( $irc_config ), $/; + +my $NICK = $irc_config->{nick} or die "no nick?"; my $DSN = 'DBI:Pg:dbname=' . $NICK; @@ -62,23 +97,6 @@ ## END CONFIG -use POE qw(Component::IRC Component::Server::HTTP); -use HTTP::Status; -use DBI; -use Regexp::Common qw /URI/; -use CGI::Simple; -use HTML::TagCloud; -use POSIX qw/strftime/; -use HTML::CalendarMonthSimple; -use Getopt::Long; -use DateTime; -use URI::Escape; -use Data::Dump qw/dump/; -use DateTime::Format::ISO8601; -use Carp qw/confess/; -use XML::Feed; -use DateTime::Format::Flexible; - my $use_twitter = 1; eval { require Net::Twitter; }; $use_twitter = 0 if ($@); @@ -117,11 +135,11 @@ # protect HTML from wiki modifications sub e { my $t = shift; - return 'uri_unescape{' . uri_escape($t) . '}'; + return 'uri_unescape{' . uri_escape($t, '^a-zA-Z0-9') . '}'; } $m =~ s/($escape_re)/$escape{$1}/gs; - $m =~ s#($RE{URI}{HTTP})#e(qq{$1})#egs || + $m =~ s#($RE{URI}{HTTP})#e(qq{$1})#egs; $m =~ s#\/(\w+)\/#$1#gs; $m =~ s#$tag_regex#e(qq{$1})#egs; $m =~ s#\*(\w+)\*#$1#gs; @@ -142,6 +160,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' } ); @@ -178,12 +203,15 @@ name text, delay interval not null default '5 min', active boolean default true, + channel text not null, + nick text not null, + private boolean default false, last_update timestamp default 'now()', polls int default 0, updates int default 0 ); create unique index feeds_url on feeds(url); -insert into feeds (url,name) values ('http://wiki.razmjenavjestina.org/feed/workspace/razmjenavjestina?category=Recent%20Changes','wiki'); +insert into feeds (url,name,channel,nick) values ('http://wiki.razmjenavjestina.org/feed/workspace/razmjenavjestina?category=Recent%20Changes','wiki','$CHANNEL','dpavlin'); }, }; @@ -336,25 +364,26 @@ my @where; my @args; + my $msg; if (my $search = $args->{search}) { $search =~ s/^\s+//; $search =~ s/\s+$//; push @where, 'message ilike ? or nick ilike ?'; push @args, ( ( '%' . $search . '%' ) x 2 ); - _log "search for '$search'"; + $msg = "Search for '$search'"; } if ($args->{tag} && $tags->{ $args->{tag} }) { push @where, 'id in (' . join(',', @{ $tags->{ $args->{tag} } }) . ')'; - _log "search for tags $args->{tag}"; + $msg = "Search for tags $args->{tag}"; } if (my $date = $args->{date} ) { $date = check_date( $date ); push @where, 'date(time) = ?'; push @args, $date; - _log "search for date $date"; + $msg = "search for date $date"; } $sql .= " where " . join(" and ", @where) if @where; @@ -368,6 +397,8 @@ eval { $sth->execute( @args ) }; return if $@; + my $nr_results = $sth->rows; + my $last_row = { date => '', time => '', @@ -388,10 +419,14 @@ return @rows if ($args->{full_rows}); - my @msgs = ( - "Showing " . ($#rows + 1) . " messages..." + $msg .= ' produced ' . ( + $nr_results == 0 ? 'no results' : + $nr_results == 0 ? 'one result' : + $nr_results . ' results' ); + my @msgs = ( $msg ); + if ($context) { my @ids = @rows; @rows = (); @@ -448,6 +483,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}; @@ -611,7 +648,7 @@ # RSS follow # -my $_rss; +my $_stat; sub rss_fetch { @@ -633,7 +670,7 @@ $total++; # seen allready? - next if $_rss->{$feed->link}->{seen}->{$entry->id}++ > 0; + next if $_stat->{rss}->{seen}->{$args->{channel}}->{$feed->link}->{$entry->id}++ > 0; sub prefix { my ($txt,$var) = @_; @@ -652,7 +689,6 @@ } elsif ( $link !~ m!^http! ) { $link = $args->{url} . $link; } - $link =~ s!//+!/!g; my $msg; $msg .= prefix( 'From: ' , $args->{name} || $feed->title ); @@ -663,9 +699,15 @@ if ( $args->{kernel} && $send_rss_msgs ) { $send_rss_msgs--; - _log('>>', $msg); - $sth_insert_log->execute( $CHANNEL, 1, $NICK, $msg, 'now()' ); - $args->{kernel}->post( $IRC_ALIAS => notice => $CHANNEL, $msg ); + 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 => $type => $to, $msg ); $updates++; } } @@ -683,7 +725,7 @@ sub rss_fetch_all { my $kernel = shift; my $sql = qq{ - select id, url, name + select id, url, name, channel, nick, private from feeds where active is true }; @@ -703,11 +745,11 @@ sub rss_check_updates { my $kernel = shift; - $_rss->{last_poll} ||= time(); - my $dt = time() - $_rss->{last_poll}; + $_stat->{rss}->{last_poll} ||= time(); + my $dt = time() - $_stat->{rss}->{last_poll}; warn "## rss_check_updates $dt > $rss_min_delay\n"; if ( $dt > $rss_min_delay ) { - $_rss->{last_poll} = time(); + $_stat->{rss}->{last_poll} = time(); _log rss_fetch_all( $kernel ); } } @@ -715,22 +757,20 @@ # 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); - 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]; @@ -762,7 +802,7 @@ }, irc_ping => sub { _log( "pong ", $_[ARG0] ); - $ping->{ $_[ARG0] }++; + $_stat->{ping}->{ $_[ARG0] }++; rss_check_updates( $_[KERNEL] ); }, irc_invite => sub { @@ -772,8 +812,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 { @@ -791,10 +831,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) { @@ -824,7 +864,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 = ''; @@ -838,7 +878,7 @@ search => $what, )) { _log "search [$what]: $res"; - $_[KERNEL]->post( $IRC_ALIAS => privmsg => $nick, $res ); + $_[KERNEL]->post( $irc => privmsg => $nick, $res ); } $res = ''; @@ -873,10 +913,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 = ? }); @@ -910,69 +950,107 @@ } elsif ($msg =~ m/^rss-update/) { $res = rss_fetch_all( $_[KERNEL] ); } elsif ($msg =~ m/^rss-clean/) { - $_rss = undef; + $_stat->{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 from feeds }); + 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)\s+(http://\S+)\s*(.*)!) { + } elsif ($msg =~ m!^rss-(add|remove|stop|start)(?:-(private))?\s+(http://\S+)\s*(.*)!) { + my ( $command, $sub, $url, $arg ) = ( $1,$2,$3,$4 ); + + my $channel = $1 if ( $arg =~ s/\s*(#\S+)\s*// ); + $channel = $nick if $sub eq 'private'; + my $sql = { - add => qq{ insert into feeds (url,name) values (?,?) }, + add => qq{ insert into feeds (url,name,channel,nick,private) values (?,?,?,?,?) }, # 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 = ? }, }; - if (my $q = $sql->{$1} ) { + + if ( $command eq 'add' && ! $channel ) { + $res = "ERROR: got '$msg' which doesn't have #channel in it, ignoring!"; + } elsif (my $q = $sql->{$command} ) { my $sth = $dbh->prepare( $q ); - my @data = ( $2 ); - push @data, $3 if ( $q =~ s/\?//g == 2 ); - warn "## $1 SQL $q with ",dump( @data ),"\n"; + my @data = ( $url ); + if ( $command eq 'add' ) { + push @data, ( $arg, $channel, $nick, $sub eq 'private' ? 1 : 0 ); + } + warn "## $command SQL $q with ",dump( @data ),"\n"; eval { $sth->execute( @data ) }; + if ($@) { + $res = "ERROR: $@"; + } else { + $res = "OK, RSS [$command|$sub|$url|$arg]"; + } + } else { + $res = "ERROR: don't know what to do with: $msg"; } - - $res = "OK, RSS $1 : $2 - $3"; } 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_477 => sub { - _log "# irc_477: ",$_[ARG1]; - $_[KERNEL]->post( $IRC_ALIAS => privmsg => 'nickserv', "register $NICK" ); + _log "<< irc_477: ",$_[ARG1]; + $_[KERNEL]->post( $irc => privmsg => 'nickserv', "register $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 "<< irc_505: ",$_[ARG1]; + $_[KERNEL]->post( $irc => privmsg => 'nickserv', "register $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"; - $_[KERNEL]->post( $IRC_ALIAS => privmsg => 'nickserv', "IDENTIFY $NICK" ); + _log "<< registered $NICK"; }, irc_disconnected => sub { - _log "## disconnected, reconnecting again"; - $_[KERNEL]->post($IRC_ALIAS => connect => $CONNECT); + _log "## disconnected.. sleeping for $sleep_on_error seconds and reconnecting again"; + sleep($sleep_on_error); + $_[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_433 => sub { # print "# irc_433: ",$_[ARG1], "\n"; # warn "## indetify $NICK\n"; -# $_[KERNEL]->post( $IRC_ALIAS => privmsg => 'nickserv', "IDENTIFY $NICK" ); +# $_[KERNEL]->post( $irc => privmsg => 'nickserv', "IDENTIFY $NICK" ); # }, +# irc_451 # please register + irc_notice => sub { + _log "<< notice",$_[ARG0]; + if ( $_[ARG0] =~ m!/msg\s+NickServ\s+IDENTIFY!i ) { + $_[KERNEL]->post( $irc => privmsg => 'nickserv', "IDENTIFY $NICK" ); + } + }, + irc_snotice => sub { + _log "<< snotice",$_[ARG0]; + if ( $_[ARG0] =~ m!/(QUOTE)\s+(PASS\s+\d+)!i ) { + warn ">> $1 | $2\n"; + $_[KERNEL]->post( $irc => lc($1) => $2); + } + }, _child => sub {}, _default => sub { _log sprintf "sID:%s %s %s", @@ -1055,8 +1133,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; @@ -1070,6 +1152,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" ); @@ -1139,13 +1223,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( $_stat ) . ']]>' + ); + $feed->add_entry( $feed_entry ); + } else { - _log "unknown rss request ",$request->url; - return RC_DENY; + _log "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 ( $@ ) {