--- trunk/irc-logger.pl 2006/06/25 17:40:59 37 +++ trunk/bin/irc-logger.pl 2008/04/01 19:04:32 132 @@ -2,6 +2,24 @@ use strict; $|++; +use POE qw(Component::IRC Component::Server::HTTP Component::Client::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; +use Encode; + =head1 NAME irc-logger.pl @@ -18,6 +36,10 @@ Import log from C to C database +=item --log=irc-logger.log + +=back + =head1 DESCRIPTION log all conversation on irc channel @@ -26,61 +48,135 @@ ## CONFIG -my $HOSTNAME = `hostname`; +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 $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"; -my %FOLLOWS = - ( - ACCESS => "/var/log/apache/access.log", - ERROR => "/var/log/apache/error.log", - ); +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'; +} + +my @channels = ( $CHANNEL ); + +warn "## config = ", dump( $irc_config ) if $debug; + +my $NICK = $irc_config->{nick} or die "no nick?"; my $DSN = 'DBI:Pg:dbname=' . $NICK; -my $ENCODING = 'ISO-8859-2'; my $TIMESTAMP = '%Y-%m-%d %H:%M:%S'; -## END CONFIG +my $sleep_on_error = 5; +# number of last tags to keep in circular buffer +my $last_x_tags = 50; +# don't pull rss feeds more often than this +my $rss_min_delay = 60; -use POE qw(Component::IRC Wheel::FollowTail Component::Server::HTTP); -use HTTP::Status; -use DBI; -use Encode qw/from_to is_utf8/; -use Regexp::Common qw /URI/; -use CGI::Simple; -use HTML::TagCloud; -use POSIX qw/strftime/; -use HTML::CalendarMonthSimple; -use Getopt::Long; -use DateTime; +my $http_port = $NICK =~ m/-dev/ ? 8001 : 8000; + +my $url = "http://$HOSTNAME:$http_port"; + +## END CONFIG + +my $use_twitter = 1; +eval { require Net::Twitter; }; +$use_twitter = 0 if ($@); my $import_dircproxy; +my $log_path; GetOptions( 'import-dircproxy:s' => \$import_dircproxy, + 'log:s' => \$log_path, + 'debug!' => \$debug, ); -my $dbh = DBI->connect($DSN,"","", { RaiseError => 1, AutoCommit => 1 }) || die $DBI::errstr; +#$SIG{__DIE__} = sub { +# confess "fatal error"; +#}; + +sub _log { + print strftime($TIMESTAMP,localtime()) . ' ' . join(" ",map { ref($_) ? dump( $_ ) : $_ } @_) . $/; +} + +open(STDOUT, '>', $log_path) && warn "log to $log_path: $!\n"; + -eval { - $dbh->do(qq{ select count(*) from log }); +# HTML formatters + +my %escape = ('<'=>'<', '>'=>'>', '&'=>'&', '"'=>'"'); +my $escape_re = join '|' => keys %escape; + +my $tag_regex = '\b([\w-_]+)//'; + +my %nick_enumerator; +my $max_color = 0; + +my $filter = { + message => sub { + my $m = shift || return; + + # protect HTML from wiki modifications + sub e { + my $t = shift; + 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#\/(\w+)\/#$1#gs; + $m =~ s#$tag_regex#e(qq{$1})#egs; + $m =~ s#\*(\w+)\*#$1#gs; + $m =~ s#_(\w+)_#$1#gs; + + $m =~ s#uri_unescape{([^}]+)}#uri_unescape($1)#egs; + return $m; + }, + nick => sub { + my $n = shift || return; + if (! $nick_enumerator{$n}) { + my $max = scalar keys %nick_enumerator; + $nick_enumerator{$n} = $max + 1; + } + return '' . $n . ''; + }, }; -if ($@) { - warn "creating database table in $DSN\n"; - $dbh->do(<<'_SQL_SCHEMA_'); +# 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' } ); + +my $sql_schema = { + log => qq{ create table log ( id serial, time timestamp default now(), @@ -94,18 +190,105 @@ create index log_time on log(time); create index log_channel on log(channel); create index log_nick on log(nick); + }, + meta => q{ +create table meta ( + nick text not null, + channel text not null, + name text not null, + value text, + changed timestamp default 'now()', + primary key(nick,channel,name) +); + }, + feeds => qq{ +create table feeds ( + id serial, + url text not null, + 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,channel,nick) values ('http://wiki.razmjenavjestina.org/feed/workspace/razmjenavjestina?category=Recent%20Changes','wiki','$CHANNEL','dpavlin'); + }, +}; -_SQL_SCHEMA_ +foreach my $table ( keys %$sql_schema ) { + + eval { + $dbh->do(qq{ select count(*) from $table }); + }; + + if ($@) { + warn "creating database table $table in $DSN\n"; + $dbh->do( $sql_schema->{ $table } ); + } } -my $sth = $dbh->prepare(qq{ + +=head2 meta + +Set or get some meta data into database + + meta('nick','channel','var_name', $var_value ); + + $var_value = meta('nick','channel','var_name'); + ( $var_value, $changed ) = meta('nick','channel','var_name'); + +=cut + +sub meta { + my ($nick,$channel,$name,$value) = @_; + + # normalize channel name + $channel =~ s/^#//; + + if (defined($value)) { + + my $sth = $dbh->prepare(qq{ update meta set value = ?, changed = now() where nick = ? and channel = ? and name = ? }); + + eval { $sth->execute( $value, $nick, $channel, $name ) }; + + # error or no result + if ( $@ || ! $sth->rows ) { + $sth = $dbh->prepare(qq{ insert into meta (value,nick,channel,name,changed) values (?,?,?,?,now()) }); + $sth->execute( $value, $nick, $channel, $name ); + warn "## created $nick/$channel/$name = $value\n"; + } else { + warn "## updated $nick/$channel/$name = $value\n"; + } + + return $value; + + } else { + + my $sth = $dbh->prepare(qq{ select value,changed from meta where nick = ? and channel = ? and name = ? }); + $sth->execute( $nick, $channel, $name ); + my ($v,$c) = $sth->fetchrow_array; + warn "## fetched $nick/$channel/$name = $v [$c]\n"; + return ($v,$c) if wantarray; + return $v; + + } +} + + + +my $sth_insert_log = $dbh->prepare(qq{ insert into log (channel, me, nick, message, time) values (?,?,?,?,?) }); + my $tags; -my $tag_regex = '\b([\w-_]+)//'; =head2 get_from_log @@ -126,6 +309,7 @@ } }, context => 5, + full_rows => 1, ); Order is important. Fields are first passed through C (if available) and @@ -133,19 +317,24 @@ C defines number of messages around each search hit for display. +C will return database rows for each result with C, C