2 # IrcHooks.pl: IRC Hooks stuff.
6 # NOTE: Based on code by Kevin Lenzo & Patrick Cole (c) 1997
9 #######################################################################
10 ####### IRC HOOK HELPERS IRC HOOK HELPERS IRC HOOK HELPERS ########
11 #######################################################################
14 # Usage: &hookMode($nick, $modes, @targets);
16 my ( $nick, $modes, @targets ) = @_;
20 foreach $mode ( split( //, $modes ) ) {
22 # sign. tmp parity needed to store current state
23 if ( $mode =~ /[-+]/ ) {
24 $parity = 1 if ( $mode eq "+" );
25 $parity = 0 if ( $mode eq "-" );
30 if ( $mode =~ /[bklov]/ ) {
31 my $target = shift @targets;
34 $chanstats{ lc $chan }{'Op'}++ if ( $mode eq 'o' );
35 $chanstats{ lc $chan }{'Ban'}++ if ( $mode eq 'b' );
37 $chanstats{ lc $chan }{'Deop'}++ if ( $mode eq 'o' );
38 $chanstats{ lc $chan }{'Unban'}++ if ( $mode eq 'b' );
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;
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);
53 &chanLimitVerify($chan);
57 if ( $mode =~ /[l]/ ) {
58 $channels{ lc $chan }{$mode} = $target if $parity;
59 delete $channels{ lc $chan }{$mode} if !$parity;
63 # important channel modes, targetless.
64 if ( $mode =~ /[mt]/ ) {
65 $channels{ lc $chan }{$mode}++ if $parity;
66 delete $channels{ lc $chan }{$mode} if !$parity;
72 ($msgType, $chan, $who, $message) = @_;
76 $orig{message} = $message;
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();
88 if ($msgType =~ /private/) {
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.");
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//) {
107 elsif ($message =~ s/^\Q~\E//){
108 @chans = &getNickInChans('apt');
109 if (not grep $chan, @chans){
116 if (!$addressed and $message =~ /^($mask{nick})([\;\:\>\, ]+) */) {
118 if ($1 =~ /^\Q$mynick\E$/i) {
119 $message = $newmessage;
122 # ignore messages addressed to other people or unaddressed.
123 $skipmessage++ if ($2 ne '' and $2 !~ /^ /);
128 # Determine floodwho.
130 if ($msgType =~ /public/i) {
132 $floodwho = $c = lc $chan;
133 } elsif ($msgType =~ /private/i) {
138 &FIXME("floodwho = ???");
141 my $val = &getChanConfDefault('floodRepeat', "2:5", $c);
142 my ($count, $interval) = split /:/, $val;
144 # flood repeat protection.
146 my $time = $flood{$floodwho}{$message} || 0;
148 if (!&IsFlag('o') and $msgType eq 'public' and (time() - $time < $interval)) {
149 ### public != personal who so the below is kind of pointless.
151 foreach (keys %flood) {
152 next if (/^\Q$floodwho\E$/);
153 next if (defined $chan and /^\Q$chan\E$/);
155 push(@who, grep /^\Q$message\E$/i, keys %{ $flood{$_} });
158 return if ($lobotomized);
161 push(@who,'Someone');
163 &msg($who,join(' ', @who)." already said that ". (time - $time) ." seconds ago" );
165 ### TODO: delete old floodwarn{} keys.
167 if (!exists $floodwarn{$floodwho}) {
170 $floodwarn++ if (time() - $floodwarn{$floodwho} > $interval);
174 &status("FLOOD repetition detected from $floodwho.");
175 $floodwarn{$floodwho} = time();
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");
186 &status("$b_cyan$who$ob is addressing $mynick");
189 $flood{$floodwho}{$message} = time();
190 } elsif ($msgType eq 'public' and &IsChanConf('kickOnRepeat') > 0) {
191 # unaddressed, public only.
193 ### TODO: use a separate "short-time" hash.
195 @data = keys %{ $flood{$floodwho} } if (exists $flood{$floodwho});
198 $val = &getChanConfDefault('floodMessages', "5:30", $c);
199 ($count, $interval) = split /:/, $val;
201 # flood overflow protection.
203 foreach (keys %{ $flood{$floodwho} }) {
204 next unless (time() - $flood{$floodwho}{$_} > $interval);
205 delete $flood{$floodwho}{$_};
208 my $i = scalar keys %{ $flood{$floodwho} };
210 my $expire = $param{'ignoreAutoExpire'} || 5;
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");
216 &ignoreAdd("*!$uh", $chan, $expire, "flood overflow auto-detected.");
220 $flood{$floodwho}{$message} = time();
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;
233 &DEBUG("unknown msgType => $msgType.");
235 push(@ignore, keys %{ $ignore{'*'} }) if (exists $ignore{'*'});
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
245 if ($sedmsg ne $seencache{$who}{'msg'}) {
246 &DEBUG("sed \"" . $orig{message} . "\" \"" .
247 $seencache{$who}{'msg'} . "\" \"" . $sedmsg. "\"");
248 &msg($talkchannel, "$orig{who} meant: $sedmsg");
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'}++;
259 if (&IsChanConf('minVolunteerLength') > 0) {
260 # FIXME hack to treat unaddressed as if using addrchar
263 return if ($skipmessage);
264 return unless ($addrchar or $addressed);
269 next unless (eval { $nuh =~ /^$_$/i } );
271 # better to ignore an extra message than to allow one to get
272 # through, although it would be better to go through ignore
274 if (time() - ($cache{ignoreCheckTime} || 0) > 60) {
278 &status("IGNORE <$who> $message");
283 if (!defined $userHandle) {
284 &DEBUG("line 1074: need verifyUser?");
285 &verifyUser($who, $nuh);
288 &DEBUG("hookMsg: 'nuh' not defined?");
291 ### For extra debugging purposes...
292 if ($_ = &process()) {
293 # &DEBUG("IrcHooks: process returned '$_'.");
296 # hack to remove +o from ppl with +O flag.
297 if (exists $users{$userHandle} && exists $users{$userHandle}{FLAGS} &&
298 $users{$userHandle}{FLAGS} =~ /O/
300 $users{$userHandle}{FLAGS} =~ s/o//g;
306 # this is basically run on on_join or on_quit
307 sub chanLimitVerify {
310 my $l = $channels{$chan}{'l'};
312 return unless (&IsChanConf('chanlimitcheck') > 0);
314 if (scalar keys %netsplit) {
315 &WARN("clV: netsplit active (1, chan = $chan); skipping.");
320 &DEBUG("$chan: running chanlimitCheck from chanLimitVerify.");
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);
330 my $delta = $count + $plus - $l;
334 &WARN("clc: stupid to have plus at $plus, fix it!");
337 if (exists $cache{chanlimitChange}{$chan}) {
338 if (time() - $cache{chanlimitChange}{$chan} < $int*60) {
343 &chanServCheck($chan);
345 ### TODO: unify code with chanlimitcheck()
346 return if ($delta > 5);
348 &status("clc: big change in limit for $chan ($delta);".
349 "going for it. (was: $l; now: ".($count+$plus).")");
351 $conn->mode($chan, "+l", $count+$plus);
352 $cache{chanlimitChange}{$chan} = time();
358 if (!defined $chan or $chan =~ /^\s*$/) {
359 &WARN("chanServCheck: chan == NULL.");
363 return unless (&IsChanConf('chanServCheck') > 0);
365 &VERB("chanServCheck($chan) called.",2);
367 if ( &IsParam('nickServ_pass') and !$nickserv) {
368 $conn->who('NickServ');
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}) {
378 &status("ChanServ ==> Requesting ops for $chan. (chanServCheck)");
379 &msg('ChanServ', "OP $chan");
385 # vim:ts=4:sw=4:expandtab:tw=80