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;
150 # flood repeat protection.
152 my $time = $flood{$floodwho}{$message} || 0;
155 and $msgType eq 'public'
156 and ( time() - $time < $interval ) )
158 ### public != personal who so the below is kind of pointless.
160 foreach ( keys %flood ) {
161 next if (/^\Q$floodwho\E$/);
162 next if ( defined $chan and /^\Q$chan\E$/ );
164 push( @who, grep /^\Q$message\E$/i, keys %{ $flood{$_} } );
167 return if ($lobotomized);
169 if ( !scalar @who ) {
170 push( @who, 'Someone' );
174 . " already said that "
178 ### TODO: delete old floodwarn{} keys.
180 if ( !exists $floodwarn{$floodwho} ) {
184 $floodwarn++ if ( time() - $floodwarn{$floodwho} > $interval );
188 &status("FLOOD repetition detected from $floodwho.");
189 $floodwarn{$floodwho} = time();
196 &status("$b_cyan$who$ob is short-addressing $mynick");
198 elsif ( $msgType eq 'private' ) { # private.
199 &status("$b_cyan$who$ob is /msg'ing $mynick");
202 &status("$b_cyan$who$ob is addressing $mynick");
205 $flood{$floodwho}{$message} = time();
207 elsif ( $msgType eq 'public' and &IsChanConf('kickOnRepeat') > 0 ) {
209 # unaddressed, public only.
211 ### TODO: use a separate "short-time" hash.
213 @data = keys %{ $flood{$floodwho} } if ( exists $flood{$floodwho} );
216 $val = &getChanConfDefault( 'floodMessages', "5:30", $c );
217 ( $count, $interval ) = split /:/, $val;
219 # flood overflow protection.
221 foreach ( keys %{ $flood{$floodwho} } ) {
222 next unless ( time() - $flood{$floodwho}{$_} > $interval );
223 delete $flood{$floodwho}{$_};
226 my $i = scalar keys %{ $flood{$floodwho} };
228 my $expire = $param{'ignoreAutoExpire'} || 5;
230 # &msg($who,"overflow of messages ($i > $count)");
232 "Too many queries from you, ignoring for $expire minutes." );
233 &status("FLOOD overflow detected from $floodwho; ignoring");
235 &ignoreAdd( "*!$uh", $chan, $expire,
236 "flood overflow auto-detected." );
240 $flood{$floodwho}{$message} = time();
244 if ( $msgType =~ /public/i ) { # public.
245 $talkchannel = $chan;
246 &status("<$orig{who}/$chan> $orig{message}");
247 push( @ignore, keys %{ $ignore{$chan} } ) if ( exists $ignore{$chan} );
249 elsif ( $msgType =~ /private/i ) { # private.
250 &status("[$orig{who}] $orig{message}");
251 $talkchannel = undef;
255 &DEBUG("unknown msgType => $msgType.");
257 push( @ignore, keys %{ $ignore{'*'} } ) if ( exists $ignore{'*'} );
259 if ( ( !$skipmessage or &IsChanConf('seenStoreAll') > 0 )
260 and &IsChanConf('sed') > 0
261 and &IsChanConf('seen') > 0
262 and $msgType =~ /public/
263 and $orig{message} =~ /^s\/([^;\/]*)\/([^;\/]*)\/([g]*)$/ )
265 my $sedmsg = $seencache{$who}{'msg'};
266 eval "\$sedmsg =~ s/\Q$1\E/\Q$2\E/$3;";
267 $sedmsg =~ s/^(.{255}).*$/$1.../; # 255 char max to prevent flood
269 if ( $sedmsg ne $seencache{$who}{'msg'} ) {
271 . $orig{message} . "\" \""
272 . $seencache{$who}{'msg'} . "\" \""
275 &msg( $talkchannel, "$orig{who} meant: $sedmsg" );
278 elsif ( ( !$skipmessage or &IsChanConf('seenStoreAll') > 0 )
279 and &IsChanConf('seen') > 0
280 and $msgType =~ /public/ )
282 $seencache{$who}{'time'} = time();
283 $seencache{$who}{'nick'} = $orig{who};
284 $seencache{$who}{'host'} = $uh;
285 $seencache{$who}{'chan'} = $talkchannel;
286 $seencache{$who}{'msg'} = $orig{message};
287 $seencache{$who}{'msgcount'}++;
289 if ( &IsChanConf('minVolunteerLength') > 0 ) {
291 # FIXME hack to treat unaddressed as if using addrchar
294 return if ($skipmessage);
295 return unless ( $addrchar or $addressed );
300 next unless ( eval { $nuh =~ /^$_$/i } );
302 # better to ignore an extra message than to allow one to get
303 # through, although it would be better to go through ignore
305 if ( time() - ( $cache{ignoreCheckTime} || 0 ) > 60 ) {
309 &status("IGNORE <$who> $message");
313 if ( defined $nuh ) {
314 if ( !defined $userHandle ) {
315 &DEBUG("line 1074: need verifyUser?");
316 &verifyUser( $who, $nuh );
320 &DEBUG("hookMsg: 'nuh' not defined?");
323 ### For extra debugging purposes...
324 if ( $_ = &process() ) {
326 # &DEBUG("IrcHooks: process returned '$_'.");
329 # hack to remove +o from ppl with +O flag.
330 if ( exists $users{$userHandle}
331 && exists $users{$userHandle}{FLAGS}
332 && $users{$userHandle}{FLAGS} =~ /O/ )
334 $users{$userHandle}{FLAGS} =~ s/o//g;
340 # this is basically run on on_join or on_quit
341 sub chanLimitVerify {
344 my $l = $channels{$chan}{'l'};
346 return unless ( &IsChanConf('chanlimitcheck') > 0 );
348 if ( scalar keys %netsplit ) {
349 &WARN("clV: netsplit active (1, chan = $chan); skipping.");
354 &DEBUG("$chan: running chanlimitCheck from chanLimitVerify.");
359 # only change it if it's not set.
360 my $plus = &getChanConfDefault( 'chanlimitcheckPlus', 5, $chan );
361 my $count = scalar( keys %{ $channels{$chan}{''} } );
362 my $int = &getChanConfDefault( 'chanlimitcheckInterval', 10, $chan );
364 my $delta = $count + $plus - $l;
369 &WARN("clc: stupid to have plus at $plus, fix it!");
372 if ( exists $cache{chanlimitChange}{$chan} ) {
373 if ( time() - $cache{chanlimitChange}{$chan} < $int * 60 ) {
378 &chanServCheck($chan);
380 ### TODO: unify code with chanlimitcheck()
381 return if ( $delta > 5 );
383 &status("clc: big change in limit for $chan ($delta);"
384 . "going for it. (was: $l; now: "
388 $conn->mode( $chan, "+l", $count + $plus );
389 $cache{chanlimitChange}{$chan} = time();
395 if ( !defined $chan or $chan =~ /^\s*$/ ) {
396 &WARN("chanServCheck: chan == NULL.");
400 return unless ( &IsChanConf('chanServCheck') > 0 );
402 &VERB( "chanServCheck($chan) called.", 2 );
404 if ( &IsParam('nickServ_pass') and !$nickserv ) {
405 $conn->who('NickServ');
409 # check for first hash then for next hash.
410 # TODO: a function for &ischanop()? &isvoice()?
411 if ( exists $channels{ lc $chan }
412 and exists $channels{ lc $chan }{'o'}{$ident} )
417 &status("ChanServ ==> Requesting ops for $chan. (chanServCheck)");
418 &msg( 'ChanServ', "OP $chan" );
424 # vim:ts=4:sw=4:expandtab:tw=80