]> git.donarmstrong.com Git - infobot.git/blob - src/IRC/IrcHooks.pl
lowercase chan in on_kick, found by xsdg!
[infobot.git] / src / IRC / IrcHooks.pl
1 #
2 # IrcHooks.pl: IRC Hooks stuff.
3 #      Author: dms
4 #     Version: 20000126
5 #        NOTE: Based on code by Kevin Lenzo & Patrick Cole  (c) 1997
6 #
7
8 if (&IsParam("useStrict")) { use strict; }
9
10 my $nickserv    = 0;
11
12 # GENERIC. TO COPY.
13 sub on_generic {
14     my ($self, $event) = @_;
15     my $nick = $event->nick();
16     my $chan = ($event->to)[0];
17
18     &DEBUG("on_generic: nick => '$nick'.");
19     &DEBUG("on_generic: chan => '$chan'.");
20
21     foreach ($event->args) {
22         &DEBUG("on_generic: args => '$_'.");
23     }
24 }
25
26 sub on_action {
27     my ($self, $event) = @_;
28     my ($nick, @args) = ($event->nick, $event->args);
29     my $chan = ($event->to)[0];
30
31     shift @args;
32
33     if ($chan eq $ident) {
34         &status("* [$nick] @args");
35     } else {
36         &status("* $nick/$chan @args");
37     }
38 }
39
40 sub on_chat {
41     my ($self, $event) = @_;
42     my $msg  = ($event->args)[0];
43     my $sock = ($event->to)[0];
44     my $nick = $event->nick();
45
46     if (!exists $nuh{lc $nick}) {
47         &DEBUG("chat: nuh{$nick} doesn't exist; hrm should retry.");
48         &msg($nick, "could not get whois info?");
49         return;
50     } else {
51         $message        = $msg;
52         $who            = lc $nick;
53         $orig{who}      = $nick;
54         $orig{message}  = $msg;
55         $nuh            = $nuh{$who};
56         $uh             = (split /\!/, $nuh)[1];
57         $addressed      = 1;
58         $msgType        = 'chat';
59     }
60
61     if (!exists $dcc{'CHATvrfy'}{$nick}) {
62         $userHandle     = &verifyUser($who, $nuh);
63         my $crypto      = $userList{$userHandle}{'pass'};
64         my $success     = 0;
65
66         if (!defined $crypto) {
67             &DEBUG("chat: no pass required.");
68             $success++;
69         } elsif (&ckpasswd($msg, $crypto)) {
70             $self->privmsg($sock,"Authorized.");
71             $self->privmsg($sock,"I'll respond as if through /msg and addressed in public. Addition to that, access to 'user' commands will be allowed, like 'die' and 'jump'.");
72             # hrm... it's stupid to ask for factoids _within_ dcc chat.
73             # perhaps it should be default to commands, especially
74             # commands only authorized through DCC CHAT.
75             &status("DCC CHAT: passwd is ok.");
76             $success++;
77         } else {
78             &status("DCC CHAT: incorrect pass; closing connection.");
79             &DEBUG("chat: sock => '$sock'.");
80             $sock->close();
81             delete $dcc{'CHAT'}{$nick};
82             &DEBUG("chat: after closing sock. FIXME");
83             ### BUG: close seizes bot. why?
84         }
85
86         if ($success) {
87             &status("DCC CHAT: user $nick is here!");
88             &DCCBroadcast("$nick ($uh) has joined the chat arena.");
89             $dcc{'CHATvrfy'}{$nick} = 1;
90             if ($userHandle ne "default") {
91                 &dccsay($nick,"Flags: $userList{$userHandle}{'flags'}");
92             }
93         }
94
95         return;
96     }
97
98     $userHandle = &verifyUser($who, $nuh);
99     &status("$b_red=$b_cyan$who$b_red=$ob $message");
100     if ($message =~ s/^\.//) {  # dcc chat commands.
101         ### TODO: make use of &Forker(); here?
102         &loadMyModule($myModules{'ircdcc'});
103         return '$noreply from userD' if (&userDCC() eq $noreply);
104         $conn->privmsg($dcc{'CHAT'}{$who}, "Invalid command.");
105
106     } else {                    # dcc chat arena.
107         foreach (keys %{$dcc{'CHAT'}}) {
108             $conn->privmsg($dcc{'CHAT'}{$_}, "<$who> $orig{message}");
109         }
110     }
111
112     return 'DCC CHAT MESSAGE';
113 }
114
115 sub on_endofmotd {
116     my ($self) = @_;
117
118     if (&IsParam("wingate")) {
119         my $file = "$bot_base_dir/$param{'ircUser'}.wingate";
120         open(IN, $file);
121         while (<IN>) {
122             chop;
123             next unless (/^(\S+)\*$/);
124             push(@wingateBad, $_);
125         }
126         close IN;
127     }
128
129     if ($firsttime) {
130         $conn->schedule(60, \&setupSchedulers, "");
131         $firsttime = 0;
132     }
133
134     if (&IsParam("ircUMode")) {
135         &status("Attempting change of user modes to $param{'ircUMode'}.");
136         &rawout("MODE $ident $param{'ircUMode'}");
137     }
138
139     &status("End of motd. Now lets join some channels...");
140     if (!scalar @joinchan) {
141         &WARN("joinchan array is empty!!!");
142         @joinchan = split /[\t\s]+/, $param{'join_channels'};
143     }
144
145     &joinNextChan();
146 }
147
148 sub on_dcc {
149     my ($self, $event) = @_;
150     my $type = uc( ($event->args)[1] );
151     my $nick = $event->nick();
152
153     # pity Net::IRC doesn't store nuh. Here's a hack :)
154     $self->whois($nick);
155     $type ||= "???";
156
157     if ($type eq 'SEND') {      # GET for us.
158         # incoming DCC SEND. we're receiving a file.
159         my $get = ($event->args)[2];
160         open(DCCGET,">$get");
161
162         $self->new_get($nick,
163                 ($event->args)[2],
164                 ($event->args)[3],
165                 ($event->args)[4],
166                 ($event->args)[5],
167                 \*DCCGET
168         );
169     } elsif ($type eq 'GET') {  # SEND for us?
170         &DEBUG("starting get.");
171         $self->new_send($event->args);
172     } elsif ($type eq 'CHAT') {
173         &DEBUG("starting chat.");
174         $self->new_chat($event);
175     } else {
176         &WARN("${b_green}DCC $type$ob (1)");
177     }
178 }
179
180 sub on_dcc_close {
181     my ($self, $event) = @_;
182     my $nick = $event->nick();
183     my $sock = ($event->to)[0];
184
185     # DCC CHAT close on fork exit workaround.
186     return if ($bot_pid != $$);
187
188     &DEBUG("dcc_close: nick => '$nick'.");
189
190     if (exists $dcc{'SEND'}{$nick} and -f "$param{tempDir}/$nick.txt") {
191         &status("${b_green}DCC SEND$ob close from $b_cyan$nick$ob");
192
193         &status("dcc_close: purging $nick.txt from Debian.pl");
194         unlink "$param{tempDir}/$nick.txt";
195
196         delete $dcc{'SEND'}{$nick};
197     } elsif (exists $dcc{'CHAT'}{$nick} and $dcc{'CHAT'}{$nick} eq $sock) {
198         &status("${b_green}DCC CHAT$ob close from $b_cyan$nick$ob");
199         delete $dcc{'CHAT'}{$nick};
200     } else {
201         &status("${b_green}DCC$ob UNKNOWN close from $b_cyan$nick$ob (2)");
202     }
203 }
204
205 sub on_dcc_open {
206     my ($self, $event) = @_;
207     my $type = uc( ($event->args)[0] );
208     my $nick = $event->nick();
209     my $sock = ($event->to)[0];
210     $msgType = 'chat';
211
212     $type ||= "???";
213
214     if ($type eq 'SEND') {
215         &status("${b_green}DCC lGET$ob established with $b_cyan$nick$ob");
216     } elsif ($type eq 'CHAT') {
217         &status("${b_green}DCC CHAT$ob established with $b_cyan$nick$ob $b_yellow($ob$nuh{$nick}$b_yellow)$ob");
218         $userHandle     = &verifyUser($nick, $nuh{lc $nick});
219         my $crypto      = $userList{$userHandle}{'pass'};
220         $dcc{'CHAT'}{$nick} = $sock;
221
222         if (defined $crypto) {
223             &dccsay($nick,"Enter Password, $userHandle.");
224         } else {
225             &dccsay($nick,"Welcome to blootbot DCC CHAT interface, $userHandle.");
226         }
227     } elsif ($type eq 'SEND') {
228         &DEBUG("Starting DCC receive.");
229         foreach ($event->args) {
230             &DEBUG("  => '$_'.");
231         }
232     } else {
233         &WARN("${b_green}DCC $type$ob (3)");
234     }
235 }
236
237 sub on_disconnect {
238     my ($self, $event) = @_;
239     my $from = $event->from();
240     my $what = ($event->args)[0];
241
242     &status("disconnect from $from ($what).");
243     $ircstats{'DisconnectTime'}         = time();
244     $ircstats{'DisconnectReason'}       = $what;
245     $ircstats{'DisconnectCount'}++;
246
247     # clear any variables on reconnection.
248     $nickserv = 0;
249
250     &clearIRCVars();
251     if (!$self->connect()) {
252         &WARN("not connected? help me. gonna call ircCheck() in 1800s");
253         $conn->schedule(1800, \&ircCheck(), "");
254     }
255 }
256
257 sub on_endofnames {
258     my ($self, $event) = @_;
259     my $chan = ($event->args)[1];
260
261     if (exists $jointime{$chan}) {
262         my $delta_time = sprintf("%.03f", &gettimeofday() - $jointime{$chan});
263         $delta_time    = 0      if ($delta_time < 0);
264
265         &status("$b_blue$chan$ob: sync in ${delta_time}s.");
266     }
267
268     rawout("MODE $chan");
269
270     my $txt;
271     my @array;
272     foreach ("o","v","") {
273         my $count = scalar(keys %{$channels{$chan}{$_}});
274         next unless ($count);
275
276         $txt = "total" if ($_ eq "");
277         $txt = "voice" if ($_ eq "v");
278         $txt = "ops"   if ($_ eq "o");
279
280         push(@array, "$count $txt");
281     }
282     my $chanstats = join(' || ', @array);
283     &status("$b_blue$chan$ob: [$chanstats]");
284
285     if (scalar @joinchan) {     # remaining channels to join.
286         &joinNextChan();
287     } else {
288         ### chanserv support.
289         ### TODO: what if we rejoin a channel.. need to set a var that
290         ###       we've done the request-for-ops-on-join.
291         return unless (&IsParam("chanServ_ops"));
292         return unless ($nickserv);
293
294         my @chans = split(/[\s\t]+/, $param{'chanServ_ops'});
295
296         ### TODO: since this function has a chan arg, why don't we use
297         ###       that instead of doing the following?
298         foreach $chan (keys %channels) {
299             next unless (grep /^$chan$/i, @chans);
300
301             if (!exists $channels{$chan}{'o'}{$ident}) {
302                 &status("ChanServ ==> Requesting ops for $chan.");
303                 &rawout("PRIVMSG ChanServ :OP $chan $ident");
304             }
305         }
306     }
307
308 }
309
310 sub on_init {
311     my ($self, $event) = @_;
312     my (@args) = ($event->args);
313     shift @args;
314
315     &status("@args");
316 }
317
318 sub on_invite {
319     my ($self, $event) = @_;
320     my $chan = ($event->args)[0];
321     my $nick = $event->nick;
322
323     &DEBUG("on_invite: chan => '$chan', nick => '$nick'.");
324
325     # chan + possible_key.
326     ### do we need to know the key if we're invited???
327     ### grep the channel list?
328     foreach (split /[\s\t]+/, $param{'join_channels'}) {
329         next unless /^\Q$chan\E(,\S+)?$/i;
330         s/,/ /;
331
332         next if ($nick =~ /^\Q$ident\E$/);
333         if (&validChan($chan)) {
334             &msg($who, "i'm already in \002$chan\002.");
335             next;
336         }
337
338         &status("invited to $b_blue$_$ob by $b_cyan$who$ob");
339         &joinchan($self, $_);
340     }
341 }
342
343 sub on_join {
344     my ($self, $event) = @_;
345     my ($user,$host) = split(/\@/, $event->userhost);
346     $chan       = lc( ($event->to)[0] );        # CASING!!!!
347     $who        = $event->nick();
348
349     $chanstats{$chan}{'Join'}++;
350     $userstats{lc $who}{'Join'} = time() if (&IsParam("seenStats"));
351
352     &joinfloodCheck($who, $chan, $event->userhost);
353
354     # netjoin detection.
355     my $netsplit = 0;
356     if (exists $netsplit{lc $who}) {
357         delete $netsplit{lc $who};
358         $netsplit = 1;
359     }
360
361     if ($netsplit and !$netsplittime) {
362         &status("ok.... re-running chanlimitCheck in 60.");
363         $conn->schedule(60, sub {
364                 &chanlimitCheck();
365                 $netsplittime = undef;
366         } );
367
368         $netsplittime = time();
369     }
370
371     # how to tell if there's a netjoin???
372
373     my $netsplitstr = "";
374     $netsplitstr = " $b_yellow\[${ob}NETSPLIT VICTIM$b_yellow]$ob" if ($netsplit);
375     &status(">>> join/$b_blue$chan$ob $b_cyan$who$ob $b_yellow($ob$user\@$host$b_yellow)$ob$netsplitstr");
376
377     $channels{$chan}{''}{$who}++;
378     $nuh{lc $who} = $who."!".$user."\@".$host unless (exists $nuh{lc $who});
379
380     ### ROOTWARN:
381     &rootWarn($who,$user,$host,$chan)
382                 if (&IsParam("rootWarn") &&
383                     $user =~ /^r(oo|ew|00)t$/i &&
384                     $channels{$chan}{'o'}{$ident});
385
386     # used to determine sync time.
387     if ($who =~ /^$ident$/i) {
388         if (defined( my $whojoin = $joinverb{$chan} )) {
389             &msg($chan, "Okay, I'm here. (courtesy of $whojoin)");
390             delete $joinverb{$chan};
391         }
392
393         ### TODO: move this to &joinchan()?
394         $jointime{$chan} = &gettimeofday();
395         rawout("WHO $chan");
396     } else {
397         ### TODO: this may go wild on a netjoin :)
398         ### WINGATE:
399         &wingateCheck();
400     }
401 }
402
403 sub on_kick {
404     my ($self, $event) = @_;
405     my ($chan,$reason) = $event->args;
406     my $kicker  = $event->nick;
407     my $kickee  = ($event->to)[0];
408     my $uh      = $event->userhost();
409
410     &status(">>> kick/$b_blue$chan$ob [$b$kickee!$uh$ob] by $b_cyan$kicker$ob $b_yellow($ob$reason$b_yellow)$ob");
411
412     $chan = lc $chan;   # forgot about this, found by xsdg, 20001229.
413     $chanstats{$chan}{'Kick'}++;
414
415     if ($kickee eq $ident) {
416         &clearChanVars($chan);
417
418         &status("SELF attempting to rejoin lost channel $chan");
419         &joinchan($chan);
420     } else {
421         &DeleteUserInfo($kickee,$chan);
422     }
423 }
424
425 sub on_mode {
426     my ($self, $event)  = @_;
427     my ($user, $host)   = split(/\@/, $event->userhost);
428     my @args = $event->args();
429     my $nick = $event->nick();
430     my $chan = ($event->to)[0];
431
432     $args[0] =~ s/\s$//;
433
434     if ($nick eq $chan) {       # UMODE
435         &status(">>> mode $b_yellow\[$ob$b@args$b_yellow\]$ob by $b_cyan$nick$ob");
436     } else {                    # MODE
437         &status(">>> mode/$b_blue$chan$ob $b_yellow\[$ob$b@args$b_yellow\]$ob by $b_cyan$nick$ob");
438         &hookMode($chan, @args);
439     }
440 }
441
442 sub on_modeis {
443     my ($self, $event) = @_;
444     my $nick = $event->nick();
445     my ($myself,$chan,@args) = $event->args();
446
447     &hookMode(lc $chan, @args);         # CASING.
448 }
449
450 sub on_msg {
451     my ($self, $event) = @_;
452     my $nick = $event->nick;
453     my $chan = lc ( ($event->to)[0] );  # CASING.
454     my $msg = ($event->args)[0];
455
456     ($user,$host) = split(/\@/, $event->userhost);
457     $uh         = $event->userhost();
458     $nuh        = $nick."!".$uh;
459
460     &hookMsg('private', $chan, $nick, $msg);
461 }
462
463 sub on_names {
464     my ($self, $event) = @_;
465     my @args = $event->args;
466     my $chan = lc $args[2];             # CASING, the last of them!
467
468     foreach (split / /, @args[3..$#args]) {
469         $channels{$chan}{'o'}{$_}++     if s/\@//;
470         $channels{$chan}{'v'}{$_}++     if s/\+//;
471         $channels{$chan}{''}{$_}++;
472     }
473 }
474
475 sub on_nick {
476     my ($self, $event) = @_;
477     my $nick = $event->nick();
478     my $newnick = ($event->args)[0];
479
480     if (exists $netsplit{lc $newnick}) {
481         &status("Netsplit: $newnick/$nick came back from netsplit and changed to original nick! removing from hash.");
482         delete $netsplit{lc $newnick};
483     }
484
485     my ($chan,$mode);
486     foreach $chan (keys %channels) {
487         foreach $mode (keys %{$channels{$chan}}) {
488             next unless (exists $channels{$chan}{$mode}{$nick});
489
490             $channels{$chan}{$mode}{$newnick} = $channels{$chan}{$mode}{$nick};
491         }
492     }
493     &DeleteUserInfo($nick,keys %channels);
494     $nuh{lc $newnick} = $nuh{lc $nick};
495     delete $nuh{lc $nick};
496
497     # successful self-nick change.
498     if ($nick eq $ident) {
499         &status(">>> I materialized into $b_green$newnick$ob from $nick");
500         $ident = $newnick;
501     } else {
502         &status(">>> $b_cyan$nick$ob materializes into $b_green$newnick$ob");
503     }
504 }
505
506 sub on_nick_taken {
507     my ($self) = @_;
508     my $nick = $self->nick;
509     my $newnick = substr($nick,0,7)."-";
510
511     &status("nick taken; changing to temporary nick.");
512     &nick($newnick);
513     &getNickInUse(1);
514 }
515
516 sub on_notice {
517     my ($self, $event) = @_;
518     my $nick = $event->nick();
519     my $chan = ($event->to)[0];
520     my $args = ($event->args)[0];
521
522     if ($nick =~ /^NickServ$/i) {               # nickserv.
523         &status("NickServ: <== '$args'");
524
525         if ($args =~ /^This nickname is registered/i) {
526             &status("nickserv told us to register; doing it.");
527             if (&IsParam("nickServ_pass")) {
528                 &status("NickServ: ==> Identifying.");
529                 &rawout("PRIVMSG NickServ :IDENTIFY $param{'nickServ_pass'}");
530                 return;
531             } else {
532                 &status("We can't tell nickserv a passwd ;(");
533             }
534         }
535
536         # password accepted.
537         if ($args =~ /^Password a/i) {
538             $nickserv++;
539         }
540     } elsif ($nick =~ /^ChanServ$/i) {          # chanserv.
541         &status("ChanServ: <== '$args'.");
542     } else {
543         if ($chan =~ /^$mask{chan}$/) { # channel notice.
544             &status("-$nick/$chan- $args");
545         } else {
546             $server = $nick unless (defined $server);
547             &status("-$nick- $args");   # private or server notice.
548         }
549     }
550 }
551
552 sub on_other {
553     my ($self, $event) = @_;
554     my $chan = ($event->to)[0];
555     my $nick = $event->nick;
556
557     &status("!!! other called.");
558     &status("!!! $event->args");
559 }
560
561 sub on_part {
562     my ($self, $event) = @_;
563     my $chan = lc( ($event->to)[0] );   # CASING!!!
564     my $nick = $event->nick;
565     my $userhost = $event->userhost;
566
567     if (!exists $floodjoin{$chan}{$nick}{Time}) {
568         &WARN("on_part: $nick/$chan not in floodjoin hash?");
569     } else {
570         delete $floodjoin{$chan}{$nick};
571     }
572
573     $chanstats{$chan}{'Part'}++;
574     &DeleteUserInfo($nick,$chan);
575     &clearChanVars($chan) if ($nick eq $ident);
576     if (!&IsNickInAnyChan($nick) and &IsParam("seenStats")) {
577         delete $userstats{lc $nick};
578     }
579
580     &status(">>> part/$b_blue$chan$ob $b_cyan$nick$ob $b_yellow($ob$userhost$b_yellow)$ob");
581 }
582
583 sub on_ping {
584     my ($self, $event) = @_;
585     my $nick = $event->nick;
586
587     $self->ctcp_reply($nick, join(' ', ($event->args)));
588     &status(">>> ${b_green}CTCP PING$ob request from $b_cyan$nick$ob received.");
589 }
590
591 sub on_ping_reply {
592     my ($self, $event) = @_;
593     my $nick = $event->nick;
594     my $lag = time() - ($event->args)[1];
595
596     &status(">>> ${b_green}CTCP PING$ob reply from $b_cyan$nick$ob: $lag sec.");
597 }
598
599 sub on_public {
600     my ($self, $event) = @_;
601     my $msg  = ($event->args)[0];
602     my $chan = lc( ($event->to)[0] );   # CASING.
603     my $nick = $event->nick;
604     $uh      = $event->userhost();
605     $nuh     = $nick."!".$uh;
606     ($user,$host) = split(/\@/, $uh);
607
608     if ($$ != $bot_pid) {
609         &ERROR("SHOULD NEVER HAPPEN.");
610         exit(0);
611     }
612
613     ### DEBUGGING.
614     if ($statcount < 200) {
615         foreach $chan (grep /[A-Z]/, keys %channels) {
616             &DEBUG("leak: chan => '$chan'.");
617             my ($i,$j);
618             foreach $i (keys %{$channels{$chan}}) {  
619                 foreach (keys %{$channels{$chan}{$i}}) {
620                     &DEBUG("leak:   \$channels{$chan}{$i}{$_} ...");
621                 }
622             }
623         }
624     }
625
626
627     $msgtime = time();
628     $lastWho{$chan} = $nick;
629     ### TODO: use $nick or lc $nick?
630     if (&IsParam("seenStats")) {
631         $userstats{lc $nick}{'Count'}++;
632         $userstats{lc $nick}{'Time'} = time();
633     }
634
635 #    if (&IsParam("hehCounter")) {
636 #       #...
637 #    }
638
639     &hookMsg('public', $chan, $nick, $msg);
640     $chanstats{$chan}{'PublicMsg'}++;
641 }
642
643 sub on_quit {
644     my ($self, $event) = @_;
645     my $nick = $event->nick();
646     my $reason = ($event->args)[0];
647
648     foreach (keys %channels) {
649         # fixes inconsistent chanstats bug #1.
650         next unless (&IsNickInChan($nick,$_));
651         $chanstats{$_}{'SignOff'}++;
652     }
653     &DeleteUserInfo($nick, keys %channels);
654     if (exists $nuh{lc $nick}) {
655         delete $nuh{lc $nick};
656     } else {
657         &DEBUG("on_quit: nuh{lc $nick} does not exist! FIXME");
658     }
659     delete $userstats{lc $nick} if (&IsParam("seenStats"));
660
661     # should fix chanstats inconsistencies bug #2.
662     if ($reason=~/^($mask{host})\s($mask{host})$/) {    # netsplit.
663         $reason = "NETSPLIT: $1 <=> $2";
664
665         $netsplit{lc $nick} = time();
666         if (!exists $netsplitservers{$1}{$2}) {
667             &status("netsplit detected between $1 and $2.");
668             $netsplitservers{$1}{$2} = time();
669         }
670     }
671
672     &status(">>> $b_cyan$nick$ob has signed off IRC $b_red($ob$reason$b_red)$ob");
673     if ($nick =~ /^\Q$ident\E$/) {
674         &DEBUG("!!! THIS SHOULD NEVER HAPPEN. FIXME HOPEFULLY");
675     }
676     if ($nick !~ /^\Q$ident\E$/ and $nick =~ /^\Q$param{'ircNick'}\E$/i) {
677         &status("own nickname became free; changing.");
678         &nick($param{'ircNick'});
679     }
680 }
681
682 sub on_targettoofast {
683     my ($self, $event) = @_;
684     my $nick = $event->nick();
685     my $chan = ($event->to)[0];
686
687     &DEBUG("on_targettoofast: nick => '$nick'.");
688     &DEBUG("on_targettoofast: chan => '$chan'.");
689
690     foreach ($event->args) {
691         &DEBUG("on_targettoofast: args => '$_'.");
692     }
693
694 ###    .* wait (\d+) second/) {
695         &status("X1 $msg");
696         my $sleep = $3 + 10;
697
698         &status("going to sleep for $sleep...");
699         sleep $sleep;
700         &joinNextChan();
701 ### }
702 }
703
704 sub on_topic {
705     my ($self, $event) = @_;
706
707     if (scalar($event->args) == 1) {    # change.
708         my $topic = ($event->args)[0];
709         my $chan  = ($event->to)[0];
710         my $nick  = $event->nick();
711
712         ###
713         # WARNING:
714         #       race condition here. To fix, change '1' to '0'.
715         #       This will keep track of topics set by bot only.
716         ###
717         # UPDATE:
718         #       this may be fixed at a later date with topic queueing.
719         ###
720
721         $topic{$chan}{'Current'} = $topic if (1 and &IsParam("topic") == 1);
722         $chanstats{$chan}{'Topic'}++;
723
724         &status(">>> topic/$b_blue$chan$ob by $b_cyan$nick$ob -> $topic");
725     } else {                                            # join.
726         my ($nick, $chan, $topic) = $event->args;
727         if (&IsParam("topic")) {
728             $topic{$chan}{'Current'}    = $topic;
729             &topicAddHistory($chan,$topic);
730         }
731
732         $topic = &fixString($topic, 1);
733         &status(">>> topic/$b_blue$chan$ob is $topic");
734     }
735 }
736
737 sub on_topicinfo {
738     my ($self, $event) = @_;
739     my ($myself,$chan,$setby,$time) = $event->args();
740
741     my $timestr;
742     if (time() - $time > 60*60*24) {
743         $timestr        = "at ". localtime $time;
744     } else {
745         $timestr        = &Time2String(time() - $time) ." ago";
746     }
747
748     &status(">>> set by $b_cyan$setby$ob $timestr");
749 }
750
751 sub on_version {
752     my ($self, $event) = @_;
753     my $nick = $event->nick;
754
755     &status(">>> ${b_green}CTCP VERSION$ob request from $b_cyan$nick$ob");
756     $self->ctcp_reply($nick, "VERSION $bot_version");
757 }
758
759 sub on_who {
760     my ($self, $event) = @_;
761     my @args    = $event->args;
762
763     $nuh{lc $args[5]} = $args[5]."!".$args[2]."\@".$args[3];
764 }
765
766 sub on_whoisuser {
767     my ($self, $event) = @_;
768     my @args    = $event->args;
769
770     $nuh{lc $args[1]} = $args[1]."!".$args[2]."\@".$args[3];
771 }
772
773 #######################################################################
774 ####### IRC HOOK HELPERS   IRC HOOK HELPERS   IRC HOOK HELPERS ########
775 #######################################################################
776
777 #####
778 # Usage: &hookMode($chan, $modes, @targets);
779 sub hookMode {
780     my ($chan, $modes, @targets) = @_;
781     my $parity  = 0;
782
783     $chan = lc $chan;           # !!!.
784
785     my $mode;
786     foreach $mode (split(//, $modes)) {
787         # sign.
788         if ($mode =~ /[-+]/) {
789             $parity = 1         if ($mode eq "+");
790             $parity = 0         if ($mode eq "-");
791             next;
792         }
793
794         # mode with target.
795         if ($mode =~ /[bklov]/) {
796             my $target = shift @targets;
797
798             if ($parity) {
799                 $chanstats{$chan}{'Op'}++    if ($mode eq "o");
800                 $chanstats{$chan}{'Ban'}++   if ($mode eq "b");
801             } else {
802                 $chanstats{$chan}{'Deop'}++  if ($mode eq "o");
803                 $chanstats{$chan}{'Unban'}++ if ($mode eq "b");
804             }
805
806             # modes w/ target affecting nick => cache it.
807             if ($mode =~ /[ov]/) {
808                 $channels{$chan}{$mode}{$target}++      if  $parity;
809                 delete $channels{$chan}{$mode}{$target} if !$parity;
810             }
811
812             if ($mode =~ /[l]/) {
813                 $channels{$chan}{$mode} = $target       if  $parity;
814                 delete $channels{$chan}{$mode}          if !$parity;
815             }
816         }
817
818         # important channel modes, targetless.
819         if ($mode =~ /[mt]/) {
820             $channels{$chan}{$mode}++                   if  $parity;
821             delete $channels{$chan}{$mode}              if !$parity;
822         }
823     }
824 }
825
826 sub hookMsg {
827     ($msgType, $chan, $who, $message) = @_;
828     my $skipmessage     = 0;
829     $addressed          = 0;
830     $addressedother     = 0;
831     $orig{message}      = $message;
832     $orig{who}          = $who;
833     $addrchar           = 0;
834
835     $message    =~ s/[\cA-\c_]//ig;     # strip control characters
836     $message    =~ s/^\s+//;            # initial whitespaces.
837     $who        =~ tr/A-Z/a-z/;         # lowercase.
838
839     &showProc();
840
841     # addressing.
842     if ($msgType =~ /private/) {
843         # private messages.
844         $addressed = 1;
845     } else {
846         # public messages.
847         # addressing revamped by the xk.
848         ### below needs to be fixed...
849         if (&IsParam("addressCharacter")) {
850             if ($message =~ s/^$param{'addressCharacter'}//) {
851                 $addrchar  = 1;
852                 $addressed = 1;
853             }
854         }
855
856         if ($message =~ /^($mask{nick})([\;\:\>\, ]+) */) {
857             my $newmessage = $';
858             if ($1 =~ /^\Q$ident\E$/i) {
859                 $message   = $newmessage;
860                 $addressed = 1;
861             } else {
862                 # ignore messages addressed to other people or unaddressed.
863                 $skipmessage++ if ($2 ne "" and $2 !~ /^ /);
864             }
865         }
866     }
867
868     # Determine floodwho.
869     if ($msgType =~ /public/i) {                # public.
870         $floodwho = lc $chan;
871     } elsif ($msgType =~ /private/i) {  # private.
872         $floodwho = lc $who;
873     } else {                            # dcc?
874         &DEBUG("FIXME: floodwho = ???");
875     }
876
877     my ($count, $interval) = split(/:/, $param{'floodRepeat'} || "2:10");
878
879     # flood repeat protection.
880     if ($addressed) {
881         my $time = $flood{$floodwho}{$message};
882
883         if (defined $time and (time - $time < $interval)) {
884             ### public != personal who so the below is kind of pointless.
885             my @who;
886             foreach (keys %flood) {
887                 next if (/^\Q$floodwho\E$/ or /^\Q$chan\E$/);
888                 push(@who, grep /^\Q$message\E$/i, keys %{$flood{$_}});
889             }
890             if (scalar @who) {
891                 &msg($who, "you already said what ".
892                                 join(' ', @who)." have said.");
893             } else {
894                 &msg($who,"Someone already said that ". (time - $time) ." seconds ago" );
895             }
896
897             ### TODO: delete old floodwarn{} keys.
898             my $floodwarn = 0;
899             if (!exists $floodwarn{$floodwho}) {
900                 $floodwarn++;
901             } else {
902                 $floodwarn++ if (time() - $floodwarn{$floodwho} > $interval);
903             }
904
905             if ($floodwarn) {
906                 &status("FLOOD repetition detected from $floodwho.");
907                 $floodwarn{$floodwho} = time();
908             }
909
910             return;
911         }
912
913         if ($addrchar) {
914             &status("$b_cyan$who$ob is short-addressing me");
915         } else {
916             &status("$b_cyan$who$ob is addressing me");
917         }
918
919         $flood{$floodwho}{$message} = time();
920     }
921
922     ($count, $interval) = split(/:/, $param{'floodMessages'} || "5:30");
923     # flood overflow protection.
924     if ($addressed) {
925         foreach (keys %{$flood{$floodwho}}) {
926             next unless (time() - $flood{$floodwho}{$_} > $interval);
927             delete $flood{$floodwho}{$_};
928         }
929
930         my $i = scalar keys %{$flood{$floodwho}};
931         if ($i > $count) {
932             &msg($who,"overflow of messages ($i > $count)");
933             &status("FLOOD overflow detected from $floodwho; ignoring");
934
935             my $expire = $param{'ignoreAutoExpire'} || 5;
936             $ignoreList{"*!$uh"} = time() + ($expire * 60);
937             return;
938         }
939
940         $flood{$floodwho}{$message} = time();
941     }
942
943     # public.
944     if ($msgType =~ /public/i) {
945         $talkchannel = $chan;
946         &status("<$orig{who}/$chan> $orig{message}");
947     }
948
949     # private.
950     if ($msgType =~ /private/i) {
951         &status("[$orig{who}] $orig{message}");
952     }
953
954     if ((!$skipmessage or &IsParam("seenStoreAll")) and
955         &IsParam("seen") and
956         $msgType =~ /public/
957     ) {
958         $seencache{$who}{'time'} = time();
959         $seencache{$who}{'nick'} = $orig{who};
960         $seencache{$who}{'host'} = $uh;
961         $seencache{$who}{'chan'} = $talkchannel;
962         $seencache{$who}{'msg'}  = $orig{message};
963         $seencache{$who}{'msgcount'}++;
964     }
965
966     return if ($skipmessage);
967     return unless (&IsParam("minVolunteerLength") or $addressed);
968
969     local $ignore = 0;
970     foreach (keys %ignoreList) {
971         my $ignoreRE = $_;
972         my @parts = split /\*/, "a${ignoreRE}a";
973         my $recast = join '\S*', map quotemeta($_), @parts;
974         $recast =~ s/^a(.*)a$/$1/;
975         if ($nuh =~ /^$recast$/) {
976             $ignore++;
977             last;
978         }
979     }
980
981     if (defined $nuh) {
982         $userHandle = &verifyUser($who, $nuh);
983     } else {
984         &DEBUG("hookMsg: 'nuh' not defined?");
985     }
986
987 ### For extra debugging purposes...
988     if ($_ = &process()) {
989 #       &DEBUG("IrcHooks: process returned '$_'.");
990     }
991
992     return;
993 }
994
995 1;