]> git.donarmstrong.com Git - infobot.git/blob - src/IRC/IrcHelpers.pl
1e7c5ba942ca5b58a7dc8a7ea510923db181e101
[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     if ($chan =~ tr/A-Z/a-z/) {
20         &VERB("hookMode: cased $chan.",2);
21     }
22
23     my $mode;
24     foreach $mode (split(//, $modes)) {
25         # sign.
26         if ($mode =~ /[-+]/) {
27             $parity = 1         if ($mode eq "+");
28             $parity = 0         if ($mode eq "-");
29             next;
30         }
31
32         # mode with target.
33         if ($mode =~ /[bklov]/) {
34             my $target = shift @targets;
35
36             if ($parity) {
37                 $chanstats{$chan}{'Op'}++    if ($mode eq "o");
38                 $chanstats{$chan}{'Ban'}++   if ($mode eq "b");
39             } else {
40                 $chanstats{$chan}{'Deop'}++  if ($mode eq "o");
41                 $chanstats{$chan}{'Unban'}++ if ($mode eq "b");
42             }
43
44             # modes w/ target affecting nick => cache it.
45             if ($mode =~ /[bov]/) {
46                 $channels{$chan}{$mode}{$target}++      if  $parity;
47                 delete $channels{$chan}{$mode}{$target} if !$parity;
48
49                 # lets do some custom stuff.
50                 if ($mode eq "o" and $parity) {
51                     if ($nick eq "ChanServ" and $target =~ /^\Q$ident\E$/i) {
52                         &VERB("hookmode: chanserv deopped us! asking",2);
53                         &chanServCheck($chan);
54                     }
55
56                     &chanLimitVerify($chan);
57                 }
58             }
59
60             if ($mode =~ /[l]/) {
61                 $channels{$chan}{$mode} = $target       if  $parity;
62                 delete $channels{$chan}{$mode}          if !$parity;
63             }
64         }
65
66         # important channel modes, targetless.
67         if ($mode =~ /[mt]/) {
68             $channels{$chan}{$mode}++                   if  $parity;
69             delete $channels{$chan}{$mode}              if !$parity;
70         }
71     }
72 }
73
74 sub hookMsg {
75     ($msgType, $chan, $who, $message) = @_;
76     my $skipmessage     = 0;
77     $addressed          = 0;
78     $addressedother     = 0;
79     $orig{message}      = $message;
80     $orig{who}          = $who;
81     $addrchar           = 0;
82
83     $message    =~ s/[\cA-\c_]//ig;     # strip control characters
84     $message    =~ s/^\s+//;            # initial whitespaces.
85     $who        =~ tr/A-Z/a-z/;         # lowercase.
86     my $mynick = $conn->nick();
87
88     &showProc();
89
90     # addressing.
91     if ($msgType =~ /private/) {
92         # private messages.
93         $addressed = 1;
94     } else {
95         # public messages.
96         # addressing revamped by the xk.
97         ### below needs to be fixed...
98         if (&IsParam("addressCharacter")) {
99             if ($message =~ s/^\Q$param{'addressCharacter'}\E//) {
100                 $addrchar  = 1;
101                 $addressed = 1;
102             }
103         }
104
105         if ($message =~ /^($mask{nick})([\;\:\>\, ]+) */) {
106             my $newmessage = $';
107             if ($1 =~ /^\Q$mynick\E$/i) {
108                 $message   = $newmessage;
109                 $addressed = 1;
110             } else {
111                 # ignore messages addressed to other people or unaddressed.
112                 $skipmessage++ if ($2 ne "" and $2 !~ /^ /);
113             }
114         }
115     }
116
117     # Determine floodwho.
118     my $c       = "_default";
119     if ($msgType =~ /public/i) {
120         # public.
121         $floodwho = $c = lc $chan;
122     } elsif ($msgType =~ /private/i) {
123         # private.
124         $floodwho = lc $who;
125     } else {
126         # dcc?
127         &FIXME("floodwho = ???");
128     }
129
130     my $val = &getChanConfDefault("floodRepeat", "2:5", $c);
131     my ($count, $interval) = split /:/, $val;
132
133     # flood repeat protection.
134     if ($addressed) {
135         my $time = $flood{$floodwho}{$message} || 0;
136
137         if ($msgType eq "public" and (time() - $time < $interval)) {
138             ### public != personal who so the below is kind of pointless.
139             my @who;
140             foreach (keys %flood) {
141                 next if (/^\Q$floodwho\E$/);
142                 next if (defined $chan and /^\Q$chan\E$/);
143
144                 push(@who, grep /^\Q$message\E$/i, keys %{ $flood{$_} });
145             }
146
147             return if ($lobotomized);
148
149             if (!scalar @who) {
150                 push(@who,"Someone");
151             }
152             &msg($who,join(' ', @who)." already said that ". (time - $time) ." seconds ago" );
153
154             ### TODO: delete old floodwarn{} keys.
155             my $floodwarn = 0;
156             if (!exists $floodwarn{$floodwho}) {
157                 $floodwarn++;
158             } else {
159                 $floodwarn++ if (time() - $floodwarn{$floodwho} > $interval);
160             }
161
162             if ($floodwarn) {
163                 &status("FLOOD repetition detected from $floodwho.");
164                 $floodwarn{$floodwho} = time();
165             }
166
167             return;
168         }
169
170         if ($addrchar) {
171             &status("$b_cyan$who$ob is short-addressing $mynick");
172         } elsif ($msgType eq "private") {       # private.
173             &status("$b_cyan$who$ob is /msg'ing $mynick");
174         } else {                                # public?
175             &status("$b_cyan$who$ob is addressing $mynick");
176         }
177
178         $flood{$floodwho}{$message} = time();
179     } elsif ($msgType eq "public" and &IsChanConf("kickOnRepeat")) {
180         # unaddressed, public only.
181
182         ### TODO: use a separate "short-time" hash.
183         my @data;
184         @data   = keys %{ $flood{$floodwho} } if (exists $flood{$floodwho});
185     }
186
187     $val = &getChanConfDefault("floodMessages", "5:30", $c);
188     ($count, $interval) = split /:/, $val;
189
190     # flood overflow protection.
191     if ($addressed) {
192         foreach (keys %{ $flood{$floodwho} }) {
193             next unless (time() - $flood{$floodwho}{$_} > $interval);
194             delete $flood{$floodwho}{$_};
195         }
196
197         my $i = scalar keys %{ $flood{$floodwho} };
198         if ($i > $count) {
199             my $expire = $param{'ignoreAutoExpire'} || 5;
200
201 #           &msg($who,"overflow of messages ($i > $count)");
202             &msg($who,"Too many queries from you, ignoring for $expire minutes.");
203             &status("FLOOD overflow detected from $floodwho; ignoring");
204
205             &ignoreAdd("*!$uh", $chan, $expire, "flood overflow auto-detected.");
206             return;
207         }
208
209         $flood{$floodwho}{$message} = time();
210     }
211
212     my @ignore;
213     if ($msgType =~ /public/i) {                    # public.
214         $talkchannel    = $chan;
215         &status("<$orig{who}/$chan> $orig{message}");
216         push(@ignore, keys %{ $ignore{$chan} }) if (exists $ignore{$chan});
217     } elsif ($msgType =~ /private/i) {             # private.
218         &status("[$orig{who}] $orig{message}");
219         $talkchannel    = undef;
220         $chan           = "_default";
221     } else {
222         &DEBUG("unknown msgType => $msgType.");
223     }
224     push(@ignore, keys %{ $ignore{"*"} }) if (exists $ignore{"*"});
225
226     if ((!$skipmessage or &IsChanConf("seenStoreAll") > 0) and
227         &IsChanConf("seen") > 0 and
228         $msgType =~ /public/
229     ) {
230         $seencache{$who}{'time'} = time();
231         $seencache{$who}{'nick'} = $orig{who};
232         $seencache{$who}{'host'} = $uh;
233         $seencache{$who}{'chan'} = $talkchannel;
234         $seencache{$who}{'msg'}  = $orig{message};
235         $seencache{$who}{'msgcount'}++;
236     }
237
238     return if ($skipmessage);
239     return unless (&IsChanConf("minVolunteerLength") or $addressed);
240
241     foreach (@ignore) {
242         s/\*/\\S*/g;
243
244         next unless (eval { $nuh =~ /^$_$/i } );
245
246         # better to ignore an extra message than to allow one to get
247         # through, although it would be better to go through ignore
248         # checking again.
249         if (time() - ($cache{ignoreCheckTime} || 0) > 60) {
250             &ignoreCheck();
251         }
252
253         &status("IGNORE <$who> $message");
254         return;
255     }
256
257     if (defined $nuh) {
258         if (!defined $userHandle) {
259             &DEBUG("line 1074: need verifyUser?");
260             &verifyUser($who, $nuh);
261         }
262     } else {
263         &DEBUG("hookMsg: 'nuh' not defined?");
264     }
265
266 ### For extra debugging purposes...
267     if ($_ = &process()) {
268 #       &DEBUG("IrcHooks: process returned '$_'.");
269     }
270
271     # hack to remove +o from ppl with +O flag.
272     if (exists $users{$userHandle} && exists $users{$userHandle}{FLAGS} &&
273         $users{$userHandle}{FLAGS} =~ /O/
274     ) {
275         $users{$userHandle}{FLAGS} =~ s/o//g;
276     }
277
278     return;
279 }
280
281 # this is basically run on on_join or on_quit
282 sub chanLimitVerify {
283     my($c)      = @_;
284     $chan       = $c;
285     my $l       = $channels{$chan}{'l'};
286
287     return unless (&IsChanConf("chanlimitcheck"));
288
289     if (scalar keys %netsplit) {
290         &WARN("clV: netsplit active (1, chan = $chan); skipping.");
291         return;
292     }
293
294     if (!defined $l) {
295         &DEBUG("$chan: running chanlimitCheck from chanLimitVerify.");
296         &chanlimitCheck();
297         return;
298     }
299
300     # only change it if it's not set.
301     my $plus  = &getChanConfDefault("chanlimitcheckPlus", 5, $chan);
302     my $count = scalar(keys %{ $channels{$chan}{''} });
303     my $int   = &getChanConfDefault("chanlimitcheckInterval", 10, $chan);
304
305     my $delta = $count + $plus - $l;
306 #   $delta    =~ s/^\-//;
307
308     if ($plus <= 3) {
309         &WARN("clc: stupid to have plus at $plus, fix it!");
310     }
311
312     if (exists $cache{chanlimitChange}{$chan}) {
313         if (time() - $cache{chanlimitChange}{$chan} < $int*60) {
314             return;
315         }
316     }
317
318     &chanServCheck($chan);
319
320     ### TODO: unify code with chanlimitcheck()
321     return if ($delta > 5);
322
323     &status("clc: big change in limit for $chan ($delta);".
324                 "going for it. (was: $l; now: ".($count+$plus).")");
325
326     $conn->mode($chan, "+l", $count+$plus);
327     $cache{chanlimitChange}{$chan} = time();
328 }
329
330 sub chanServCheck {
331     ($chan) = @_;
332
333     if (!defined $chan or $chan =~ /^\s*$/) {
334         &WARN("chanServCheck: chan == NULL.");
335         return 0;
336     }
337
338     if ($chan =~ tr/A-Z/a-z/) {
339         &DEBUG("chanServCheck: lowercased chan ($chan)");
340     }
341
342     if (! &IsChanConf("chanServ_ops") ) {
343         return 0;
344     }
345
346     &VERB("chanServCheck($chan) called.",2);
347
348     if ( &IsParam("nickServ_pass") and !$nickserv) {
349         $conn->who("NickServ");
350         return 0;
351     }
352
353     # check for first hash then for next hash.
354     # TODO: a function for &ischanop()? &isvoice()?
355     if (exists $channels{$chan} and exists $channels{$chan}{'o'}{$ident}) {
356         return 0;
357     }
358
359     &status("ChanServ ==> Requesting ops for $chan. (chanServCheck)");
360     &rawout("PRIVMSG ChanServ :OP $chan $ident");
361     return 1;
362 }
363
364 1;