]> iEval git - app-statsbot.git/blame - lib/App/Statsbot.pm
Improve CTCP responses
[app-statsbot.git] / lib / App / Statsbot.pm
CommitLineData
3cdbe256
MG
1package App::Statsbot;
2
3use 5.014000;
4use strict;
5use warnings;
6
c5d2570c 7our $VERSION = '0.001002';
3cdbe256
MG
8
9use POE;
10use POE::Component::IRC::State;
11use POE::Component::IRC::Plugin::AutoJoin;
12use POE::Component::IRC::Plugin::Connector;
13use POE::Component::IRC::Plugin::CTCP;
14use IRC::Utils qw/parse_user/;
15
6e23c401 16use Carp;
3cdbe256
MG
17use DBI;
18use DBD::SQLite;
19use Text::ParseWords qw/shellwords/;
20use Time::Duration qw/duration duration_exact/;
21use Time::Duration::Parse qw/parse_duration/;
22
23use List::Util qw/max/;
24
25our $DEBUG = '';
26our $TICK = 10;
27our $NICKNAME = 'statsbot';
28our $SERVER = 'irc.freenode.net';
29our $PORT = 6667;
30our $SSL = '';
31our @CHANNELS;
32our $DB = '/var/lib/statsbot/db';
33
34my $dbh;
35my $insert;
36my $update;
37my $irc;
38
39my %state;
40
6e23c401
MG
41sub _yield { $irc->yield(@_) }
42sub _nick_name { $irc->nick_name }
43
44sub _uptime {
45 my ($starttime, $nick) = @_;
46 my $sth=$dbh->prepare('SELECT start,end FROM presence WHERE end > ? AND nick == ?');
47 $sth->execute($starttime, $nick);
48
49 my $uptime=0;
50 while (my ($start, $end)=$sth->fetchrow_array) {
51 $uptime+=$end-max($start,$starttime)
52 }
53 return $uptime
54}
55
3cdbe256
MG
56sub run {
57 $irc=POE::Component::IRC::State->spawn;
58 POE::Session->create(
59 inline_states => {
60 _start => \&bot_start,
61 irc_public => \&on_public,
62
63 irc_chan_sync => \&tick,
64 tick => \&tick,
65
66 irc_disconnected => \&on_fatal,
67 irc_error => \&on_fatal,
68 },
69 options => { trace => $DEBUG },
70 );
71
6e23c401 72 $dbh=DBI->connect("dbi:SQLite:dbname=$DB") or croak "Cannot connect to database: $!";
3cdbe256 73 $dbh->do('CREATE TABLE presence (start INTEGER, end INTEGER, nick TEXT)');
6e23c401
MG
74 $insert=$dbh->prepare('INSERT INTO presence (start, end, nick) VALUES (?,?,?)') or croak "Cannot prepare query: $!";
75 $update=$dbh->prepare('UPDATE presence SET end = ? WHERE start == ? AND nick == ?') or croak "Cannot prepare query: $!";
3cdbe256
MG
76 $poe_kernel->run();
77};
78
79sub tick{
80 my %nicks = map {$_ => 1} $irc->nicks;
81 for my $nick (keys %state) {
82 $update->execute(time, $state{$nick}, $nick);
83 delete $state{$nick} unless (exists $nicks{$nick});
84 delete $nicks{$nick};
85 }
86
87 for (keys %nicks) {
88 $state{$_}=time;
89 $insert->execute($state{$_}, $state{$_}, $_);
90 }
91 $_[KERNEL]->delay(tick => $TICK);
92}
93
6e23c401 94sub bot_start{ ## no critic (RequireArgUnpacking)
3cdbe256
MG
95 $_[KERNEL]->delay(tick => $TICK);
96
97 $irc->plugin_add(CTCP => POE::Component::IRC::Plugin::CTCP->new(
d6102de2
MG
98 version => "Statsbot/$VERSION",
99 source => 'https://metacpan.org/pod/App::Statsbot',
3cdbe256
MG
100 userinfo => 'A bot which keeps logs and computes channel statistics',
101 clientinfo => 'PING VERSION CLIENTINFO USERINFO SOURCE',
102 ));
103 $irc->plugin_add(AutoJoin => POE::Component::IRC::Plugin::AutoJoin->new(
104 Channels => [ @CHANNELS ],
105 RejoinOnKick => 1,
106 Rejoin_delay => 20,
107 Retry_when_banned => 60,
108 ));
109 $irc->plugin_add(Connecter => POE::Component::IRC::Plugin::Connector->new(
110 servers => [ $SERVER ],
111 ));
112
6e23c401
MG
113 _yield(register => 'all');
114 _yield(
3cdbe256
MG
115 connect => {
116 Nick => $NICKNAME,
117 Username => 'statsbot',
118 Ircname => 'Logging and statistics bot',
119 Server => $SERVER,
120 Port => $PORT,
121 UseSSL => $SSL,
122 }
123 );
124}
125
6e23c401 126sub on_fatal{ croak "Fatal error: $_[ARG0]" }
3cdbe256
MG
127
128sub on_public{
129 my ($targets,$message)=@_[ARG1,ARG2];
6e23c401 130 my $botnick = _nick_name;
410c016e 131
896cfdd3
MG
132 if ($message =~ /(?:$botnick[:,])?\s*!?help\s*(.*)/sx) {
133 _yield(privmsg => $targets, 'Try !presence username interval [truncate]');
134 _yield(privmsg => $targets, q/For example, !presence mgv '2 days'/);
135 _yield(privmsg => $targets, q/or !presence mgv '1 year' 4/);
410c016e
MG
136 return;
137 }
138
6e23c401 139 return unless $message =~ /(?:$botnick[:,])?\s*!?presence\s*(.*)/sx;
3cdbe256
MG
140 my ($nick, $time, $truncate) = shellwords $1;
141
142 $truncate//=-1;
143
144 unless (defined $time) {
145 $time='1 days';
146 $truncate=-1;
147 }
148
149 eval {
150 $time = parse_duration $time;
151 } or do {
6e23c401 152 _yield(privmsg => $targets, "cannot parse timespec: $time");
3cdbe256
MG
153 return;
154 };
155
6e23c401 156 my $uptime=_uptime time-$time, $nick;
3cdbe256
MG
157
158 my $ret;
159 if ($truncate == -1) {
160 use integer;
161 $ret=($uptime/3600).' hours';
162 } else {
163 $ret=duration $uptime,$truncate;
164 }
165
166 $time=duration_exact $time;
167
6e23c401 168 _yield(privmsg => $targets, "$nick was here $ret during the last $time");
3cdbe256
MG
169}
170
171
1721;
173__END__
174
175=encoding utf-8
176
177=head1 NAME
178
179App::Statsbot - simple IRC bot that tracks time spent in a channel
180
181=head1 SYNOPSIS
182
183 use App::Statsbot;
184 @App::Statsbot::CHANNELS = '#oooes';
185 $App::Statsbot::DEBUG = 1;
186 App::Statsbot->run
187
188 # Bot will respond to queries of the forms:
189 # < mgv> !presence mgv
190 # < mgv> presence mgv '1 day'
191 # < mgv> BOTNICK: !presence mgv '1 year' 2
192 # < mgv> BOTNICK: presence mgv
193
194=head1 DESCRIPTION
195
196App::Statsbot is a simple IRC bot that tracks the people that inhabit
197a channel. It is able to answer queries of the form "In the last <time
198interval>, how much time did <nick> spend in this channel?".
199
200It is configured via global variables in the App::Statsbot package.
201
202=over
203
204=item $DEBUG
205
206If true, print some debug information. Defaults to false.
207
208=item $TICK
209
210How often (in seconds) to poll the channel for nicks. Defaults to 10
211seconds.
212
213=item $NICKNAME
214
215The nickname of the bot. Defaults to "statsbot".
216
217=item $SERVER
218
219The IRC server. Defaults to "irc.freenode.net".
220
221=item $PORT
222
223The port. Defaults to 6667.
224
225=item $SSL
226
227If true, connect via SSL. Defaults to false.
228
229=item @CHANNELS
230
231Array of channels to connect to. Defaults to an empty array, which is
232not very useful.
233
234=item $DB
235
236Path to SQLite database. Must be writable. Will be created if it does
237not exist. Defaults to C</var/lib/statsbot/db>.
238
239=back
240
241After configuration, the bot can be started using the B<run> function,
242which can be called as either a regular function or a method.
243
244=head1 SEE ALSO
245
246L<statsbot>
247
248=head1 AUTHOR
249
250Marius Gavrilescu, E<lt>marius@ieval.roE<gt>
251
252=head1 COPYRIGHT AND LICENSE
253
254Copyright (C) 2013-2015 by Marius Gavrilescu
255
256This library is free software; you can redistribute it and/or modify
257it under the same terms as Perl itself, either Perl version 5.20.2 or,
258at your option, any later version of Perl 5 you may have available.
259
260
261=cut
This page took 0.045716 seconds and 4 git commands to generate.