]> git.donarmstrong.com Git - infobot.git/blob - src/IRC/IrcHooks.pl
- minor output (debug) removed.
[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 (&IsParam("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     &DEBUG("dcc_close: nick => '$nick'.");
216
217     if (exists $dcc{'SEND'}{$nick} and -f "$param{tempDir}/$nick.txt") {
218         &status("${b_green}DCC SEND$ob close from $b_cyan$nick$ob");
219
220         &status("dcc_close: purging $nick.txt from Debian.pl");
221         unlink "$param{tempDir}/$nick.txt";
222
223         delete $dcc{'SEND'}{$nick};
224     } elsif (exists $dcc{'CHAT'}{$nick} and $dcc{'CHAT'}{$nick} eq $sock) {
225         &status("${b_green}DCC CHAT$ob close from $b_cyan$nick$ob");
226         delete $dcc{'CHAT'}{$nick};
227     } else {
228         &status("${b_green}DCC$ob UNKNOWN close from $b_cyan$nick$ob (2)");
229     }
230 }
231
232 sub on_dcc_open {
233     my ($self, $event) = @_;
234     my $type = uc( ($event->args)[0] );
235     my $nick = $event->nick();
236     my $sock = ($event->to)[0];
237     $msgType = 'chat';
238
239     $type ||= "???";
240
241     if ($type eq 'SEND') {
242         &status("${b_green}DCC lGET$ob established with $b_cyan$nick$ob");
243     } elsif ($type eq 'CHAT') {
244         &status("${b_green}DCC CHAT$ob established with $b_cyan$nick$ob $b_yellow($ob$nuh{$nick}$b_yellow)$ob");
245         $userHandle     = &verifyUser($nick, $nuh{lc $nick});
246         my $crypto      = $users{$userHandle}{PASS};
247         $dcc{'CHAT'}{$nick} = $sock;
248
249         foreach (keys %{ $users{$userHandle} }) {
250             &VERB("   $_ => $users{$userHandle}{$_}",2);
251         }
252
253         if (defined $crypto) {
254 ###         &dccsay($nick,"Enter your password, $userHandle.");
255             &dccsay($nick,"Enter your password.");
256         } else {
257             &dccsay($nick,"Welcome to blootbot DCC CHAT interface, $userHandle.");
258         }
259     } elsif ($type eq 'SEND') {
260         &DEBUG("Starting DCC receive.");
261         foreach ($event->args) {
262             &DEBUG("  => '$_'.");
263         }
264     } else {
265         &WARN("${b_green}DCC $type$ob (3)");
266     }
267 }
268
269 sub on_disconnect {
270     my ($self, $event) = @_;
271     my $from = $event->from();
272     my $what = ($event->args)[0];
273
274     &status("disconnect from $from ($what).");
275     $ircstats{'DisconnectTime'}         = time();
276     $ircstats{'DisconnectReason'}       = $what;
277     $ircstats{'DisconnectCount'}++;
278
279     # clear any variables on reconnection.
280     $nickserv = 0;
281
282     &clearIRCVars();
283     if (!$self->connect()) {
284         &WARN("not connected? help me. gonna call ircCheck() in 1800s");
285         $conn->schedule(1800, \&ircCheck(), "");
286     }
287 }
288
289 sub on_endofnames {
290     my ($self, $event) = @_;
291     my $chan = ($event->args)[1];
292
293     if (exists $jointime{$chan}) {
294         my $delta_time = sprintf("%.03f", &gettimeofday() - $jointime{$chan});
295         $delta_time    = 0      if ($delta_time < 0);
296
297         &status("$b_blue$chan$ob: sync in ${delta_time}s.");
298     }
299
300     rawout("MODE $chan");
301
302     my $txt;
303     my @array;
304     foreach ("o","v","") {
305         my $count = scalar(keys %{$channels{$chan}{$_}});
306         next unless ($count);
307
308         $txt = "total" if ($_ eq "");
309         $txt = "voice" if ($_ eq "v");
310         $txt = "ops"   if ($_ eq "o");
311
312         push(@array, "$count $txt");
313     }
314     my $chanstats = join(' || ', @array);
315     &status("$b_blue$chan$ob: [$chanstats]");
316
317     if (scalar @joinchan) {     # remaining channels to join.
318         &joinNextChan();
319     } else {
320         ### chanserv support.
321         ### TODO: what if we rejoin a channel.. need to set a var that
322         ###       we've done the request-for-ops-on-join.
323         return unless (&IsChanConf("chanServ_ops") > 0);
324         return unless ($nickserv);
325
326         if (!exists $channels{$chan}{'o'}{$ident}) {
327             &status("ChanServ ==> Requesting ops for $chan.");
328             &rawout("PRIVMSG ChanServ :OP $chan $ident");
329         }
330     }
331 }
332
333 sub on_init {
334     my ($self, $event) = @_;
335     my (@args) = ($event->args);
336     shift @args;
337
338     &status("@args");
339 }
340
341 sub on_invite {
342     my ($self, $event) = @_;
343     my $chan = ($event->args)[0];
344     my $nick = $event->nick;
345
346     &DEBUG("on_invite: chan => '$chan', nick => '$nick'.");
347
348     # chan + possible_key.
349     ### do we need to know the key if we're invited???
350     ### grep the channel list?
351     foreach (split /[\s\t]+/, $param{'join_channels'}) {
352         next unless /^\Q$chan\E(,\S+)?$/i;
353         s/,/ /;
354
355         next if ($nick =~ /^\Q$ident\E$/);
356         if (&validChan($chan)) {
357             &msg($who, "i'm already in \002$chan\002.");
358             next;
359         }
360
361         &status("invited to $b_blue$_$ob by $b_cyan$who$ob");
362         &joinchan($self, $_);
363     }
364 }
365
366 sub on_join {
367     my ($self, $event) = @_;
368     my ($user,$host) = split(/\@/, $event->userhost);
369     $chan       = lc( ($event->to)[0] );        # CASING!!!!
370     $who        = $event->nick();
371
372     $chanstats{$chan}{'Join'}++;
373     $userstats{lc $who}{'Join'} = time() if (&IsParam("seenStats"));
374
375     &joinfloodCheck($who, $chan, $event->userhost);
376
377     # netjoin detection.
378     my $netsplit = 0;
379     if (exists $netsplit{lc $who}) {
380         delete $netsplit{lc $who};
381         $netsplit = 1;
382     }
383
384     if ($netsplit and !$netsplittime) {
385         &status("ok.... re-running chanlimitCheck in 60.");
386         $conn->schedule(60, sub {
387                 &chanlimitCheck();
388                 $netsplittime = undef;
389         } );
390
391         $netsplittime = time();
392     }
393
394     # how to tell if there's a netjoin???
395
396     my $netsplitstr = "";
397     $netsplitstr = " $b_yellow\[${ob}NETSPLIT VICTIM$b_yellow]$ob" if ($netsplit);
398     &status(">>> join/$b_blue$chan$ob $b_cyan$who$ob $b_yellow($ob$user\@$host$b_yellow)$ob$netsplitstr");
399
400     $channels{$chan}{''}{$who}++;
401     $nuh{lc $who} = $who."!".$user."\@".$host unless (exists $nuh{lc $who});
402
403     ### on-join ban. (TODO: kick)
404     if (exists $bans{$chan}) {
405         ### TODO: need to do $chan and _default
406         foreach (keys %{ $bans{$chan} }) {
407             s/\*/\\S*/g;
408             next unless /^\Q$nuh\E$/i;
409
410             foreach (keys %{ $channels{$chan}{'b'} }) {
411                 &DEBUG(" bans_on_chan($chan) => $_");
412             }
413
414             ### TODO: check $channels{$chan}{'b'} if ban already exists.
415             &ban( "*!*@".&makeHostMask($host), $chan);
416             last;
417         }
418     }
419
420     ### ROOTWARN:
421     &rootWarn($who,$user,$host,$chan)
422                 if (&IsChanConf("rootWarn") &&
423                     $user =~ /^r(oo|ew|00)t$/i &&
424                     $channels{$chan}{'o'}{$ident});
425
426     # used to determine sync time.
427     if ($who =~ /^$ident$/i) {
428         if (defined( my $whojoin = $joinverb{$chan} )) {
429             &msg($chan, "Okay, I'm here. (courtesy of $whojoin)");
430             delete $joinverb{$chan};
431         }
432
433         ### TODO: move this to &joinchan()?
434         $jointime{$chan} = &gettimeofday();
435         rawout("WHO $chan");
436     } else {
437         ### TODO: this may go wild on a netjoin :)
438         ### WINGATE:
439         &wingateCheck();
440     }
441 }
442
443 sub on_kick {
444     my ($self, $event) = @_;
445     my ($chan,$reason) = $event->args;
446     my $kicker  = $event->nick;
447     my $kickee  = ($event->to)[0];
448     my $uh      = $event->userhost();
449
450     &status(">>> kick/$b_blue$chan$ob [$b$kickee!$uh$ob] by $b_cyan$kicker$ob $b_yellow($ob$reason$b_yellow)$ob");
451
452     $chan = lc $chan;   # forgot about this, found by xsdg, 20001229.
453     $chanstats{$chan}{'Kick'}++;
454
455     if ($kickee eq $ident) {
456         &clearChanVars($chan);
457
458         &status("SELF attempting to rejoin lost channel $chan");
459         &joinchan($chan);
460     } else {
461         &DeleteUserInfo($kickee,$chan);
462     }
463 }
464
465 sub on_mode {
466     my ($self, $event)  = @_;
467     my ($user, $host)   = split(/\@/, $event->userhost);
468     my @args = $event->args();
469     my $nick = $event->nick();
470     my $chan = ($event->to)[0];
471
472     $args[0] =~ s/\s$//;
473
474     if ($nick eq $chan) {       # UMODE
475         &status(">>> mode $b_yellow\[$ob$b@args$b_yellow\]$ob by $b_cyan$nick$ob");
476     } else {                    # MODE
477         &status(">>> mode/$b_blue$chan$ob $b_yellow\[$ob$b@args$b_yellow\]$ob by $b_cyan$nick$ob");
478         &hookMode($chan, @args);
479     }
480 }
481
482 sub on_modeis {
483     my ($self, $event) = @_;
484     my $nick = $event->nick();
485     my ($myself,$chan,@args) = $event->args();
486
487     &hookMode(lc $chan, @args);         # CASING.
488 }
489
490 sub on_msg {
491     my ($self, $event) = @_;
492     my $nick = $event->nick;
493     my $msg  = ($event->args)[0];
494
495     ($user,$host) = split(/\@/, $event->userhost);
496     $uh         = $event->userhost();
497     $nuh        = $nick."!".$uh;
498
499     &hookMsg('private', undef, $nick, $msg);
500 }
501
502 sub on_names {
503     my ($self, $event) = @_;
504     my @args = $event->args;
505     my $chan = lc $args[2];             # CASING, the last of them!
506
507     foreach (split / /, @args[3..$#args]) {
508         $channels{$chan}{'o'}{$_}++     if s/\@//;
509         $channels{$chan}{'v'}{$_}++     if s/\+//;
510         $channels{$chan}{''}{$_}++;
511     }
512 }
513
514 sub on_nick {
515     my ($self, $event) = @_;
516     my $nick = $event->nick();
517     my $newnick = ($event->args)[0];
518
519     if (exists $netsplit{lc $newnick}) {
520         &status("Netsplit: $newnick/$nick came back from netsplit and changed to original nick! removing from hash.");
521         delete $netsplit{lc $newnick};
522     }
523
524     my ($chan,$mode);
525     foreach $chan (keys %channels) {
526         foreach $mode (keys %{$channels{$chan}}) {
527             next unless (exists $channels{$chan}{$mode}{$nick});
528
529             $channels{$chan}{$mode}{$newnick} = $channels{$chan}{$mode}{$nick};
530         }
531     }
532     &DeleteUserInfo($nick,keys %channels);
533     $nuh{lc $newnick} = $nuh{lc $nick};
534     delete $nuh{lc $nick};
535
536     # successful self-nick change.
537     if ($nick eq $ident) {
538         &status(">>> I materialized into $b_green$newnick$ob from $nick");
539         $ident = $newnick;
540     } else {
541         &status(">>> $b_cyan$nick$ob materializes into $b_green$newnick$ob");
542     }
543 }
544
545 sub on_nick_taken {
546     my ($self) = @_;
547     my $nick = $self->nick;
548     my $newnick = substr($nick,0,7)."-";
549
550     &status("nick taken; changing to temporary nick.");
551     &nick($newnick);
552     &getNickInUse(1);
553 }
554
555 sub on_notice {
556     my ($self, $event) = @_;
557     my $nick = $event->nick();
558     my $chan = ($event->to)[0];
559     my $args = ($event->args)[0];
560
561     if ($nick =~ /^NickServ$/i) {               # nickserv.
562         &status("NickServ: <== '$args'");
563
564         if ($args =~ /^This nickname is registered/i) {
565             &status("nickserv told us to register; doing it.");
566             if (&IsParam("nickServ_pass")) {
567                 &status("NickServ: ==> Identifying.");
568                 &rawout("PRIVMSG NickServ :IDENTIFY $param{'nickServ_pass'}");
569                 return;
570             } else {
571                 &status("We can't tell nickserv a passwd ;(");
572             }
573         }
574
575         # password accepted.
576         if ($args =~ /^Password a/i) {
577             $nickserv++;
578         }
579     } elsif ($nick =~ /^ChanServ$/i) {          # chanserv.
580         &status("ChanServ: <== '$args'.");
581     } else {
582         if ($chan =~ /^$mask{chan}$/) { # channel notice.
583             &status("-$nick/$chan- $args");
584         } else {
585             $server = $nick unless (defined $server);
586             &status("-$nick- $args");   # private or server notice.
587         }
588     }
589 }
590
591 sub on_other {
592     my ($self, $event) = @_;
593     my $chan = ($event->to)[0];
594     my $nick = $event->nick;
595
596     &status("!!! other called.");
597     &status("!!! $event->args");
598 }
599
600 sub on_part {
601     my ($self, $event) = @_;
602     my $chan = lc( ($event->to)[0] );   # CASING!!!
603     my $nick = $event->nick;
604     my $userhost = $event->userhost;
605
606     if (exists $floodjoin{$chan}{$nick}{Time}) {
607         delete $floodjoin{$chan}{$nick};
608     }
609
610     $chanstats{$chan}{'Part'}++;
611     &DeleteUserInfo($nick,$chan);
612     &clearChanVars($chan) if ($nick eq $ident);
613     if (!&IsNickInAnyChan($nick) and &IsParam("seenStats")) {
614         delete $userstats{lc $nick};
615     }
616
617     &status(">>> part/$b_blue$chan$ob $b_cyan$nick$ob $b_yellow($ob$userhost$b_yellow)$ob");
618 }
619
620 sub on_ping {
621     my ($self, $event) = @_;
622     my $nick = $event->nick;
623
624     $self->ctcp_reply($nick, join(' ', ($event->args)));
625     &status(">>> ${b_green}CTCP PING$ob request from $b_cyan$nick$ob received.");
626 }
627
628 sub on_ping_reply {
629     my ($self, $event) = @_;
630     my $nick = $event->nick;
631     my $lag = time() - ($event->args)[1];
632
633     &status(">>> ${b_green}CTCP PING$ob reply from $b_cyan$nick$ob: $lag sec.");
634 }
635
636 sub on_public {
637     my ($self, $event) = @_;
638     my $msg  = ($event->args)[0];
639     my $chan = lc( ($event->to)[0] );   # CASING.
640     my $nick = $event->nick;
641     $uh      = $event->userhost();
642     $nuh     = $nick."!".$uh;
643     ($user,$host) = split(/\@/, $uh);
644
645     if ($bot_pid != $$) {
646         &ERROR("run-away fork; exiting.");
647         &delForked($forker);
648     }
649
650     ### DEBUGGING.
651     if ($statcount < 200) {
652         foreach $chan (grep /[A-Z]/, keys %channels) {
653             &DEBUG("leak: chan => '$chan'.");
654             my ($i,$j);
655             foreach $i (keys %{$channels{$chan}}) {  
656                 foreach (keys %{$channels{$chan}{$i}}) {
657                     &DEBUG("leak:   \$channels{$chan}{$i}{$_} ...");
658                 }
659             }
660         }
661     }
662
663
664     $msgtime = time();
665     $lastWho{$chan} = $nick;
666     ### TODO: use $nick or lc $nick?
667     if (&IsParam("seenStats")) {
668         $userstats{lc $nick}{'Count'}++;
669         $userstats{lc $nick}{'Time'} = time();
670     }
671
672 #    if (&IsParam("hehCounter")) {
673 #       #...
674 #    }
675
676     &hookMsg('public', $chan, $nick, $msg);
677     $chanstats{$chan}{'PublicMsg'}++;
678 }
679
680 sub on_quit {
681     my ($self, $event) = @_;
682     my $nick = $event->nick();
683     my $reason = ($event->args)[0];
684
685     foreach (keys %channels) {
686         # fixes inconsistent chanstats bug #1.
687         next unless (&IsNickInChan($nick,$_));
688         $chanstats{$_}{'SignOff'}++;
689     }
690     &DeleteUserInfo($nick, keys %channels);
691     if (exists $nuh{lc $nick}) {
692         delete $nuh{lc $nick};
693     } else {
694         &DEBUG("on_quit: nuh{lc $nick} does not exist! FIXME");
695     }
696     delete $userstats{lc $nick} if (&IsParam("seenStats"));
697
698     # should fix chanstats inconsistencies bug #2.
699     if ($reason=~/^($mask{host})\s($mask{host})$/) {    # netsplit.
700         $reason = "NETSPLIT: $1 <=> $2";
701
702         $netsplit{lc $nick} = time();
703         if (!exists $netsplitservers{$1}{$2}) {
704             &status("netsplit detected between $1 and $2.");
705             $netsplitservers{$1}{$2} = time();
706         }
707     }
708
709     &status(">>> $b_cyan$nick$ob has signed off IRC $b_red($ob$reason$b_red)$ob");
710     if ($nick =~ /^\Q$ident\E$/) {
711         &DEBUG("!!! THIS SHOULD NEVER HAPPEN. FIXME HOPEFULLY");
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 &IsParam("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 (&IsParam("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 &IsParam("seenStoreAll")) and
1037         &IsParam("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: remove verifyUser");
1065         }
1066         $userHandle = &verifyUser($who, $nuh);
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;