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 my $nick_postfix_re = '[\;\:\>\, ]+';
116 if (getChanConf('exactAddress',0,$chan)) {
117 $nick_postfix_re = '[\;\:\>\,] '
119 if ( $message =~ /^($mask{nick})($nick_postfix_re) */ ) {
121 if ( $1 =~ /^\Q$mynick\E$/i ) {
122 $message = $newmessage;
127 # ignore messages addressed to other people or unaddressed.
128 $skipmessage++ if ( $2 ne '' and $2 !~ /^ / );
133 # Determine floodwho.
135 if ( $msgType =~ /public/i ) {
138 $floodwho = $c = lc $chan;
140 elsif ( $msgType =~ /private/i ) {
148 &FIXME('floodwho = ???');
151 my $val = &getChanConfDefault( 'floodRepeat', '2:5', $c );
152 my ( $count, $interval ) = split /:/, $val;
153 my ($count_ra,$interval_ra) = split /:/,
154 getChanConfDefault('floodRepeatAll','2:3',$c);
156 # flood repeat protection.
158 my $time = $flood{$floodwho}{$message} || 0;
159 my $time_ra = $flood{$floodwho}{"\0"} || 0;
162 and $msgType eq 'public' and ((time() - $time < $interval) or (time - $time_ra < $interval_ra))) {
163 return if ($lobotomized);
164 if (time() - $time < $interval ) {
165 ### public != personal who so the below is kind of pointless.
167 foreach ( keys %flood ) {
168 next if (/^\Q$floodwho\E$/);
169 next if ( defined $chan and /^\Q$chan\E$/ );
171 push( @who, grep /^\Q$message\E$/i, keys %{ $flood{$_} } );
175 if ( !scalar @who ) {
176 push( @who, 'Someone' );
180 . ' already said that '
186 "You already asked me something ".(time - $time_ra). " seconds ago."
189 ### TODO: delete old floodwarn{} keys.
191 if ( !exists $floodwarn{$floodwho} ) {
195 $floodwarn++ if ( time() - $floodwarn{$floodwho} > $interval );
199 &status("FLOOD repetition detected from $floodwho.");
200 $floodwarn{$floodwho} = time();
206 &status("$b_cyan$who$ob is short-addressing $mynick");
208 elsif ( $msgType eq 'private' ) { # private.
209 &status("$b_cyan$who$ob is /msg'ing $mynick");
212 &status("$b_cyan$who$ob is addressing $mynick");
215 $flood{$floodwho}{$message} = time();
216 $flood{$floodwho}{"\0"} = time;
218 elsif ( $msgType eq 'public' and &IsChanConf('kickOnRepeat') > 0 ) {
220 # unaddressed, public only.
222 ### TODO: use a separate 'short-time' hash.
224 @data = keys %{ $flood{$floodwho} } if ( exists $flood{$floodwho} );
227 $val = &getChanConfDefault( 'floodMessages', '5:30', $c );
228 ( $count, $interval ) = split /:/, $val;
230 # flood overflow protection.
232 foreach ( keys %{ $flood{$floodwho} } ) {
233 next unless ( time() - $flood{$floodwho}{$_} > $interval );
234 delete $flood{$floodwho}{$_};
237 my $i = scalar keys %{ $flood{$floodwho} };
239 my $expire = $param{'ignoreAutoExpire'} || 5;
241 # &msg($who,"overflow of messages ($i > $count)");
243 "Too many queries from you, ignoring for $expire minutes." );
244 &status("FLOOD overflow detected from $floodwho; ignoring");
246 &ignoreAdd( "*!$uh", $chan, $expire,
247 'flood overflow auto-detected.' );
251 $flood{$floodwho}{$message} = time();
252 $flood{$floodwho}{"\0"} = time();
256 if ( $msgType =~ /public/i ) { # public.
257 $talkchannel = $chan;
258 &status("<$orig{who}/$chan> $orig{message}");
259 push( @ignore, keys %{ $ignore{$chan} } ) if ( exists $ignore{$chan} );
261 elsif ( $msgType =~ /private/i ) { # private.
262 &status("[$orig{who}] $orig{message}");
263 $talkchannel = undef;
267 &DEBUG("unknown msgType => $msgType.");
269 push( @ignore, keys %{ $ignore{'*'} } ) if ( exists $ignore{'*'} );
271 push @ignore_msg, keys %{$ignore{'*_msg'}} if exists $ignore{'*_msg'};
273 if ( ( !$skipmessage or &IsChanConf('seenStoreAll') > 0 )
274 and &IsChanConf('sed') > 0
275 and &IsChanConf('seen') > 0
276 and $msgType =~ /public/
277 and $orig{message} =~ /^s\/([^;\/]*)\/([^;\/]*)\/([g]*)$/ )
279 my $sedmsg = $seencache{$who}{'msg'};
280 eval "\$sedmsg =~ s/\Q$1\E/\Q$2\E/$3;";
281 $sedmsg =~ s/^(.{255}).*$/$1.../; # 255 char max to prevent flood
283 if ( $sedmsg ne $seencache{$who}{'msg'} ) {
285 . $orig{message} . "\" \""
286 . $seencache{$who}{'msg'} . "\" \""
289 &msg( $talkchannel, "$orig{who} meant: $sedmsg" );
292 elsif ( ( !$skipmessage or &IsChanConf('seenStoreAll') > 0 )
293 and &IsChanConf('seen') > 0
294 and $msgType =~ /public/ )
296 $seencache{$who}{'time'} = time();
297 $seencache{$who}{'nick'} = $orig{who};
298 $seencache{$who}{'host'} = $uh;
299 $seencache{$who}{'chan'} = $talkchannel;
300 $seencache{$who}{'msg'} = $orig{message};
301 $seencache{$who}{'msgcount'}++;
303 if ( &IsChanConf('minVolunteerLength') > 0 ) {
305 # FIXME hack to treat unaddressed as if using addrchar
308 return if ($skipmessage);
309 return unless ( $addrchar or $addressed );
314 next unless ( eval { $nuh =~ /^$_$/i } );
316 # better to ignore an extra message than to allow one to get
317 # through, although it would be better to go through ignore
319 if ( time() - ( $cache{ignoreCheckTime} || 0 ) > 60 ) {
323 &status("IGNORE <$who> $message");
327 for my $msg_regex (@ignore_msg) {
329 next unless ( eval { $message =~ /\Q$msg_regex\E/i } );
331 # better to ignore an extra message than to allow one to get
332 # through, although it would be better to go through ignore
334 if ( time() - ( $cache{ignoreCheckTime} || 0 ) > 60 ) {
338 &status("IGNORE <$who> $message");
342 if ( defined $nuh ) {
343 if ( !defined $userHandle ) {
344 &DEBUG('line 1074: need verifyUser?');
345 &verifyUser( $who, $nuh );
349 &DEBUG("hookMsg: 'nuh' not defined?");
352 ### For extra debugging purposes...
353 if ( $_ = &process() ) {
355 # &DEBUG("IrcHooks: process returned '$_'.");
358 # hack to remove +o from ppl with +O flag.
359 if ( exists $users{$userHandle}
360 && exists $users{$userHandle}{FLAGS}
361 && $users{$userHandle}{FLAGS} =~ /O/ )
363 $users{$userHandle}{FLAGS} =~ s/o//g;
369 # this is basically run on on_join or on_quit
370 sub chanLimitVerify {
373 my $l = $channels{$chan}{'l'};
375 return unless ( &IsChanConf('chanlimitcheck') > 0 );
377 if ( scalar keys %netsplit ) {
378 &WARN("clV: netsplit active (1, chan = $chan); skipping.");
383 &DEBUG("$chan: running chanlimitCheck from chanLimitVerify.");
388 # only change it if it's not set.
389 my $plus = &getChanConfDefault( 'chanlimitcheckPlus', 5, $chan );
390 my $count = scalar( keys %{ $channels{$chan}{''} } );
391 my $int = &getChanConfDefault( 'chanlimitcheckInterval', 10, $chan );
393 my $delta = $count + $plus - $l;
398 &WARN("clc: stupid to have plus at $plus, fix it!");
401 if ( exists $cache{chanlimitChange}{$chan} ) {
402 if ( time() - $cache{chanlimitChange}{$chan} < $int * 60 ) {
407 &chanServCheck($chan);
409 ### TODO: unify code with chanlimitcheck()
410 return if ( $delta > 5 );
412 &status("clc: big change in limit for $chan ($delta);"
413 . "going for it. (was: $l; now: "
417 $conn->mode( $chan, '+l', $count + $plus );
418 $cache{chanlimitChange}{$chan} = time();
424 if ( !defined $chan or $chan =~ /^\s*$/ ) {
425 &WARN('chanServCheck: chan == NULL.');
429 return unless ( &IsChanConf('chanServCheck') > 0 );
431 &VERB( "chanServCheck($chan) called.", 2 );
433 if ( &IsParam('nickServ_pass') and !$nickserv ) {
434 $conn->who('NickServ');
438 # check for first hash then for next hash.
439 # TODO: a function for &ischanop()? &isvoice()?
440 if ( exists $channels{ lc $chan }
441 and exists $channels{ lc $chan }{'o'}{$ident} )
446 &status("ChanServ ==> Requesting ops for $chan. (chanServCheck)");
447 &msg( 'ChanServ', "OP $chan" );
453 # vim:ts=4:sw=4:expandtab:tw=80