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