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