]> git.donarmstrong.com Git - infobot.git/blob - src/IRC/IrcHelpers.pl
avoid reassigning to temp upon decode_utf8
[infobot.git] / src / IRC / IrcHelpers.pl
1 #
2 # IrcHooks.pl: IRC Hooks stuff.
3 #      Author: dms
4 #     Version: 20010413
5 #     Created: 20010413
6 #        NOTE: Based on code by Kevin Lenzo & Patrick Cole  (c) 1997
7 #
8
9 #######################################################################
10 ####### IRC HOOK HELPERS   IRC HOOK HELPERS   IRC HOOK HELPERS ########
11 #######################################################################
12
13 #####
14 # Usage: &hookMode($nick, $modes, @targets);
15 sub hookMode {
16     my ( $nick, $modes, @targets ) = @_;
17     my $parity = 0;
18
19     my $mode;
20     foreach $mode ( split( //, $modes ) ) {
21
22         # sign. tmp parity needed to store current state
23         if ( $mode =~ /[-+]/ ) {
24             $parity = 1 if ( $mode eq '+' );
25             $parity = 0 if ( $mode eq '-' );
26             next;
27         }
28
29         # mode with target.
30         if ( $mode =~ /[bklov]/ ) {
31             my $target = shift @targets;
32
33             if ($parity) {
34                 $chanstats{ lc $chan }{'Op'}++  if ( $mode eq 'o' );
35                 $chanstats{ lc $chan }{'Ban'}++ if ( $mode eq 'b' );
36             }
37             else {
38                 $chanstats{ lc $chan }{'Deop'}++  if ( $mode eq 'o' );
39                 $chanstats{ lc $chan }{'Unban'}++ if ( $mode eq 'b' );
40             }
41
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;
46
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);
52                     }
53
54                     &chanLimitVerify($chan);
55                 }
56             }
57
58             if ( $mode =~ /[l]/ ) {
59                 $channels{ lc $chan }{$mode} = $target if $parity;
60                 delete $channels{ lc $chan }{$mode} if !$parity;
61             }
62         }
63
64         # important channel modes, targetless.
65         if ( $mode =~ /[mt]/ ) {
66             $channels{ lc $chan }{$mode}++      if $parity;
67             delete $channels{ lc $chan }{$mode} if !$parity;
68         }
69     }
70 }
71
72 sub hookMsg {
73     ( $msgType, $chan, $who, $message ) = @_;
74     my $skipmessage = 0;
75     $addressed      = 0;
76     $addressedother = 0;
77     $orig{message}  = $message;
78     $orig{who}      = $who;
79     $addrchar       = 0;
80
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();
85
86     &showProc();
87
88     # addressing.
89     if ( $msgType =~ /private/ ) {
90
91         # private messages.
92         $addressed = 1;
93         if ( &IsChanConf('addressCharacter') > 0 ) {
94             $addressCharacter = getChanConf('addressCharacter');
95             if ( $message =~ s/^\Q$addressCharacter\E// ) {
96                 &msg( $who,
97 "The addressCharacter \"$addressCharacter\" is to get my attention in a normal channel. Please leave it off when messaging me directly."
98                 );
99             }
100         }
101     }
102     else {
103
104         # public messages.
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// ) {
110                 $addrchar  = 1;
111                 $addressed = 1;
112             }
113         }
114
115         my $nick_postfix_re = '[\;\:\>\, ]+';
116         if (getChanConf('exactAddress',0,$chan)) {
117             $nick_postfix_re = '[\;\:\>\,] '
118         }
119         if ( $message =~ /^($mask{nick})($nick_postfix_re) */ ) {
120             my $newmessage = $';
121             if ( $1 =~ /^\Q$mynick\E$/i ) {
122                 $message   = $newmessage;
123                 $addressed = 1;
124             }
125             else {
126
127                 # ignore messages addressed to other people or unaddressed.
128                 $skipmessage++ if ( $2 ne '' and $2 !~ /^ / );
129             }
130         }
131     }
132
133     # Determine floodwho.
134     my $c = '_default';
135     if ( $msgType =~ /public/i ) {
136
137         # public.
138         $floodwho = $c = lc $chan;
139     }
140     elsif ( $msgType =~ /private/i ) {
141
142         # private.
143         $floodwho = lc $who;
144     }
145     else {
146
147         # dcc?
148         &FIXME('floodwho = ???');
149     }
150
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);
155
156     # flood repeat protection.
157     if ($addressed) {
158         my $time = $flood{$floodwho}{$message} || 0;
159         my $time_ra = $flood{$floodwho}{"\0"} || 0;
160
161         if (    !&IsFlag('o')
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.
166                 my @who;
167                 foreach ( keys %flood ) {
168                     next if (/^\Q$floodwho\E$/);
169                     next if ( defined $chan and /^\Q$chan\E$/ );
170                     
171                     push( @who, grep /^\Q$message\E$/i, keys %{ $flood{$_} } );
172                 }
173                 
174                 
175                 if ( !scalar @who ) {
176                     push( @who, 'Someone' );
177                 }
178                 &msg( $who,
179                       join( ' ', @who )
180                       . ' already said that '
181                       . ( time - $time )
182                       . ' seconds ago' );
183             }
184             else {
185                 &msg($who,
186                      "You already asked me something ".(time - $time_ra). " seconds ago."
187                     );
188             }
189             ### TODO: delete old floodwarn{} keys.
190             my $floodwarn = 0;
191             if ( !exists $floodwarn{$floodwho} ) {
192                 $floodwarn++;
193             }
194             else {
195                 $floodwarn++ if ( time() - $floodwarn{$floodwho} > $interval );
196             }
197             
198             if ($floodwarn) {
199                 &status("FLOOD repetition detected from $floodwho.");
200                 $floodwarn{$floodwho} = time();
201             }
202             return;
203         }
204
205         if ($addrchar) {
206             &status("$b_cyan$who$ob is short-addressing $mynick");
207         }
208         elsif ( $msgType eq 'private' ) {    # private.
209             &status("$b_cyan$who$ob is /msg'ing $mynick");
210         }
211         else {                               # public?
212             &status("$b_cyan$who$ob is addressing $mynick");
213         }
214
215         $flood{$floodwho}{$message} = time();
216         $flood{$floodwho}{"\0"} = time;
217     }
218     elsif ( $msgType eq 'public' and &IsChanConf('kickOnRepeat') > 0 ) {
219
220         # unaddressed, public only.
221
222         ### TODO: use a separate 'short-time' hash.
223         my @data;
224         @data = keys %{ $flood{$floodwho} } if ( exists $flood{$floodwho} );
225     }
226
227     $val = &getChanConfDefault( 'floodMessages', '5:30', $c );
228     ( $count, $interval ) = split /:/, $val;
229
230     # flood overflow protection.
231     if ($addressed) {
232         foreach ( keys %{ $flood{$floodwho} } ) {
233             next unless ( time() - $flood{$floodwho}{$_} > $interval );
234             delete $flood{$floodwho}{$_};
235         }
236
237         my $i = scalar keys %{ $flood{$floodwho} };
238         if ( $i > $count ) {
239             my $expire = $param{'ignoreAutoExpire'} || 5;
240
241             #       &msg($who,"overflow of messages ($i > $count)");
242             &msg( $who,
243                 "Too many queries from you, ignoring for $expire minutes." );
244             &status("FLOOD overflow detected from $floodwho; ignoring");
245
246             &ignoreAdd( "*!$uh", $chan, $expire,
247                 'flood overflow auto-detected.' );
248             return;
249         }
250
251         $flood{$floodwho}{$message} = time();
252         $flood{$floodwho}{"\0"} = time();
253     }
254
255     my @ignore;
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} );
260     }
261     elsif ( $msgType =~ /private/i ) {    # private.
262         &status("[$orig{who}] $orig{message}");
263         $talkchannel = undef;
264         $chan        = '_default';
265     }
266     else {
267         &DEBUG("unknown msgType => $msgType.");
268     }
269     push( @ignore, keys %{ $ignore{'*'} } ) if ( exists $ignore{'*'} );
270     my @ignore_msg = ();
271     push @ignore_msg, keys %{$ignore{'*_msg'}} if exists $ignore{'*_msg'};
272
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]*)$/ )
278     {
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
282
283         if ( $sedmsg ne $seencache{$who}{'msg'} ) {
284             &DEBUG( "sed \""
285                   . $orig{message} . "\" \""
286                   . $seencache{$who}{'msg'} . "\" \""
287                   . $sedmsg
288                   . "\"" );
289             &msg( $talkchannel, "$orig{who} meant: $sedmsg" );
290         }
291     }
292     elsif ( ( !$skipmessage or &IsChanConf('seenStoreAll') > 0 )
293         and &IsChanConf('seen') > 0
294         and $msgType =~ /public/ )
295     {
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'}++;
302     }
303     if ( &IsChanConf('minVolunteerLength') > 0 ) {
304
305         # FIXME hack to treat unaddressed as if using addrchar
306         $addrchar = 1;
307     }
308     return if ($skipmessage);
309     return unless ( $addrchar or $addressed );
310
311     foreach (@ignore) {
312         s/\*/\\S*/g;
313
314         next unless ( eval { $nuh =~ /^$_$/i } );
315
316         # better to ignore an extra message than to allow one to get
317         # through, although it would be better to go through ignore
318         # checking again.
319         if ( time() - ( $cache{ignoreCheckTime} || 0 ) > 60 ) {
320             &ignoreCheck();
321         }
322
323         &status("IGNORE <$who> $message");
324         return;
325     }
326
327     for my $msg_regex (@ignore_msg) {
328
329         next unless ( eval { $message =~ /\Q$msg_regex\E/i } );
330
331         # better to ignore an extra message than to allow one to get
332         # through, although it would be better to go through ignore
333         # checking again.
334         if ( time() - ( $cache{ignoreCheckTime} || 0 ) > 60 ) {
335             &ignoreCheck();
336         }
337
338         &status("IGNORE <$who> $message");
339         return;
340     }
341
342     if ( defined $nuh ) {
343         if ( !defined $userHandle ) {
344             &DEBUG('line 1074: need verifyUser?');
345             &verifyUser( $who, $nuh );
346         }
347     }
348     else {
349         &DEBUG("hookMsg: 'nuh' not defined?");
350     }
351
352 ### For extra debugging purposes...
353     if ( $_ = &process() ) {
354
355         #       &DEBUG("IrcHooks: process returned '$_'.");
356     }
357
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/ )
362     {
363         $users{$userHandle}{FLAGS} =~ s/o//g;
364     }
365
366     return;
367 }
368
369 # this is basically run on on_join or on_quit
370 sub chanLimitVerify {
371     my ($c) = @_;
372     $chan = $c;
373     my $l = $channels{$chan}{'l'};
374
375     return unless ( &IsChanConf('chanlimitcheck') > 0 );
376
377     if ( scalar keys %netsplit ) {
378         &WARN("clV: netsplit active (1, chan = $chan); skipping.");
379         return;
380     }
381
382     if ( !defined $l ) {
383         &DEBUG("$chan: running chanlimitCheck from chanLimitVerify.");
384         &chanlimitCheck();
385         return;
386     }
387
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 );
392
393     my $delta = $count + $plus - $l;
394
395     #   $delta    =~ s/^\-//;
396
397     if ( $plus <= 3 ) {
398         &WARN("clc: stupid to have plus at $plus, fix it!");
399     }
400
401     if ( exists $cache{chanlimitChange}{$chan} ) {
402         if ( time() - $cache{chanlimitChange}{$chan} < $int * 60 ) {
403             return;
404         }
405     }
406
407     &chanServCheck($chan);
408
409     ### TODO: unify code with chanlimitcheck()
410     return if ( $delta > 5 );
411
412     &status("clc: big change in limit for $chan ($delta);"
413           . "going for it. (was: $l; now: "
414           . ( $count + $plus )
415           . ')' );
416
417     $conn->mode( $chan, '+l', $count + $plus );
418     $cache{chanlimitChange}{$chan} = time();
419 }
420
421 sub chanServCheck {
422     ($chan) = @_;
423
424     if ( !defined $chan or $chan =~ /^\s*$/ ) {
425         &WARN('chanServCheck: chan == NULL.');
426         return 0;
427     }
428
429     return unless ( &IsChanConf('chanServCheck') > 0 );
430
431     &VERB( "chanServCheck($chan) called.", 2 );
432
433     if ( &IsParam('nickServ_pass') and !$nickserv ) {
434         $conn->who('NickServ');
435         return 0;
436     }
437
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} )
442     {
443         return 0;
444     }
445
446     &status("ChanServ ==> Requesting ops for $chan. (chanServCheck)");
447     &msg( 'ChanServ', "OP $chan" );
448     return 1;
449 }
450
451 1;
452
453 # vim:ts=4:sw=4:expandtab:tw=80