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

Properties

Name Value
svn:executable *

  ViewVC Help
Powered by ViewVC 1.1.26