]> git.donarmstrong.com Git - infobot.git/blob - src/IRC/IrcHooks.pl
- more fixes to new-style config, should be 99% of it.
[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     &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 (&IsChanConf("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 &IsChanConf("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 (&IsChanConf("seenStats")) {
668         $userstats{lc $nick}{'Count'}++;
669         $userstats{lc $nick}{'Time'} = time();
670     }
671
672 #    if (&IsChanConf("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 (&IsChanConf("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.");
712     }
713
714     if ($nick !~ /^\Q$ident\E$/ and $nick =~ /^\Q$param{'ircNick'}\E$/i) {
715         &status("own nickname became free; changing.");
716         &nick($param{'ircNick'});
717     }
718 }
719
720 sub on_targettoofast {
721     my ($self, $event) = @_;
722     my $nick = $event->nick();
723     my $chan = ($event->to)[0];
724
725     &DEBUG("on_targettoofast: nick => '$nick'.");
726     &DEBUG("on_targettoofast: chan => '$chan'.");
727
728     foreach ($event->args) {
729         &DEBUG("on_targettoofast: args => '$_'.");
730     }
731
732 ###    .* wait (\d+) second/) {
733         &status("X1 $msg");
734         my $sleep = $3 + 10;
735
736         &status("going to sleep for $sleep...");
737         sleep $sleep;
738         &joinNextChan();
739 ### }
740 }
741
742 sub on_topic {
743     my ($self, $event) = @_;
744
745     if (scalar($event->args) == 1) {    # change.
746         my $topic = ($event->args)[0];
747         my $chan  = ($event->to)[0];
748         my $nick  = $event->nick();
749
750         ###
751         # WARNING:
752         #       race condition here. To fix, change '1' to '0'.
753         #       This will keep track of topics set by bot only.
754         ###
755         # UPDATE:
756         #       this may be fixed at a later date with topic queueing.
757         ###
758
759         $topic{$chan}{'Current'} = $topic if (1 and &IsChanConf("topic") == 1);
760         $chanstats{$chan}{'Topic'}++;
761
762         &status(">>> topic/$b_blue$chan$ob by $b_cyan$nick$ob -> $topic");
763     } else {                                            # join.
764         my ($nick, $chan, $topic) = $event->args;
765         if (&IsChanConf("topic")) {
766             $topic{$chan}{'Current'}    = $topic;
767             &topicAddHistory($chan,$topic);
768         }
769
770         $topic = &fixString($topic, 1);
771         &status(">>> topic/$b_blue$chan$ob is $topic");
772     }
773 }
774
775 sub on_topicinfo {
776     my ($self, $event) = @_;
777     my ($myself,$chan,$setby,$time) = $event->args();
778
779     my $timestr;
780     if (time() - $time > 60*60*24) {
781         $timestr        = "at ". localtime $time;
782     } else {
783         $timestr        = &Time2String(time() - $time) ." ago";
784     }
785
786     &status(">>> set by $b_cyan$setby$ob $timestr");
787 }
788
789 sub on_crversion {
790     my ($self, $event) = @_;
791     my $nick    = $event->nick();
792     my $ver;
793
794     if (scalar $event->args() != 1) {   # old.
795         $ver    = join ' ', $event->args();
796         $ver    =~ s/^VERSION //;
797     } else {                            # new.
798         $ver    = ($event->args())[0];
799     }
800
801     if (grep /^\Q$nick\E$/i, @vernick) {
802         &WARN("nick $nick found in vernick; skipping.");
803         return;
804     }
805     push(@vernick, $nick);
806
807     if ($ver =~ /bitchx/i) {
808         $ver{bitchx}{$nick}     = $ver;
809     } elsif ($ver =~ /xc\!|xchat/i) {
810         $ver{xchat}{$nick}      = $ver;
811     } elsif ($ver =~ /irssi/i) {
812         $ver{irssi}{$nick}      = $ver;
813     } elsif ($ver =~ /epic/i) {
814         $ver{epic}{$nick}       = $ver;
815     } elsif ($ver =~ /mirc/i) {
816         $ver{mirc}{$nick}       = $ver;
817     } elsif ($ver =~ /ircle/i) {
818         $ver{ircle}{$nick}      = $ver;
819     } elsif ($ver =~ /ircII/i) {
820         $ver{ircII}{$nick}      = $ver;
821     } elsif ($ver =~ /sirc /i) {
822         $ver{sirc}{$nick}       = $ver;
823     } elsif ($ver =~ /kvirc/i) {
824         $ver{kvirc}{$nick}      = $ver;
825     } elsif ($ver =~ /eggdrop/i) {
826         $ver{eggdrop}{$nick}    = $ver;
827     } elsif ($ver =~ /xircon/i) {
828         $ver{xircon}{$nick}     = $ver;
829     } else {
830         $ver{other}{$nick}      = $ver;
831     }
832 }
833
834 sub on_version {
835     my ($self, $event) = @_;
836     my $nick = $event->nick;
837
838     &status(">>> ${b_green}CTCP VERSION$ob request from $b_cyan$nick$ob");
839     $self->ctcp_reply($nick, "VERSION $bot_version");
840 }
841
842 sub on_who {
843     my ($self, $event) = @_;
844     my @args    = $event->args;
845
846     $nuh{lc $args[5]} = $args[5]."!".$args[2]."\@".$args[3];
847 }
848
849 sub on_whoisuser {
850     my ($self, $event) = @_;
851     my @args    = $event->args;
852
853     $nuh{lc $args[1]} = $args[1]."!".$args[2]."\@".$args[3];
854 }
855
856 #######################################################################
857 ####### IRC HOOK HELPERS   IRC HOOK HELPERS   IRC HOOK HELPERS ########
858 #######################################################################
859
860 #####
861 # Usage: &hookMode($chan, $modes, @targets);
862 sub hookMode {
863     my ($chan, $modes, @targets) = @_;
864     my $parity  = 0;
865
866     $chan = lc $chan;           # !!!.
867
868     my $mode;
869     foreach $mode (split(//, $modes)) {
870         # sign.
871         if ($mode =~ /[-+]/) {
872             $parity = 1         if ($mode eq "+");
873             $parity = 0         if ($mode eq "-");
874             next;
875         }
876
877         # mode with target.
878         if ($mode =~ /[bklov]/) {
879             my $target = shift @targets;
880
881             if ($parity) {
882                 $chanstats{$chan}{'Op'}++    if ($mode eq "o");
883                 $chanstats{$chan}{'Ban'}++   if ($mode eq "b");
884             } else {
885                 $chanstats{$chan}{'Deop'}++  if ($mode eq "o");
886                 $chanstats{$chan}{'Unban'}++ if ($mode eq "b");
887             }
888
889             # modes w/ target affecting nick => cache it.
890             if ($mode =~ /[bov]/) {
891                 $channels{$chan}{$mode}{$target}++      if  $parity;
892                 delete $channels{$chan}{$mode}{$target} if !$parity;
893             }
894
895             if ($mode =~ /[l]/) {
896                 $channels{$chan}{$mode} = $target       if  $parity;
897                 delete $channels{$chan}{$mode}          if !$parity;
898             }
899         }
900
901         # important channel modes, targetless.
902         if ($mode =~ /[mt]/) {
903             $channels{$chan}{$mode}++                   if  $parity;
904             delete $channels{$chan}{$mode}              if !$parity;
905         }
906     }
907 }
908
909 sub hookMsg {
910     ($msgType, $chan, $who, $message) = @_;
911     my $skipmessage     = 0;
912     $addressed          = 0;
913     $addressedother     = 0;
914     $orig{message}      = $message;
915     $orig{who}          = $who;
916     $addrchar           = 0;
917
918     $message    =~ s/[\cA-\c_]//ig;     # strip control characters
919     $message    =~ s/^\s+//;            # initial whitespaces.
920     $who        =~ tr/A-Z/a-z/;         # lowercase.
921
922     &showProc();
923
924     # addressing.
925     if ($msgType =~ /private/) {
926         # private messages.
927         $addressed = 1;
928     } else {
929         # public messages.
930         # addressing revamped by the xk.
931         ### below needs to be fixed...
932         if (&IsParam("addressCharacter")) {
933             if ($message =~ s/^$param{'addressCharacter'}//) {
934                 $addrchar  = 1;
935                 $addressed = 1;
936             }
937         }
938
939         if ($message =~ /^($mask{nick})([\;\:\>\, ]+) */) {
940             my $newmessage = $';
941             if ($1 =~ /^\Q$ident\E$/i) {
942                 $message   = $newmessage;
943                 $addressed = 1;
944             } else {
945                 # ignore messages addressed to other people or unaddressed.
946                 $skipmessage++ if ($2 ne "" and $2 !~ /^ /);
947             }
948         }
949     }
950
951     # Determine floodwho.
952     if ($msgType =~ /public/i) {                # public.
953         $floodwho = lc $chan;
954     } elsif ($msgType =~ /private/i) {  # private.
955         $floodwho = lc $who;
956     } else {                            # dcc?
957         &DEBUG("FIXME: floodwho = ???");
958     }
959
960     my ($count, $interval) = split(/:/, $param{'floodRepeat'} || "2:10");
961
962     # flood repeat protection.
963     if ($addressed) {
964         my $time = $flood{$floodwho}{$message};
965
966         if (defined $time and (time - $time < $interval)) {
967             ### public != personal who so the below is kind of pointless.
968             my @who;
969             foreach (keys %flood) {
970                 next if (/^\Q$floodwho\E$/ or /^\Q$chan\E$/);
971                 push(@who, grep /^\Q$message\E$/i, keys %{$flood{$_}});
972             }
973             if (scalar @who) {
974                 &msg($who, "you already said what ".
975                                 join(' ', @who)." have said.");
976             } else {
977                 &msg($who,"Someone already said that ". (time - $time) ." seconds ago" );
978             }
979
980             ### TODO: delete old floodwarn{} keys.
981             my $floodwarn = 0;
982             if (!exists $floodwarn{$floodwho}) {
983                 $floodwarn++;
984             } else {
985                 $floodwarn++ if (time() - $floodwarn{$floodwho} > $interval);
986             }
987
988             if ($floodwarn) {
989                 &status("FLOOD repetition detected from $floodwho.");
990                 $floodwarn{$floodwho} = time();
991             }
992
993             return;
994         }
995
996         if ($addrchar) {
997             &status("$b_cyan$who$ob is short-addressing me");
998         } else {
999             &status("$b_cyan$who$ob is addressing me");
1000         }
1001
1002         $flood{$floodwho}{$message} = time();
1003     }
1004
1005     ($count, $interval) = split(/:/, $param{'floodMessages'} || "5:30");
1006     # flood overflow protection.
1007     if ($addressed) {
1008         foreach (keys %{$flood{$floodwho}}) {
1009             next unless (time() - $flood{$floodwho}{$_} > $interval);
1010             delete $flood{$floodwho}{$_};
1011         }
1012
1013         my $i = scalar keys %{$flood{$floodwho}};
1014         if ($i > $count) {
1015             &msg($who,"overflow of messages ($i > $count)");
1016             &status("FLOOD overflow detected from $floodwho; ignoring");
1017
1018             my $expire = $param{'ignoreAutoExpire'} || 5;
1019             &ignoreAdd("*!$uh", $chan, $expire, "flood overflow auto-detected.");
1020             return;
1021         }
1022
1023         $flood{$floodwho}{$message} = time();
1024     }
1025
1026     if ($msgType =~ /public/i) {                    # public.
1027         $talkchannel    = $chan;
1028         &status("<$orig{who}/$chan> $orig{message}");
1029     } elsif ($msgType =~ /private/i) {             # private.
1030         &status("[$orig{who}] $orig{message}");
1031         $talkchannel    = undef;
1032         $chan           = "_default";
1033     } else {
1034         &DEBUG("unknown msgType => $msgType.");
1035     }
1036
1037     if ((!$skipmessage or &IsChanConf("seenStoreAll")) and
1038         &IsChanConf("seen") and
1039         $msgType =~ /public/
1040     ) {
1041         $seencache{$who}{'time'} = time();
1042         $seencache{$who}{'nick'} = $orig{who};
1043         $seencache{$who}{'host'} = $uh;
1044         $seencache{$who}{'chan'} = $talkchannel;
1045         $seencache{$who}{'msg'}  = $orig{message};
1046         $seencache{$who}{'msgcount'}++;
1047     }
1048
1049     return if ($skipmessage);
1050     return unless (&IsParam("minVolunteerLength") or $addressed);
1051
1052     local $ignore       = 0;
1053     if (exists $ignore{lc $chan}) {
1054         foreach (keys %{ $ignore{lc $chan} }) {
1055             s/\*/\\S*/g;
1056
1057             next unless ($nuh =~ /^\Q$_\E$/i);
1058             $ignore++;
1059             last;
1060         }
1061     }
1062
1063     if (defined $nuh) {
1064         if (!defined $userHandle) {
1065             &DEBUG("line 1074: need verifyUser?");
1066             &verifyUser($who, $nuh);
1067         }
1068     } else {
1069         &DEBUG("hookMsg: 'nuh' not defined?");
1070     }
1071
1072 ### For extra debugging purposes...
1073     if ($_ = &process()) {
1074 #       &DEBUG("IrcHooks: process returned '$_'.");
1075     }
1076
1077     return;
1078 }
1079
1080 1;