/[irc-logger]/trunk/bin/irc-logger.pl
This is repository of my old source code which isn't updated any more. Go to git.rot13.org for current projects!
ViewVC logotype

Annotation of /trunk/bin/irc-logger.pl

Parent Directory Parent Directory | Revision Log Revision Log


Revision 122 - (hide annotations)
Fri Mar 14 14:37:46 2008 UTC (16 years, 1 month ago) by dpavlin
File MIME type: text/plain
File size: 36593 byte(s)
use POE::Component::Client::HTTP to fetch RSS feeds, so we don't
block on that. Added --debug flag to spit more debug output.
1 dpavlin 4 #!/usr/bin/perl -w
2     use strict;
3     $|++;
4    
5 dpavlin 122 use POE qw(Component::IRC Component::Server::HTTP Component::Client::HTTP);
6 dpavlin 102 use HTTP::Status;
7     use DBI;
8     use Regexp::Common qw /URI/;
9     use CGI::Simple;
10     use HTML::TagCloud;
11     use POSIX qw/strftime/;
12     use HTML::CalendarMonthSimple;
13     use Getopt::Long;
14     use DateTime;
15     use URI::Escape;
16     use Data::Dump qw/dump/;
17     use DateTime::Format::ISO8601;
18     use Carp qw/confess/;
19     use XML::Feed;
20     use DateTime::Format::Flexible;
21 dpavlin 119 use IPC::DirQueue;
22     use File::Slurp;
23 dpavlin 102
24 dpavlin 5 =head1 NAME
25    
26     irc-logger.pl
27    
28     =head1 SYNOPSIS
29    
30     ./irc-logger.pl
31    
32 dpavlin 37 =head2 Options
33    
34     =over 4
35    
36     =item --import-dircproxy=filename
37    
38     Import log from C<dircproxy> to C<irc-logger> database
39    
40 dpavlin 45 =item --log=irc-logger.log
41    
42     =back
43    
44 dpavlin 5 =head1 DESCRIPTION
45    
46     log all conversation on irc channel
47    
48     =cut
49    
50 dpavlin 4 ## CONFIG
51    
52 dpavlin 119 my $debug = 0;
53    
54 dpavlin 109 my $irc_config = {
55     nick => 'irc-logger',
56     server => 'irc.freenode.net',
57     port => 6667,
58     ircname => 'Anna the bot: try /msg irc-logger help',
59     };
60    
61 dpavlin 119 my $queue_dir = './queue';
62    
63 dpavlin 73 my $HOSTNAME = `hostname -f`;
64     chomp($HOSTNAME);
65 dpavlin 33
66 dpavlin 109
67 dpavlin 4 my $CHANNEL = '#razmjenavjestina';
68    
69 dpavlin 109 if ( $HOSTNAME =~ m/llin/ ) {
70 dpavlin 114 $irc_config->{nick} = 'irc-logger-llin';
71 dpavlin 109 # $irc_config = {
72 dpavlin 114 # nick => 'irc-logger-llin',
73 dpavlin 109 # server => 'localhost',
74     # port => 6668,
75     # };
76     $CHANNEL = '#irc-logger';
77     } elsif ( $HOSTNAME =~ m/lugarin/ ) {
78     $irc_config->{server} = 'irc.carnet.hr';
79 dpavlin 102 $CHANNEL = '#riss';
80     }
81    
82 dpavlin 109 my @channels = ( $CHANNEL );
83 dpavlin 102
84 dpavlin 122 warn "## config = ", dump( $irc_config ) if $debug;
85 dpavlin 109
86     my $NICK = $irc_config->{nick} or die "no nick?";
87    
88 dpavlin 19 my $DSN = 'DBI:Pg:dbname=' . $NICK;
89 dpavlin 5
90 dpavlin 34 my $TIMESTAMP = '%Y-%m-%d %H:%M:%S';
91 dpavlin 14
92 dpavlin 41 my $sleep_on_error = 5;
93    
94 dpavlin 79 # number of last tags to keep in circular buffer
95     my $last_x_tags = 50;
96    
97 dpavlin 85 # don't pull rss feeds more often than this
98     my $rss_min_delay = 60;
99    
100 dpavlin 70 my $http_port = $NICK =~ m/-dev/ ? 8001 : 8000;
101    
102 dpavlin 73 my $url = "http://$HOSTNAME:$http_port";
103    
104 dpavlin 4 ## END CONFIG
105    
106 dpavlin 54 my $use_twitter = 1;
107     eval { require Net::Twitter; };
108     $use_twitter = 0 if ($@);
109    
110 dpavlin 37 my $import_dircproxy;
111 dpavlin 45 my $log_path;
112 dpavlin 37 GetOptions(
113     'import-dircproxy:s' => \$import_dircproxy,
114 dpavlin 45 'log:s' => \$log_path,
115 dpavlin 119 'queue:s' => \$queue_dir,
116 dpavlin 122 'debug!' => \$debug,
117 dpavlin 37 );
118    
119 dpavlin 87 #$SIG{__DIE__} = sub {
120     # confess "fatal error";
121     #};
122 dpavlin 68
123 dpavlin 45 sub _log {
124 dpavlin 116 print strftime($TIMESTAMP,localtime()) . ' ' . join(" ",map { ref($_) ? dump( $_ ) : $_ } @_) . $/;
125 dpavlin 45 }
126    
127 dpavlin 116 open(STDOUT, '>', $log_path) && warn "log to $log_path: $!\n";
128    
129 dpavlin 119 # queue
130    
131     if ( ! -d $queue_dir ) {
132     warn "## creating queue directory $queue_dir";
133     mkdir $queue_dir or die "can't create queue directory $queue_dir: $!";
134     }
135    
136     my $dq = IPC::DirQueue->new({ dir => $queue_dir });
137    
138 dpavlin 70 # HTML formatters
139    
140     my %escape = ('<'=>'&lt;', '>'=>'&gt;', '&'=>'&amp;', '"'=>'&quot;');
141     my $escape_re = join '|' => keys %escape;
142    
143     my $tag_regex = '\b([\w-_]+)//';
144    
145     my %nick_enumerator;
146     my $max_color = 0;
147    
148     my $filter = {
149     message => sub {
150     my $m = shift || return;
151    
152     # protect HTML from wiki modifications
153     sub e {
154     my $t = shift;
155 dpavlin 98 return 'uri_unescape{' . uri_escape($t, '^a-zA-Z0-9') . '}';
156 dpavlin 70 }
157    
158     $m =~ s/($escape_re)/$escape{$1}/gs;
159 dpavlin 98 $m =~ s#($RE{URI}{HTTP})#e(qq{<a href="$1">$1</a>})#egs;
160 dpavlin 70 $m =~ s#\/(\w+)\/#<i>$1</i>#gs;
161 dpavlin 73 $m =~ s#$tag_regex#e(qq{<a href="$url?tag=$1" class="tag">$1</a>})#egs;
162 dpavlin 70 $m =~ s#\*(\w+)\*#<b>$1</b>#gs;
163     $m =~ s#_(\w+)_#<u>$1</u>#gs;
164    
165     $m =~ s#uri_unescape{([^}]+)}#uri_unescape($1)#egs;
166     return $m;
167     },
168     nick => sub {
169     my $n = shift || return;
170     if (! $nick_enumerator{$n}) {
171     my $max = scalar keys %nick_enumerator;
172     $nick_enumerator{$n} = $max + 1;
173     }
174     return '<span class="nick col-' .
175     ( $nick_enumerator{$n} % $max_color ) .
176     '">' . $n . '</span>';
177     },
178     };
179    
180 dpavlin 112 # POE IRC
181     my $poe_irc = POE::Component::IRC->spawn( %$irc_config ) or
182     die "can't start ", dump( $irc_config ), ": $!";
183    
184     my $irc = $poe_irc->session_id();
185     _log "IRC session_id $irc";
186    
187 dpavlin 5 my $dbh = DBI->connect($DSN,"","", { RaiseError => 1, AutoCommit => 1 }) || die $DBI::errstr;
188 dpavlin 88 $dbh->do( qq{ set client_encoding = 'UTF-8' } );
189 dpavlin 5
190 dpavlin 50 my $sql_schema = {
191 dpavlin 85 log => qq{
192 dpavlin 5 create table log (
193     id serial,
194     time timestamp default now(),
195     channel text not null,
196 dpavlin 19 me boolean default false,
197 dpavlin 5 nick text not null,
198     message text not null,
199     primary key(id)
200     );
201    
202     create index log_time on log(time);
203     create index log_channel on log(channel);
204     create index log_nick on log(nick);
205 dpavlin 85 },
206     meta => q{
207 dpavlin 50 create table meta (
208     nick text not null,
209     channel text not null,
210     name text not null,
211     value text,
212 dpavlin 85 changed timestamp default 'now()',
213 dpavlin 50 primary key(nick,channel,name)
214     );
215 dpavlin 85 },
216     feeds => qq{
217     create table feeds (
218     id serial,
219     url text not null,
220     name text,
221 dpavlin 86 delay interval not null default '5 min',
222 dpavlin 85 active boolean default true,
223 dpavlin 97 channel text not null,
224     nick text not null,
225     private boolean default false,
226 dpavlin 85 last_update timestamp default 'now()',
227     polls int default 0,
228     updates int default 0
229     );
230     create unique index feeds_url on feeds(url);
231 dpavlin 97 insert into feeds (url,name,channel,nick) values ('http://wiki.razmjenavjestina.org/feed/workspace/razmjenavjestina?category=Recent%20Changes','wiki','$CHANNEL','dpavlin');
232 dpavlin 85 },
233 dpavlin 50 };
234 dpavlin 5
235 dpavlin 50 foreach my $table ( keys %$sql_schema ) {
236    
237     eval {
238     $dbh->do(qq{ select count(*) from $table });
239     };
240    
241     if ($@) {
242     warn "creating database table $table in $DSN\n";
243     $dbh->do( $sql_schema->{ $table } );
244     }
245 dpavlin 19 }
246 dpavlin 5
247 dpavlin 50
248     =head2 meta
249    
250     Set or get some meta data into database
251    
252     meta('nick','channel','var_name', $var_value );
253    
254     $var_value = meta('nick','channel','var_name');
255     ( $var_value, $changed ) = meta('nick','channel','var_name');
256    
257     =cut
258    
259     sub meta {
260     my ($nick,$channel,$name,$value) = @_;
261    
262     # normalize channel name
263     $channel =~ s/^#//;
264    
265     if (defined($value)) {
266    
267     my $sth = $dbh->prepare(qq{ update meta set value = ?, changed = now() where nick = ? and channel = ? and name = ? });
268    
269     eval { $sth->execute( $value, $nick, $channel, $name ) };
270    
271     # error or no result
272     if ( $@ || ! $sth->rows ) {
273     $sth = $dbh->prepare(qq{ insert into meta (value,nick,channel,name,changed) values (?,?,?,?,now()) });
274     $sth->execute( $value, $nick, $channel, $name );
275 dpavlin 95 warn "## created $nick/$channel/$name = $value\n";
276 dpavlin 50 } else {
277 dpavlin 95 warn "## updated $nick/$channel/$name = $value\n";
278 dpavlin 50 }
279    
280     return $value;
281    
282     } else {
283    
284     my $sth = $dbh->prepare(qq{ select value,changed from meta where nick = ? and channel = ? and name = ? });
285     $sth->execute( $nick, $channel, $name );
286     my ($v,$c) = $sth->fetchrow_array;
287 dpavlin 95 warn "## fetched $nick/$channel/$name = $v [$c]\n";
288 dpavlin 50 return ($v,$c) if wantarray;
289     return $v;
290    
291     }
292     }
293    
294    
295    
296 dpavlin 87 my $sth_insert_log = $dbh->prepare(qq{
297 dpavlin 5 insert into log
298 dpavlin 37 (channel, me, nick, message, time)
299     values (?,?,?,?,?)
300 dpavlin 5 });
301    
302 dpavlin 50
303 dpavlin 28 my $tags;
304    
305 dpavlin 11 =head2 get_from_log
306 dpavlin 5
307 dpavlin 11 my @messages = get_from_log(
308     limit => 42,
309     search => '%what to stuff in ilike%',
310 dpavlin 13 fmt => {
311     time => '{%s} ',
312     time_channel => '{%s %s} ',
313     nick => '%s: ',
314 dpavlin 20 me_nick => '***%s ',
315 dpavlin 13 message => '%s',
316     },
317 dpavlin 20 filter => {
318     message => sub {
319     # modify message content
320     return shift;
321     }
322 dpavlin 21 },
323     context => 5,
324 dpavlin 42 full_rows => 1,
325 dpavlin 11 );
326    
327 dpavlin 20 Order is important. Fields are first passed through C<filter> (if available) and
328     then throgh C<< sprintf($fmt->{message}, $message >> if available.
329    
330 dpavlin 21 C<context> defines number of messages around each search hit for display.
331    
332 dpavlin 42 C<full_rows> will return database rows for each result with C<date>, C<time>, C<channel>,
333     C<me>, C<nick> and C<message> keys.
334    
335 dpavlin 11 =cut
336    
337     sub get_from_log {
338     my $args = {@_};
339    
340 dpavlin 68 if ( ! $args->{fmt} ) {
341     $args->{fmt} = {
342     date => '[%s] ',
343     time => '{%s} ',
344     time_channel => '{%s %s} ',
345     nick => '%s: ',
346     me_nick => '***%s ',
347     message => '%s',
348     };
349     }
350 dpavlin 13
351 dpavlin 21 my $sql_message = qq{
352 dpavlin 11 select
353     time::date as date,
354     time::time as time,
355     channel,
356 dpavlin 20 me,
357 dpavlin 11 nick,
358     message
359     from log
360     };
361 dpavlin 21
362     my $sql_context = qq{
363     select
364     id
365     from log
366     };
367    
368     my $context = $1 if ($args->{search} && $args->{search} =~ s/\s*\+(\d+)\s*/ /);
369    
370     my $sql = $context ? $sql_context : $sql_message;
371    
372 dpavlin 67 sub check_date {
373 dpavlin 68 my $date = shift || return;
374     my $new_date = eval { DateTime::Format::ISO8601->parse_datetime( $date )->ymd; };
375 dpavlin 64 if ( $@ ) {
376 dpavlin 68 warn "invalid date $date\n";
377     $new_date = DateTime->now->ymd;
378 dpavlin 64 }
379 dpavlin 68 return $new_date;
380 dpavlin 67 }
381    
382 dpavlin 68 my @where;
383     my @args;
384 dpavlin 100 my $msg;
385 dpavlin 68
386     if (my $search = $args->{search}) {
387     $search =~ s/^\s+//;
388     $search =~ s/\s+$//;
389     push @where, 'message ilike ? or nick ilike ?';
390     push @args, ( ( '%' . $search . '%' ) x 2 );
391 dpavlin 100 $msg = "Search for '$search'";
392 dpavlin 63 }
393 dpavlin 68
394     if ($args->{tag} && $tags->{ $args->{tag} }) {
395     push @where, 'id in (' . join(',', @{ $tags->{ $args->{tag} } }) . ')';
396 dpavlin 100 $msg = "Search for tags $args->{tag}";
397 dpavlin 68 }
398    
399     if (my $date = $args->{date} ) {
400     $date = check_date( $date );
401     push @where, 'date(time) = ?';
402     push @args, $date;
403 dpavlin 100 $msg = "search for date $date";
404 dpavlin 68 }
405    
406     $sql .= " where " . join(" and ", @where) if @where;
407    
408 dpavlin 11 $sql .= " order by log.time desc";
409 dpavlin 35 $sql .= " limit " . $args->{limit} if ($args->{limit});
410 dpavlin 11
411 dpavlin 68 #warn "### sql: $sql ", dump( @args );
412    
413 dpavlin 11 my $sth = $dbh->prepare( $sql );
414 dpavlin 69 eval { $sth->execute( @args ) };
415     return if $@;
416 dpavlin 68
417 dpavlin 100 my $nr_results = $sth->rows;
418    
419 dpavlin 11 my $last_row = {
420     date => '',
421     time => '',
422     channel => '',
423     nick => '',
424     };
425    
426     my @rows;
427    
428     while (my $row = $sth->fetchrow_hashref) {
429     unshift @rows, $row;
430     }
431    
432 dpavlin 42 # normalize nick names
433     map {
434     $_->{nick} =~ s/^_*(.*?)_*$/$1/
435     } @rows;
436    
437     return @rows if ($args->{full_rows});
438    
439 dpavlin 100 $msg .= ' produced ' . (
440     $nr_results == 0 ? 'no results' :
441     $nr_results == 0 ? 'one result' :
442     $nr_results . ' results'
443 dpavlin 16 );
444 dpavlin 11
445 dpavlin 100 my @msgs = ( $msg );
446    
447 dpavlin 21 if ($context) {
448     my @ids = @rows;
449     @rows = ();
450    
451     my $last_to = 0;
452    
453     my $sth = $dbh->prepare( $sql_message . qq{ where id >= ? and id < ? } );
454     foreach my $row_id (sort { $a->{id} <=> $b->{id} } @ids) {
455     my $id = $row_id->{id} || die "can't find id in row";
456    
457     my ($from, $to) = ($id - $context, $id + $context);
458     $from = $last_to if ($from < $last_to);
459     $last_to = $to;
460     $sth->execute( $from, $to );
461    
462     #warn "## id: $id from: $from to: $to returned: ", $sth->rows, "\n";
463    
464     while (my $row = $sth->fetchrow_hashref) {
465     push @rows, $row;
466     }
467    
468     }
469     }
470    
471 dpavlin 35 # sprintf which can take coderef as first parametar
472     sub cr_sprintf {
473     my $fmt = shift || return;
474     if (ref($fmt) eq 'CODE') {
475     $fmt->(@_);
476     } else {
477     sprintf($fmt, @_);
478     }
479     }
480    
481 dpavlin 11 foreach my $row (@rows) {
482    
483     $row->{time} =~ s#\.\d+##;
484    
485     my $msg = '';
486    
487 dpavlin 35 $msg = cr_sprintf($args->{fmt}->{date}, $row->{date}) . ' ' if ($last_row->{date} ne $row->{date});
488 dpavlin 26 my $t = $row->{time};
489    
490 dpavlin 13 if ($last_row->{channel} ne $row->{channel}) {
491 dpavlin 35 $msg .= cr_sprintf($args->{fmt}->{time_channel}, $t, $row->{channel});
492 dpavlin 13 } else {
493 dpavlin 35 $msg .= cr_sprintf($args->{fmt}->{time}, $t);
494 dpavlin 13 }
495 dpavlin 11
496 dpavlin 12 my $append = 1;
497 dpavlin 11
498 dpavlin 22 my $nick = $row->{nick};
499 dpavlin 42 # if ($nick =~ s/^_*(.*?)_*$/$1/) {
500     # $row->{nick} = $nick;
501     # }
502 dpavlin 24
503 dpavlin 105 $append = 0 if $row->{me};
504    
505 dpavlin 22 if ($last_row->{nick} ne $nick) {
506 dpavlin 20 # obfu way to find format for me_nick if needed or fallback to default
507     my $fmt = $row->{me} ? ( $args->{fmt}->{me_nick} || $args->{fmt}->{nick} ) : $args->{fmt}->{nick};
508     $fmt ||= '%s';
509    
510     $nick = $args->{filter}->{nick}->($nick) if (ref($args->{filter}->{nick}) eq 'CODE');
511    
512 dpavlin 35 $msg .= cr_sprintf( $fmt, $nick );
513 dpavlin 12 $append = 0;
514     }
515    
516 dpavlin 20 $args->{fmt}->{message} ||= '%s';
517     if (ref($args->{filter}->{message}) eq 'CODE') {
518 dpavlin 35 $msg .= cr_sprintf($args->{fmt}->{message},
519 dpavlin 20 $args->{filter}->{message}->(
520 dpavlin 15 $row->{message}
521     )
522     );
523     } else {
524 dpavlin 35 $msg .= cr_sprintf($args->{fmt}->{message}, $row->{message});
525 dpavlin 15 }
526 dpavlin 11
527 dpavlin 12 if ($append && @msgs) {
528     $msgs[$#msgs] .= " " . $msg;
529     } else {
530     push @msgs, $msg;
531     }
532 dpavlin 11
533     $last_row = $row;
534     }
535    
536     return @msgs;
537     }
538    
539 dpavlin 37 # tags support
540 dpavlin 11
541 dpavlin 37 my $cloud = HTML::TagCloud->new;
542 dpavlin 4
543 dpavlin 37 =head2 add_tag
544 dpavlin 4
545 dpavlin 70 add_tag( id => 42, message => 'irc message', nick => 'foobar' [, me => 1 ] );
546 dpavlin 37
547     =cut
548    
549 dpavlin 70 my @last_tags;
550    
551 dpavlin 37 sub add_tag {
552     my $arg = {@_};
553    
554     return unless ($arg->{id} && $arg->{message});
555    
556     my $m = $arg->{message};
557    
558 dpavlin 70 my @tags;
559    
560 dpavlin 37 while ($m =~ s#$tag_regex##s) {
561     my $tag = $1;
562     next if (! $tag || $tag =~ m/https?:/i);
563     push @{ $tags->{$tag} }, $arg->{id};
564     #warn "+tag $tag: $arg->{id}\n";
565 dpavlin 73 $cloud->add($tag, "$url?tag=$tag", scalar @{$tags->{$tag}} + 1);
566 dpavlin 70 push @tags, $tag;
567    
568 dpavlin 37 }
569 dpavlin 70
570     if ( @tags ) {
571 dpavlin 74 pop @last_tags if $#last_tags == $last_x_tags;
572     unshift @last_tags, { tags => [ @tags ], %$arg };
573 dpavlin 70 }
574    
575 dpavlin 37 }
576    
577     =head2 seed_tags
578    
579     Read all tags from database and create in-memory cache for tags
580    
581     =cut
582    
583     sub seed_tags {
584 dpavlin 74 my $sth = $dbh->prepare(qq{ select id,message,nick,me,time from log where message like '%//%' order by time asc });
585 dpavlin 37 $sth->execute;
586     while (my $row = $sth->fetchrow_hashref) {
587     add_tag( %$row );
588     }
589    
590     foreach my $tag (keys %$tags) {
591 dpavlin 73 $cloud->add($tag, "$url?tag=$tag", scalar @{$tags->{$tag}} + 1);
592 dpavlin 37 }
593     }
594    
595     seed_tags;
596    
597    
598 dpavlin 36 =head2 save_message
599    
600 dpavlin 37 save_message(
601     channel => '#foobar',
602     me => 0,
603     nick => 'dpavlin',
604 dpavlin 70 message => 'test message',
605 dpavlin 37 time => '2006-06-25 18:57:18',
606     );
607 dpavlin 36
608 dpavlin 37 C<time> is optional, it will use C<< now() >> if it's not available.
609    
610     C<me> if not specified will be C<0> (not C</me> message)
611    
612 dpavlin 36 =cut
613    
614     sub save_message {
615 dpavlin 37 my $a = {@_};
616 dpavlin 70 confess "have msg" if $a->{msg};
617 dpavlin 37 $a->{me} ||= 0;
618 dpavlin 38 $a->{time} ||= strftime($TIMESTAMP,localtime());
619 dpavlin 37
620 dpavlin 45 _log
621 dpavlin 37 $a->{channel}, " ",
622     $a->{me} ? "***" . $a->{nick} : "<" . $a->{nick} . ">",
623 dpavlin 70 " " . $a->{message};
624 dpavlin 37
625 dpavlin 87 $sth_insert_log->execute($a->{channel}, $a->{me}, $a->{nick}, $a->{message}, $a->{time});
626 dpavlin 70 add_tag( id => $dbh->last_insert_id(undef,undef,"log",undef), %$a );
627 dpavlin 36 }
628    
629 dpavlin 50
630 dpavlin 37 if ($import_dircproxy) {
631     open(my $l, $import_dircproxy) || die "can't open $import_dircproxy: $!";
632     warn "importing $import_dircproxy...\n";
633 dpavlin 69 my $tz_offset = 1 * 60 * 60; # TZ GMT+2
634 dpavlin 37 while(<$l>) {
635     chomp;
636     if (/^@(\d+)\s(\S+)\s(.+)$/) {
637     my ($time, $nick, $msg) = ($1,$2,$3);
638    
639     my $dt = DateTime->from_epoch( epoch => $time + $tz_offset );
640    
641     my $me = 0;
642     $me = 1 if ($nick =~ m/^\[\S+]/);
643     $nick =~ s/^[\[<]([^!]+).*$/$1/;
644    
645     $msg =~ s/^ACTION\s+// if ($me);
646    
647     save_message(
648     channel => $CHANNEL,
649     me => $me,
650     nick => $nick,
651 dpavlin 70 message => $msg,
652 dpavlin 37 time => $dt->ymd . " " . $dt->hms,
653     ) if ($nick !~ m/^-/);
654    
655     } else {
656 dpavlin 45 _log "can't parse: $_";
657 dpavlin 37 }
658     }
659     close($l);
660     warn "import over\n";
661     exit;
662     }
663    
664 dpavlin 85 #
665     # RSS follow
666     #
667 dpavlin 37
668 dpavlin 108 my $_stat;
669 dpavlin 85
670 dpavlin 122 POE::Component::Client::HTTP->spawn(
671     Alias => 'rss-fetch',
672     Timeout => 30,
673     );
674 dpavlin 85
675 dpavlin 122 sub rss_parse_xml {
676 dpavlin 85 my ($args) = @_;
677    
678 dpavlin 122 warn "## rss_parse_xml ",dump( @_ ) if $debug;
679    
680 dpavlin 85 # how many messages to send out when feed is seen for the first time?
681     my $send_rss_msgs = 1;
682    
683 dpavlin 87 _log "RSS fetch", $args->{url};
684    
685 dpavlin 122 my $feed = XML::Feed->parse( \$args->{xml} );
686 dpavlin 85 if ( ! $feed ) {
687 dpavlin 122 _log "can't fetch RSS ", $args->{url}, XML::Feed->errstr;
688 dpavlin 85 return;
689     }
690 dpavlin 92
691 dpavlin 117 $_stat->{rss}->{url2link}->{ $args->{url} } = $feed->link;
692    
693 dpavlin 87 my ( $total, $updates ) = ( 0, 0 );
694 dpavlin 85 for my $entry ($feed->entries) {
695 dpavlin 87 $total++;
696 dpavlin 85
697 dpavlin 120 my $seen_times = $_stat->{rss}->{seen}->{$args->{channel}}->{$feed->link}->{$entry->id}++;
698 dpavlin 85 # seen allready?
699 dpavlin 122 warn "## $seen_times ",$entry->id if $debug;
700 dpavlin 120 next if $seen_times > 0;
701 dpavlin 85
702     sub prefix {
703     my ($txt,$var) = @_;
704 dpavlin 93 $var =~ s/\s+/ /gs;
705 dpavlin 85 $var =~ s/^\s+//g;
706 dpavlin 93 $var =~ s/\s+$//g;
707 dpavlin 85 return $txt . $var if $var;
708     }
709    
710 dpavlin 94 # fix absolute and relative links to feed entries
711     my $link = $entry->link;
712     if ( $link =~ m!^/! ) {
713     my $host = $args->{url};
714     $host =~ s!^(http://[^/]+).*$!$1!; #!vim
715     $link = "$host/$link";
716     } elsif ( $link !~ m!^http! ) {
717     $link = $args->{url} . $link;
718     }
719    
720 dpavlin 85 my $msg;
721 dpavlin 90 $msg .= prefix( 'From: ' , $args->{name} || $feed->title );
722 dpavlin 85 $msg .= prefix( ' by ' , $entry->author );
723 dpavlin 92 $msg .= prefix( ' | ' , $entry->title );
724 dpavlin 94 $msg .= prefix( ' | ' , $link );
725 dpavlin 85 # $msg .= prefix( ' id ' , $entry->id );
726 dpavlin 114 if ( my $tags = $entry->category ) {
727     $tags =~ s!^\s+!!;
728     $tags =~ s!\s*$! !;
729 dpavlin 118 $tags =~ s!,?\s+!// !g;
730 dpavlin 114 $msg .= prefix( ' ' , $tags );
731     }
732 dpavlin 85
733 dpavlin 120 if ( $seen_times == 0 && $send_rss_msgs ) {
734 dpavlin 85 $send_rss_msgs--;
735 dpavlin 106 if ( ! $args->{private} ) {
736     # FIXME bug! should be save_message
737 dpavlin 119 save_message( channel => $args->{channel}, me => 1, nick => $NICK, message => $msg );
738     # $sth_insert_log->execute( $args->{channel}, 1, $NICK, $msg, 'now()' );
739 dpavlin 106 }
740 dpavlin 97 my ( $type, $to ) = ( 'notice', $args->{channel} );
741     ( $type, $to ) = ( 'privmsg', $args->{nick} ) if $args->{private};
742 dpavlin 119
743 dpavlin 122 _log("RSS generated $type to $to:", $msg);
744 dpavlin 119 # XXX enqueue message to send later
745     sub enqueue_post {
746     my $post = dump( @_ );
747     warn "## queue_post $post\n" if $debug;
748     $dq->enqueue_string( $post );
749     }
750     enqueue_post( $type => $to => $msg );
751    
752 dpavlin 85 $updates++;
753     }
754     }
755    
756     my $sql = qq{ update feeds set last_update = now(), polls = polls + 1 };
757     $sql .= qq{, updates = updates + $updates } if $updates;
758     $sql .= qq{where id = } . $args->{id};
759 dpavlin 86 eval { $dbh->do( $sql ) };
760 dpavlin 85
761 dpavlin 87 _log "RSS got $total items of which $updates new";
762    
763 dpavlin 85 return $updates;
764     }
765    
766     sub rss_fetch_all {
767     my $kernel = shift;
768     my $sql = qq{
769 dpavlin 97 select id, url, name, channel, nick, private
770 dpavlin 85 from feeds
771     where active is true
772     };
773     # limit to newer feeds only if we are not sending messages out
774 dpavlin 122 $sql .= qq{ and last_update + delay < now() } if defined ( $_stat->{rss}->{fetch} );
775 dpavlin 85 my $sth = $dbh->prepare( $sql );
776     $sth->execute();
777     warn "# ",$sth->rows," active RSS feeds\n";
778     my $count = 0;
779     while (my $row = $sth->fetchrow_hashref) {
780 dpavlin 122 warn "## queued rss-fetch for ", $row->{url} if $debug;
781     $_stat->{rss}->{fetch}->{ $row->{url} } = $row;
782     $kernel->post(
783     'rss-fetch',
784     'request',
785     'rss_response',
786     HTTP::Request->new( GET => $row->{url} ),
787     );
788 dpavlin 85 }
789 dpavlin 122 return "OK, scheduled " . $sth->rows . " feeds for refresh";
790 dpavlin 85 }
791    
792    
793     sub rss_check_updates {
794     my $kernel = shift;
795 dpavlin 108 $_stat->{rss}->{last_poll} ||= time();
796     my $dt = time() - $_stat->{rss}->{last_poll};
797 dpavlin 95 if ( $dt > $rss_min_delay ) {
798 dpavlin 119 warn "## rss_check_updates $dt > $rss_min_delay\n";
799 dpavlin 108 $_stat->{rss}->{last_poll} = time();
800 dpavlin 85 _log rss_fetch_all( $kernel );
801     }
802 dpavlin 119 # XXX send queue messages
803     while ( my $job = $dq->pickup_queued_job() ) {
804     my $data = read_file( $job->get_data_path ) || die "can't load ", $job->get_data_path, ": $!";
805     # $kernel->post( $irc => $type => $to, $msg );
806     my @data = eval $data;
807 dpavlin 122 _log "IRC post from queue:", @data;
808 dpavlin 119 $kernel->post( $irc => @data );
809     $job->finish;
810     warn "## done queued job: ",dump( @data ) if $debug;
811     }
812 dpavlin 85 }
813    
814     POE::Session->create( inline_states => {
815     _start => sub {
816 dpavlin 109 $_[KERNEL]->post( $irc => register => 'all' );
817     $_[KERNEL]->post( $irc => connect => {} );
818 dpavlin 4 },
819 dpavlin 109 irc_001 => sub {
820     my ($kernel,$sender) = @_[KERNEL,SENDER];
821     my $poco_object = $sender->get_heap();
822     _log "connected to",$poco_object->server_name();
823     $kernel->post( $sender => join => $_ ) for @channels;
824 dpavlin 122 # seen RSS cache
825     _log rss_fetch_all( $kernel );
826 dpavlin 109 undef;
827     },
828 dpavlin 122 # irc_255 => sub { # server is done blabbing
829     # $_[KERNEL]->post( $irc => join => $CHANNEL);
830     # },
831 dpavlin 4 irc_public => sub {
832 dpavlin 7 my $kernel = $_[KERNEL];
833     my $nick = (split /!/, $_[ARG0])[0];
834     my $channel = $_[ARG1]->[0];
835     my $msg = $_[ARG2];
836 dpavlin 4
837 dpavlin 70 save_message( channel => $channel, me => 0, nick => $nick, message => $msg);
838 dpavlin 50 meta( $nick, $channel, 'last-msg', $msg );
839 dpavlin 95 rss_check_updates( $kernel );
840 dpavlin 4 },
841 dpavlin 19 irc_ctcp_action => sub {
842     my $kernel = $_[KERNEL];
843     my $nick = (split /!/, $_[ARG0])[0];
844     my $channel = $_[ARG1]->[0];
845     my $msg = $_[ARG2];
846    
847 dpavlin 70 save_message( channel => $channel, me => 1, nick => $nick, message => $msg);
848 dpavlin 50
849 dpavlin 54 if ( $use_twitter ) {
850 dpavlin 58 if ( my $twitter = meta( $nick, $NICK, 'twitter' ) ) {
851 dpavlin 54 my ($login,$passwd) = split(/\s+/,$twitter,2);
852     _log("sending twitter for $nick/$login on $channel ");
853     my $bot = Net::Twitter->new( username=>$login, password=>$passwd );
854     $bot->update("<${channel}> $msg");
855     }
856 dpavlin 50 }
857    
858 dpavlin 19 },
859 dpavlin 43 irc_ping => sub {
860 dpavlin 84 _log( "pong ", $_[ARG0] );
861 dpavlin 109 $_stat->{ping}->{ $_[ARG0] }++;
862 dpavlin 85 rss_check_updates( $_[KERNEL] );
863 dpavlin 43 },
864     irc_invite => sub {
865     my $kernel = $_[KERNEL];
866     my $nick = (split /!/, $_[ARG0])[0];
867     my $channel = $_[ARG1];
868    
869 dpavlin 85 _log "invited to $channel by $nick";
870 dpavlin 43
871 dpavlin 109 $_[KERNEL]->post( $irc => privmsg => $nick, "how nice of you to invite me to $channel, I'll be right there..." );
872     $_[KERNEL]->post( $irc => 'join' => $channel );
873 dpavlin 43
874     },
875 dpavlin 7 irc_msg => sub {
876     my $kernel = $_[KERNEL];
877     my $nick = (split /!/, $_[ARG0])[0];
878     my $msg = $_[ARG2];
879 dpavlin 50 my $channel = $_[ARG1]->[0];
880 dpavlin 119 warn "# ARG = ",dump( @_[ARG0,ARG1,ARG2] ) if $debug;
881 dpavlin 7
882 dpavlin 8 my $res = "unknown command '$msg', try /msg $NICK help!";
883 dpavlin 11 my @out;
884 dpavlin 7
885 dpavlin 45 _log "<< $msg";
886 dpavlin 7
887 dpavlin 8 if ($msg =~ m/^help/i) {
888 dpavlin 7
889 dpavlin 11 $res = "usage: /msg $NICK comand | commands: stat - user/message stat | last - show backtrace | grep foobar - find foobar";
890 dpavlin 8
891 dpavlin 111 } elsif ($msg =~ m/^(privmsg|notice)\s+(\S+)\s+(.*)$/i) {
892 dpavlin 10
893 dpavlin 111 _log ">> /$1 $2 $3";
894     $_[KERNEL]->post( $irc => $1 => $2, $3 );
895 dpavlin 10 $res = '';
896    
897 dpavlin 8 } elsif ($msg =~ m/^stat.*?\s*(\d*)/i) {
898    
899 dpavlin 7 my $nr = $1 || 10;
900    
901     my $sth = $dbh->prepare(qq{
902 dpavlin 40 select
903 dpavlin 57 trim(both '_' from nick) as nick,
904 dpavlin 40 count(*) as count,
905     sum(length(message)) as len
906     from log
907 dpavlin 57 group by trim(both '_' from nick)
908 dpavlin 40 order by len desc,count desc
909     limit $nr
910 dpavlin 7 });
911     $sth->execute();
912     $res = "Top $nr users: ";
913 dpavlin 8 my @users;
914 dpavlin 7 while (my $row = $sth->fetchrow_hashref) {
915 dpavlin 40 push @users,$row->{nick} . ': ' . $row->{count} . '/' . $row->{len} . '=' . sprintf("%.2f", $row->{len}/$row->{count});
916 dpavlin 7 }
917 dpavlin 8 $res .= join(" | ", @users);
918     } elsif ($msg =~ m/^last.*?\s*(\d*)/i) {
919    
920 dpavlin 50 my $limit = $1 || meta( $nick, $channel, 'last-size' ) || 10;
921    
922     foreach my $res (get_from_log( limit => $limit )) {
923 dpavlin 45 _log "last: $res";
924 dpavlin 109 $_[KERNEL]->post( $irc => privmsg => $nick, $res );
925 dpavlin 8 }
926    
927 dpavlin 11 $res = '';
928 dpavlin 8
929 dpavlin 21 } elsif ($msg =~ m/^(search|grep)\s+(.*)\s*$/i) {
930 dpavlin 8
931 dpavlin 11 my $what = $2;
932 dpavlin 8
933 dpavlin 21 foreach my $res (get_from_log(
934     limit => 20,
935     search => $what,
936     )) {
937 dpavlin 45 _log "search [$what]: $res";
938 dpavlin 109 $_[KERNEL]->post( $irc => privmsg => $nick, $res );
939 dpavlin 8 }
940    
941     $res = '';
942 dpavlin 11
943 dpavlin 42 } elsif ($msg =~ m/^(?:count|poll)\s+(.*)(?:\s+(\d+))?\s*$/i) {
944    
945     my ($what,$limit) = ($1,$2);
946     $limit ||= 100;
947    
948     my $stat;
949    
950     foreach my $res (get_from_log(
951     limit => $limit,
952     search => $what,
953     full_rows => 1,
954     )) {
955     while ($res->{message} =~ s/\Q$what\E(\+|\-)//) {
956     $stat->{vote}->{$1}++;
957     $stat->{from}->{ $res->{nick} }++;
958     }
959     }
960    
961     my @nicks;
962 dpavlin 43 foreach my $nick (sort { $stat->{from}->{$a} <=> $stat->{from}->{$b} } keys %{ $stat->{from} }) {
963     push @nicks, $nick . ( $stat->{from}->{$nick} == 1 ? '' :
964     "(" . $stat->{from}->{$nick} . ")"
965     );
966 dpavlin 42 }
967    
968     $res =
969 dpavlin 43 "$what ++ " . ( $stat->{vote}->{'+'} || 0 ) .
970     " : " . ( $stat->{vote}->{'-'} || 0 ) . " --" .
971 dpavlin 42 " from " . ( join(", ", @nicks) || 'nobody' );
972    
973 dpavlin 109 $_[KERNEL]->post( $irc => notice => $nick, $res );
974 dpavlin 43
975     } elsif ($msg =~ m/^ping/) {
976 dpavlin 109 $res = "ping = " . dump( $_stat->{ping} );
977 dpavlin 51 } elsif ($msg =~ m/^conf(?:ig)*\s*(last-size|twitter)*\s*(.*)/) {
978 dpavlin 50 if ( ! defined( $1 ) ) {
979     my $sth = $dbh->prepare(qq{ select name,value,changed from meta where nick = ? and channel = ? });
980     $sth->execute( $nick, $channel );
981 dpavlin 52 $res = "config for $nick on $channel";
982 dpavlin 50 while ( my ($n,$v) = $sth->fetchrow_array ) {
983 dpavlin 52 $res .= " | $n = $v";
984 dpavlin 50 }
985 dpavlin 51 } elsif ( ! $2 ) {
986 dpavlin 50 my $val = meta( $nick, $channel, $1 );
987     $res = "current $1 = " . ( $val ? $val : 'undefined' );
988 dpavlin 51 } else {
989     my $validate = {
990     'last-size' => qr/^\d+/,
991     'twitter' => qr/^\w+\s+\w+/,
992     };
993    
994     my ( $op, $val ) = ( $1, $2 );
995    
996     if ( my $regex = $validate->{$op} ) {
997     if ( $val =~ $regex ) {
998     meta( $nick, $channel, $op, $val );
999     $res = "saved $op = $val";
1000     } else {
1001     $res = "config option $op = $val doesn't validate against $regex";
1002     }
1003     } else {
1004     $res = "config option $op doesn't exist";
1005     }
1006 dpavlin 50 }
1007 dpavlin 85 } elsif ($msg =~ m/^rss-update/) {
1008     $res = rss_fetch_all( $_[KERNEL] );
1009 dpavlin 91 } elsif ($msg =~ m/^rss-list/) {
1010 dpavlin 97 my $sth = $dbh->prepare(qq{ select url,name,last_update,active,channel,nick,private from feeds });
1011 dpavlin 91 $sth->execute;
1012     while (my @row = $sth->fetchrow_array) {
1013 dpavlin 109 $_[KERNEL]->post( $irc => privmsg => $nick, join(' | ',@row) );
1014 dpavlin 91 }
1015     $res = '';
1016 dpavlin 117 } elsif ($msg =~ m!^rss-(add|remove|stop|start|clean)(?:-(private))?\s+(http://\S+)\s*(.*)!) {
1017 dpavlin 97 my ( $command, $sub, $url, $arg ) = ( $1,$2,$3,$4 );
1018    
1019     my $channel = $1 if ( $arg =~ s/\s*(#\S+)\s*// );
1020     $channel = $nick if $sub eq 'private';
1021    
1022 dpavlin 85 my $sql = {
1023 dpavlin 103 add => qq{ insert into feeds (url,name,channel,nick,private) values (?,?,?,?,?) },
1024 dpavlin 85 # remove => qq{ delete from feeds where url = ? and name = ? },
1025 dpavlin 91 start => qq{ update feeds set active = true where url = ? },
1026     stop => qq{ update feeds set active = false where url = ? },
1027 dpavlin 117 clean => qq{ update feeds set last_update = now() - delay where url = ? },
1028 dpavlin 85 };
1029 dpavlin 97
1030 dpavlin 99 if ( $command eq 'add' && ! $channel ) {
1031     $res = "ERROR: got '$msg' which doesn't have #channel in it, ignoring!";
1032     } elsif (my $q = $sql->{$command} ) {
1033 dpavlin 85 my $sth = $dbh->prepare( $q );
1034 dpavlin 97 my @data = ( $url );
1035     if ( $command eq 'add' ) {
1036     push @data, ( $arg, $channel, $nick, $sub eq 'private' ? 1 : 0 );
1037     }
1038     warn "## $command SQL $q with ",dump( @data ),"\n";
1039 dpavlin 91 eval { $sth->execute( @data ) };
1040 dpavlin 97 if ($@) {
1041     $res = "ERROR: $@";
1042     } else {
1043 dpavlin 117 $res = "OK, RSS executed $command " . ( $sub ? "-$sub" : '' ) ."on $channel url $url";
1044     if ( $command eq 'clean' ) {
1045     my $seen = $_stat->{rss}->{seen} || die "no seen?";
1046     my $want_link = $_stat->{rss}->{url2link}->{$url} || warn "no url2link($url)";
1047     foreach my $c ( keys %$seen ) {
1048     my $c_hash = $seen->{$c} || die "no seen->{$c}";
1049     die "not HASH with rss links but ", dump($c_hash) unless ref($c_hash) eq 'HASH';
1050     foreach my $link ( keys %$c_hash ) {
1051     next unless $link eq $want_link;
1052     _log "RSS removed seen $c $url $link";
1053     }
1054     }
1055     }
1056 dpavlin 97 }
1057     } else {
1058     $res = "ERROR: don't know what to do with: $msg";
1059 dpavlin 85 }
1060 dpavlin 117 } elsif ($msg =~ m/^rss-clean/) {
1061     # this makes sense because we didn't catch rss-clean http://... before!
1062     $_stat->{rss} = undef;
1063     $dbh->do( qq{ update feeds set last_update = now() - delay } );
1064     $res = "OK, cleaned RSS cache";
1065 dpavlin 7 }
1066    
1067 dpavlin 8 if ($res) {
1068 dpavlin 45 _log ">> [$nick] $res";
1069 dpavlin 109 $_[KERNEL]->post( $irc => privmsg => $nick, $res );
1070 dpavlin 8 }
1071 dpavlin 7
1072 dpavlin 85 rss_check_updates( $_[KERNEL] );
1073 dpavlin 7 },
1074 dpavlin 107 irc_372 => sub {
1075     _log "<< motd",$_[ARG0],$_[ARG1];
1076     },
1077     irc_375 => sub {
1078     _log "<< motd", $_[ARG0], "start";
1079     },
1080     irc_376 => sub {
1081     _log "<< motd", $_[ARG0], "end";
1082     },
1083 dpavlin 114 # irc_433 => sub {
1084     # print "# irc_433: ",$_[ARG1], "\n";
1085     # warn "## indetify $NICK\n";
1086     # $_[KERNEL]->post( $irc => privmsg => 'nickserv', "IDENTIFY $NICK" );
1087     # },
1088     # irc_451 # please register
1089 dpavlin 10 irc_477 => sub {
1090 dpavlin 103 _log "<< irc_477: ",$_[ARG1];
1091 dpavlin 114 _log ">> IDENTIFY $NICK";
1092     $_[KERNEL]->post( $irc => privmsg => 'NickServ', "IDENTIFY $NICK" );
1093 dpavlin 10 },
1094 dpavlin 7 irc_505 => sub {
1095 dpavlin 103 _log "<< irc_505: ",$_[ARG1];
1096 dpavlin 114 _log ">> register $NICK";
1097     $_[KERNEL]->post( $irc => privmsg => 'NickServ', "register $NICK" );
1098     # $_[KERNEL]->post( $irc => privmsg => 'nickserv', "IDENTIFY $NICK" );
1099 dpavlin 109 # $_[KERNEL]->post( $irc => privmsg => 'nickserv', "set hide email on" );
1100     # $_[KERNEL]->post( $irc => privmsg => 'nickserv', "set email dpavlin\@rot13.org" );
1101 dpavlin 8 },
1102     irc_registered => sub {
1103 dpavlin 109 _log "<< registered $NICK";
1104 dpavlin 10 },
1105 dpavlin 41 irc_disconnected => sub {
1106 dpavlin 103 _log "## disconnected.. sleeping for $sleep_on_error seconds and reconnecting again";
1107     sleep($sleep_on_error);
1108 dpavlin 109 $_[KERNEL]->post( $irc => connect => {} );
1109 dpavlin 41 },
1110     irc_socketerr => sub {
1111 dpavlin 45 _log "## socket error... sleeping for $sleep_on_error seconds and retry";
1112 dpavlin 41 sleep($sleep_on_error);
1113 dpavlin 109 $_[KERNEL]->post( $irc => connect => {} );
1114 dpavlin 41 },
1115 dpavlin 109 irc_notice => sub {
1116 dpavlin 116 _log "<< notice from ", $_[ARG0], $_[ARG1], $_[ARG2];
1117     my $m = $_[ARG2];
1118     if ( $m =~ m!/msg.*(NickServ).*(IDENTIFY)!i ) {
1119     _log ">> suggested to $1 $2";
1120     $_[KERNEL]->post( $irc => privmsg => $1, "$2 $NICK" );
1121     } elsif ( $m =~ m!\Q$NICK\E.*registered!i ) {
1122     _log ">> registreted, so IDENTIFY";
1123 dpavlin 109 $_[KERNEL]->post( $irc => privmsg => 'nickserv', "IDENTIFY $NICK" );
1124 dpavlin 116 } else {
1125 dpavlin 119 warn "## ignore $m\n" if $debug;
1126 dpavlin 109 }
1127     },
1128 dpavlin 103 irc_snotice => sub {
1129 dpavlin 116 _log "<< snotice", $_[ARG0]; #dump( $_[ARG0],$_[ARG1], $_[ARG2] );
1130 dpavlin 103 if ( $_[ARG0] =~ m!/(QUOTE)\s+(PASS\s+\d+)!i ) {
1131     warn ">> $1 | $2\n";
1132 dpavlin 109 $_[KERNEL]->post( $irc => lc($1) => $2);
1133 dpavlin 103 }
1134     },
1135 dpavlin 4 _child => sub {},
1136     _default => sub {
1137 dpavlin 45 _log sprintf "sID:%s %s %s",
1138     $_[SESSION]->ID, $_[ARG0],
1139 dpavlin 34 ref($_[ARG1]) eq "ARRAY" ? join(",", map { ref($_) eq "ARRAY" ? join(";", @{$_}) : $_ } @{ $_[ARG1] }) :
1140     $_[ARG1] ? $_[ARG1] :
1141     "";
1142 dpavlin 4 0; # false for signals
1143     },
1144 dpavlin 122 rss_response => sub {
1145     my ($request_packet, $response_packet) = @_[ARG0, ARG1];
1146     my $request_object = $request_packet->[0];
1147     my $response_object = $response_packet->[0];
1148    
1149     my $row = delete( $_stat->{rss}->{fetch}->{ $request_object->uri } );
1150     if ( $row ) {
1151     $row->{xml} = $response_object->content;
1152     rss_parse_xml( $row );
1153     } else {
1154     warn "## can't find rss->fetch for ", $request_object->uri;
1155     }
1156     },
1157 dpavlin 4 },
1158     );
1159    
1160 dpavlin 13 # http server
1161    
1162 dpavlin 114 _log "WEB archive at $url";
1163    
1164 dpavlin 13 my $httpd = POE::Component::Server::HTTP->new(
1165 dpavlin 70 Port => $http_port,
1166 dpavlin 83 PreHandler => {
1167     '/' => sub {
1168     $_[0]->header(Connection => 'close')
1169     }
1170     },
1171 dpavlin 13 ContentHandler => { '/' => \&root_handler },
1172     Headers => { Server => 'irc-logger' },
1173     );
1174    
1175     my $style = <<'_END_OF_STYLE_';
1176 dpavlin 16 p { margin: 0; padding: 0.1em; }
1177 dpavlin 13 .time, .channel { color: #808080; font-size: 60%; }
1178 dpavlin 28 .date { float: right; background: #e0e0e0; color: #404040; font-size: 120%; padding: 0.25em; border: 1px dashed #808080; }
1179 dpavlin 20 .nick { color: #000000; font-size: 80%; padding: 2px; font-family: courier, courier new, monospace ; }
1180 dpavlin 13 .message { color: #000000; font-size: 100%; }
1181 dpavlin 16 .search { float: right; }
1182 dpavlin 60 a:link.tag, a:visited.tag { border: 1px dashed #ccc; backgound: #ccc; text-decoration: none }
1183     a:hover.tag { border: 1px solid #eee }
1184     hr { border: 1px dashed #ccc; height: 1px; clear: both; }
1185     /*
1186 dpavlin 20 .col-0 { background: #ffff66 }
1187     .col-1 { background: #a0ffff }
1188     .col-2 { background: #99ff99 }
1189     .col-3 { background: #ff9999 }
1190     .col-4 { background: #ff66ff }
1191 dpavlin 60 */
1192 dpavlin 65 .calendar { border: 1px solid red; width: 100%; }
1193     .month { border: 0px; width: 100%; }
1194 dpavlin 13 _END_OF_STYLE_
1195    
1196 dpavlin 70 $max_color = 0;
1197 dpavlin 20
1198 dpavlin 60 my @cols = qw(
1199     #ffcccc #ccffe6 #ccccff #e6ccff #ffccff #ffcce6 #ff9999 #ffcc99 #ffff99
1200     #ccff99 #99ff99 #99ffcc #99ccff #9999ff #cc99ff #ff6666 #ffb366 #ffff66
1201     #66ff66 #66ffb3 #66b3ff #6666ff #ff3333 #33ff33 #3399ff #3333ff #ff3399
1202     #a0a0a0 #ff0000 #ffff00 #80ff00 #0000ff #8000ff #ff00ff #ff0080 #994d00
1203     #999900 #009900 #cc0066 #c0c0c0 #ccff99 #99ff33 #808080 #660033 #ffffff
1204     );
1205    
1206     foreach my $c (@cols) {
1207     $style .= ".col-${max_color} { background: $c }\n";
1208     $max_color++;
1209     }
1210 dpavlin 114 _log "WEB defined $max_color colors for users...";
1211 dpavlin 60
1212 dpavlin 13 sub root_handler {
1213     my ($request, $response) = @_;
1214     $response->code(RC_OK);
1215 dpavlin 16
1216 dpavlin 83 # this doesn't seem to work, so moved to PreHandler
1217     #$response->header(Connection => 'close');
1218    
1219 dpavlin 73 return RC_OK if $request->uri =~ m/favicon.ico$/;
1220    
1221 dpavlin 16 my $q;
1222    
1223     if ( $request->method eq 'POST' ) {
1224     $q = new CGI::Simple( $request->content );
1225     } elsif ( $request->uri =~ /\?(.+)$/ ) {
1226     $q = new CGI::Simple( $1 );
1227     } else {
1228     $q = new CGI::Simple;
1229     }
1230    
1231     my $search = $q->param('search') || $q->param('grep') || '';
1232 dpavlin 108 my $r_url = $request->url;
1233 dpavlin 16
1234 dpavlin 108 my @commands = qw( tags last-tag follow stat );
1235     my $commands_re = join('|',@commands);
1236    
1237     if ($r_url =~ m#/rss(?:/($commands_re.*)\w*(?:=(\d+))?)?#i) {
1238 dpavlin 77 my $show = lc($1);
1239 dpavlin 79 my $nr = $2;
1240 dpavlin 77
1241 dpavlin 71 my $type = 'RSS'; # Atom
1242 dpavlin 70
1243 dpavlin 72 $response->content_type( 'application/' . lc($type) . '+xml' );
1244 dpavlin 70
1245     my $html = '<!-- error -->';
1246 dpavlin 74 #warn "create $type feed from ",dump( @last_tags );
1247 dpavlin 70
1248     my $feed = XML::Feed->new( $type );
1249 dpavlin 85 $feed->link( $url );
1250 dpavlin 70
1251 dpavlin 108 my $rc = RC_OK;
1252    
1253 dpavlin 77 if ( $show eq 'tags' ) {
1254 dpavlin 79 $nr ||= 50;
1255 dpavlin 77 $feed->title( "tags from $CHANNEL" );
1256     $feed->link( "$url/tags" );
1257     $feed->description( "tag cloud created from messages on channel $CHANNEL which have tags// in them" );
1258 dpavlin 70 my $feed_entry = XML::Feed::Entry->new($type);
1259 dpavlin 79 $feed_entry->title( "$nr tags from $CHANNEL" );
1260 dpavlin 77 $feed_entry->author( $NICK );
1261     $feed_entry->link( '/#tags' );
1262 dpavlin 75
1263 dpavlin 73 $feed_entry->content(
1264 dpavlin 77 qq{<![CDATA[<style type="text/css">}
1265     . $cloud->css
1266     . qq{</style>}
1267 dpavlin 79 . $cloud->html( $nr )
1268 dpavlin 77 . qq{]]>}
1269 dpavlin 70 );
1270     $feed->add_entry( $feed_entry );
1271 dpavlin 77
1272 dpavlin 79 } elsif ( $show eq 'last-tag' ) {
1273 dpavlin 77
1274 dpavlin 79 $nr ||= $last_x_tags;
1275 dpavlin 80 $nr = $last_x_tags if $nr > $last_x_tags;
1276 dpavlin 79
1277     $feed->title( "last $nr tagged messages from $CHANNEL" );
1278 dpavlin 77 $feed->description( "collects messages which have tags// in them" );
1279    
1280     foreach my $m ( @last_tags ) {
1281     # warn dump( $m );
1282     #my $tags = join(' ', @{$m->{tags}} );
1283     my $feed_entry = XML::Feed::Entry->new($type);
1284     $feed_entry->title( $m->{nick} . '@' . $m->{time} );
1285     $feed_entry->author( $m->{nick} );
1286     $feed_entry->link( '/#' . $m->{id} );
1287     $feed_entry->issued( DateTime::Format::Flexible->build( $m->{time} ) );
1288    
1289     my $message = $filter->{message}->( $m->{message} );
1290     $message .= "<br/>\n" unless $message =~ m!<(/p|br/?)>!;
1291 dpavlin 79 # warn "## message = $message\n";
1292 dpavlin 77
1293     #$feed_entry->summary(
1294     $feed_entry->content(
1295     "<![CDATA[$message]]>"
1296     );
1297     $feed_entry->category( join(', ', @{$m->{tags}}) );
1298     $feed->add_entry( $feed_entry );
1299 dpavlin 79
1300     $nr--;
1301     last if $nr <= 0;
1302    
1303 dpavlin 77 }
1304 dpavlin 79
1305 dpavlin 85 } elsif ( $show =~ m/^follow/ ) {
1306    
1307     $feed->title( "Feeds which this bot follows" );
1308    
1309     my $sth = $dbh->prepare( qq{ select * from feeds order by last_update desc } );
1310     $sth->execute;
1311     while (my $row = $sth->fetchrow_hashref) {
1312     my $feed_entry = XML::Feed::Entry->new($type);
1313     $feed_entry->title( $row->{name} );
1314     $feed_entry->link( $row->{url} );
1315     $feed_entry->issued( DateTime::Format::Flexible->build( $row->{last_update} ) );
1316     $feed_entry->content(
1317     '<![CDATA[<pre>' . dump( $row ) . '</pre>]]>'
1318     );
1319     $feed->add_entry( $feed_entry );
1320     }
1321    
1322 dpavlin 108 } elsif ( $show =~ m/^stat/ ) {
1323    
1324 dpavlin 97 my $feed_entry = XML::Feed::Entry->new($type);
1325     $feed_entry->title( "Internal stats" );
1326     $feed_entry->content(
1327 dpavlin 108 '<![CDATA[<pre>' . dump( $_stat ) . '</pre>]]>'
1328 dpavlin 97 );
1329     $feed->add_entry( $feed_entry );
1330    
1331 dpavlin 79 } else {
1332 dpavlin 114 _log "WEB unknown rss request $r_url";
1333 dpavlin 108 $feed->title( "unknown $r_url" );
1334     foreach my $c ( @commands ) {
1335     my $feed_entry = XML::Feed::Entry->new($type);
1336     $feed_entry->title( "rss/$c" );
1337     $feed_entry->link( "$url/rss/$c" );
1338     $feed->add_entry( $feed_entry );
1339     }
1340     $rc = RC_DENY;
1341 dpavlin 70 }
1342    
1343     $response->content( $feed->as_xml );
1344 dpavlin 108 return $rc;
1345 dpavlin 70 }
1346    
1347     if ( $@ ) {
1348     warn "$@";
1349     }
1350    
1351 dpavlin 86 $response->content_type("text/html; charset=UTF-8");
1352 dpavlin 70
1353 dpavlin 35 my $html =
1354 dpavlin 77 qq{<html><head><title>$NICK</title><style type="text/css">$style}
1355     . $cloud->css
1356     . qq{</style></head><body>}
1357     . qq{
1358 dpavlin 32 <form method="post" class="search" action="/">
1359 dpavlin 16 <input type="text" name="search" value="$search" size="10">
1360     <input type="submit" value="search">
1361     </form>
1362 dpavlin 77 }
1363     . $cloud->html(500)
1364     . qq{<p>};
1365 dpavlin 76
1366     if ($request->url =~ m#/tags?#) {
1367     # nop
1368     } elsif ($request->url =~ m#/history#) {
1369 dpavlin 35 my $sth = $dbh->prepare(qq{
1370 dpavlin 65 select date(time) as date,count(*) as nr,sum(length(message)) as len
1371 dpavlin 35 from log
1372     group by date(time)
1373     order by date(time) desc
1374     });
1375     $sth->execute();
1376     my ($l_yyyy,$l_mm) = (0,0);
1377 dpavlin 65 $html .= qq{<table class="calendar"><tr>};
1378 dpavlin 35 my $cal;
1379 dpavlin 65 my $ord = 0;
1380 dpavlin 35 while (my $row = $sth->fetchrow_hashref) {
1381     # this is probably PostgreSQL specific, expects ISO date
1382     my ($yyyy,$mm,$dd) = split(/-/, $row->{date});
1383     if ($yyyy != $l_yyyy || $mm != $l_mm) {
1384 dpavlin 65 if ( $cal ) {
1385     $html .= qq{<td valign="top">} . $cal->as_HTML() . qq{</td>};
1386     $ord++;
1387     $html .= qq{</tr><tr>} if ( $ord % 3 == 0 );
1388     }
1389 dpavlin 35 $cal = new HTML::CalendarMonthSimple('month'=>$mm,'year'=>$yyyy);
1390 dpavlin 65 $cal->border(1);
1391     $cal->width('30%');
1392     $cal->cellheight('5em');
1393     $cal->tableclass('month');
1394     #$cal->cellclass('day');
1395     $cal->sunday('SUN');
1396     $cal->saturday('SAT');
1397     $cal->weekdays('MON','TUE','WED','THU','FRI');
1398 dpavlin 35 ($l_yyyy,$l_mm) = ($yyyy,$mm);
1399     }
1400 dpavlin 79 $cal->setcontent($dd, qq[
1401 dpavlin 73 <a href="$url?date=$row->{date}">$row->{nr}</a><br/>$row->{len}
1402 dpavlin 89 ]) if $cal;
1403 dpavlin 65
1404 dpavlin 35 }
1405 dpavlin 65 $html .= qq{<td valign="top">} . $cal->as_HTML() . qq{</td></tr></table>};
1406 dpavlin 35
1407     } else {
1408     $html .= join("</p><p>",
1409 dpavlin 13 get_from_log(
1410 dpavlin 68 limit => ( $q->param('last') || $q->param('date') ) ? undef : 100,
1411 dpavlin 28 search => $search || undef,
1412 dpavlin 29 tag => $q->param('tag') || undef,
1413 dpavlin 68 date => $q->param('date') || undef,
1414 dpavlin 13 fmt => {
1415 dpavlin 35 date => sub {
1416     my $date = shift || return;
1417 dpavlin 73 qq{<hr/><div class="date"><a href="$url?date=$date">$date</a></div>};
1418 dpavlin 35 },
1419 dpavlin 13 time => '<span class="time">%s</span> ',
1420     time_channel => '<span class="channel">%s %s</span> ',
1421 dpavlin 20 nick => '%s:&nbsp;',
1422     me_nick => '***%s&nbsp;',
1423 dpavlin 13 message => '<span class="message">%s</span>',
1424     },
1425 dpavlin 70 filter => $filter,
1426 dpavlin 13 )
1427 dpavlin 35 );
1428     }
1429    
1430     $html .= qq{</p>
1431     <hr/>
1432     <p>See <a href="/history">history</a> of all messages.</p>
1433     </body></html>};
1434    
1435     $response->content( $html );
1436 dpavlin 74 warn "<< ", $request->method, " ", $request->uri, " created ", length($html), " bytes\n";
1437 dpavlin 13 return RC_OK;
1438     }
1439    
1440 dpavlin 4 POE::Kernel->run;

Properties

Name Value
svn:executable *

  ViewVC Help
Powered by ViewVC 1.1.26