/[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 119 - (hide annotations)
Fri Mar 14 00:17:49 2008 UTC (16 years, 1 month ago) by dpavlin
File MIME type: text/plain
File size: 35822 byte(s)
Added experimental implementation of on-disk message queue
Idea is to fork processes and leave messages for delibery
in disk queue.
Sprinkle debug messages.
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 dpavlin 119
674 dpavlin 85 # how many messages to send out when feed is seen for the first time?
675     my $send_rss_msgs = 1;
676    
677 dpavlin 87 _log "RSS fetch", $args->{url};
678    
679 dpavlin 85 my $feed = XML::Feed->parse(URI->new( $args->{url} ));
680     if ( ! $feed ) {
681     _log("can't fetch RSS ", $args->{url});
682     return;
683     }
684 dpavlin 92
685 dpavlin 117 $_stat->{rss}->{url2link}->{ $args->{url} } = $feed->link;
686    
687 dpavlin 87 my ( $total, $updates ) = ( 0, 0 );
688 dpavlin 85 for my $entry ($feed->entries) {
689 dpavlin 87 $total++;
690 dpavlin 85
691     # seen allready?
692 dpavlin 108 next if $_stat->{rss}->{seen}->{$args->{channel}}->{$feed->link}->{$entry->id}++ > 0;
693 dpavlin 85
694     sub prefix {
695     my ($txt,$var) = @_;
696 dpavlin 93 $var =~ s/\s+/ /gs;
697 dpavlin 85 $var =~ s/^\s+//g;
698 dpavlin 93 $var =~ s/\s+$//g;
699 dpavlin 85 return $txt . $var if $var;
700     }
701    
702 dpavlin 94 # fix absolute and relative links to feed entries
703     my $link = $entry->link;
704     if ( $link =~ m!^/! ) {
705     my $host = $args->{url};
706     $host =~ s!^(http://[^/]+).*$!$1!; #!vim
707     $link = "$host/$link";
708     } elsif ( $link !~ m!^http! ) {
709     $link = $args->{url} . $link;
710     }
711    
712 dpavlin 85 my $msg;
713 dpavlin 90 $msg .= prefix( 'From: ' , $args->{name} || $feed->title );
714 dpavlin 85 $msg .= prefix( ' by ' , $entry->author );
715 dpavlin 92 $msg .= prefix( ' | ' , $entry->title );
716 dpavlin 94 $msg .= prefix( ' | ' , $link );
717 dpavlin 85 # $msg .= prefix( ' id ' , $entry->id );
718 dpavlin 114 if ( my $tags = $entry->category ) {
719     $tags =~ s!^\s+!!;
720     $tags =~ s!\s*$! !;
721 dpavlin 118 $tags =~ s!,?\s+!// !g;
722 dpavlin 114 $msg .= prefix( ' ' , $tags );
723     }
724 dpavlin 85
725     if ( $args->{kernel} && $send_rss_msgs ) {
726     $send_rss_msgs--;
727 dpavlin 106 if ( ! $args->{private} ) {
728     # FIXME bug! should be save_message
729 dpavlin 119 save_message( channel => $args->{channel}, me => 1, nick => $NICK, message => $msg );
730     # $sth_insert_log->execute( $args->{channel}, 1, $NICK, $msg, 'now()' );
731 dpavlin 106 }
732 dpavlin 97 my ( $type, $to ) = ( 'notice', $args->{channel} );
733     ( $type, $to ) = ( 'privmsg', $args->{nick} ) if $args->{private};
734 dpavlin 119
735 dpavlin 112 _log(">> $type $to", $msg);
736 dpavlin 119 # $args->{kernel}->post( $irc => $type => $to, $msg );
737     # XXX enqueue message to send later
738     sub enqueue_post {
739     my $post = dump( @_ );
740     warn "## queue_post $post\n" if $debug;
741     $dq->enqueue_string( $post );
742     }
743     enqueue_post( $type => $to => $msg );
744    
745 dpavlin 85 $updates++;
746     }
747     }
748    
749     my $sql = qq{ update feeds set last_update = now(), polls = polls + 1 };
750     $sql .= qq{, updates = updates + $updates } if $updates;
751     $sql .= qq{where id = } . $args->{id};
752 dpavlin 86 eval { $dbh->do( $sql ) };
753 dpavlin 85
754 dpavlin 87 _log "RSS got $total items of which $updates new";
755    
756 dpavlin 85 return $updates;
757     }
758    
759     sub rss_fetch_all {
760     my $kernel = shift;
761     my $sql = qq{
762 dpavlin 97 select id, url, name, channel, nick, private
763 dpavlin 85 from feeds
764     where active is true
765     };
766     # limit to newer feeds only if we are not sending messages out
767     $sql .= qq{ and last_update + delay < now() } if $kernel;
768     my $sth = $dbh->prepare( $sql );
769     $sth->execute();
770     warn "# ",$sth->rows," active RSS feeds\n";
771     my $count = 0;
772     while (my $row = $sth->fetchrow_hashref) {
773     $row->{kernel} = $kernel if $kernel;
774     $count += rss_fetch( $row );
775     }
776     return "OK, fetched $count posts from " . $sth->rows . " feeds";
777     }
778    
779    
780     sub rss_check_updates {
781     my $kernel = shift;
782 dpavlin 108 $_stat->{rss}->{last_poll} ||= time();
783     my $dt = time() - $_stat->{rss}->{last_poll};
784 dpavlin 95 if ( $dt > $rss_min_delay ) {
785 dpavlin 119 warn "## rss_check_updates $dt > $rss_min_delay\n";
786 dpavlin 108 $_stat->{rss}->{last_poll} = time();
787 dpavlin 85 _log rss_fetch_all( $kernel );
788     }
789 dpavlin 119 # XXX send queue messages
790     while ( my $job = $dq->pickup_queued_job() ) {
791     my $data = read_file( $job->get_data_path ) || die "can't load ", $job->get_data_path, ": $!";
792     # $kernel->post( $irc => $type => $to, $msg );
793     my @data = eval $data;
794     _log ">> post from queue ", $irc, @data;
795     $kernel->post( $irc => @data );
796     $job->finish;
797     warn "## done queued job: ",dump( @data ) if $debug;
798     }
799 dpavlin 85 }
800    
801     # seed rss seen cache so we won't send out all items on startup
802 dpavlin 119 _log rss_fetch_all if ! $debug;
803 dpavlin 85
804     POE::Session->create( inline_states => {
805     _start => sub {
806 dpavlin 109 $_[KERNEL]->post( $irc => register => 'all' );
807     $_[KERNEL]->post( $irc => connect => {} );
808 dpavlin 4 },
809 dpavlin 109 irc_001 => sub {
810     my ($kernel,$sender) = @_[KERNEL,SENDER];
811     my $poco_object = $sender->get_heap();
812     _log "connected to",$poco_object->server_name();
813     $kernel->post( $sender => join => $_ ) for @channels;
814     undef;
815     },
816 dpavlin 9 irc_255 => sub { # server is done blabbing
817 dpavlin 109 $_[KERNEL]->post( $irc => join => $CHANNEL);
818 dpavlin 4 },
819     irc_public => sub {
820 dpavlin 7 my $kernel = $_[KERNEL];
821     my $nick = (split /!/, $_[ARG0])[0];
822     my $channel = $_[ARG1]->[0];
823     my $msg = $_[ARG2];
824 dpavlin 4
825 dpavlin 70 save_message( channel => $channel, me => 0, nick => $nick, message => $msg);
826 dpavlin 50 meta( $nick, $channel, 'last-msg', $msg );
827 dpavlin 95 rss_check_updates( $kernel );
828 dpavlin 4 },
829 dpavlin 19 irc_ctcp_action => sub {
830     my $kernel = $_[KERNEL];
831     my $nick = (split /!/, $_[ARG0])[0];
832     my $channel = $_[ARG1]->[0];
833     my $msg = $_[ARG2];
834    
835 dpavlin 70 save_message( channel => $channel, me => 1, nick => $nick, message => $msg);
836 dpavlin 50
837 dpavlin 54 if ( $use_twitter ) {
838 dpavlin 58 if ( my $twitter = meta( $nick, $NICK, 'twitter' ) ) {
839 dpavlin 54 my ($login,$passwd) = split(/\s+/,$twitter,2);
840     _log("sending twitter for $nick/$login on $channel ");
841     my $bot = Net::Twitter->new( username=>$login, password=>$passwd );
842     $bot->update("<${channel}> $msg");
843     }
844 dpavlin 50 }
845    
846 dpavlin 19 },
847 dpavlin 43 irc_ping => sub {
848 dpavlin 84 _log( "pong ", $_[ARG0] );
849 dpavlin 109 $_stat->{ping}->{ $_[ARG0] }++;
850 dpavlin 85 rss_check_updates( $_[KERNEL] );
851 dpavlin 43 },
852     irc_invite => sub {
853     my $kernel = $_[KERNEL];
854     my $nick = (split /!/, $_[ARG0])[0];
855     my $channel = $_[ARG1];
856    
857 dpavlin 85 _log "invited to $channel by $nick";
858 dpavlin 43
859 dpavlin 109 $_[KERNEL]->post( $irc => privmsg => $nick, "how nice of you to invite me to $channel, I'll be right there..." );
860     $_[KERNEL]->post( $irc => 'join' => $channel );
861 dpavlin 43
862     },
863 dpavlin 7 irc_msg => sub {
864     my $kernel = $_[KERNEL];
865     my $nick = (split /!/, $_[ARG0])[0];
866     my $msg = $_[ARG2];
867 dpavlin 50 my $channel = $_[ARG1]->[0];
868 dpavlin 119 warn "# ARG = ",dump( @_[ARG0,ARG1,ARG2] ) if $debug;
869 dpavlin 7
870 dpavlin 8 my $res = "unknown command '$msg', try /msg $NICK help!";
871 dpavlin 11 my @out;
872 dpavlin 7
873 dpavlin 45 _log "<< $msg";
874 dpavlin 7
875 dpavlin 8 if ($msg =~ m/^help/i) {
876 dpavlin 7
877 dpavlin 11 $res = "usage: /msg $NICK comand | commands: stat - user/message stat | last - show backtrace | grep foobar - find foobar";
878 dpavlin 8
879 dpavlin 111 } elsif ($msg =~ m/^(privmsg|notice)\s+(\S+)\s+(.*)$/i) {
880 dpavlin 10
881 dpavlin 111 _log ">> /$1 $2 $3";
882     $_[KERNEL]->post( $irc => $1 => $2, $3 );
883 dpavlin 10 $res = '';
884    
885 dpavlin 8 } elsif ($msg =~ m/^stat.*?\s*(\d*)/i) {
886    
887 dpavlin 7 my $nr = $1 || 10;
888    
889     my $sth = $dbh->prepare(qq{
890 dpavlin 40 select
891 dpavlin 57 trim(both '_' from nick) as nick,
892 dpavlin 40 count(*) as count,
893     sum(length(message)) as len
894     from log
895 dpavlin 57 group by trim(both '_' from nick)
896 dpavlin 40 order by len desc,count desc
897     limit $nr
898 dpavlin 7 });
899     $sth->execute();
900     $res = "Top $nr users: ";
901 dpavlin 8 my @users;
902 dpavlin 7 while (my $row = $sth->fetchrow_hashref) {
903 dpavlin 40 push @users,$row->{nick} . ': ' . $row->{count} . '/' . $row->{len} . '=' . sprintf("%.2f", $row->{len}/$row->{count});
904 dpavlin 7 }
905 dpavlin 8 $res .= join(" | ", @users);
906     } elsif ($msg =~ m/^last.*?\s*(\d*)/i) {
907    
908 dpavlin 50 my $limit = $1 || meta( $nick, $channel, 'last-size' ) || 10;
909    
910     foreach my $res (get_from_log( limit => $limit )) {
911 dpavlin 45 _log "last: $res";
912 dpavlin 109 $_[KERNEL]->post( $irc => privmsg => $nick, $res );
913 dpavlin 8 }
914    
915 dpavlin 11 $res = '';
916 dpavlin 8
917 dpavlin 21 } elsif ($msg =~ m/^(search|grep)\s+(.*)\s*$/i) {
918 dpavlin 8
919 dpavlin 11 my $what = $2;
920 dpavlin 8
921 dpavlin 21 foreach my $res (get_from_log(
922     limit => 20,
923     search => $what,
924     )) {
925 dpavlin 45 _log "search [$what]: $res";
926 dpavlin 109 $_[KERNEL]->post( $irc => privmsg => $nick, $res );
927 dpavlin 8 }
928    
929     $res = '';
930 dpavlin 11
931 dpavlin 42 } elsif ($msg =~ m/^(?:count|poll)\s+(.*)(?:\s+(\d+))?\s*$/i) {
932    
933     my ($what,$limit) = ($1,$2);
934     $limit ||= 100;
935    
936     my $stat;
937    
938     foreach my $res (get_from_log(
939     limit => $limit,
940     search => $what,
941     full_rows => 1,
942     )) {
943     while ($res->{message} =~ s/\Q$what\E(\+|\-)//) {
944     $stat->{vote}->{$1}++;
945     $stat->{from}->{ $res->{nick} }++;
946     }
947     }
948    
949     my @nicks;
950 dpavlin 43 foreach my $nick (sort { $stat->{from}->{$a} <=> $stat->{from}->{$b} } keys %{ $stat->{from} }) {
951     push @nicks, $nick . ( $stat->{from}->{$nick} == 1 ? '' :
952     "(" . $stat->{from}->{$nick} . ")"
953     );
954 dpavlin 42 }
955    
956     $res =
957 dpavlin 43 "$what ++ " . ( $stat->{vote}->{'+'} || 0 ) .
958     " : " . ( $stat->{vote}->{'-'} || 0 ) . " --" .
959 dpavlin 42 " from " . ( join(", ", @nicks) || 'nobody' );
960    
961 dpavlin 109 $_[KERNEL]->post( $irc => notice => $nick, $res );
962 dpavlin 43
963     } elsif ($msg =~ m/^ping/) {
964 dpavlin 109 $res = "ping = " . dump( $_stat->{ping} );
965 dpavlin 51 } elsif ($msg =~ m/^conf(?:ig)*\s*(last-size|twitter)*\s*(.*)/) {
966 dpavlin 50 if ( ! defined( $1 ) ) {
967     my $sth = $dbh->prepare(qq{ select name,value,changed from meta where nick = ? and channel = ? });
968     $sth->execute( $nick, $channel );
969 dpavlin 52 $res = "config for $nick on $channel";
970 dpavlin 50 while ( my ($n,$v) = $sth->fetchrow_array ) {
971 dpavlin 52 $res .= " | $n = $v";
972 dpavlin 50 }
973 dpavlin 51 } elsif ( ! $2 ) {
974 dpavlin 50 my $val = meta( $nick, $channel, $1 );
975     $res = "current $1 = " . ( $val ? $val : 'undefined' );
976 dpavlin 51 } else {
977     my $validate = {
978     'last-size' => qr/^\d+/,
979     'twitter' => qr/^\w+\s+\w+/,
980     };
981    
982     my ( $op, $val ) = ( $1, $2 );
983    
984     if ( my $regex = $validate->{$op} ) {
985     if ( $val =~ $regex ) {
986     meta( $nick, $channel, $op, $val );
987     $res = "saved $op = $val";
988     } else {
989     $res = "config option $op = $val doesn't validate against $regex";
990     }
991     } else {
992     $res = "config option $op doesn't exist";
993     }
994 dpavlin 50 }
995 dpavlin 85 } elsif ($msg =~ m/^rss-update/) {
996     $res = rss_fetch_all( $_[KERNEL] );
997 dpavlin 91 } elsif ($msg =~ m/^rss-list/) {
998 dpavlin 97 my $sth = $dbh->prepare(qq{ select url,name,last_update,active,channel,nick,private from feeds });
999 dpavlin 91 $sth->execute;
1000     while (my @row = $sth->fetchrow_array) {
1001 dpavlin 109 $_[KERNEL]->post( $irc => privmsg => $nick, join(' | ',@row) );
1002 dpavlin 91 }
1003     $res = '';
1004 dpavlin 117 } elsif ($msg =~ m!^rss-(add|remove|stop|start|clean)(?:-(private))?\s+(http://\S+)\s*(.*)!) {
1005 dpavlin 97 my ( $command, $sub, $url, $arg ) = ( $1,$2,$3,$4 );
1006    
1007     my $channel = $1 if ( $arg =~ s/\s*(#\S+)\s*// );
1008     $channel = $nick if $sub eq 'private';
1009    
1010 dpavlin 85 my $sql = {
1011 dpavlin 103 add => qq{ insert into feeds (url,name,channel,nick,private) values (?,?,?,?,?) },
1012 dpavlin 85 # remove => qq{ delete from feeds where url = ? and name = ? },
1013 dpavlin 91 start => qq{ update feeds set active = true where url = ? },
1014     stop => qq{ update feeds set active = false where url = ? },
1015 dpavlin 117 clean => qq{ update feeds set last_update = now() - delay where url = ? },
1016 dpavlin 85 };
1017 dpavlin 97
1018 dpavlin 99 if ( $command eq 'add' && ! $channel ) {
1019     $res = "ERROR: got '$msg' which doesn't have #channel in it, ignoring!";
1020     } elsif (my $q = $sql->{$command} ) {
1021 dpavlin 85 my $sth = $dbh->prepare( $q );
1022 dpavlin 97 my @data = ( $url );
1023     if ( $command eq 'add' ) {
1024     push @data, ( $arg, $channel, $nick, $sub eq 'private' ? 1 : 0 );
1025     }
1026     warn "## $command SQL $q with ",dump( @data ),"\n";
1027 dpavlin 91 eval { $sth->execute( @data ) };
1028 dpavlin 97 if ($@) {
1029     $res = "ERROR: $@";
1030     } else {
1031 dpavlin 117 $res = "OK, RSS executed $command " . ( $sub ? "-$sub" : '' ) ."on $channel url $url";
1032     if ( $command eq 'clean' ) {
1033     my $seen = $_stat->{rss}->{seen} || die "no seen?";
1034     my $want_link = $_stat->{rss}->{url2link}->{$url} || warn "no url2link($url)";
1035     foreach my $c ( keys %$seen ) {
1036     my $c_hash = $seen->{$c} || die "no seen->{$c}";
1037     die "not HASH with rss links but ", dump($c_hash) unless ref($c_hash) eq 'HASH';
1038     foreach my $link ( keys %$c_hash ) {
1039     next unless $link eq $want_link;
1040     _log "RSS removed seen $c $url $link";
1041     }
1042     }
1043     }
1044 dpavlin 97 }
1045     } else {
1046     $res = "ERROR: don't know what to do with: $msg";
1047 dpavlin 85 }
1048 dpavlin 117 } elsif ($msg =~ m/^rss-clean/) {
1049     # this makes sense because we didn't catch rss-clean http://... before!
1050     $_stat->{rss} = undef;
1051     $dbh->do( qq{ update feeds set last_update = now() - delay } );
1052     $res = "OK, cleaned RSS cache";
1053 dpavlin 7 }
1054    
1055 dpavlin 8 if ($res) {
1056 dpavlin 45 _log ">> [$nick] $res";
1057 dpavlin 109 $_[KERNEL]->post( $irc => privmsg => $nick, $res );
1058 dpavlin 8 }
1059 dpavlin 7
1060 dpavlin 85 rss_check_updates( $_[KERNEL] );
1061 dpavlin 7 },
1062 dpavlin 107 irc_372 => sub {
1063     _log "<< motd",$_[ARG0],$_[ARG1];
1064     },
1065     irc_375 => sub {
1066     _log "<< motd", $_[ARG0], "start";
1067     },
1068     irc_376 => sub {
1069     _log "<< motd", $_[ARG0], "end";
1070     },
1071 dpavlin 114 # irc_433 => sub {
1072     # print "# irc_433: ",$_[ARG1], "\n";
1073     # warn "## indetify $NICK\n";
1074     # $_[KERNEL]->post( $irc => privmsg => 'nickserv', "IDENTIFY $NICK" );
1075     # },
1076     # irc_451 # please register
1077 dpavlin 10 irc_477 => sub {
1078 dpavlin 103 _log "<< irc_477: ",$_[ARG1];
1079 dpavlin 114 _log ">> IDENTIFY $NICK";
1080     $_[KERNEL]->post( $irc => privmsg => 'NickServ', "IDENTIFY $NICK" );
1081 dpavlin 10 },
1082 dpavlin 7 irc_505 => sub {
1083 dpavlin 103 _log "<< irc_505: ",$_[ARG1];
1084 dpavlin 114 _log ">> register $NICK";
1085     $_[KERNEL]->post( $irc => privmsg => 'NickServ', "register $NICK" );
1086     # $_[KERNEL]->post( $irc => privmsg => 'nickserv', "IDENTIFY $NICK" );
1087 dpavlin 109 # $_[KERNEL]->post( $irc => privmsg => 'nickserv', "set hide email on" );
1088     # $_[KERNEL]->post( $irc => privmsg => 'nickserv', "set email dpavlin\@rot13.org" );
1089 dpavlin 8 },
1090     irc_registered => sub {
1091 dpavlin 109 _log "<< registered $NICK";
1092 dpavlin 10 },
1093 dpavlin 41 irc_disconnected => sub {
1094 dpavlin 103 _log "## disconnected.. sleeping for $sleep_on_error seconds and reconnecting again";
1095     sleep($sleep_on_error);
1096 dpavlin 109 $_[KERNEL]->post( $irc => connect => {} );
1097 dpavlin 41 },
1098     irc_socketerr => sub {
1099 dpavlin 45 _log "## socket error... sleeping for $sleep_on_error seconds and retry";
1100 dpavlin 41 sleep($sleep_on_error);
1101 dpavlin 109 $_[KERNEL]->post( $irc => connect => {} );
1102 dpavlin 41 },
1103 dpavlin 109 irc_notice => sub {
1104 dpavlin 116 _log "<< notice from ", $_[ARG0], $_[ARG1], $_[ARG2];
1105     my $m = $_[ARG2];
1106     if ( $m =~ m!/msg.*(NickServ).*(IDENTIFY)!i ) {
1107     _log ">> suggested to $1 $2";
1108     $_[KERNEL]->post( $irc => privmsg => $1, "$2 $NICK" );
1109     } elsif ( $m =~ m!\Q$NICK\E.*registered!i ) {
1110     _log ">> registreted, so IDENTIFY";
1111 dpavlin 109 $_[KERNEL]->post( $irc => privmsg => 'nickserv', "IDENTIFY $NICK" );
1112 dpavlin 116 } else {
1113 dpavlin 119 warn "## ignore $m\n" if $debug;
1114 dpavlin 109 }
1115     },
1116 dpavlin 103 irc_snotice => sub {
1117 dpavlin 116 _log "<< snotice", $_[ARG0]; #dump( $_[ARG0],$_[ARG1], $_[ARG2] );
1118 dpavlin 103 if ( $_[ARG0] =~ m!/(QUOTE)\s+(PASS\s+\d+)!i ) {
1119     warn ">> $1 | $2\n";
1120 dpavlin 109 $_[KERNEL]->post( $irc => lc($1) => $2);
1121 dpavlin 103 }
1122     },
1123 dpavlin 4 _child => sub {},
1124     _default => sub {
1125 dpavlin 45 _log sprintf "sID:%s %s %s",
1126     $_[SESSION]->ID, $_[ARG0],
1127 dpavlin 34 ref($_[ARG1]) eq "ARRAY" ? join(",", map { ref($_) eq "ARRAY" ? join(";", @{$_}) : $_ } @{ $_[ARG1] }) :
1128     $_[ARG1] ? $_[ARG1] :
1129     "";
1130 dpavlin 4 0; # false for signals
1131     },
1132     },
1133     );
1134    
1135 dpavlin 13 # http server
1136    
1137 dpavlin 114 _log "WEB archive at $url";
1138    
1139 dpavlin 13 my $httpd = POE::Component::Server::HTTP->new(
1140 dpavlin 70 Port => $http_port,
1141 dpavlin 83 PreHandler => {
1142     '/' => sub {
1143     $_[0]->header(Connection => 'close')
1144     }
1145     },
1146 dpavlin 13 ContentHandler => { '/' => \&root_handler },
1147     Headers => { Server => 'irc-logger' },
1148     );
1149    
1150     my $style = <<'_END_OF_STYLE_';
1151 dpavlin 16 p { margin: 0; padding: 0.1em; }
1152 dpavlin 13 .time, .channel { color: #808080; font-size: 60%; }
1153 dpavlin 28 .date { float: right; background: #e0e0e0; color: #404040; font-size: 120%; padding: 0.25em; border: 1px dashed #808080; }
1154 dpavlin 20 .nick { color: #000000; font-size: 80%; padding: 2px; font-family: courier, courier new, monospace ; }
1155 dpavlin 13 .message { color: #000000; font-size: 100%; }
1156 dpavlin 16 .search { float: right; }
1157 dpavlin 60 a:link.tag, a:visited.tag { border: 1px dashed #ccc; backgound: #ccc; text-decoration: none }
1158     a:hover.tag { border: 1px solid #eee }
1159     hr { border: 1px dashed #ccc; height: 1px; clear: both; }
1160     /*
1161 dpavlin 20 .col-0 { background: #ffff66 }
1162     .col-1 { background: #a0ffff }
1163     .col-2 { background: #99ff99 }
1164     .col-3 { background: #ff9999 }
1165     .col-4 { background: #ff66ff }
1166 dpavlin 60 */
1167 dpavlin 65 .calendar { border: 1px solid red; width: 100%; }
1168     .month { border: 0px; width: 100%; }
1169 dpavlin 13 _END_OF_STYLE_
1170    
1171 dpavlin 70 $max_color = 0;
1172 dpavlin 20
1173 dpavlin 60 my @cols = qw(
1174     #ffcccc #ccffe6 #ccccff #e6ccff #ffccff #ffcce6 #ff9999 #ffcc99 #ffff99
1175     #ccff99 #99ff99 #99ffcc #99ccff #9999ff #cc99ff #ff6666 #ffb366 #ffff66
1176     #66ff66 #66ffb3 #66b3ff #6666ff #ff3333 #33ff33 #3399ff #3333ff #ff3399
1177     #a0a0a0 #ff0000 #ffff00 #80ff00 #0000ff #8000ff #ff00ff #ff0080 #994d00
1178     #999900 #009900 #cc0066 #c0c0c0 #ccff99 #99ff33 #808080 #660033 #ffffff
1179     );
1180    
1181     foreach my $c (@cols) {
1182     $style .= ".col-${max_color} { background: $c }\n";
1183     $max_color++;
1184     }
1185 dpavlin 114 _log "WEB defined $max_color colors for users...";
1186 dpavlin 60
1187 dpavlin 13 sub root_handler {
1188     my ($request, $response) = @_;
1189     $response->code(RC_OK);
1190 dpavlin 16
1191 dpavlin 83 # this doesn't seem to work, so moved to PreHandler
1192     #$response->header(Connection => 'close');
1193    
1194 dpavlin 73 return RC_OK if $request->uri =~ m/favicon.ico$/;
1195    
1196 dpavlin 16 my $q;
1197    
1198     if ( $request->method eq 'POST' ) {
1199     $q = new CGI::Simple( $request->content );
1200     } elsif ( $request->uri =~ /\?(.+)$/ ) {
1201     $q = new CGI::Simple( $1 );
1202     } else {
1203     $q = new CGI::Simple;
1204     }
1205    
1206     my $search = $q->param('search') || $q->param('grep') || '';
1207 dpavlin 108 my $r_url = $request->url;
1208 dpavlin 16
1209 dpavlin 108 my @commands = qw( tags last-tag follow stat );
1210     my $commands_re = join('|',@commands);
1211    
1212     if ($r_url =~ m#/rss(?:/($commands_re.*)\w*(?:=(\d+))?)?#i) {
1213 dpavlin 77 my $show = lc($1);
1214 dpavlin 79 my $nr = $2;
1215 dpavlin 77
1216 dpavlin 71 my $type = 'RSS'; # Atom
1217 dpavlin 70
1218 dpavlin 72 $response->content_type( 'application/' . lc($type) . '+xml' );
1219 dpavlin 70
1220     my $html = '<!-- error -->';
1221 dpavlin 74 #warn "create $type feed from ",dump( @last_tags );
1222 dpavlin 70
1223     my $feed = XML::Feed->new( $type );
1224 dpavlin 85 $feed->link( $url );
1225 dpavlin 70
1226 dpavlin 108 my $rc = RC_OK;
1227    
1228 dpavlin 77 if ( $show eq 'tags' ) {
1229 dpavlin 79 $nr ||= 50;
1230 dpavlin 77 $feed->title( "tags from $CHANNEL" );
1231     $feed->link( "$url/tags" );
1232     $feed->description( "tag cloud created from messages on channel $CHANNEL which have tags// in them" );
1233 dpavlin 70 my $feed_entry = XML::Feed::Entry->new($type);
1234 dpavlin 79 $feed_entry->title( "$nr tags from $CHANNEL" );
1235 dpavlin 77 $feed_entry->author( $NICK );
1236     $feed_entry->link( '/#tags' );
1237 dpavlin 75
1238 dpavlin 73 $feed_entry->content(
1239 dpavlin 77 qq{<![CDATA[<style type="text/css">}
1240     . $cloud->css
1241     . qq{</style>}
1242 dpavlin 79 . $cloud->html( $nr )
1243 dpavlin 77 . qq{]]>}
1244 dpavlin 70 );
1245     $feed->add_entry( $feed_entry );
1246 dpavlin 77
1247 dpavlin 79 } elsif ( $show eq 'last-tag' ) {
1248 dpavlin 77
1249 dpavlin 79 $nr ||= $last_x_tags;
1250 dpavlin 80 $nr = $last_x_tags if $nr > $last_x_tags;
1251 dpavlin 79
1252     $feed->title( "last $nr tagged messages from $CHANNEL" );
1253 dpavlin 77 $feed->description( "collects messages which have tags// in them" );
1254    
1255     foreach my $m ( @last_tags ) {
1256     # warn dump( $m );
1257     #my $tags = join(' ', @{$m->{tags}} );
1258     my $feed_entry = XML::Feed::Entry->new($type);
1259     $feed_entry->title( $m->{nick} . '@' . $m->{time} );
1260     $feed_entry->author( $m->{nick} );
1261     $feed_entry->link( '/#' . $m->{id} );
1262     $feed_entry->issued( DateTime::Format::Flexible->build( $m->{time} ) );
1263    
1264     my $message = $filter->{message}->( $m->{message} );
1265     $message .= "<br/>\n" unless $message =~ m!<(/p|br/?)>!;
1266 dpavlin 79 # warn "## message = $message\n";
1267 dpavlin 77
1268     #$feed_entry->summary(
1269     $feed_entry->content(
1270     "<![CDATA[$message]]>"
1271     );
1272     $feed_entry->category( join(', ', @{$m->{tags}}) );
1273     $feed->add_entry( $feed_entry );
1274 dpavlin 79
1275     $nr--;
1276     last if $nr <= 0;
1277    
1278 dpavlin 77 }
1279 dpavlin 79
1280 dpavlin 85 } elsif ( $show =~ m/^follow/ ) {
1281    
1282     $feed->title( "Feeds which this bot follows" );
1283    
1284     my $sth = $dbh->prepare( qq{ select * from feeds order by last_update desc } );
1285     $sth->execute;
1286     while (my $row = $sth->fetchrow_hashref) {
1287     my $feed_entry = XML::Feed::Entry->new($type);
1288     $feed_entry->title( $row->{name} );
1289     $feed_entry->link( $row->{url} );
1290     $feed_entry->issued( DateTime::Format::Flexible->build( $row->{last_update} ) );
1291     $feed_entry->content(
1292     '<![CDATA[<pre>' . dump( $row ) . '</pre>]]>'
1293     );
1294     $feed->add_entry( $feed_entry );
1295     }
1296    
1297 dpavlin 108 } elsif ( $show =~ m/^stat/ ) {
1298    
1299 dpavlin 97 my $feed_entry = XML::Feed::Entry->new($type);
1300     $feed_entry->title( "Internal stats" );
1301     $feed_entry->content(
1302 dpavlin 108 '<![CDATA[<pre>' . dump( $_stat ) . '</pre>]]>'
1303 dpavlin 97 );
1304     $feed->add_entry( $feed_entry );
1305    
1306 dpavlin 79 } else {
1307 dpavlin 114 _log "WEB unknown rss request $r_url";
1308 dpavlin 108 $feed->title( "unknown $r_url" );
1309     foreach my $c ( @commands ) {
1310     my $feed_entry = XML::Feed::Entry->new($type);
1311     $feed_entry->title( "rss/$c" );
1312     $feed_entry->link( "$url/rss/$c" );
1313     $feed->add_entry( $feed_entry );
1314     }
1315     $rc = RC_DENY;
1316 dpavlin 70 }
1317    
1318     $response->content( $feed->as_xml );
1319 dpavlin 108 return $rc;
1320 dpavlin 70 }
1321    
1322     if ( $@ ) {
1323     warn "$@";
1324     }
1325    
1326 dpavlin 86 $response->content_type("text/html; charset=UTF-8");
1327 dpavlin 70
1328 dpavlin 35 my $html =
1329 dpavlin 77 qq{<html><head><title>$NICK</title><style type="text/css">$style}
1330     . $cloud->css
1331     . qq{</style></head><body>}
1332     . qq{
1333 dpavlin 32 <form method="post" class="search" action="/">
1334 dpavlin 16 <input type="text" name="search" value="$search" size="10">
1335     <input type="submit" value="search">
1336     </form>
1337 dpavlin 77 }
1338     . $cloud->html(500)
1339     . qq{<p>};
1340 dpavlin 76
1341     if ($request->url =~ m#/tags?#) {
1342     # nop
1343     } elsif ($request->url =~ m#/history#) {
1344 dpavlin 35 my $sth = $dbh->prepare(qq{
1345 dpavlin 65 select date(time) as date,count(*) as nr,sum(length(message)) as len
1346 dpavlin 35 from log
1347     group by date(time)
1348     order by date(time) desc
1349     });
1350     $sth->execute();
1351     my ($l_yyyy,$l_mm) = (0,0);
1352 dpavlin 65 $html .= qq{<table class="calendar"><tr>};
1353 dpavlin 35 my $cal;
1354 dpavlin 65 my $ord = 0;
1355 dpavlin 35 while (my $row = $sth->fetchrow_hashref) {
1356     # this is probably PostgreSQL specific, expects ISO date
1357     my ($yyyy,$mm,$dd) = split(/-/, $row->{date});
1358     if ($yyyy != $l_yyyy || $mm != $l_mm) {
1359 dpavlin 65 if ( $cal ) {
1360     $html .= qq{<td valign="top">} . $cal->as_HTML() . qq{</td>};
1361     $ord++;
1362     $html .= qq{</tr><tr>} if ( $ord % 3 == 0 );
1363     }
1364 dpavlin 35 $cal = new HTML::CalendarMonthSimple('month'=>$mm,'year'=>$yyyy);
1365 dpavlin 65 $cal->border(1);
1366     $cal->width('30%');
1367     $cal->cellheight('5em');
1368     $cal->tableclass('month');
1369     #$cal->cellclass('day');
1370     $cal->sunday('SUN');
1371     $cal->saturday('SAT');
1372     $cal->weekdays('MON','TUE','WED','THU','FRI');
1373 dpavlin 35 ($l_yyyy,$l_mm) = ($yyyy,$mm);
1374     }
1375 dpavlin 79 $cal->setcontent($dd, qq[
1376 dpavlin 73 <a href="$url?date=$row->{date}">$row->{nr}</a><br/>$row->{len}
1377 dpavlin 89 ]) if $cal;
1378 dpavlin 65
1379 dpavlin 35 }
1380 dpavlin 65 $html .= qq{<td valign="top">} . $cal->as_HTML() . qq{</td></tr></table>};
1381 dpavlin 35
1382     } else {
1383     $html .= join("</p><p>",
1384 dpavlin 13 get_from_log(
1385 dpavlin 68 limit => ( $q->param('last') || $q->param('date') ) ? undef : 100,
1386 dpavlin 28 search => $search || undef,
1387 dpavlin 29 tag => $q->param('tag') || undef,
1388 dpavlin 68 date => $q->param('date') || undef,
1389 dpavlin 13 fmt => {
1390 dpavlin 35 date => sub {
1391     my $date = shift || return;
1392 dpavlin 73 qq{<hr/><div class="date"><a href="$url?date=$date">$date</a></div>};
1393 dpavlin 35 },
1394 dpavlin 13 time => '<span class="time">%s</span> ',
1395     time_channel => '<span class="channel">%s %s</span> ',
1396 dpavlin 20 nick => '%s:&nbsp;',
1397     me_nick => '***%s&nbsp;',
1398 dpavlin 13 message => '<span class="message">%s</span>',
1399     },
1400 dpavlin 70 filter => $filter,
1401 dpavlin 13 )
1402 dpavlin 35 );
1403     }
1404    
1405     $html .= qq{</p>
1406     <hr/>
1407     <p>See <a href="/history">history</a> of all messages.</p>
1408     </body></html>};
1409    
1410     $response->content( $html );
1411 dpavlin 74 warn "<< ", $request->method, " ", $request->uri, " created ", length($html), " bytes\n";
1412 dpavlin 13 return RC_OK;
1413     }
1414    
1415 dpavlin 4 POE::Kernel->run;

Properties

Name Value
svn:executable *

  ViewVC Help
Powered by ViewVC 1.1.26