]> git.donarmstrong.com Git - infobot.git/blob - src/IRC/IrcHelpers.pl
* Merge back with trunk to r1810
[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         if ( $message =~ /^($mask{nick})([\;\:\>\, ]+) */ ) {
116             my $newmessage = $';
117             if ( $1 =~ /^\Q$mynick\E$/i ) {
118                 $message   = $newmessage;
119                 $addressed = 1;
120             }
121             else {
122
123                 # ignore messages addressed to other people or unaddressed.
124                 $skipmessage++ if ( $2 ne '' and $2 !~ /^ / );
125             }
126         }
127     }
128
129     # Determine floodwho.
130     my $c = '_default';
131     if ( $msgType =~ /public/i ) {
132
133         # public.
134         $floodwho = $c = lc $chan;
135     }
136     elsif ( $msgType =~ /private/i ) {
137
138         # private.
139         $floodwho = lc $who;
140     }
141     else {
142
143         # dcc?
144         &FIXME('floodwho = ???');
145     }
146
147     my $val = &getChanConfDefault( 'floodRepeat', '2:5', $c );
148     my ( $count, $interval ) = split /:/, $val;
149
150     # flood repeat protection.
151     if ($addressed) {
152         my $time = $flood{$floodwho}{$message} || 0;
153
154         if (    !&IsFlag('o')
155             and $msgType eq 'public'
156             and ( time() - $time < $interval ) )
157         {
158             ### public != personal who so the below is kind of pointless.
159             my @who;
160             foreach ( keys %flood ) {
161                 next if (/^\Q$floodwho\E$/);
162                 next if ( defined $chan and /^\Q$chan\E$/ );
163
164                 push( @who, grep /^\Q$message\E$/i, keys %{ $flood{$_} } );
165             }
166
167             return if ($lobotomized);
168
169             if ( !scalar @who ) {
170                 push( @who, 'Someone' );
171             }
172             &msg( $who,
173                     join( ' ', @who )
174                   . ' already said that '
175                   . ( time - $time )
176                   . ' seconds ago' );
177
178             ### TODO: delete old floodwarn{} keys.
179             my $floodwarn = 0;
180             if ( !exists $floodwarn{$floodwho} ) {
181                 $floodwarn++;
182             }
183             else {
184                 $floodwarn++ if ( time() - $floodwarn{$floodwho} > $interval );
185             }
186
187             if ($floodwarn) {
188                 &status("FLOOD repetition detected from $floodwho.");
189                 $floodwarn{$floodwho} = time();
190             }
191
192             return;
193         }
194
195         if ($addrchar) {
196             &status("$b_cyan$who$ob is short-addressing $mynick");
197         }
198         elsif ( $msgType eq 'private' ) {    # private.
199             &status("$b_cyan$who$ob is /msg'ing $mynick");
200         }
201         else {                               # public?
202             &status("$b_cyan$who$ob is addressing $mynick");
203         }
204
205         $flood{$floodwho}{$message} = time();
206     }
207     elsif ( $msgType eq 'public' and &IsChanConf('kickOnRepeat') > 0 ) {
208
209         # unaddressed, public only.
210
211         ### TODO: use a separate 'short-time' hash.
212         my @data;
213         @data = keys %{ $flood{$floodwho} } if ( exists $flood{$floodwho} );
214     }
215
216     $val = &getChanConfDefault( 'floodMessages', '5:30', $c );
217     ( $count, $interval ) = split /:/, $val;
218
219     # flood overflow protection.
220     if ($addressed) {
221         foreach ( keys %{ $flood{$floodwho} } ) {
222             next unless ( time() - $flood{$floodwho}{$_} > $interval );
223             delete $flood{$floodwho}{$_};
224         }
225
226         my $i = scalar keys %{ $flood{$floodwho} };
227         if ( $i > $count ) {
228             my $expire = $param{'ignoreAutoExpire'} || 5;
229
230             #       &msg($who,"overflow of messages ($i > $count)");
231             &msg( $who,
232                 "Too many queries from you, ignoring for $expire minutes." );
233             &status("FLOOD overflow detected from $floodwho; ignoring");
234
235             &ignoreAdd( "*!$uh", $chan, $expire,
236                 'flood overflow auto-detected.' );
237             return;
238         }
239
240         $flood{$floodwho}{$message} = time();
241     }
242
243     my @ignore;
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} );
248     }
249     elsif ( $msgType =~ /private/i ) {    # private.
250         &status("[$orig{who}] $orig{message}");
251         $talkchannel = undef;
252         $chan        = '_default';
253     }
254     else {
255         &DEBUG("unknown msgType => $msgType.");
256     }
257     push( @ignore, keys %{ $ignore{'*'} } ) if ( exists $ignore{'*'} );
258
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]*)$/ )
264     {
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
268
269         if ( $sedmsg ne $seencache{$who}{'msg'} ) {
270             &DEBUG( "sed \""
271                   . $orig{message} . "\" \""
272                   . $seencache{$who}{'msg'} . "\" \""
273                   . $sedmsg
274                   . "\"" );
275             &msg( $talkchannel, "$orig{who} meant: $sedmsg" );
276         }
277     }
278     elsif ( ( !$skipmessage or &IsChanConf('seenStoreAll') > 0 )
279         and &IsChanConf('seen') > 0
280         and $msgType =~ /public/ )
281     {
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'}++;
288     }
289     if ( &IsChanConf('minVolunteerLength') > 0 ) {
290
291         # FIXME hack to treat unaddressed as if using addrchar
292         $addrchar = 1;
293     }
294     return if ($skipmessage);
295     return unless ( $addrchar or $addressed );
296
297     foreach (@ignore) {
298         s/\*/\\S*/g;
299
300         next unless ( eval { $nuh =~ /^$_$/i } );
301
302         # better to ignore an extra message than to allow one to get
303         # through, although it would be better to go through ignore
304         # checking again.
305         if ( time() - ( $cache{ignoreCheckTime} || 0 ) > 60 ) {
306             &ignoreCheck();
307         }
308
309         &status("IGNORE <$who> $message");
310         return;
311     }
312
313     if ( defined $nuh ) {
314         if ( !defined $userHandle ) {
315             &DEBUG('line 1074: need verifyUser?');
316             &verifyUser( $who, $nuh );
317         }
318     }
319     else {
320         &DEBUG("hookMsg: 'nuh' not defined?");
321     }
322
323 ### For extra debugging purposes...
324     if ( $_ = &process() ) {
325
326         #       &DEBUG("IrcHooks: process returned '$_'.");
327     }
328
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/ )
333     {
334         $users{$userHandle}{FLAGS} =~ s/o//g;
335     }
336
337     return;
338 }
339
340 # this is basically run on on_join or on_quit
341 sub chanLimitVerify {
342     my ($c) = @_;
343     $chan = $c;
344     my $l = $channels{$chan}{'l'};
345
346     return unless ( &IsChanConf('chanlimitcheck') > 0 );
347
348     if ( scalar keys %netsplit ) {
349         &WARN("clV: netsplit active (1, chan = $chan); skipping.");
350         return;
351     }
352
353     if ( !defined $l ) {
354         &DEBUG("$chan: running chanlimitCheck from chanLimitVerify.");
355         &chanlimitCheck();
356         return;
357     }
358
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 );
363
364     my $delta = $count + $plus - $l;
365
366     #   $delta    =~ s/^\-//;
367
368     if ( $plus <= 3 ) {
369         &WARN("clc: stupid to have plus at $plus, fix it!");
370     }
371
372     if ( exists $cache{chanlimitChange}{$chan} ) {
373         if ( time() - $cache{chanlimitChange}{$chan} < $int * 60 ) {
374             return;
375         }
376     }
377
378     &chanServCheck($chan);
379
380     ### TODO: unify code with chanlimitcheck()
381     return if ( $delta > 5 );
382
383     &status("clc: big change in limit for $chan ($delta);"
384           . "going for it. (was: $l; now: "
385           . ( $count + $plus )
386           . ')' );
387
388     $conn->mode( $chan, '+l', $count + $plus );
389     $cache{chanlimitChange}{$chan} = time();
390 }
391
392 sub chanServCheck {
393     ($chan) = @_;
394
395     if ( !defined $chan or $chan =~ /^\s*$/ ) {
396         &WARN('chanServCheck: chan == NULL.');
397         return 0;
398     }
399
400     return unless ( &IsChanConf('chanServCheck') > 0 );
401
402     &VERB( "chanServCheck($chan) called.", 2 );
403
404     if ( &IsParam('nickServ_pass') and !$nickserv ) {
405         $conn->who('NickServ');
406         return 0;
407     }
408
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} )
413     {
414         return 0;
415     }
416
417     &status("ChanServ ==> Requesting ops for $chan. (chanServCheck)");
418     &msg( 'ChanServ', "OP $chan" );
419     return 1;
420 }
421
422 1;
423
424 # vim:ts=4:sw=4:expandtab:tw=80