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