1 # -*- Mode: perl; tab-width: 4; indent-tabs-mode: nil; -*-
2 ################################
4 ################################
6 package BotModules::Seen;
13 # SpottedNickChange would be a nice one to do if you
14 # can solve the problem of working out which channel
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);
28 'seen' => 'Says how long it\'s been since the last time someone was seen. Syntax: seen victim',
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>';
37 # RegisterConfig - Called when initialised, should call registerVariables
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.
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.";
59 if ($self->isAdmin($event) and $message =~ /^\s*(un)?mute\s+seen\s+in\s+(\S+)\s*$/osi){
60 my $mute = !defined($1);
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);
67 return $self->SUPER::Told(@_);
69 return 0; # we've dealt with it, no need to do anything else.
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.";
80 if (!(grep {$event->{'channel'} eq $_} @{$self->{'directOnlyChannels'}}) and $message =~ /^\s*!seen\s+(\S+)\s*$/osi) {
81 $self->DoSeen($event, $1);
83 return $self->SUPER::Heard(@_);
85 return 0; # we've dealt with it, no need to do anything else.
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.";
97 return $self->SUPER::Felt(@_);
99 return 0; # we've dealt with it, no need to do anything else.
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.";
111 return $self->SUPER::Felt(@_);
113 return 0; # we've dealt with it, no need to do anything else.
116 # SpottedNickChange - Called when someone changes nick
117 sub SpottedNickChange {
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(@_);
127 my ($event, $who) = @_;
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});
137 my @nicksToList = ();
138 if ($who =~ m!^/(\S+)/$!) { # shouldn't allow mix and match or blank RE or spaces.
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.');
146 @nicksToList = grep {$_ =~ $re} (keys %{$seen->{'times'}});
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.');
157 @nicksToList = grep {$_ =~ $re} (keys %{$seen->{'times'}});
159 @nicksToList = (lc $who) if defined($seen->{'times'}{lc $who}); # short circuit for the majority of uses
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;
172 my $minutes = int $seconds / 60;
175 my $hours = int $minutes / 60;
178 my $days = int $hours / 24;
181 my $weeks = int $days / 7;
184 # good god, nice connection
191 $time .= "$weeks week";
193 $time .= "$weeks weeks";
202 $time .= "$days day";
204 $time .= "$days days";
213 $time .= "$hours hour";
215 $time .= "$hours hours";
224 $time .= "$minutes minute";
226 $time .= "$minutes minutes";
232 $time .= 'right about now';
241 $time .= 'a second ago';
242 } elsif ($seconds == 2) {
243 $time .= 'a couple of seconds ago';
245 $time .= "$seconds seconds ago";
248 my $what = $seen->{'states'}->{$nick};
249 $self->say($event, "$nick was last seen $time, $what");
253 if ($who =~ /^[aeiou]/o) {
257 $self->say($event, "I've never seen anyone matching the pattern '$who', sorry.");
259 $self->say($event, "I've never seen a$n '$who', sorry.");
266 untie(%{$seen->{'times'}});
267 untie(%{$seen->{'states'}});
270 sub MuteOrUnmuteChannel {
272 my ($event, $mute, $channel) = @_;
274 if (grep {$_ eq $channel} @{$self->{'directOnlyChannels'}}){
275 $self->say($event,"I'm already ignoring !seen <name> in $channel.");
277 push @{$self->{'directOnlyChannels'}}, $channel;
278 $self->say($event, "I won't respond to !seen <name> in $channel anymore unless told directly.");
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.");
287 $self->say($event, "I'm already responding to !seen <name> in $channel.");