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