]> git.donarmstrong.com Git - infobot.git/blob - src/IRC/IrcHelpers.pl
* Fixed a bug that caused seen stats flush to crash related to sqlite not having...
[infobot.git] / src / IRC / IrcHelpers.pl
1 #
2 # IrcHooks.pl: IRC Hooks stuff.
3 #      Author: dms
4 #     Version: 20010413
5 #     Created: 20010413
6 #        NOTE: Based on code by Kevin Lenzo & Patrick Cole  (c) 1997
7 #
8
9 #######################################################################
10 ####### IRC HOOK HELPERS   IRC HOOK HELPERS   IRC HOOK HELPERS ########
11 #######################################################################
12
13 #####
14 # Usage: &hookMode($nick, $modes, @targets);
15 sub hookMode {
16     my ( $nick, $modes, @targets ) = @_;
17     my $parity = 0;
18
19     my $mode;
20     foreach $mode ( split( //, $modes ) ) {
21
22         # sign. tmp parity needed to store current state
23         if ( $mode =~ /[-+]/ ) {
24             $parity = 1 if ( $mode eq "+" );
25             $parity = 0 if ( $mode eq "-" );
26             next;
27         }
28
29         # mode with target.
30         if ( $mode =~ /[bklov]/ ) {
31             my $target = shift @targets;
32
33             if ($parity) {
34                 $chanstats{ lc $chan }{'Op'}++  if ( $mode eq 'o' );
35                 $chanstats{ lc $chan }{'Ban'}++ if ( $mode eq 'b' );
36             } else {
37                 $chanstats{ lc $chan }{'Deop'}++  if ( $mode eq 'o' );
38                 $chanstats{ lc $chan }{'Unban'}++ if ( $mode eq 'b' );
39             }
40
41             # modes w/ target affecting nick => cache it.
42             if ( $mode =~ /[bov]/ ) {
43                 $channels{ lc $chan }{$mode}{$target}++      if $parity;
44                 delete $channels{ lc $chan }{$mode}{$target} if !$parity;
45
46                 # lets do some custom stuff.
47                 if ( $mode =~ /o/ and not $parity ) {
48                     if ( $target =~ /^\Q$ident\E$/i ) {
49                         &VERB( "hookmode: someone deopped us!", 2 );
50                         &chanServCheck($chan);
51                     }
52
53                     &chanLimitVerify($chan);
54                 }
55             }
56
57             if ( $mode =~ /[l]/ ) {
58                 $channels{ lc $chan }{$mode} = $target if $parity;
59                 delete $channels{ lc $chan }{$mode} if !$parity;
60             }
61         }
62
63         # important channel modes, targetless.
64         if ( $mode =~ /[mt]/ ) {
65             $channels{ lc $chan }{$mode}++      if $parity;
66             delete $channels{ lc $chan }{$mode} if !$parity;
67         }
68     }
69 }
70
71 sub hookMsg {
72     ($msgType, $chan, $who, $message) = @_;
73     my $skipmessage     = 0;
74     $addressed          = 0;
75     $addressedother     = 0;
76     $orig{message}      = $message;
77     $orig{who}          = $who;
78     $addrchar           = 0;
79
80     $message    =~ s/[\cA-\c_]//ig;     # strip control characters
81     $message    =~ s/^\s+//;            # initial whitespaces.
82     $who        =~ tr/A-Z/a-z/;         # lowercase.
83     my $mynick = $conn->nick();
84
85     &showProc();
86
87     # addressing.
88     if ($msgType =~ /private/) {
89         # private messages.
90         $addressed = 1;
91         if (&IsChanConf('addressCharacter') > 0) {
92             $addressCharacter = getChanConf('addressCharacter');
93             if ($message =~ s/^\Q$addressCharacter\E//) {
94                 &msg($who, "The addressCharacter \"$addressCharacter\" is to get my attention in a normal channel. Please leave it off when messaging me directly.");
95             }
96         }
97     } else {
98         # public messages.
99         # addressing revamped by the xk.
100         ### below needs to be fixed...
101         if (&IsChanConf('addressCharacter') > 0) {
102             $addressCharacter = getChanConf('addressCharacter');
103             if ($message =~ s/^\Q$addressCharacter\E//) {
104                 $addrchar  = 1;
105                 $addressed = 1;
106             }
107         }
108
109         if ($message =~ /^($mask{nick})([\;\:\>\, ]+) */) {
110             my $newmessage = $';
111             if ($1 =~ /^\Q$mynick\E$/i) {
112                 $message   = $newmessage;
113                 $addressed = 1;
114             } else {
115                 # ignore messages addressed to other people or unaddressed.
116                 $skipmessage++ if ($2 ne '' and $2 !~ /^ /);
117             }
118         }
119     }
120
121     # Determine floodwho.
122     my $c       = '_default';
123     if ($msgType =~ /public/i) {
124         # public.
125         $floodwho = $c = lc $chan;
126     } elsif ($msgType =~ /private/i) {
127         # private.
128         $floodwho = lc $who;
129     } else {
130         # dcc?
131         &FIXME("floodwho = ???");
132     }
133
134     my $val = &getChanConfDefault('floodRepeat', "2:5", $c);
135     my ($count, $interval) = split /:/, $val;
136
137     # flood repeat protection.
138     if ($addressed) {
139         my $time = $flood{$floodwho}{$message} || 0;
140
141         if (!&IsFlag('o') and $msgType eq 'public' and (time() - $time < $interval)) {
142             ### public != personal who so the below is kind of pointless.
143             my @who;
144             foreach (keys %flood) {
145                 next if (/^\Q$floodwho\E$/);
146                 next if (defined $chan and /^\Q$chan\E$/);
147
148                 push(@who, grep /^\Q$message\E$/i, keys %{ $flood{$_} });
149             }
150
151             return if ($lobotomized);
152
153             if (!scalar @who) {
154                 push(@who,'Someone');
155             }
156             &msg($who,join(' ', @who)." already said that ". (time - $time) ." seconds ago" );
157
158             ### TODO: delete old floodwarn{} keys.
159             my $floodwarn = 0;
160             if (!exists $floodwarn{$floodwho}) {
161                 $floodwarn++;
162             } else {
163                 $floodwarn++ if (time() - $floodwarn{$floodwho} > $interval);
164             }
165
166             if ($floodwarn) {
167                 &status("FLOOD repetition detected from $floodwho.");
168                 $floodwarn{$floodwho} = time();
169             }
170
171             return;
172         }
173
174         if ($addrchar) {
175             &status("$b_cyan$who$ob is short-addressing $mynick");
176         } elsif ($msgType eq 'private') {       # private.
177             &status("$b_cyan$who$ob is /msg'ing $mynick");
178         } else {                                # public?
179             &status("$b_cyan$who$ob is addressing $mynick");
180         }
181
182         $flood{$floodwho}{$message} = time();
183     } elsif ($msgType eq 'public' and &IsChanConf('kickOnRepeat') > 0) {
184         # unaddressed, public only.
185
186         ### TODO: use a separate "short-time" hash.
187         my @data;
188         @data   = keys %{ $flood{$floodwho} } if (exists $flood{$floodwho});
189     }
190
191     $val = &getChanConfDefault('floodMessages', "5:30", $c);
192     ($count, $interval) = split /:/, $val;
193
194     # flood overflow protection.
195     if ($addressed) {
196         foreach (keys %{ $flood{$floodwho} }) {
197             next unless (time() - $flood{$floodwho}{$_} > $interval);
198             delete $flood{$floodwho}{$_};
199         }
200
201         my $i = scalar keys %{ $flood{$floodwho} };
202         if ($i > $count) {
203             my $expire = $param{'ignoreAutoExpire'} || 5;
204
205 #           &msg($who,"overflow of messages ($i > $count)");
206             &msg($who,"Too many queries from you, ignoring for $expire minutes.");
207             &status("FLOOD overflow detected from $floodwho; ignoring");
208
209             &ignoreAdd("*!$uh", $chan, $expire, "flood overflow auto-detected.");
210             return;
211         }
212
213         $flood{$floodwho}{$message} = time();
214     }
215
216     my @ignore;
217     if ($msgType =~ /public/i) {                    # public.
218         $talkchannel    = $chan;
219         &status("<$orig{who}/$chan> $orig{message}");
220         push(@ignore, keys %{ $ignore{$chan} }) if (exists $ignore{$chan});
221     } elsif ($msgType =~ /private/i) {             # private.
222         &status("[$orig{who}] $orig{message}");
223         $talkchannel    = undef;
224         $chan           = '_default';
225     } else {
226         &DEBUG("unknown msgType => $msgType.");
227     }
228     push(@ignore, keys %{ $ignore{'*'} }) if (exists $ignore{'*'});
229
230     if ((!$skipmessage or &IsChanConf('seenStoreAll') > 0) and
231             &IsChanConf('sed') > 0 and &IsChanConf('seen') > 0 and
232             $msgType =~ /public/ and
233             $orig{message} =~ /^s\/([^;\/]*)\/([^;\/]*)\/([g]*)$/) {
234         my $sedmsg = $seencache{$who}{'msg'};
235         eval "\$sedmsg =~ s/\Q$1\E/\Q$2\E/$3;";
236         $sedmsg =~ s/^(.{255}).*$/$1.../; # 255 char max to prevent flood
237
238         if ($sedmsg ne $seencache{$who}{'msg'}) {
239             &DEBUG("sed \"" . $orig{message} . "\" \"" .
240                     $seencache{$who}{'msg'} . "\" \"" . $sedmsg. "\"");
241             &msg($talkchannel, "$orig{who} meant: $sedmsg");
242         }
243     } elsif ((!$skipmessage or &IsChanConf('seenStoreAll') > 0) and
244             &IsChanConf('seen') > 0 and $msgType =~ /public/) {
245         $seencache{$who}{'time'} = time();
246         $seencache{$who}{'nick'} = $orig{who};
247         $seencache{$who}{'host'} = $uh;
248         $seencache{$who}{'chan'} = $talkchannel;
249         $seencache{$who}{'msg'}  = $orig{message};
250         $seencache{$who}{'msgcount'}++;
251     }
252     if (&IsChanConf('minVolunteerLength') > 0) {
253         # FIXME hack to treat unaddressed as if using addrchar
254         $addrchar = 1;
255     }
256     return if ($skipmessage);
257     return unless ($addrchar or $addressed);
258
259     foreach (@ignore) {
260         s/\*/\\S*/g;
261
262         next unless (eval { $nuh =~ /^$_$/i } );
263
264         # better to ignore an extra message than to allow one to get
265         # through, although it would be better to go through ignore
266         # checking again.
267         if (time() - ($cache{ignoreCheckTime} || 0) > 60) {
268             &ignoreCheck();
269         }
270
271         &status("IGNORE <$who> $message");
272         return;
273     }
274
275     if (defined $nuh) {
276         if (!defined $userHandle) {
277             &DEBUG("line 1074: need verifyUser?");
278             &verifyUser($who, $nuh);
279         }
280     } else {
281         &DEBUG("hookMsg: 'nuh' not defined?");
282     }
283
284 ### For extra debugging purposes...
285     if ($_ = &process()) {
286 #       &DEBUG("IrcHooks: process returned '$_'.");
287     }
288
289     # hack to remove +o from ppl with +O flag.
290     if (exists $users{$userHandle} && exists $users{$userHandle}{FLAGS} &&
291         $users{$userHandle}{FLAGS} =~ /O/
292     ) {
293         $users{$userHandle}{FLAGS} =~ s/o//g;
294     }
295
296     return;
297 }
298
299 # this is basically run on on_join or on_quit
300 sub chanLimitVerify {
301     my($c)      = @_;
302     $chan       = $c;
303     my $l       = $channels{$chan}{'l'};
304
305     return unless (&IsChanConf('chanlimitcheck') > 0);
306
307     if (scalar keys %netsplit) {
308         &WARN("clV: netsplit active (1, chan = $chan); skipping.");
309         return;
310     }
311
312     if (!defined $l) {
313         &DEBUG("$chan: running chanlimitCheck from chanLimitVerify.");
314         &chanlimitCheck();
315         return;
316     }
317
318     # only change it if it's not set.
319     my $plus  = &getChanConfDefault('chanlimitcheckPlus', 5, $chan);
320     my $count = scalar(keys %{ $channels{$chan}{''} });
321     my $int   = &getChanConfDefault('chanlimitcheckInterval', 10, $chan);
322
323     my $delta = $count + $plus - $l;
324 #   $delta    =~ s/^\-//;
325
326     if ($plus <= 3) {
327         &WARN("clc: stupid to have plus at $plus, fix it!");
328     }
329
330     if (exists $cache{chanlimitChange}{$chan}) {
331         if (time() - $cache{chanlimitChange}{$chan} < $int*60) {
332             return;
333         }
334     }
335
336     &chanServCheck($chan);
337
338     ### TODO: unify code with chanlimitcheck()
339     return if ($delta > 5);
340
341     &status("clc: big change in limit for $chan ($delta);".
342                 "going for it. (was: $l; now: ".($count+$plus).")");
343
344     $conn->mode($chan, "+l", $count+$plus);
345     $cache{chanlimitChange}{$chan} = time();
346 }
347
348 sub chanServCheck {
349     ($chan) = @_;
350
351     if (!defined $chan or $chan =~ /^\s*$/) {
352         &WARN("chanServCheck: chan == NULL.");
353         return 0;
354     }
355
356     return unless (&IsChanConf('chanServCheck') > 0);
357
358     &VERB("chanServCheck($chan) called.",2);
359
360     if ( &IsParam('nickServ_pass') and !$nickserv) {
361         $conn->who('NickServ');
362         return 0;
363     }
364
365     # check for first hash then for next hash.
366     # TODO: a function for &ischanop()? &isvoice()?
367     if (exists $channels{lc $chan} and exists $channels{lc $chan}{'o'}{$ident}) {
368         return 0;
369     }
370
371     &status("ChanServ ==> Requesting ops for $chan. (chanServCheck)");
372     &msg('ChanServ', "OP $chan");
373     return 1;
374 }
375
376 1;
377
378 # vim:ts=4:sw=4:expandtab:tw=80