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