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' );
38 $chanstats{ lc $chan }{'Deop'}++ if ( $mode eq 'o' );
39 $chanstats{ lc $chan }{'Unban'}++ if ( $mode eq 'b' );
42 # modes w/ target affecting nick => cache it.
43 if ( $mode =~ /[bov]/ ) {
44 $channels{ lc $chan }{$mode}{$target}++ if $parity;
45 delete $channels{ lc $chan }{$mode}{$target} if !$parity;
47 # lets do some custom stuff.
48 if ( $mode =~ /o/ and not $parity ) {
49 if ( $target =~ /^\Q$ident\E$/i ) {
50 &VERB( 'hookmode: someone deopped us!', 2 );
51 &chanServCheck($chan);
54 &chanLimitVerify($chan);
58 if ( $mode =~ /[l]/ ) {
59 $channels{ lc $chan }{$mode} = $target if $parity;
60 delete $channels{ lc $chan }{$mode} if !$parity;
64 # important channel modes, targetless.
65 if ( $mode =~ /[mt]/ ) {
66 $channels{ lc $chan }{$mode}++ if $parity;
67 delete $channels{ lc $chan }{$mode} if !$parity;
73 ( $msgType, $chan, $who, $message ) = @_;
77 $orig{message} = $message;
81 $message =~ s/[\cA-\c_]//ig; # strip control characters
82 $message =~ s/^\s+//; # initial whitespaces.
83 $who =~ tr/A-Z/a-z/; # lowercase.
84 my $mynick = $conn->nick();
89 if ( $msgType =~ /private/ ) {
93 if ( &IsChanConf('addressCharacter') > 0 ) {
94 $addressCharacter = getChanConf('addressCharacter');
95 if ( $message =~ s/^\Q$addressCharacter\E// ) {
97 "The addressCharacter \"$addressCharacter\" is to get my attention in a normal channel. Please leave it off when messaging me directly."
105 # addressing revamped by the xk.
106 ### below needs to be fixed...
107 if ( &IsChanConf('addressCharacter') > 0 ) {
108 $addressCharacter = getChanConf('addressCharacter');
109 if ( $message =~ s/^\Q$addressCharacter\E// ) {
115 if ( $message =~ /^($mask{nick})([\;\:\>\, ]+) */ ) {
117 if ( $1 =~ /^\Q$mynick\E$/i ) {
118 $message = $newmessage;
123 # ignore messages addressed to other people or unaddressed.
124 $skipmessage++ if ( $2 ne '' and $2 !~ /^ / );
129 # Determine floodwho.
131 if ( $msgType =~ /public/i ) {
134 $floodwho = $c = lc $chan;
136 elsif ( $msgType =~ /private/i ) {
144 &FIXME('floodwho = ???');
147 my $val = &getChanConfDefault( 'floodRepeat', '2:5', $c );
148 my ( $count, $interval ) = split /:/, $val;
149 my ($count_ra,$interval_ra) = split /:/,
150 getChanConfDefault('floodRepeatAll','2:3',$c);
152 # flood repeat protection.
154 my $time = $flood{$floodwho}{$message} || 0;
155 my $time_ra = $flood{$floodwho}{"\0"} || 0;
158 and $msgType eq 'public' and ((time() - $time < $interval) or (time - $time_ra < $interval_ra))) {
159 return if ($lobotomized);
160 if (time() - $time < $interval ) {
161 ### public != personal who so the below is kind of pointless.
163 foreach ( keys %flood ) {
164 next if (/^\Q$floodwho\E$/);
165 next if ( defined $chan and /^\Q$chan\E$/ );
167 push( @who, grep /^\Q$message\E$/i, keys %{ $flood{$_} } );
171 if ( !scalar @who ) {
172 push( @who, 'Someone' );
176 . ' already said that '
182 "You already asked me something ".(time - $time_ra). " seconds ago."
185 ### TODO: delete old floodwarn{} keys.
187 if ( !exists $floodwarn{$floodwho} ) {
191 $floodwarn++ if ( time() - $floodwarn{$floodwho} > $interval );
195 &status("FLOOD repetition detected from $floodwho.");
196 $floodwarn{$floodwho} = time();
202 &status("$b_cyan$who$ob is short-addressing $mynick");
204 elsif ( $msgType eq 'private' ) { # private.
205 &status("$b_cyan$who$ob is /msg'ing $mynick");
208 &status("$b_cyan$who$ob is addressing $mynick");
211 $flood{$floodwho}{$message} = time();
212 $flood{$floodwho}{"\0"} = time;
214 elsif ( $msgType eq 'public' and &IsChanConf('kickOnRepeat') > 0 ) {
216 # unaddressed, public only.
218 ### TODO: use a separate 'short-time' hash.
220 @data = keys %{ $flood{$floodwho} } if ( exists $flood{$floodwho} );
223 $val = &getChanConfDefault( 'floodMessages', '5:30', $c );
224 ( $count, $interval ) = split /:/, $val;
226 # flood overflow protection.
228 foreach ( keys %{ $flood{$floodwho} } ) {
229 next unless ( time() - $flood{$floodwho}{$_} > $interval );
230 delete $flood{$floodwho}{$_};
233 my $i = scalar keys %{ $flood{$floodwho} };
235 my $expire = $param{'ignoreAutoExpire'} || 5;
237 # &msg($who,"overflow of messages ($i > $count)");
239 "Too many queries from you, ignoring for $expire minutes." );
240 &status("FLOOD overflow detected from $floodwho; ignoring");
242 &ignoreAdd( "*!$uh", $chan, $expire,
243 'flood overflow auto-detected.' );
247 $flood{$floodwho}{$message} = time();
248 $flood{$floodwho}{"\0"} = time();
252 if ( $msgType =~ /public/i ) { # public.
253 $talkchannel = $chan;
254 &status("<$orig{who}/$chan> $orig{message}");
255 push( @ignore, keys %{ $ignore{$chan} } ) if ( exists $ignore{$chan} );
257 elsif ( $msgType =~ /private/i ) { # private.
258 &status("[$orig{who}] $orig{message}");
259 $talkchannel = undef;
263 &DEBUG("unknown msgType => $msgType.");
265 push( @ignore, keys %{ $ignore{'*'} } ) if ( exists $ignore{'*'} );
267 push @ignore_msg, keys %{$ignore{'*_msg'}} if exists $ignore{'*_msg'};
269 if ( ( !$skipmessage or &IsChanConf('seenStoreAll') > 0 )
270 and &IsChanConf('sed') > 0
271 and &IsChanConf('seen') > 0
272 and $msgType =~ /public/
273 and $orig{message} =~ /^s\/([^;\/]*)\/([^;\/]*)\/([g]*)$/ )
275 my $sedmsg = $seencache{$who}{'msg'};
276 eval "\$sedmsg =~ s/\Q$1\E/\Q$2\E/$3;";
277 $sedmsg =~ s/^(.{255}).*$/$1.../; # 255 char max to prevent flood
279 if ( $sedmsg ne $seencache{$who}{'msg'} ) {
281 . $orig{message} . "\" \""
282 . $seencache{$who}{'msg'} . "\" \""
285 &msg( $talkchannel, "$orig{who} meant: $sedmsg" );
288 elsif ( ( !$skipmessage or &IsChanConf('seenStoreAll') > 0 )
289 and &IsChanConf('seen') > 0
290 and $msgType =~ /public/ )
292 $seencache{$who}{'time'} = time();
293 $seencache{$who}{'nick'} = $orig{who};
294 $seencache{$who}{'host'} = $uh;
295 $seencache{$who}{'chan'} = $talkchannel;
296 $seencache{$who}{'msg'} = $orig{message};
297 $seencache{$who}{'msgcount'}++;
299 if ( &IsChanConf('minVolunteerLength') > 0 ) {
301 # FIXME hack to treat unaddressed as if using addrchar
304 return if ($skipmessage);
305 return unless ( $addrchar or $addressed );
310 next unless ( eval { $nuh =~ /^$_$/i } );
312 # better to ignore an extra message than to allow one to get
313 # through, although it would be better to go through ignore
315 if ( time() - ( $cache{ignoreCheckTime} || 0 ) > 60 ) {
319 &status("IGNORE <$who> $message");
323 for my $msg_regex (@ignore_msg) {
325 next unless ( eval { $message =~ /\Q$msg_regex\E/i } );
327 # better to ignore an extra message than to allow one to get
328 # through, although it would be better to go through ignore
330 if ( time() - ( $cache{ignoreCheckTime} || 0 ) > 60 ) {
334 &status("IGNORE <$who> $message");
338 if ( defined $nuh ) {
339 if ( !defined $userHandle ) {
340 &DEBUG('line 1074: need verifyUser?');
341 &verifyUser( $who, $nuh );
345 &DEBUG("hookMsg: 'nuh' not defined?");
348 ### For extra debugging purposes...
349 if ( $_ = &process() ) {
351 # &DEBUG("IrcHooks: process returned '$_'.");
354 # hack to remove +o from ppl with +O flag.
355 if ( exists $users{$userHandle}
356 && exists $users{$userHandle}{FLAGS}
357 && $users{$userHandle}{FLAGS} =~ /O/ )
359 $users{$userHandle}{FLAGS} =~ s/o//g;
365 # this is basically run on on_join or on_quit
366 sub chanLimitVerify {
369 my $l = $channels{$chan}{'l'};
371 return unless ( &IsChanConf('chanlimitcheck') > 0 );
373 if ( scalar keys %netsplit ) {
374 &WARN("clV: netsplit active (1, chan = $chan); skipping.");
379 &DEBUG("$chan: running chanlimitCheck from chanLimitVerify.");
384 # only change it if it's not set.
385 my $plus = &getChanConfDefault( 'chanlimitcheckPlus', 5, $chan );
386 my $count = scalar( keys %{ $channels{$chan}{''} } );
387 my $int = &getChanConfDefault( 'chanlimitcheckInterval', 10, $chan );
389 my $delta = $count + $plus - $l;
394 &WARN("clc: stupid to have plus at $plus, fix it!");
397 if ( exists $cache{chanlimitChange}{$chan} ) {
398 if ( time() - $cache{chanlimitChange}{$chan} < $int * 60 ) {
403 &chanServCheck($chan);
405 ### TODO: unify code with chanlimitcheck()
406 return if ( $delta > 5 );
408 &status("clc: big change in limit for $chan ($delta);"
409 . "going for it. (was: $l; now: "
413 $conn->mode( $chan, '+l', $count + $plus );
414 $cache{chanlimitChange}{$chan} = time();
420 if ( !defined $chan or $chan =~ /^\s*$/ ) {
421 &WARN('chanServCheck: chan == NULL.');
425 return unless ( &IsChanConf('chanServCheck') > 0 );
427 &VERB( "chanServCheck($chan) called.", 2 );
429 if ( &IsParam('nickServ_pass') and !$nickserv ) {
430 $conn->who('NickServ');
434 # check for first hash then for next hash.
435 # TODO: a function for &ischanop()? &isvoice()?
436 if ( exists $channels{ lc $chan }
437 and exists $channels{ lc $chan }{'o'}{$ident} )
442 &status("ChanServ ==> Requesting ops for $chan. (chanServCheck)");
443 &msg( 'ChanServ', "OP $chan" );
449 # vim:ts=4:sw=4:expandtab:tw=80