]> git.donarmstrong.com Git - infobot.git/blob - src/IRC/IrcHelpers.pl
* Merge changes from prposed changes
[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             elsif ($message =~ s/^\Q~\E//){
108                 @chans = &getNickInChans('apt');
109                 if (not grep $chan, @chans){
110                     $addrchar = 1;
111                     $addressed = 1;
112                 }
113             }
114         }
115
116         if (!$addressed and $message =~ /^($mask{nick})([\;\:\>\, ]+) */) {
117             my $newmessage = $';
118             if ($1 =~ /^\Q$mynick\E$/i) {
119                 $message   = $newmessage;
120                 $addressed = 1;
121             } else {
122                 # ignore messages addressed to other people or unaddressed.
123                 $skipmessage++ if ($2 ne '' and $2 !~ /^ /);
124             }
125         }
126     }
127
128     # Determine floodwho.
129     my $c       = '_default';
130     if ($msgType =~ /public/i) {
131         # public.
132         $floodwho = $c = lc $chan;
133     } elsif ($msgType =~ /private/i) {
134         # private.
135         $floodwho = lc $who;
136     } else {
137         # dcc?
138         &FIXME("floodwho = ???");
139     }
140
141     my $val = &getChanConfDefault('floodRepeat', "2:5", $c);
142     my ($count, $interval) = split /:/, $val;
143
144     # flood repeat protection.
145     if ($addressed) {
146         my $time = $flood{$floodwho}{$message} || 0;
147
148         if (!&IsFlag('o') and $msgType eq 'public' and (time() - $time < $interval)) {
149             ### public != personal who so the below is kind of pointless.
150             my @who;
151             foreach (keys %flood) {
152                 next if (/^\Q$floodwho\E$/);
153                 next if (defined $chan and /^\Q$chan\E$/);
154
155                 push(@who, grep /^\Q$message\E$/i, keys %{ $flood{$_} });
156             }
157
158             return if ($lobotomized);
159
160             if (!scalar @who) {
161                 push(@who,'Someone');
162             }
163             &msg($who,join(' ', @who)." already said that ". (time - $time) ." seconds ago" );
164
165             ### TODO: delete old floodwarn{} keys.
166             my $floodwarn = 0;
167             if (!exists $floodwarn{$floodwho}) {
168                 $floodwarn++;
169             } else {
170                 $floodwarn++ if (time() - $floodwarn{$floodwho} > $interval);
171             }
172
173             if ($floodwarn) {
174                 &status("FLOOD repetition detected from $floodwho.");
175                 $floodwarn{$floodwho} = time();
176             }
177
178             return;
179         }
180
181         if ($addrchar) {
182             &status("$b_cyan$who$ob is short-addressing $mynick");
183         } elsif ($msgType eq 'private') {       # private.
184             &status("$b_cyan$who$ob is /msg'ing $mynick");
185         } else {                                # public?
186             &status("$b_cyan$who$ob is addressing $mynick");
187         }
188
189         $flood{$floodwho}{$message} = time();
190     } elsif ($msgType eq 'public' and &IsChanConf('kickOnRepeat') > 0) {
191         # unaddressed, public only.
192
193         ### TODO: use a separate "short-time" hash.
194         my @data;
195         @data   = keys %{ $flood{$floodwho} } if (exists $flood{$floodwho});
196     }
197
198     $val = &getChanConfDefault('floodMessages', "5:30", $c);
199     ($count, $interval) = split /:/, $val;
200
201     # flood overflow protection.
202     if ($addressed) {
203         foreach (keys %{ $flood{$floodwho} }) {
204             next unless (time() - $flood{$floodwho}{$_} > $interval);
205             delete $flood{$floodwho}{$_};
206         }
207
208         my $i = scalar keys %{ $flood{$floodwho} };
209         if ($i > $count) {
210             my $expire = $param{'ignoreAutoExpire'} || 5;
211
212 #           &msg($who,"overflow of messages ($i > $count)");
213             &msg($who,"Too many queries from you, ignoring for $expire minutes.");
214             &status("FLOOD overflow detected from $floodwho; ignoring");
215
216             &ignoreAdd("*!$uh", $chan, $expire, "flood overflow auto-detected.");
217             return;
218         }
219
220         $flood{$floodwho}{$message} = time();
221     }
222
223     my @ignore;
224     if ($msgType =~ /public/i) {                    # public.
225         $talkchannel    = $chan;
226         &status("<$orig{who}/$chan> $orig{message}");
227         push(@ignore, keys %{ $ignore{$chan} }) if (exists $ignore{$chan});
228     } elsif ($msgType =~ /private/i) {             # private.
229         &status("[$orig{who}] $orig{message}");
230         $talkchannel    = undef;
231         $chan           = '_default';
232     } else {
233         &DEBUG("unknown msgType => $msgType.");
234     }
235     push(@ignore, keys %{ $ignore{'*'} }) if (exists $ignore{'*'});
236
237     if ((!$skipmessage or &IsChanConf('seenStoreAll') > 0) and
238             &IsChanConf('sed') > 0 and &IsChanConf('seen') > 0 and
239             $msgType =~ /public/ and
240             $orig{message} =~ /^s\/([^;\/]*)\/([^;\/]*)\/([g]*)$/) {
241         my $sedmsg = $seencache{$who}{'msg'};
242         eval "\$sedmsg =~ s/\Q$1\E/\Q$2\E/$3;";
243         $sedmsg =~ s/^(.{255}).*$/$1.../; # 255 char max to prevent flood
244
245         if ($sedmsg ne $seencache{$who}{'msg'}) {
246             &DEBUG("sed \"" . $orig{message} . "\" \"" .
247                     $seencache{$who}{'msg'} . "\" \"" . $sedmsg. "\"");
248             &msg($talkchannel, "$orig{who} meant: $sedmsg");
249         }
250     } elsif ((!$skipmessage or &IsChanConf('seenStoreAll') > 0) and
251             &IsChanConf('seen') > 0 and $msgType =~ /public/) {
252         $seencache{$who}{'time'} = time();
253         $seencache{$who}{'nick'} = $orig{who};
254         $seencache{$who}{'host'} = $uh;
255         $seencache{$who}{'chan'} = $talkchannel;
256         $seencache{$who}{'msg'}  = $orig{message};
257         $seencache{$who}{'msgcount'}++;
258     }
259     if (&IsChanConf('minVolunteerLength') > 0) {
260         # FIXME hack to treat unaddressed as if using addrchar
261         $addrchar = 1;
262     }
263     return if ($skipmessage);
264     return unless ($addrchar or $addressed);
265
266     foreach (@ignore) {
267         s/\*/\\S*/g;
268
269         next unless (eval { $nuh =~ /^$_$/i } );
270
271         # better to ignore an extra message than to allow one to get
272         # through, although it would be better to go through ignore
273         # checking again.
274         if (time() - ($cache{ignoreCheckTime} || 0) > 60) {
275             &ignoreCheck();
276         }
277
278         &status("IGNORE <$who> $message");
279         return;
280     }
281
282     if (defined $nuh) {
283         if (!defined $userHandle) {
284             &DEBUG("line 1074: need verifyUser?");
285             &verifyUser($who, $nuh);
286         }
287     } else {
288         &DEBUG("hookMsg: 'nuh' not defined?");
289     }
290
291 ### For extra debugging purposes...
292     if ($_ = &process()) {
293 #       &DEBUG("IrcHooks: process returned '$_'.");
294     }
295
296     # hack to remove +o from ppl with +O flag.
297     if (exists $users{$userHandle} && exists $users{$userHandle}{FLAGS} &&
298         $users{$userHandle}{FLAGS} =~ /O/
299     ) {
300         $users{$userHandle}{FLAGS} =~ s/o//g;
301     }
302
303     return;
304 }
305
306 # this is basically run on on_join or on_quit
307 sub chanLimitVerify {
308     my($c)      = @_;
309     $chan       = $c;
310     my $l       = $channels{$chan}{'l'};
311
312     return unless (&IsChanConf('chanlimitcheck') > 0);
313
314     if (scalar keys %netsplit) {
315         &WARN("clV: netsplit active (1, chan = $chan); skipping.");
316         return;
317     }
318
319     if (!defined $l) {
320         &DEBUG("$chan: running chanlimitCheck from chanLimitVerify.");
321         &chanlimitCheck();
322         return;
323     }
324
325     # only change it if it's not set.
326     my $plus  = &getChanConfDefault('chanlimitcheckPlus', 5, $chan);
327     my $count = scalar(keys %{ $channels{$chan}{''} });
328     my $int   = &getChanConfDefault('chanlimitcheckInterval', 10, $chan);
329
330     my $delta = $count + $plus - $l;
331 #   $delta    =~ s/^\-//;
332
333     if ($plus <= 3) {
334         &WARN("clc: stupid to have plus at $plus, fix it!");
335     }
336
337     if (exists $cache{chanlimitChange}{$chan}) {
338         if (time() - $cache{chanlimitChange}{$chan} < $int*60) {
339             return;
340         }
341     }
342
343     &chanServCheck($chan);
344
345     ### TODO: unify code with chanlimitcheck()
346     return if ($delta > 5);
347
348     &status("clc: big change in limit for $chan ($delta);".
349                 "going for it. (was: $l; now: ".($count+$plus).")");
350
351     $conn->mode($chan, "+l", $count+$plus);
352     $cache{chanlimitChange}{$chan} = time();
353 }
354
355 sub chanServCheck {
356     ($chan) = @_;
357
358     if (!defined $chan or $chan =~ /^\s*$/) {
359         &WARN("chanServCheck: chan == NULL.");
360         return 0;
361     }
362
363     return unless (&IsChanConf('chanServCheck') > 0);
364
365     &VERB("chanServCheck($chan) called.",2);
366
367     if ( &IsParam('nickServ_pass') and !$nickserv) {
368         $conn->who('NickServ');
369         return 0;
370     }
371
372     # check for first hash then for next hash.
373     # TODO: a function for &ischanop()? &isvoice()?
374     if (exists $channels{lc $chan} and exists $channels{lc $chan}{'o'}{$ident}) {
375         return 0;
376     }
377
378     &status("ChanServ ==> Requesting ops for $chan. (chanServCheck)");
379     &msg('ChanServ', "OP $chan");
380     return 1;
381 }
382
383 1;
384
385 # vim:ts=4:sw=4:expandtab:tw=80