2 # IrcHooks.pl: IRC Hooks stuff.
6 # NOTE: Based on code by Kevin Lenzo & Patrick Cole (c) 1997
9 if (&IsParam("useStrict")) { use strict; }
11 #######################################################################
12 ####### IRC HOOK HELPERS IRC HOOK HELPERS IRC HOOK HELPERS ########
13 #######################################################################
16 # Usage: &hookMode($nick, $modes, @targets);
18 my ($nick, $modes, @targets) = @_;
21 if ($chan =~ tr/A-Z/a-z/) {
22 &VERB("hookMode: cased $chan.",2);
26 foreach $mode (split(//, $modes)) {
28 if ($mode =~ /[-+]/) {
29 $parity = 1 if ($mode eq "+");
30 $parity = 0 if ($mode eq "-");
35 if ($mode =~ /[bklov]/) {
36 my $target = shift @targets;
39 $chanstats{$chan}{'Op'}++ if ($mode eq "o");
40 $chanstats{$chan}{'Ban'}++ if ($mode eq "b");
42 $chanstats{$chan}{'Deop'}++ if ($mode eq "o");
43 $chanstats{$chan}{'Unban'}++ if ($mode eq "b");
46 # modes w/ target affecting nick => cache it.
47 if ($mode =~ /[bov]/) {
48 $channels{$chan}{$mode}{$target}++ if $parity;
49 delete $channels{$chan}{$mode}{$target} if !$parity;
51 # lets do some custom stuff.
52 if ($mode eq "o" and $parity) {
53 if ($nick eq "ChanServ" and $target =~ /^\Q$ident\E$/i) {
54 &VERB("hookmode: chanserv deopped us! asking",2);
55 &chanServCheck($chan);
58 &chanLimitVerify($chan);
63 $channels{$chan}{$mode} = $target if $parity;
64 delete $channels{$chan}{$mode} if !$parity;
68 # important channel modes, targetless.
69 if ($mode =~ /[mt]/) {
70 $channels{$chan}{$mode}++ if $parity;
71 delete $channels{$chan}{$mode} if !$parity;
77 ($msgType, $chan, $who, $message) = @_;
81 $orig{message} = $message;
85 $message =~ s/[\cA-\c_]//ig; # strip control characters
86 $message =~ s/^\s+//; # initial whitespaces.
87 $who =~ tr/A-Z/a-z/; # lowercase.
92 if ($msgType =~ /private/) {
97 # addressing revamped by the xk.
98 ### below needs to be fixed...
99 if (&IsParam("addressCharacter")) {
100 if ($message =~ s/^\Q$param{'addressCharacter'}\E//) {
106 if ($message =~ /^($mask{nick})([\;\:\>\, ]+) */) {
108 if ($1 =~ /^\Q$ident\E$/i) {
109 $message = $newmessage;
112 # ignore messages addressed to other people or unaddressed.
113 $skipmessage++ if ($2 ne "" and $2 !~ /^ /);
118 # Determine floodwho.
120 if ($msgType =~ /public/i) { # public.
121 $floodwho = $c = lc $chan;
122 } elsif ($msgType =~ /private/i) { # private.
125 &DEBUG("FIXME: floodwho = ???");
128 my $val = &getChanConfDefault("floodRepeat", "2:5", $c);
129 my ($count, $interval) = split /:/, $val;
131 # flood repeat protection.
133 my $time = $flood{$floodwho}{$message} || 0;
135 if ($msgType eq "public" and (time() - $time < $interval)) {
136 ### public != personal who so the below is kind of pointless.
138 foreach (keys %flood) {
139 next if (/^\Q$floodwho\E$/);
140 next if (defined $chan and /^\Q$chan\E$/);
142 push(@who, grep /^\Q$message\E$/i, keys %{ $flood{$_} });
145 return if ($lobotomized);
148 &msg($who, "you already said what ".
149 join(' ', @who)." have said.");
151 &msg($who,"Someone already said that ". (time - $time) ." seconds ago" );
154 ### TODO: delete old floodwarn{} keys.
156 if (!exists $floodwarn{$floodwho}) {
159 $floodwarn++ if (time() - $floodwarn{$floodwho} > $interval);
163 &status("FLOOD repetition detected from $floodwho.");
164 $floodwarn{$floodwho} = time();
171 &status("$b_cyan$who$ob is short-addressing me");
172 } elsif ($msgType eq "private") { # private.
173 &status("$b_cyan$who$ob is /msg'ing me");
175 &status("$b_cyan$who$ob is addressing me");
178 $flood{$floodwho}{$message} = time();
179 } elsif ($msgType eq "public" and &IsChanConf("kickOnRepeat")) {
180 # unaddressed, public only.
182 ### TODO: use a separate "short-time" hash.
184 @data = keys %{ $flood{$floodwho} } if (exists $flood{$floodwho});
187 $val = &getChanConfDefault("floodMessages", "5:30", $c);
188 ($count, $interval) = split /:/, $val;
190 # flood overflow protection.
192 foreach (keys %{ $flood{$floodwho} }) {
193 next unless (time() - $flood{$floodwho}{$_} > $interval);
194 delete $flood{$floodwho}{$_};
197 my $i = scalar keys %{ $flood{$floodwho} };
199 &msg($who,"overflow of messages ($i > $count)");
200 &status("FLOOD overflow detected from $floodwho; ignoring");
202 my $expire = $param{'ignoreAutoExpire'} || 5;
203 &ignoreAdd("*!$uh", $chan, $expire, "flood overflow auto-detected.");
207 $flood{$floodwho}{$message} = time();
211 if ($msgType =~ /public/i) { # public.
212 $talkchannel = $chan;
213 &status("<$orig{who}/$chan> $orig{message}");
214 push(@ignore, keys %{ $ignore{$chan} }) if (exists $ignore{$chan});
215 } elsif ($msgType =~ /private/i) { # private.
216 &status("[$orig{who}] $orig{message}");
217 $talkchannel = undef;
220 &DEBUG("unknown msgType => $msgType.");
222 push(@ignore, keys %{ $ignore{"*"} }) if (exists $ignore{"*"});
224 if ((!$skipmessage or &IsChanConf("seenStoreAll")) and
225 &IsChanConf("seen") and
228 $seencache{$who}{'time'} = time();
229 $seencache{$who}{'nick'} = $orig{who};
230 $seencache{$who}{'host'} = $uh;
231 $seencache{$who}{'chan'} = $talkchannel;
232 $seencache{$who}{'msg'} = $orig{message};
233 $seencache{$who}{'msgcount'}++;
236 return if ($skipmessage);
237 return unless (&IsParam("minVolunteerLength") or $addressed);
242 next unless (eval { $nuh =~ /^$_$/i } );
244 # better to ignore an extra message than to allow one to get
245 # through, although it would be better to go through ignore
247 if (time() - ($cache{ignoreCheckTime} || 0) > 60) {
251 &status("IGNORE <$who> $message");
256 if (!defined $userHandle) {
257 &DEBUG("line 1074: need verifyUser?");
258 &verifyUser($who, $nuh);
261 &DEBUG("hookMsg: 'nuh' not defined?");
264 ### For extra debugging purposes...
265 if ($_ = &process()) {
266 # &DEBUG("IrcHooks: process returned '$_'.");
272 # this is basically run on on_join or on_quit
273 sub chanLimitVerify {
276 my $l = $channels{$chan}{'l'};
278 return unless (&IsChanConf("chanlimitcheck"));
280 if (scalar keys %netsplit) {
281 &WARN("clV: netsplit active (1, chan = $chan); skipping.");
286 &DEBUG("running chanlimitCheck from chanLimitVerify; FIXME! (chan = $chan)");
291 # only change it if it's not set.
292 my $plus = &getChanConfDefault("chanlimitcheckPlus", 5, $chan);
293 my $count = scalar(keys %{ $channels{$chan}{''} });
294 my $int = &getChanConfDefault("chanlimitcheckInterval", 10, $chan);
296 my $delta = $count + $plus - $l;
300 &WARN("clc: stupid to have plus at $plus, fix it!");
303 if (exists $cache{chanlimitChange}{$chan}) {
304 if (time() - $cache{chanlimitChange}{$chan} < $int*60) {
309 &chanServCheck($chan);
311 ### todo: unify code with chanlimitcheck()
312 return if ($delta > 5);
314 &status("clc: big change in limit for $chan ($delta);".
315 "going for it. (was: $l; now: ".($count+$plus).")");
317 $conn->mode($chan, "+l", $count+$plus);
318 $cache{chanlimitChange}{$chan} = time();
324 if (!defined $chan or $chan =~ /^\s*$/) {
325 &WARN("chanServCheck: chan == NULL.");
329 if ($chan =~ tr/A-Z/a-z/) {
330 &DEBUG("chanServCheck: lowercased chan ($chan)");
333 if (! &IsChanConf("chanServ_ops") ) {
337 &VERB("chanServCheck($chan) called.",2);
339 if ( &IsParam("nickServ_pass") and !$nickserv) {
340 &DEBUG("chanServ_ops($chan): nickserv enabled but not alive? (ircCheck)");
341 $conn->who("NickServ");
344 # check for first hash then for next hash.
345 # todo: a function for &ischanop()? &isvoice()?
346 if (exists $channels{$chan} and exists $channels{$chan}{'o'}{$ident}) {
350 &status("ChanServ ==> Requesting ops for $chan. (chanServCheck)");
351 &rawout("PRIVMSG ChanServ :OP $chan $ident");