]> git.donarmstrong.com Git - infobot.git/blob - src/IRC/IrcHooks.pl
78bd14f4d4bc3cf02db607a3bf2a533a578e5efd
[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 use vars qw(%chanconf);
8
9 # GENERIC. TO COPY.
10 sub on_generic {
11     $conn = shift(@_);
12     my ($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     $conn = shift(@_);
26     my ($event) = @_;
27     my ( $nick, $args ) = ( $event->nick, $event->args );
28     my $chan = ( $event->to )[0];
29
30     if ( $chan eq $ident ) {
31         &status("* [$nick] $args");
32     }
33     else {
34         &status("* $nick/$chan $args");
35     }
36 }
37
38 sub on_chat {
39     $conn = shift(@_);
40     my ($event) = @_;
41     my $msg     = ( $event->args )[0];
42     my $sock    = ( $event->to )[0];
43     my $nick    = lc $event->nick();
44
45     if ( !exists $nuh{$nick} ) {
46         &DEBUG("chat: nuh{$nick} doesn't exist; trying WHOIS .");
47         $conn->whois($nick);
48         return;
49     }
50
51     ### set vars that would have been set in hookMsg.
52     $userHandle    = '';                        # reset.
53     $who           = lc $nick;
54     $message       = $msg;
55     $orig{who}     = $nick;
56     $orig{message} = $msg;
57     $nuh           = $nuh{$who};
58     $uh            = ( split /\!/, $nuh )[1];
59     $h             = ( split /\@/, $uh )[1];
60     $addressed     = 1;
61     $msgType       = 'chat';
62
63     if ( !exists $dcc{'CHATvrfy'}{$nick} ) {
64         $userHandle = &verifyUser( $who, $nuh );
65         my $crypto  = $users{$userHandle}{PASS};
66         my $success = 0;
67
68         if ( $userHandle eq '_default' ) {
69             &WARN('DCC CHAT: _default/guest not allowed.');
70             return;
71         }
72
73         ### TODO: prevent users without CRYPT chatting.
74         if ( !defined $crypto ) {
75             &TODO('dcc close chat');
76             &msg( $who, 'nope, no guest logins allowed...' );
77             return;
78         }
79
80         if ( &ckpasswd( $msg, $crypto ) ) {
81
82             # stolen from eggdrop.
83             $conn->privmsg( $sock, "Connected to $ident" );
84             $conn->privmsg( $sock,
85                 'Commands start with "." (like ".quit" or ".help")' );
86             $conn->privmsg( $sock,
87                 'Everything else goes out to the party line.' );
88
89             &dccStatus(2) unless ( exists $sched{'dccStatus'}{RUNNING} );
90
91             $success++;
92
93         }
94         else {
95             &status('DCC CHAT: incorrect pass; closing connection.');
96             &DEBUG("chat: sock => '$sock'.");
97 ###         $sock->close();
98             delete $dcc{'CHAT'}{$nick};
99             &FIXME('chat: after closing sock.');
100             ### BUG: close seizes bot. why?
101         }
102
103         if ($success) {
104             &status("DCC CHAT: user $nick is here!");
105             &DCCBroadcast("*** $nick ($uh) joined the party line.");
106
107             $dcc{'CHATvrfy'}{$nick} = $userHandle;
108
109             return if ( $userHandle eq '_default' );
110
111             &dccsay( $nick, "Flags: $users{$userHandle}{FLAGS}" );
112         }
113
114         return;
115     }
116
117     &status("$b_red=$b_cyan$who$b_red=$ob $message");
118
119     if ( $message =~ s/^\.// ) {    # dcc chat commands.
120         ### TODO: make use of &Forker(); here?
121         &loadMyModule('UserDCC');
122
123         &DCCBroadcast( "#$who# $message", 'm' );
124
125         my $retval = &userDCC();
126         return unless ( defined $retval );
127         return if ( $retval eq $noreply );
128
129         $conn->privmsg( $dcc{'CHAT'}{$who}, 'Invalid command.' );
130
131     }
132     else {    # dcc chat arena.
133
134         foreach ( keys %{ $dcc{'CHAT'} } ) {
135             $conn->privmsg( $dcc{'CHAT'}{$_}, "<$who> $orig{message}" );
136         }
137     }
138
139     return 'DCC CHAT MESSAGE';
140 }
141
142 # is there isoff? how do we know if someone signs off?
143 sub on_ison {
144     $conn = shift(@_);
145     my ($event) = @_;
146     my $x1      = ( $event->args )[0];
147     my $x2      = ( $event->args )[1];
148     $x2 =~ s/\s$//;
149
150     &DEBUG("on_ison: x1 = '$x1', x2 => '$x2'");
151 }
152
153 sub on_connected {
154     $conn = shift(@_);
155
156     # update IRCStats.
157     $ident = $conn->nick();
158     $ircstats{'ConnectTime'} = time();
159     $ircstats{'ConnectCount'}++;
160     if ( defined $ircstats{'DisconnectTime'} ) {
161         $ircstats{'OffTime'} += time() - $ircstats{'DisconnectTime'};
162     }
163
164     # first time run.
165     if ( !exists $users{_default} ) {
166         &status('!!! First time run... adding _default user.');
167         $users{_default}{FLAGS} = 'amrt';
168         $users{_default}{HOSTS}{'*!*@*'} = 1;
169     }
170
171     if ( scalar keys %users < 2 ) {
172         &status( '!' x 40 );
173         &status("!!! Ok.  Now type '/msg $ident PASS <pass>' to get master access through DCC CHAT.");
174         &status( '!' x 40 );
175     }
176     # end of first time run.
177
178     if ( &IsChanConf('Wingate') > 0 ) {
179         my $file = "$bot_base_dir/$param{'ircUser'}.wingate";
180         open( IN, $file );
181         while (<IN>) {
182             chop;
183             next unless (/^(\S+)\*$/);
184             push( @wingateBad, $_ );
185         }
186         close IN;
187     }
188
189     if ($firsttime) {
190         &ScheduleThis( 1, 'setupSchedulers' );
191         $firsttime = 0;
192     }
193
194     if ( &IsParam('ircUMode') ) {
195         &VERB( "Attempting change of user modes to $param{'ircUMode'}.", 2 );
196         if ( $param{'ircUMode'} !~ /^[-+]/ ) {
197             &WARN('ircUMode had no +- prefix; adding +');
198             $param{'ircUMode'} = '+' . $param{'ircUMode'};
199         }
200
201         &rawout("MODE $ident $param{'ircUMode'}");
202     }
203
204     # ok, we're free to do whatever we want now. go for it!
205     $running = 1;
206
207     # add ourself to notify.
208     $conn->ison( $conn->nick() );
209
210     # Q, as on quakenet.org.
211     if ( &IsParam('Q_pass') ) {
212         &status('Authing to Q...');
213         &rawout("PRIVMSG Q\@CServe.quakenet.org :AUTH $param{'Q_user'} $param{'Q_pass'}");
214     }
215
216     &status("$ident End of motd. Now lets join some channels...");
217
218     #&joinNextChan();
219 }
220
221 sub on_endofwho {
222     $conn = shift(@_);
223     my ($event) = @_;
224
225     #    &DEBUG("endofwho: chan => $chan");
226     $chan ||= ( $event->args )[1];
227
228     #    &DEBUG("endofwho: chan => $chan");
229
230     if ( exists $cache{countryStats} ) {
231         &do_countrystats();
232     }
233 }
234
235 sub on_dcc {
236     $conn = shift(@_);
237     my ($event) = @_;
238     my $type = uc( ( $event->args )[1] );
239     my $nick = lc $event->nick();
240
241     &status("on_dcc type=$type nick=$nick sock=$sock");
242
243     # pity Net::IRC doesn't store nuh. Here's a hack :)
244     if ( !exists $nuh{ lc $nick } ) {
245         $conn->whois($nick);
246         $nuh{$nick} = 'GETTING-NOW';    # trying.
247     }
248     $type ||= '???';
249
250     if ( $type eq 'SEND' ) {            # GET for us.
251             # incoming DCC SEND. we're receiving a file.
252         my $get = ( $event->args )[2];
253         &status(
254             "DCC: not Initializing GET from $nick to '$param{tempDir}/$get'");
255
256         # FIXME: do we want to get anything?
257         return;
258
259         #open(DCCGET,">$param{tempDir}/$get");
260         #$conn->new_get($event, \*DCCGET);
261
262     }
263     elsif ( $type eq 'GET' ) {    # SEND for us?
264         &status("DCC: not Initializing SEND for $nick.");
265
266         # FIXME: do we want to do anything?
267         return;
268         $conn->new_send( $event->args );
269
270     }
271     elsif ( $type eq 'CHAT' ) {
272         &status("DCC: Initializing CHAT for $nick.");
273         $conn->new_chat($event);
274
275         #       $conn->new_chat(1, $nick, $event->host);
276
277     }
278     else {
279         &WARN("${b_green}DCC $type$ob (1)");
280     }
281 }
282
283 sub on_dcc_close {
284     $conn = shift(@_);
285     my ($event) = @_;
286     my $nick    = $event->nick();
287     my $sock    = ( $event->to )[0];
288
289     # DCC CHAT close on fork exit workaround.
290     if ( $bot_pid != $$ ) {
291         &WARN('run-away fork; exiting.');
292         &delForked($forker);
293     }
294
295     if ( exists $dcc{'SEND'}{$nick} and -f "$param{tempDir}/$nick.txt" ) {
296         &status("${b_green}DCC SEND$ob close from $b_cyan$nick$ob");
297
298         &status("dcc_close: purging DCC send $nick.txt");
299         unlink "$param{tempDir}/$nick.txt";
300
301         delete $dcc{'SEND'}{$nick};
302     }
303     elsif ( exists $dcc{'CHAT'}{$nick} and $dcc{'CHAT'}{$nick} eq $sock ) {
304         &status("${b_green}DCC CHAT$ob close from $b_cyan$nick$ob");
305         delete $dcc{'CHAT'}{$nick};
306         delete $dcc{'CHATvrfy'}{$nick};
307     }
308     else {
309         &status("${b_green}DCC$ob UNKNOWN close from $b_cyan$nick$ob (2)");
310     }
311 }
312
313 sub on_dcc_open {
314     $conn = shift(@_);
315     my ($event) = @_;
316     my $type = uc( ( $event->args )[0] );
317     my $nick = lc $event->nick();
318     my $sock = ( $event->to )[0];
319
320     &status("on_dcc_open type=$type nick=$nick sock=$sock");
321
322     $msgType = 'chat';
323     $type ||= '???';
324     ### BUG: who is set to bot's nick?
325
326     # lets do it.
327     if ( $type eq 'SEND' ) {
328         &status("${b_green}DCC lGET$ob established with $b_cyan$nick$ob");
329
330     }
331     elsif ( $type eq 'CHAT' ) {
332
333         # very cheap hack.
334         ### TODO: run ScheduleThis inside on_dcc_open_chat recursively
335         ###     1,3,5,10 seconds then fail.
336         if ( $nuh{$nick} eq 'GETTING-NOW' ) {
337             &ScheduleThis( 3 / 60, 'on_dcc_open_chat', $nick, $sock );
338         }
339         else {
340             on_dcc_open_chat( undef, $nick, $sock );
341         }
342
343     }
344     elsif ( $type eq 'SEND' ) {
345         &status('Starting DCC receive.');
346         foreach ( $event->args ) {
347             &status("  => '$_'.");
348         }
349
350     }
351     else {
352         &WARN("${b_green}DCC $type$ob (3)");
353     }
354 }
355
356 # really custom sub to get NUH since Net::IRC doesn't appear to support
357 # it.
358 sub on_dcc_open_chat {
359     my ( undef, $nick, $sock ) = @_;
360
361     if ( $nuh{$nick} eq 'GETTING-NOW' ) {
362         &FIXME("getting nuh for $nick failed.");
363         return;
364     }
365
366     &status(
367 "${b_green}DCC CHAT$ob established with $b_cyan$nick$ob $b_yellow($ob$nuh{$nick}$b_yellow)$ob"
368     );
369
370     &verifyUser( $nick, $nuh{ lc $nick } );
371
372     if ( !exists $users{$userHandle}{HOSTS} ) {
373         &performStrictReply(
374             'you have no hosts defined in my user file; rejecting.');
375         $sock->close();
376         return;
377     }
378
379     my $crypto = $users{$userHandle}{PASS};
380     $dcc{'CHAT'}{$nick} = $sock;
381
382     # TODO: don't make DCC CHAT established in the first place.
383     if ( $userHandle eq '_default' ) {
384         &dccsay( $nick, '_default/guest not allowed' );
385         $sock->close();
386         return;
387     }
388
389     if ( defined $crypto ) {
390         &status( "DCC CHAT: going to use $nick\'s crypt." );
391         &dccsay( $nick, 'Enter your password.' );
392     }
393     else {
394
395         #       &dccsay($nick,"Welcome to infobot DCC CHAT interface, $userHandle.");
396     }
397 }
398
399 sub on_disconnect {
400     $conn = shift(@_);
401     my ($event) = @_;
402     my $from    = $event->from();
403     my $what    = ( $event->args )[0];
404     my $mynick  = $conn->nick();
405
406     &status("$mynick disconnect from $from ($what).");
407     $ircstats{'DisconnectTime'}   = time();
408     $ircstats{'DisconnectReason'} = $what;
409     $ircstats{'DisconnectCount'}++;
410     $ircstats{'TotalTime'} += time() - $ircstats{'ConnectTime'}
411       if ( $ircstats{'ConnectTime'} );
412
413     # clear any variables on reconnection.
414     $nickserv = 0;
415
416     &clearIRCVars();
417
418     if ( !defined $conn ) {
419         &WARN('on_disconnect: self is undefined! WTF');
420         &DEBUG('running function irc... lets hope this works.');
421         &irc();
422         return;
423     }
424
425     &WARN('scheduling call ircCheck() in 60s');
426     &clearIRCVars();
427     &ScheduleThis( 1, 'ircCheck' );
428 }
429
430 sub on_endofnames {
431     $conn = shift(@_);
432     my ($event) = @_;
433     my $chan = ( $event->args )[1];
434
435     # sync time should be done in on_endofwho like in BitchX
436     if ( exists $cache{jointime}{$chan} ) {
437         my $delta_time =
438           sprintf( '%.03f', &timedelta( $cache{jointime}{$chan} ) );
439         $delta_time = 0 if ( $delta_time <= 0 );
440         if ( $delta_time > 100 ) {
441             &WARN("endofnames: delta_time > 100 ($delta_time)");
442         }
443
444         &status("$b_blue$chan$ob: sync in ${delta_time}s.");
445     }
446
447     $conn->mode($chan);
448
449     my $txt;
450     my @array;
451     foreach ( 'o', 'v', '' ) {
452         my $count = scalar( keys %{ $channels{$chan}{$_} } );
453         next unless ($count);
454
455         $txt = 'total' if ( $_ eq '' );
456         $txt = 'voice' if ( $_ eq 'v' );
457         $txt = 'ops'   if ( $_ eq 'o' );
458
459         push( @array, "$count $txt" );
460     }
461     my $chanstats = join( ' || ', @array );
462     &status("$b_blue$chan$ob: [$chanstats]");
463
464     &chanServCheck($chan);
465
466     # FIXME: scheduler is b0rken! flood join for now
467     # schedule used to solve ircu (OPN) 'target too fast' problems.
468     #$conn->schedule( 5, sub { &joinNextChan(); } );
469     &joinNextChan();
470 }
471
472 sub on_init {
473     $conn = shift(@_);
474     my ($event) = @_;
475     my (@args)  = ( $event->args );
476     shift @args;
477
478     &status("@args");
479 }
480
481 sub on_invite {
482     $conn = shift(@_);
483     my ($event) = @_;
484     my $chan = lc( ( $event->args )[0] );
485     my $nick = $event->nick;
486
487     if ( $nick =~ /^\Q$ident\E$/ ) {
488         &DEBUG('on_invite: self invite.');
489         return;
490     }
491
492     ### TODO: join key.
493     if ( exists $chanconf{$chan} ) {
494
495         # it's still buggy :/
496         if ( &validChan($chan) ) {
497             &msg( $who, "i'm already in \002$chan\002." );
498
499             #       return;
500         }
501
502         &status("invited to $b_blue$chan$ob by $b_cyan$nick$ob");
503         &joinchan($chan);
504     }
505 }
506
507 sub on_join {
508     $conn = shift(@_);
509     my ($event) = @_;
510     my ( $user, $host ) = split( /\@/, $event->userhost );
511     $chan    = lc( ( $event->to )[0] );    # CASING!!!!
512     $who     = $event->nick();
513     $msgType = 'public';
514     my $i = scalar( keys %{ $channels{$chan} } );
515     my $j = $cache{maxpeeps}{$chan} || 0;
516
517     if ( !&IsParam('noSHM')
518         && time() > ( $sched{shmFlush}{TIME} || time() ) + 3600 )
519     {
520         &DEBUG('looks like schedulers died somewhere... restarting...');
521         &setupSchedulers();
522     }
523
524     $chanstats{$chan}{'Join'}++;
525     $userstats{ lc $who }{'Join'} = time() if ( &IsChanConf('seenStats') > 0 );
526     $cache{maxpeeps}{$chan} = $i if ( $i > $j );
527
528     &joinfloodCheck( $who, $chan, $event->userhost );
529
530     # netjoin detection.
531     my $netsplit = 0;
532     if ( exists $netsplit{ lc $who } ) {
533         delete $netsplit{ lc $who };
534         $netsplit = 1;
535
536         if ( !scalar keys %netsplit ) {
537             &DEBUG('on_join: netsplit hash is now empty!');
538             undef %netsplitservers;
539             &netsplitCheck();    # any point in running this?
540             &chanlimitCheck();
541         }
542     }
543
544     if ( $netsplit and !exists $cache{netsplit} ) {
545         &VERB('on_join: ok.... re-running chanlimitCheck in 60.', 2);
546         $conn->schedule(
547             60,
548             sub {
549                 &chanlimitCheck();
550                 delete $cache{netsplit};
551             }
552         );
553
554         $cache{netsplit} = time();
555     }
556
557     # how to tell if there's a netjoin???
558
559     my $netsplitstr = '';
560     $netsplitstr = " $b_yellow\[${ob}NETSPLIT VICTIM$b_yellow]$ob"
561       if ($netsplit);
562     &status(
563 ">>> join/$b_blue$chan$ob $b_cyan$who$ob $b_yellow($ob$user\@$host$b_yellow)$ob$netsplitstr"
564     );
565
566     $channels{$chan}{''}{$who}++;
567     $nuh = $who . '!' . $user . '@' . $host;
568     $nuh{ lc $who } = $nuh unless ( exists $nuh{ lc $who } );
569
570     ### on-join bans.
571     my @bans;
572     push( @bans, keys %{ $bans{$chan} } ) if ( exists $bans{$chan} );
573     push( @bans, keys %{ $bans{'*'} } )   if ( exists $bans{'*'} );
574
575     foreach (@bans) {
576         my $ban = $_;
577         s/\?/./g;
578         s/\*/\\S*/g;
579         my $mask = $_;
580         next unless ( $nuh =~ /^$mask$/i );
581
582         ### TODO: check $channels{$chan}{'b'} if ban already exists.
583         foreach ( keys %{ $channels{$chan}{'b'} } ) {
584             &DEBUG(" bans_on_chan($chan) => $_");
585         }
586
587         my $reason = 'no reason';
588         foreach ( $chan, '*' ) {
589             next unless ( exists $bans{$_} );
590             next unless ( exists $bans{$_}{$ban} );
591
592             my @array = @{ $bans{$_}{$ban} };
593
594             $reason = $array[4] if ( $array[4] );
595             last;
596         }
597
598         &ban( $ban, $chan );
599         &kick( $who, $chan, $reason );
600
601         last;
602     }
603
604     # no need to go further.
605     return if ($netsplit);
606
607     # who == bot.
608     if ( $who =~ /^\Q$ident\E$/i ) {
609         if ( defined( my $whojoin = $cache{join}{$chan} ) ) {
610             &msg( $chan, "Okay, I'm here. (courtesy of $whojoin)" );
611             delete $cache{join}{$chan};
612             &joinNextChan();    # hack.
613         }
614
615         ### TODO: move this to &joinchan()?
616         $cache{jointime}{$chan} = &timeget();
617         $conn->who($chan);
618
619         return;
620     }
621
622     ### ROOTWARN:
623     &rootWarn( $who, $user, $host, $chan )
624       if ( &IsChanConf('RootWarn') > 0
625         && $user eq 'root' );
626         #&& $user =~ /^~?r(oo|ew|00)t$/i );
627
628     ### emit a message based on who just joined
629     &onjoin( $who, $user, $host, $chan ) if ( &IsChanConf('OnJoin') > 0 );
630
631     ### NEWS:
632     if ( &IsChanConf('News') > 0 && &IsChanConf('newsKeepRead') > 0 ) {
633         if ( !&loadMyModule('News') ) {    # just in case.
634             &DEBUG('could not load news.');
635         }
636         else {
637             &News::latest($chan);
638         }
639     }
640
641     ### botmail:
642     if ( &IsChanConf('botmail') > 0 ) {
643         &botmail::check( lc $who );
644     }
645
646     ### wingate:
647     &wingateCheck();
648 }
649
650 sub on_kick {
651     $conn = shift(@_);
652     my ($event) = @_;
653     my ( $chan, $reason ) = $event->args;
654     my $kicker = $event->nick;
655     my $kickee = ( $event->to )[0];
656     my $uh     = $event->userhost();
657
658     &status(
659 ">>> kick/$b_blue$chan$ob [$b$kickee!$uh$ob] by $b_cyan$kicker$ob $b_yellow($ob$reason$b_yellow)$ob"
660     );
661
662     $chan = lc $chan;    # forgot about this, found by xsdg, 20001229.
663     $chanstats{$chan}{'Kick'}++;
664
665     if ( $kickee eq $ident ) {
666         &clearChanVars($chan);
667
668         &status("SELF attempting to rejoin lost channel $chan");
669         &joinchan($chan);
670     }
671     else {
672         &delUserInfo( $kickee, $chan );
673     }
674 }
675
676 sub on_mode {
677     $conn = shift(@_);
678     my ($event) = @_;
679     my ( $user, $host ) = split( /\@/, $event->userhost );
680     my @args = $event->args();
681     my $nick = $event->nick();
682     $chan = ( $event->to )[0];
683
684     # last element is empty... so nuke it.
685     pop @args while ( $args[$#args] eq '' );
686
687     if ( $nick eq $chan ) {    # UMODE
688         &status(
689             ">>> mode $b_yellow\[$ob$b@args$b_yellow\]$ob by $b_cyan$nick$ob");
690     }
691     else {                     # MODE
692         &status(
693 ">>> mode/$b_blue$chan$ob $b_yellow\[$ob$b@args$b_yellow\]$ob by $b_cyan$nick$ob"
694         );
695         &hookMode( $nick, @args );
696     }
697 }
698
699 sub on_modeis {
700     $conn = shift(@_);
701     my ($event) = @_;
702     my ( $myself, undef, @args ) = $event->args();
703     my $nick = $event->nick();
704     $chan = ( $event->args() )[1];
705
706     &hookMode( $nick, @args );
707 }
708
709 sub on_msg {
710     $conn = shift(@_);
711     my ($event) = @_;
712     my $nick    = $event->nick;
713     my $msg     = ( $event->args )[0];
714
715     ( $user, $host ) = split( /\@/, $event->userhost );
716     $uh      = $event->userhost();
717     $nuh     = $nick . '!' . $uh;
718     $msgtime = time();
719     $h       = $host;
720
721     if ( $nick eq $ident ) {    # hopefully ourselves.
722         if ( $msg eq 'TEST' ) {
723             &status("IRCTEST: Yes, we're alive.");
724             delete $cache{connect};
725             return;
726         }
727     }
728
729     &hookMsg( 'private', undef, $nick, $msg );
730     $who     = '';
731     $chan    = '';
732     $msgType = '';
733 }
734
735 sub on_names {
736     $conn = shift(@_);
737     my ($event) = @_;
738     my @args    = $event->args;
739     my $chan    = lc $args[2];    # CASING, the last of them!
740
741     foreach ( split / /, @args[ 3 .. $#args ] ) {
742         $channels{$chan}{'o'}{$_}++ if s/\@//;
743         $channels{$chan}{'v'}{$_}++ if s/\+//;
744         $channels{$chan}{''}{$_}++;
745     }
746 }
747
748 sub on_nick {
749     $conn = shift(@_);
750     my ($event) = @_;
751     my $nick    = $event->nick();
752     my $newnick = ( $event->args )[0];
753
754     if ( exists $netsplit{ lc $newnick } ) {
755         &status(
756 "Netsplit: $newnick/$nick came back from netsplit and changed to original nick! removing from hash."
757         );
758         delete $netsplit{ lc $newnick };
759         &netsplitCheck() if ( time() != $sched{netsplitCheck}{TIME} );
760     }
761
762     my ( $chan, $mode );
763     foreach $chan ( keys %channels ) {
764         foreach $mode ( keys %{ $channels{$chan} } ) {
765             next unless ( exists $channels{$chan}{$mode}{$nick} );
766
767             $channels{$chan}{$mode}{$newnick} = $channels{$chan}{$mode}{$nick};
768         }
769     }
770
771     # TODO: do %flood* aswell.
772
773     &delUserInfo( $nick, keys %channels );
774     $nuh{ lc $newnick } = $nuh{ lc $nick };
775     delete $nuh{ lc $nick };
776
777     if ( $nick eq $conn->nick() ) {
778         &status(">>> I materialized into $b_green$newnick$ob from $nick");
779         $ident = $newnick;
780         $conn->nick($newnick);
781     }
782     else {
783         &status(">>> $b_cyan$nick$ob materializes into $b_green$newnick$ob");
784         my $mynick = $conn->nick();
785         if ( $nick =~ /^\Q$mynick\E$/i ) {
786             &getNickInUse();
787         }
788     }
789 }
790
791 sub on_nick_taken {
792     $conn = shift(@_);
793     my $nick = $conn->nick();
794
795     #my $newnick = $nick . int(rand 10);
796     my $newnick = $nick . '_';
797
798     &DEBUG("on_nick_taken: nick => $nick");
799
800     &status("nick taken ($nick); preparing nick change.");
801
802     $conn->whois($nick);
803
804     #$conn->schedule(5, sub {
805     &status("nick taken; changing to temporary nick ($nick -> $newnick).");
806     &nick($newnick);
807
808     #} );
809 }
810
811 sub on_notice {
812     $conn = shift(@_);
813     my ($event) = @_;
814     my $nick    = $event->nick();
815     my $chan    = ( $event->to )[0];
816     my $args    = ( $event->args )[0];
817     my $mynick  = $conn->nick();
818
819     if ( $nick =~ /^NickServ$/i ) {    # nickserv.
820         &status("NickServ: $mynick <== '$args'");
821
822         my $check = 0;
823         $check++ if ( $args =~ /^This nickname is registered/i );
824         $check++ if ( $args =~ /nickname.*owned/i );
825
826         if ($check) {
827             &status("nickserv told $mynick to register; doing it.");
828
829             if ( &IsParam('nickServ_pass') ) {
830                 &status("NickServ: ==> Identifying as $mynick.");
831                 &rawout("PRIVMSG NickServ :IDENTIFY $param{'nickServ_pass'}");
832                 return;
833             }
834             else {
835                 &status("$mynick can't tell nickserv a passwd ;(");
836             }
837         }
838
839         # password accepted.
840         if ( $args =~ /^Password a/i ) {
841             my $done = 0;
842
843             foreach ( &ChanConfList('chanServ_ops') ) {
844                 next unless &chanServCheck($_);
845                 next if ($done);
846                 &DEBUG('nickserv activated or restarted; doing chanserv check.');
847                 $done++;
848             }
849
850             $nickserv++;
851         }
852
853     }
854     elsif ( $nick =~ /^ChanServ$/i ) {    # chanserv.
855         &status("ChanServ: <== '$args'.");
856
857     }
858     else {
859         if ( $chan =~ /^$mask{chan}$/ ) {    # channel notice.
860             &status("-$nick/$chan- $args");
861         }
862         else {
863             $server = $nick unless ( defined $server );
864             &status("-$nick- $args");        # private or server notice.
865         }
866     }
867 }
868
869 sub on_other {
870     $conn = shift(@_);
871     my ($event) = @_;
872     my $chan    = ( $event->to )[0];
873     my $nick    = $event->nick;
874
875     &status('!!! other called.');
876     &status("!!! $event->args");
877 }
878
879 sub on_part {
880     $conn = shift(@_);
881     my ($event) = @_;
882     $chan = lc( ( $event->to )[0] );    # CASING!!!
883     my $mynick   = $conn->nick();
884     my $nick     = $event->nick;
885     my $userhost = $event->userhost;
886     $who     = $nick;
887     $msgType = 'public';
888
889     if ( !exists $channels{$chan} ) {
890         &DEBUG("on_part: found out $mynick is on $chan!");
891         $channels{$chan} = 1;
892     }
893
894     if ( exists $floodjoin{$chan}{$nick}{Time} ) {
895         delete $floodjoin{$chan}{$nick};
896     }
897
898     $chanstats{$chan}{'Part'}++;
899     &delUserInfo( $nick, $chan );
900     if ( $nick eq $ident ) {
901         &clearChanVars($chan);
902     }
903
904     if ( !&IsNickInAnyChan($nick) and &IsChanConf('seenStats') > 0 ) {
905         delete $userstats{ lc $nick };
906     }
907
908     &status(
909 ">>> part/$b_blue$chan$ob $b_cyan$nick$ob $b_yellow($ob$userhost$b_yellow)$ob"
910     );
911 }
912
913 sub on_ping {
914     $conn = shift(@_);
915     my ($event) = @_;
916     my $nick = $event->nick;
917
918     $conn->ctcp_reply( $nick, join( ' ', ( $event->args ) ) );
919     &status(
920         ">>> ${b_green}CTCP PING$ob request from $b_cyan$nick$ob received.");
921 }
922
923 sub on_ping_reply {
924     $conn = shift(@_);
925     my ($event) = @_;
926     my $nick    = $event->nick;
927     my $t       = ( $event->args )[1];
928     if ( !defined $t ) {
929         &WARN('on_ping_reply: t == undefined.');
930         return;
931     }
932
933     my $lag = time() - $t;
934
935     &status(">>> ${b_green}CTCP PING$ob reply from $b_cyan$nick$ob: $lag sec.");
936 }
937
938 sub on_public {
939     $conn = shift(@_);
940     my ($event) = @_;
941     my $msg = ( $event->args )[0];
942     $chan = lc( ( $event->to )[0] );    # CASING.
943     my $nick = $event->nick;
944     $who     = $nick;
945     $uh      = $event->userhost();
946     $nuh     = $nick . '!' . $uh;
947     $msgType = 'public';
948
949     # TODO: move this out of hookMsg to here?
950     ( $user, $host ) = split( /\@/, $uh );
951     $h = $host;
952
953     # rare case should this happen - catch it just in case.
954     if ( $bot_pid != $$ ) {
955         &ERROR('run-away fork; exiting.');
956         &delForked($forker);
957     }
958
959     $msgtime = time();
960     $lastWho{$chan} = $nick;
961     ### TODO: use $nick or lc $nick?
962     if ( &IsChanConf('seenStats') > 0 ) {
963         $userstats{ lc $nick }{'Count'}++;
964         $userstats{ lc $nick }{'Time'} = time();
965     }
966
967     # cache it.
968     my $time = time();
969     if ( !$cache{ircTextCounters} ) {
970         &DEBUG('caching ircTextCounters for first time.');
971         my @str = split( /\s+/, &getChanConf('ircTextCounters') );
972         for (@str) { $_ = quotemeta($_); }
973         $cache{ircTextCounters} = join( '|', @str );
974     }
975
976     my $str = $cache{ircTextCounters};
977     if ( $str && $msg =~ /^($str)[\s!\.]?$/i ) {
978         my $x = $1;
979
980         &VERB( "textcounters: $x matched for $who", 2 );
981         my $c = $chan || 'PRIVATE';
982
983         # better to do 'counter=counter+1'.
984         # but that will avoid time check.
985         my ( $v, $t ) = &sqlSelect(
986             'stats',
987             'counter,time',
988             {
989                 nick    => $who,
990                 type    => $x,
991                 channel => $c,
992             }
993         );
994         $v++;
995
996         # don't allow ppl to cheat the stats :-)
997         if ( ( defined $t && $time - $t > 60 ) or ( !defined $t ) ) {
998             &sqlSet(
999                 'stats',
1000                 {
1001                                 'nick' => $who,
1002                     'type'    => $x,
1003                     'channel' => $c,
1004                 },
1005                 {
1006                     time    => $time,
1007                     counter => $v,
1008                 }
1009             );
1010         }
1011     }
1012
1013     &hookMsg( 'public', $chan, $nick, $msg );
1014     $chanstats{$chan}{'PublicMsg'}++;
1015     $who     = '';
1016     $chan    = '';
1017     $msgType = '';
1018 }
1019
1020 sub on_quit {
1021     $conn = shift(@_);
1022     my ($event) = @_;
1023     my $nick    = $event->nick();
1024     my $reason  = ( $event->args )[0];
1025
1026     # hack for ICC.
1027     $msgType = 'public';
1028     $who     = $nick;
1029 ###    $chan    = $reason;      # no.
1030
1031     my $count = 0;
1032     foreach ( grep !/^_default$/, keys %channels ) {
1033
1034         # fixes inconsistent chanstats bug #1.
1035         if ( !&IsNickInChan( $nick, $_ ) ) {
1036             $count++;
1037             next;
1038         }
1039         $chanstats{$_}{'SignOff'}++;
1040     }
1041
1042     if ( $count == scalar keys %channels ) {
1043         &DEBUG("on_quit: nick $nick was not found in any chan.");
1044     }
1045
1046     # should fix chanstats inconsistencies bug #2.
1047     if ( $reason =~ /^($mask{host})\s($mask{host})$/ ) {    # netsplit.
1048         $reason = "NETSPLIT: $1 <=> $2";
1049
1050         # chanlimit code.
1051         foreach $chan ( &getNickInChans($nick) ) {
1052             next unless ( &IsChanConf('chanlimitcheck') > 0 );
1053             next unless ( exists $channels{$_}{'l'} );
1054
1055             &DEBUG("on_quit: netsplit detected on $_; disabling chan limit.");
1056             $conn->mode( $_, '-l' );
1057         }
1058
1059         $netsplit{ lc $nick } = time();
1060         if ( !exists $netsplitservers{$1}{$2} ) {
1061             &status("netsplit detected between $1 and $2 at ["
1062                   . scalar(gmtime)
1063                   . ']' );
1064             $netsplitservers{$1}{$2} = time();
1065         }
1066     }
1067
1068     my $chans = join( ' ', &getNickInChans($nick) );
1069     &status(
1070 ">>> $b_cyan$nick$ob has signed off IRC $b_red($ob$reason$b_red)$ob [$chans]"
1071     );
1072
1073     ###
1074     ### ok... lets clear out the cache
1075     ###
1076     &delUserInfo( $nick, keys %channels );
1077     if ( exists $nuh{ lc $nick } ) {
1078         delete $nuh{ lc $nick };
1079     }
1080     else {
1081
1082         # well.. it's good but weird that this has happened - lets just
1083         # be quiet about it.
1084     }
1085     delete $userstats{ lc $nick } if ( &IsChanConf('seenStats') > 0 );
1086     delete $chanstats{ lc $nick };
1087     ###
1088
1089     # if we have a temp nick, and whoever is camping on our main nick leaves
1090     # revert to main nick. Note that Net::IRC only knows our main nick
1091     if ( $nick eq $conn->nick() ) {
1092         &status("nickchange: own nick \"$nick\" became free; changing.");
1093         &nick($nick);
1094     }
1095 }
1096
1097 sub on_targettoofast {
1098     $conn = shift(@_);
1099     my ($event) = @_;
1100     my $nick = $event->nick();
1101     my ( $me, $chan, $why ) = $event->args();
1102
1103     ### TODO: incomplete.
1104     if ( $why =~ /.* wait (\d+) second/ ) {
1105         my $sleep = $1;
1106         my $max   = 10;
1107
1108         if ( $sleep > $max ) {
1109             &status("targettoofast: going to sleep for $max ($sleep)...");
1110             $sleep = $max;
1111         }
1112         else {
1113             &status("targettoofast: going to sleep for $sleep");
1114         }
1115
1116         my $delta = time() - ( $cache{sleepTime} || 0 );
1117         if ( $delta > $max + 2 ) {
1118             sleep $sleep;
1119             $cache{sleepTime} = time();
1120         }
1121
1122         return;
1123     }
1124
1125     if ( !exists $cache{TargetTooFast} ) {
1126         &DEBUG("on_ttf: failed: $why");
1127         $cache{TargetTooFast}++;
1128     }
1129 }
1130
1131 sub on_topic {
1132     $conn = shift(@_);
1133     my ($event) = @_;
1134
1135     if ( scalar( $event->args ) == 1 ) {    # change.
1136         my $topic = ( $event->args )[0];
1137         my $chan  = ( $event->to )[0];
1138         my $nick  = $event->nick();
1139
1140         ###
1141         # WARNING:
1142         #       race condition here. To fix, change '1' to '0'.
1143         #       This will keep track of topics set by bot only.
1144         ###
1145         # UPDATE:
1146         #       this may be fixed at a later date with topic queueing.
1147         ###
1148
1149         $topic{$chan}{'Current'} = $topic if (1);
1150         $chanstats{$chan}{'Topic'}++;
1151
1152         &status(">>> topic/$b_blue$chan$ob by $b_cyan$nick$ob -> $topic");
1153     }
1154     else {    # join.
1155         my ( $nick, $chan, $topic ) = $event->args;
1156         if ( &IsChanConf('Topic') > 0 ) {
1157             $topic{$chan}{'Current'} = $topic;
1158             &topicAddHistory( $chan, $topic );
1159         }
1160
1161         $topic = &fixString( $topic, 1 );
1162         &status(">>> topic/$b_blue$chan$ob is $topic");
1163     }
1164 }
1165
1166 sub on_topicinfo {
1167     $conn = shift(@_);
1168     my ($event) = @_;
1169     my ( $myself, $chan, $setby, $time ) = $event->args();
1170
1171     my $timestr;
1172     if ( time() - $time > 60 * 60 * 24 ) {
1173         $timestr = 'at ' . gmtime $time;
1174     }
1175     else {
1176         $timestr = &Time2String( time() - $time ) . ' ago';
1177     }
1178
1179     &status(">>> set by $b_cyan$setby$ob $timestr");
1180 }
1181
1182 sub on_crversion {
1183     $conn = shift(@_);
1184     my ($event) = @_;
1185     my $nick = $event->nick();
1186     my $ver;
1187
1188     if ( scalar $event->args() != 1 ) {    # old.
1189         $ver = join ' ', $event->args();
1190         $ver =~ s/^VERSION //;
1191     }
1192     else {                                 # new.
1193         $ver = ( $event->args() )[0];
1194     }
1195
1196     if ( grep /^\Q$nick\E$/i, @vernick ) {
1197         &WARN("nick $nick found in vernick ($ver); skipping.");
1198         return;
1199     }
1200     push( @vernick, $nick );
1201
1202     &DEBUG("on_crversion: Got '$ver' from $nick");
1203
1204     if ( $ver =~ /bitchx/i ) {
1205         $ver{bitchx}{$nick} = $ver;
1206
1207     }
1208     elsif ( $ver =~ /infobot/i ) {
1209         $ver{infobot}{$nick} = $ver;
1210
1211     }
1212     elsif ( $ver =~ /(xc\!|xchat)/i ) {
1213         $ver{xchat}{$nick} = $ver;
1214
1215     }
1216     elsif ( $ver =~ /irssi/i ) {
1217         $ver{irssi}{$nick} = $ver;
1218
1219     }
1220     elsif ( $ver =~ /(epic|Third Eye)/i ) {
1221         $ver{epic}{$nick} = $ver;
1222
1223     }
1224     elsif ( $ver =~ /(ircII|PhoEniX)/i ) {
1225         $ver{ircII}{$nick} = $ver;
1226
1227     }
1228     elsif ( $ver =~ /mirc/i ) {
1229         # Apparently, mIRC gets the reply as "VERSION " and doesnt like the
1230         # space, so mirc matching is considered bugged.
1231         $ver{mirc}{$nick} = $ver;
1232
1233     }
1234     elsif ( $ver =~ /ircle/i ) {
1235         $ver{ircle}{$nick} = $ver;
1236
1237     }
1238     elsif ( $ver =~ /chatzilla/i ) {
1239         $ver{chatzilla}{$nick} = $ver;
1240
1241     }
1242     elsif ( $ver =~ /pirch/i ) {
1243         $ver{pirch}{$nick} = $ver;
1244
1245     }
1246     elsif ( $ver =~ /sirc /i ) {
1247         $ver{sirc}{$nick} = $ver;
1248
1249     }
1250     elsif ( $ver =~ /kvirc/i ) {
1251         $ver{kvirc}{$nick} = $ver;
1252
1253     }
1254     elsif ( $ver =~ /eggdrop/i ) {
1255         $ver{eggdrop}{$nick} = $ver;
1256
1257     }
1258     elsif ( $ver =~ /xircon/i ) {
1259         $ver{xircon}{$nick} = $ver;
1260
1261     }
1262     else {
1263         &DEBUG("verstats: other: $nick => '$ver'.");
1264         $ver{other}{$nick} = $ver;
1265     }
1266 }
1267
1268 sub on_version {
1269     $conn = shift(@_);
1270     my ($event) = @_;
1271     my $nick = $event->nick;
1272
1273     &status(">>> ${b_green}CTCP VERSION$ob request from $b_cyan$nick$ob");
1274     $conn->ctcp_reply( $nick, "VERSION $bot_version" );
1275 }
1276
1277 sub on_who {
1278     $conn = shift(@_);
1279     my ($event) = @_;
1280     my @args    = $event->args;
1281     my $str     = $args[5] . '!' . $args[2] . '@' . $args[3];
1282
1283     if ( $cache{on_who_Hack} ) {
1284         $cache{nuhInfo}{ lc $args[5] }{Nick} = $args[5];
1285         $cache{nuhInfo}{ lc $args[5] }{User} = $args[2];
1286         $cache{nuhInfo}{ lc $args[5] }{Host} = $args[3];
1287         $cache{nuhInfo}{ lc $args[5] }{NUH}  = "$args[5]!$args[2]\@$args[3]";
1288         return;
1289     }
1290
1291     if ( $args[5] =~ /^nickserv$/i and !$nickserv ) {
1292         &DEBUG('ok... we did a who for nickserv.');
1293         &rawout("PRIVMSG NickServ :IDENTIFY $param{'nickServ_pass'}");
1294     }
1295
1296     $nuh{ lc $args[5] } = $args[5] . '!' . $args[2] . '@' . $args[3];
1297 }
1298
1299 sub on_whois {
1300     $conn = shift(@_);
1301     my ($event) = @_;
1302     my @args = $event->args;
1303
1304     $nuh{ lc $args[1] } = $args[1] . '!' . $args[2] . '@' . $args[3];
1305 }
1306
1307 sub on_whoischannels {
1308     $conn = shift(@_);
1309     my ($event) = @_;
1310     my @args = $event->args;
1311
1312     &DEBUG("on_whoischannels: @args");
1313 }
1314
1315 sub on_useronchannel {
1316     $conn = shift(@_);
1317     my ($event) = @_;
1318     my @args = $event->args;
1319
1320     &DEBUG("on_useronchannel: @args");
1321     &joinNextChan();
1322 }
1323
1324 ###
1325 ### since joinnextchan is hooked onto on_endofnames, these are needed.
1326 ###
1327
1328 sub on_chanfull {
1329     $conn = shift(@_);
1330     my ($event) = @_;
1331     my @args = $event->args;
1332
1333     &status(">>> chanfull/$b_blue$args[1]$ob, removing autojoin");
1334     delete $chanconf{$chan}{autojoin};
1335     &joinNextChan();
1336 }
1337
1338 sub on_inviteonly {
1339     $conn = shift(@_);
1340     my ($event) = @_;
1341     my @args = $event->args;
1342
1343     &status(">>> inviteonly/$b_cyan$args[1]$ob, removing autojoin");
1344     delete $chanconf{$chan}{autojoin};
1345     &joinNextChan();
1346 }
1347
1348 sub on_banned {
1349     $conn = shift(@_);
1350     my ($event) = @_;
1351     my @args    = $event->args;
1352     my $chan    = $args[1];
1353
1354     &status(
1355 ">>> banned/$b_blue$chan$ob $b_cyan$args[0]$ob, removing autojoin for $chan"
1356     );
1357     delete $chanconf{$chan}{autojoin};
1358     &joinNextChan();
1359 }
1360
1361 sub on_badchankey {
1362     $conn = shift(@_);
1363     my ($event) = @_;
1364     my @args    = $event->args;
1365     my $chan    = $args[1];
1366
1367     &DEBUG("on_badchankey: args => @args, removing autojoin for $chan");
1368     delete $chanconf{$chan}{autojoin};
1369     &joinNextChan();
1370 }
1371
1372 sub on_useronchan {
1373     $conn = shift(@_);
1374     my ($event) = @_;
1375     my @args = $event->args;
1376
1377     &DEBUG("on_useronchan: args => @args");
1378     &joinNextChan();
1379 }
1380
1381 # TODO not used yet
1382 sub on_stdin {
1383     my $line = <STDIN>;
1384     chomp($line);
1385     &FIXME("on_stdin: line => \"$line\"");
1386 }
1387
1388 1;
1389
1390 # vim:ts=4:sw=4:expandtab:tw=80