]> git.somenet.org - irc/bugbot.git/blob - BotModules/Seen.bm
GITOLITE.txt
[irc/bugbot.git] / BotModules / Seen.bm
1 # -*- Mode: perl; tab-width: 4; indent-tabs-mode: nil; -*-
2 ################################
3 # Seen Module                  #
4 ################################
5
6 package BotModules::Seen;
7 use vars qw(@ISA);
8 @ISA = qw(BotModules);
9 use AnyDBM_File;
10 use Fcntl;
11 1;
12
13 # SpottedNickChange would be a nice one to do if you 
14 # can solve the problem of working out which channel
15 # to say stuff in...
16
17 # database for seen data
18 our $seen = {'times' => {}, 'states' => {}};
19 # the times that the relevant nicks were last seen active
20 tie(%{$seen->{'times'}}, 'AnyDBM_File', 'seen-times', O_RDWR|O_CREAT, 0666);
21 # what the relevant nicks were last seen doing
22 tie(%{$seen->{'states'}}, 'AnyDBM_File', 'seen-states', O_RDWR|O_CREAT, 0666);
23
24 sub Help {
25     my $self = shift;
26     my ($event) = @_;
27     my %commands = {
28         'seen' => 'Says how long it\'s been since the last time someone was seen. Syntax: seen victim',
29     };
30     if ($self->isAdmin($event)) {
31         $commands{'mute'} = 'Stop responding to !seen <name> in a channel unless told directly. Syntax: mute seen in <channel>';
32         $commands{'unmute'} = 'Start responding to !seen <name> in a channel. Syntax: unmute seen in <channel>';
33     }
34     return \%commands;
35 }
36
37 # RegisterConfig - Called when initialised, should call registerVariables
38 sub RegisterConfig {
39     my $self = shift;
40     $self->SUPER::RegisterConfig(@_);
41     $self->registerVariables(
42       # [ name, save?, settable? ]
43         ['overrides', 1, 1, {'therapist' => 'Look, dude, I\'m feeling fine, mm\'k?'}], # canned responses
44         ['maxLines', 1, 1, 5],
45         ['directOnlyChannels', 1, 1, []], #list of channels where we're only observing and not responding to !seen unless told.
46     );
47 }
48
49 sub Told {
50     my $self = shift;
51     my ($event, $message) = @_;
52     my $now = $event->{'time'};
53     $self->{'_lastSpoken'}->{$event->{'user'}} = $now;
54     if ($event->{'channel'} ne '') {
55         my $channel = $event->{'channel'};
56         $seen->{'times'}->{lc $event->{'from'}} = $now;
57         $seen->{'states'}->{lc $event->{'from'}} = "saying '$message' to me in $channel.";
58     }
59     if ($self->isAdmin($event) and $message =~ /^\s*(un)?mute\s+seen\s+in\s+(\S+)\s*$/osi){
60         my $mute = !defined($1);
61         my $channel = lc $2;
62         $channel =~ s/^\#?/\#/;  # Add # character if needed.
63         $self->MuteOrUnmuteChannel($event, $mute, $channel);
64     }elsif ($message =~ /^\s*!?seen\s+(\S+?)[\s?.!]*$/osi) {
65         $self->DoSeen($event, $1);
66     } else {
67         return $self->SUPER::Told(@_);
68     }
69     return 0; # we've dealt with it, no need to do anything else.
70 }
71
72 sub Heard {
73     my $self = shift;
74     my ($event, $message) = @_;
75     if ($event->{'channel'} ne '') {
76         my $channel = $event->{'channel'};
77         $seen->{'times'}->{lc $event->{'from'}} = $event->{'time'};
78         $seen->{'states'}->{lc $event->{'from'}} = "saying '$message' in $channel.";
79     }
80     if (!(grep {$event->{'channel'} eq $_} @{$self->{'directOnlyChannels'}}) and $message =~ /^\s*!seen\s+(\S+)\s*$/osi) {
81         $self->DoSeen($event, $1);
82     } else {
83         return $self->SUPER::Heard(@_);
84     }
85     return 0; # we've dealt with it, no need to do anything else.
86 }
87
88 sub Felt {
89     my $self = shift;
90     my ($event, $message) = @_;
91     if ($event->{'channel'} ne '') {
92         my $nick = $event->{'from'};
93         my $channel = $event->{'channel'};
94         $seen->{'times'}->{lc $event->{'from'}} = $event->{'time'};
95         $seen->{'states'}->{lc $event->{'from'}} = "saying '* $nick $message' in $channel.";
96     } else {
97         return $self->SUPER::Felt(@_);
98     }
99     return 0; # we've dealt with it, no need to do anything else.
100 }
101
102 sub Saw {
103     my $self = shift;
104     my ($event, $message) = @_;
105     if ($event->{'channel'} ne '') {
106         my $nick = $event->{'from'};
107         my $channel = $event->{'channel'};
108         $seen->{'times'}->{lc $event->{'from'}} = $event->{'time'};
109         $seen->{'states'}->{lc $event->{'from'}} = "saying '* $nick $message' in $channel.";
110     } else {
111         return $self->SUPER::Felt(@_);
112     }
113     return 0; # we've dealt with it, no need to do anything else.
114 }
115
116 # SpottedNickChange - Called when someone changes nick
117 sub SpottedNickChange {
118     my $self = shift;
119     my ($event, $from, $to) = @_;
120     $seen->{'times'}->{lc $event->{'from'}} = $event->{'time'};
121     $seen->{'states'}->{lc $event->{'from'}} = "changing nick to $to.";
122     return $self->SUPER::SpottedNickChange(@_);
123 }
124
125 sub DoSeen {
126     my $self = shift;
127     my ($event, $who) = @_;
128     my $pattern;
129     if (lc $who eq lc $event->{'from'}) {
130         $self->say($event, 'You\'re right here, duh!');
131     } elsif (lc $who eq lc $event->{'nick'}) {
132         $self->say($event, 'I\'m right here, duh!');
133     } elsif (defined($self->{'overrides'}->{$who})) {
134         $self->say($event, $self->{'overrides'}->{$who});
135     } else {
136         my $regexp;
137         my @nicksToList = ();
138         if ($who =~ m!^/(\S+)/$!) {  # shouldn't allow mix and match or blank RE or spaces.
139           $regexp = $1;
140             my $re = $self->sanitizeRegexp($regexp);  # security + safety first!
141             $re = qr/$re/i; #precompile for performance
142             if ('' =~ $re){ # will match everything, throw error.
143                 $self->say($event, 'That pattern matches everything, please be more specific.');
144                 return;
145             }
146             @nicksToList = grep {$_ =~ $re} (keys %{$seen->{'times'}});
147           $pattern = 1;
148         } else {
149             if ($who =~ /\*/){  # no point going through the motions if there's no wildcard.
150           $regexp = quotemeta(lc $who);
151                 $regexp =~ s/\\\*/\\S*/g; # replace the escaped * from quotemeta with a \S*  (XXX wanted: the ? wildcard)
152                 my $re = qr/^$regexp$/;
153                 if ('' =~ $re){ # will match everything, throw error.
154                     $self->say($event, 'That pattern matches everything, please be more specific.');
155                     return;
156                 }
157                 @nicksToList = grep {$_ =~ $re} (keys %{$seen->{'times'}});
158             } else {
159                 @nicksToList = (lc $who) if defined($seen->{'times'}{lc $who}); # short circuit for the majority of uses
160             }
161           $pattern = 0;
162         }
163         if (@nicksToList > $self->{'maxLines'}) { # if it's more than the set threshold, don't flood :)
164             $self->say($event,"There are more than $self->{'maxLines'} nicks matching that wildcard, please be more specific.");
165         } elsif (@nicksToList > 0) {
166             foreach my $nick (@nicksToList) {
167               my $seconds = $seen->{'times'}->{$nick};
168               $seconds = $event->{'time'} - $seconds;
169               my $time = '';
170               
171               if ($seconds > 90) {
172                 my $minutes = int $seconds / 60;
173                 $seconds %= 60;
174                 if ($minutes > 90) {
175                     my $hours = int $minutes / 60;
176                     $minutes %= 60;
177                     if ($hours > 36) {
178                         my $days = int $hours / 24;
179                         $hours %= 24;
180                         if ($days > 10) {
181                             my $weeks = int $days / 7;
182                             $days %= 7;
183                             if ($weeks > 10) {
184                                 # good god, nice connection
185                             }
186                             if ($weeks != 0) {
187                                 if ($time ne '') {
188                                     $time .= ', ';
189                                 }
190                                 if ($weeks == 1) {
191                                     $time .= "$weeks week";
192                                 } else {
193                                     $time .= "$weeks weeks";
194                                 }
195                             }
196                         }
197                         if ($days != 0) {
198                             if ($time ne '') {
199                                 $time .= ', ';
200                             }
201                             if ($days == 1) {
202                                 $time .= "$days day";
203                             } else {
204                                 $time .= "$days days";
205                             }
206                         }
207                     }
208                     if ($hours != 0) {
209                         if ($time ne '') {
210                             $time .= ', ';
211                         }
212                         if ($hours == 1) {
213                             $time .= "$hours hour";
214                         } else {
215                             $time .= "$hours hours";
216                         }
217                     }
218                 }
219                 if ($minutes != 0) {
220                     if ($time ne '') {
221                         $time .= ', ';
222                     }
223                     if ($minutes == 1) {
224                         $time .= "$minutes minute";
225                     } else {
226                         $time .= "$minutes minutes";
227                     }
228                 }
229             }
230             if ($seconds == 0) {
231                 if ($time eq '') {
232                     $time .= 'right about now';
233                 } else {
234                     $time .= ' ago';
235                 }
236             } else {
237                 if ($time ne '') {
238                     $time .= ' and ';
239                 }
240                 if ($seconds == 1) {
241                     $time .= 'a second ago';
242                 } elsif ($seconds == 2) {
243                     $time .= 'a couple of seconds ago';
244                 } else {
245                     $time .= "$seconds seconds ago";
246                 }
247             }
248               my $what = $seen->{'states'}->{$nick};
249               $self->say($event, "$nick was last seen $time, $what");
250             }
251         } else {
252             my $n = '';
253             if ($who =~ /^[aeiou]/o) {
254               $n = 'n';
255             }
256             if ($pattern == 1) {
257               $self->say($event, "I've never seen anyone matching the pattern '$who', sorry.");
258             } else {
259                 $self->say($event, "I've never seen a$n '$who', sorry.");
260             }
261         }
262     }
263 }
264
265 sub Unload {
266   untie(%{$seen->{'times'}});
267   untie(%{$seen->{'states'}});
268 }
269
270 sub MuteOrUnmuteChannel {
271     my $self = shift;
272     my ($event, $mute, $channel) = @_;
273     if ($mute){
274         if (grep {$_ eq $channel} @{$self->{'directOnlyChannels'}}){
275             $self->say($event,"I'm already ignoring !seen <name> in $channel.");
276         } else{
277             push @{$self->{'directOnlyChannels'}}, $channel;
278             $self->say($event, "I won't respond to !seen <name> in $channel anymore unless told directly.");
279             $self->saveConfig();
280         }
281     } else {
282         if (grep {$_ eq $channel} @{$self->{'directOnlyChannels'}}){
283             @{$self->{'directOnlyChannels'}} = map {$_ ne $channel} @{$self->{'directOnlyChannels'}};
284             $self->say($event,"I'll start responding to !seen <name> in $channel now.");
285             $self->saveConfig();
286         } else{
287             $self->say($event, "I'm already responding to !seen <name> in $channel.");
288         }
289     }
290 }