]> git.donarmstrong.com Git - infobot.git/blob - src/IRC/Irc.pl
- don't list _default in chanstats
[infobot.git] / src / IRC / Irc.pl
1 #
2 #    Irc.pl: IRC core stuff.
3 #    Author: dms
4 #   Version: 20000126
5 #      NOTE: Based on code by Kevin Lenzo & Patrick Cole  (c) 1997
6 #
7
8 use strict;
9 no strict 'refs';
10
11 use vars qw(%floodjoin %nuh %dcc %cache %channels %param %mask
12         %chanconf %orig %ircPort %ircstats %last %netsplit);
13 use vars qw($irc $nickserv $ident $conn $msgType $who $talkchannel
14         $addressed);
15 use vars qw($notcount $nottime $notsize $msgcount $msgtime $msgsize
16                 $pubcount $pubtime $pubsize);
17 use vars qw($b_blue $ob);
18 use vars qw(@joinchan @ircServers);
19
20 # static scalar variables.
21 $mask{ip}       = '(\d+)\.(\d+)\.(\d+)\.(\d+)';
22 $mask{host}     = '[\d\w\_\-\/]+\.[\.\d\w\_\-\/]+';
23 $mask{chan}     = '[\#\&]\S*|_default';
24 my $isnick1     = 'a-zA-Z\[\]\{\}\_\`\^\|\\\\';
25 my $isnick2     = '0-9\-';
26 $mask{nick}     = "[$isnick1]{1}[$isnick1$isnick2]*";
27 $mask{nuh}      = '\S*!\S*\@\S*';
28
29 $nickserv       = 0;
30
31 sub ircloop {
32     my $error   = 0;
33     my $lastrun = 0;
34
35 loop:;
36     while (my $host = shift @ircServers) {
37         # JUST IN CASE. irq was complaining about this.
38         if ($lastrun == time()) {
39             &DEBUG("ircloop: hrm... lastrun == time()");
40             $error++;
41             sleep 10;
42             next;
43         }
44
45         if (!defined $host) {
46             &DEBUG("ircloop: ircServers[x] = NULL.");
47             $lastrun = time();
48             next;
49         }
50         next unless (exists $ircPort{$host});
51
52         my $retval      = &irc($host, $ircPort{$host});
53         next unless (defined $retval and $retval == 0);
54         $error++;
55
56         if ($error % 3 == 0 and $error != 0) {
57             &status("IRC: Could not connect.");
58             &status("IRC: ");
59             next;
60         }
61
62         if ($error >= 3*2) {
63             &status("IRC: cannot connect to any IRC servers; stopping.");
64             &shutdown();
65             exit 1;
66         }
67     }
68
69     &status("IRC: ok, done one cycle of IRC servers; trying again.");
70
71     &loadIRCServers();
72     goto loop;
73 }
74
75 sub irc {
76     my ($server,$port) = @_;
77
78     my $iaddr = inet_aton($server);
79     my $paddr = sockaddr_in($port, $iaddr);
80     my $proto = getprotobyname('tcp');
81
82     select STDOUT;
83     &status("Connecting to port $port of server $server ...");
84
85     # host->ip.
86     if ($server =~ /\D$/) {
87         my $packed = scalar(gethostbyname($server));
88
89         if (!defined $packed) {
90             &status("  cannot resolve $server.");
91             return 0;
92         }
93
94         my $resolve = inet_ntoa($packed);
95         &status("  resolved to $resolve.");
96         ### warning in Sys/Hostname line 78???
97         ### caused inside Net::IRC?
98     }
99
100     $irc = new Net::IRC;
101
102     my %args = (
103                 Nick    => $param{'ircNick'},
104                 Server  => $server,
105                 Port    => $port,
106                 Ircname => $param{'ircName'},
107     );
108     $args{'LocalAddr'} = $param{'ircHost'} if ($param{'ircHost'});
109     $args{'Password'} = $param{'ircPasswd'} if ($param{'ircPasswd'});
110
111     $conn = $irc->newconn(%args);
112
113     if (!defined $conn) {
114         &ERROR("internal: perl IRC connection object does not exist.");
115         return 1;
116     }
117
118     &clearIRCVars();
119
120     # change internal timeout value for scheduler.
121     $irc->{_timeout}    = 10;   # how about 60?
122     # Net::IRC debugging.
123     $irc->{_debug}      = 1;
124
125     $ircstats{'Server'} = "$server:$port";
126
127     # handler stuff.
128         $conn->add_handler('caction',   \&on_action);
129         $conn->add_handler('cdcc',      \&on_dcc);
130         $conn->add_handler('cping',     \&on_ping);
131         $conn->add_handler('crping',    \&on_ping_reply);
132         $conn->add_handler('cversion',  \&on_version);
133         $conn->add_handler('crversion', \&on_crversion);
134         $conn->add_handler('dcc_open',  \&on_dcc_open);
135         $conn->add_handler('dcc_close', \&on_dcc_close);
136         $conn->add_handler('chat',      \&on_chat);
137         $conn->add_handler('msg',       \&on_msg);
138         $conn->add_handler('public',    \&on_public);
139         $conn->add_handler('join',      \&on_join);
140         $conn->add_handler('part',      \&on_part);
141         $conn->add_handler('topic',     \&on_topic);
142         $conn->add_handler('invite',    \&on_invite);
143         $conn->add_handler('kick',      \&on_kick);
144         $conn->add_handler('mode',      \&on_mode);
145         $conn->add_handler('nick',      \&on_nick);
146         $conn->add_handler('quit',      \&on_quit);
147         $conn->add_handler('notice',    \&on_notice);
148         $conn->add_handler('whoischannels', \&on_whoischannels);
149         $conn->add_handler('useronchannel', \&on_useronchannel);
150         $conn->add_handler('whois',     \&on_whois);
151         $conn->add_handler('other',     \&on_other);
152         $conn->add_global_handler('disconnect', \&on_disconnect);
153         $conn->add_global_handler([251,252,253,254,255], \&on_init);
154 ###     $conn->add_global_handler([251,252,253,254,255,302], \&on_init);
155         $conn->add_global_handler(303, \&on_ison); # notify.
156         $conn->add_global_handler(315, \&on_endofwho);
157         $conn->add_global_handler(422, \&on_endofwho); # nomotd.
158         $conn->add_global_handler(324, \&on_modeis);
159         $conn->add_global_handler(333, \&on_topicinfo);
160         $conn->add_global_handler(352, \&on_who);
161         $conn->add_global_handler(353, \&on_names);
162         $conn->add_global_handler(366, \&on_endofnames);
163         $conn->add_global_handler(376, \&on_endofmotd); # on_connect.
164         $conn->add_global_handler(433, \&on_nick_taken);
165         $conn->add_global_handler(439, \&on_targettoofast);
166         # for proper joinnextChan behaviour
167         $conn->add_global_handler(471, \&on_chanfull);
168         $conn->add_global_handler(473, \&on_inviteonly);
169         $conn->add_global_handler(474, \&on_banned);
170         $conn->add_global_handler(475, \&on_badchankey);
171         $conn->add_global_handler(443, \&on_useronchan);
172
173     # end of handler stuff.
174
175     $irc->start;
176 }
177
178 ######################################################################
179 ######## IRC ALIASES   IRC ALIASES   IRC ALIASES   IRC ALIASES #######
180 ######################################################################
181
182 sub rawout {
183     my ($buf) = @_;
184     $buf =~ s/\n//gi;
185
186     # slow down a bit if traffic is "high".
187     # need to take into account time of last message sent.
188     if ($last{buflen} > 256 and length($buf) > 256) {
189         sleep 1;
190     }
191
192     $conn->sl($buf) if (&whatInterface() =~ /IRC/);
193
194     $last{buflen} = length($buf);
195 }
196
197 sub say {
198     my ($msg) = @_;
199     if (!defined $msg) {
200         $msg ||= "NULL";
201         &WARN("say: msg == $msg.");
202         return;
203     }
204
205     &status("</$talkchannel> $msg");
206     if (&whatInterface() =~ /IRC/) {
207         $msg    = "zero" if ($msg =~ /^0+$/);
208         my $t   = time();
209
210         if ($t == $pubtime) {
211             $pubcount++;
212             $pubsize += length $msg;
213
214             my $i = &getChanConfDefault("sendPublicLimitLines", 3);
215             my $j = &getChanConfDefault("sendPublicLimitBytes", 1000);
216
217             if ( ($pubcount % $i) == 0 and $pubcount) {
218                 sleep 1;
219             } elsif ($pubsize > $j) {
220                 sleep 1;
221                 $pubsize -= $j;
222             }
223
224         } else {
225             $pubcount   = 0;
226             $pubtime    = $t;
227             $pubsize    = length $msg;
228         }
229
230         $conn->privmsg($talkchannel, $msg);
231     }
232 }
233
234 sub msg {
235     my ($nick, $msg) = @_;
236     if (!defined $nick) {
237         &ERROR("msg: nick == NULL.");
238         return;
239     }
240
241     if (!defined $msg) {
242         $msg ||= "NULL";
243         &WARN("msg: msg == $msg.");
244         return;
245     }
246
247     if ($msgType =~ /chat/i) {
248         # todo: warn that we're using msg() to do DCC CHAT?
249         &dccsay($nick, $msg);
250         # todo: make dccsay deal with flood protection?
251         return;
252     }
253
254     &status(">$nick< $msg");
255
256     if (&whatInterface() =~ /IRC/) {
257         my $t = time();
258
259         if ($t == $msgtime) {
260             $msgcount++;
261             $msgsize += length $msg;
262
263             my $i = &getChanConfDefault("sendPrivateLimitLines", 3);
264             my $j = &getChanConfDefault("sendPrivateLimitBytes", 1000);
265             if ( ($msgcount % $i) == 0 and $msgcount) {
266                 sleep 1;
267             } elsif ($msgsize > $j) {
268                 sleep 1;
269                 $msgsize -= $j;
270             }
271
272         } else {
273             $msgcount   = 0;
274             $msgtime    = $t;
275             $msgsize    = length $msg;
276         }
277
278         $conn->privmsg($nick, $msg);
279     }
280 }
281
282 # Usage: &action(nick || chan, txt);
283 sub action {
284     my ($target, $txt) = @_;
285     if (!defined $txt) {
286         &WARN("action: txt == NULL.");
287         return;
288     }
289
290     if (length $txt > 480) {
291         &status("action: txt too long; truncating.");
292         chop($txt) while (length $txt > 480);
293     }
294
295     &status("* $ident/$target $txt");
296     $conn->me($target, $txt);
297 }
298
299 # Usage: &notice(nick || chan, txt);
300 sub notice {
301     my ($target, $txt) = @_;
302     if (!defined $txt) {
303         &WARN("notice: txt == NULL.");
304         return;
305     }
306
307     &status("-$target- $txt");
308
309     my $t       = time();
310
311     if ($t == $nottime) {
312         $notcount++;
313         $notsize += length $txt;
314
315         my $i = &getChanConfDefault("sendNoticeLimitLines", 3);
316         my $j = &getChanConfDefault("sendNoticeLimitBytes", 1000);
317
318         if ( ($notcount % $i) == 0 and $notcount) {
319             sleep 1;
320         } elsif ($notsize > $j) {
321             sleep 1;
322             $notsize -= $j;
323         }
324
325     } else {
326         $notcount       = 0;
327         $nottime        = $t;
328         $notsize        = length $txt;
329     }
330
331     $conn->notice($target, $txt);
332 }
333
334 sub DCCBroadcast {
335     my ($txt,$flag) = @_;
336
337     ### FIXME: flag not supported yet.
338
339     foreach (keys %{ $dcc{'CHAT'} }) {
340         $conn->privmsg($dcc{'CHAT'}{$_}, $txt);
341     }
342 }
343
344 ##########
345 ### perform commands.
346 ###
347
348 # Usage: &performReply($reply);
349 sub performReply {
350     my ($reply) = @_;
351
352     if (!defined $reply or $reply =~ /^\s*$/) {
353         &DEBUG("performReply: reply == NULL.");
354         return;
355     }
356
357     $reply =~ /([\.\?\s]+)$/;
358
359     &checkMsgType($reply);
360
361     if ($msgType eq 'public') {
362         if (rand() < 0.5 or $reply =~ /[\.\?]$/) {
363             $reply = "$orig{who}: ".$reply;
364         } else {
365             $reply = "$reply, ".$orig{who};
366         }
367         &say($reply);
368
369     } elsif ($msgType eq 'private') {
370         if (rand() > 0.5) {
371             $reply = "$reply, ".$orig{who};
372         }
373         &msg($who, $reply);
374
375     } elsif ($msgType eq 'chat') {
376         if (!exists $dcc{'CHAT'}{$who}) {
377             &VERB("pSR: dcc{'CHAT'}{$who} does not exist.",2);
378             return;
379         }
380         $conn->privmsg($dcc{'CHAT'}{$who}, $reply);
381
382     } else {
383         &ERROR("PR: msgType invalid? ($msgType).");
384     }
385 }
386
387 # ...
388 sub performAddressedReply {
389     return unless ($addressed);
390     &performReply(@_);
391 }
392
393 sub pSReply {
394     &performStrictReply(@_);
395 }
396
397 # Usage: &performStrictReply($reply);
398 sub performStrictReply {
399     my ($reply) = @_;
400
401     &checkMsgType($reply);
402
403     if ($msgType eq 'private') {
404         &msg($who, $reply);
405     } elsif ($msgType eq 'public') {
406         &say($reply);
407     } elsif ($msgType eq 'chat') {
408         &dccsay(lc $who, $reply);
409     } else {
410         &ERROR("pSR: msgType invalid? ($msgType).");
411     }
412 }
413
414 sub dccsay {
415     my($who, $reply) = @_;
416
417     if (!defined $reply or $reply =~ /^\s*$/) {
418         &WARN("dccsay: reply == NULL.");
419         return;
420     }
421
422     if (!exists $dcc{'CHAT'}{$who}) {
423         &VERB("pSR: dcc{'CHAT'}{$who} does not exist. (2)",2);
424         return;
425     }
426
427     &status("=>$who<= $reply");         # dcc chat.
428     $conn->privmsg($dcc{'CHAT'}{$who}, $reply);
429 }
430
431 sub dcc_close {
432     my($who) = @_;
433     my $type;
434
435     foreach $type (keys %dcc) {
436         &FIXME("dcc_close: $who");
437         my @who = grep /^\Q$who\E$/i, keys %{ $dcc{$type} };
438         next unless (scalar @who);
439         $who = $who[0];
440         &DEBUG("dcc_close... close $who!");
441     }
442 }
443
444 sub joinchan {
445     my ($chan)  = @_;
446     my $key     = &getChanConf("chankey", $chan) || "";
447
448     # forgot for about 2 years to implement channel keys when moving
449     # over to Net::IRC...
450
451     # hopefully validChan is right.
452     if (&validChan($chan)) {
453         &status("join: already on $chan");
454     } else {
455         &status("joining $b_blue$chan$ob");
456
457         return if ($conn->join($chan, $key));
458
459         &DEBUG("joinchan: join failed. trying connect!");
460         &clearIRCVars();
461         $conn->connect();
462     }
463 }
464
465 sub part {
466     my $chan;
467
468     foreach $chan (@_) {
469         next if ($chan eq "");
470         $chan =~ tr/A-Z/a-z/;   # lowercase.
471
472         if ($chan !~ /^$mask{chan}$/) {
473             &WARN("part: chan is invalid ($chan)");
474             next;
475         }
476
477         &status("parting $chan");
478         if (!&validChan($chan)) {
479             &WARN("part: not on $chan; doing anyway");
480 #           next;
481         }
482
483         $conn->part($chan);
484         # deletion of $channels{chan} is done in &entryEvt().
485     }
486 }
487
488 sub mode {
489     my ($chan, @modes) = @_;
490     my $modes = join(" ", @modes);
491
492     if (&validChan($chan) == 0) {
493         &ERROR("mode: invalid chan => '$chan'.");
494         return;
495     }
496
497     &DEBUG("mode: MODE $chan $modes");
498
499     # should move to use Net::IRC's $conn->mode()... but too lazy.
500     rawout("MODE $chan $modes");
501 }
502
503 sub op {
504     my ($chan, @who) = @_;
505     my $os      = "o" x scalar(@who);
506
507     &mode($chan, "+$os @who");
508 }
509
510 sub deop {
511     my ($chan, @who) = @_;
512     my $os = "o" x scalar(@who);
513
514     &mode($chan, "-$os ".@who);
515 }
516
517 sub kick {
518     my ($nick,$chan,$msg) = @_;
519     my (@chans) = ($chan eq "") ? (keys %channels) : lc($chan);
520
521     if ($chan ne "" and &validChan($chan) == 0) {
522         &ERROR("kick: invalid channel $chan.");
523         return;
524     }
525
526     $nick =~ tr/A-Z/a-z/;
527
528     foreach $chan (@chans) {
529         if (!&IsNickInChan($nick,$chan)) {
530             &status("kick: $nick is not on $chan.") if (scalar @chans == 1);
531             next;
532         }
533
534         if (!exists $channels{$chan}{o}{$ident}) {
535             &status("kick: do not have ops on $chan :(");
536             next;
537         }
538
539         &status("Kicking $nick from $chan.");
540         $conn->kick($chan, $nick, $msg);
541     }
542 }
543
544 sub ban {
545     my ($mask,$chan) = @_;
546     my (@chans) = ($chan =~ /^\*?$/) ? (keys %channels) : lc($chan);
547     my $ban     = 0;
548
549     if ($chan !~ /^\*?$/ and &validChan($chan) == 0) {
550         &ERROR("ban: invalid channel $chan.");
551         return;
552     }
553
554     foreach $chan (@chans) {
555         if (!exists $channels{$chan}{o}{$ident}) {
556             &status("ban: do not have ops on $chan :(");
557             next;
558         }
559
560         &status("Banning $mask from $chan.");
561         &rawout("MODE $chan +b $mask");
562         $ban++;
563     }
564
565     return $ban;
566 }
567
568 sub unban {
569     my ($mask,$chan) = @_;
570     my (@chans) = ($chan =~ /^\*?$/) ? (keys %channels) : lc($chan);
571     my $ban     = 0;
572
573     &DEBUG("unban: mask = $mask, chan = @chans");
574
575     foreach $chan (@chans) {
576         if (!exists $channels{$chan}{o}{$ident}) {
577             &status("unBan: do not have ops on $chan :(");
578             next;
579         }
580
581         &status("Removed ban $mask from $chan.");
582         &rawout("MODE $chan -b $mask");
583         $ban++;
584     }
585
586     return $ban;
587 }
588
589 sub quit {
590     my ($quitmsg) = @_;
591     &status("QUIT $param{'ircNick'} has quit IRC ($quitmsg)");
592     if (defined $conn) {
593         $conn->quit($quitmsg);
594     } else {
595         &WARN("quit: could not quit!");
596     }
597 }
598
599 sub nick {
600     my ($nick) = @_;
601
602     if (!defined $nick) {
603         &ERROR("nick: nick == NULL.");
604         return;
605     }
606
607     if (defined $ident and $nick eq $ident) {
608         &WARN("nick: nick == ident == '$ident'.");
609         return;
610     }
611
612     my $bad     = 0;
613     $bad++ if (exists $nuh{ $param{'ircNick'} });
614     $bad++ if (&IsNickInAnyChan($param{'ircNick'}));
615
616     if ($bad) {
617         &WARN("Nick: not going to try and get my nick back. [".
618                 scalar(gmtime). "]");
619 # hrm... over time we lose track of our own nick.
620 #       return;
621     }
622
623     if ($nick =~ /^$mask{nick}$/) {
624         &rawout("NICK ".$nick);
625
626         if (defined $ident) {
627             &status("nick: Changing nick to $nick (from $ident)");
628             # following shouldn't be here :(
629             $ident      = $nick;
630         } else {
631             &DEBUG("first time nick change.");
632             $ident      = $nick;
633         }
634
635         return 1;
636     }
637     &DEBUG("nick: failed... why oh why (nick => $nick)");
638
639     return 0;
640 }
641
642 sub invite {
643     my($who, $chan) = @_;
644     # todo: check if $who or $chan are invalid.
645
646     $conn->invite($who, $chan);
647 }
648
649 ##########
650 # Channel related functions...
651 #
652
653 # Usage: &joinNextChan();
654 sub joinNextChan {
655     if (scalar @joinchan) {
656         my $chan = shift @joinchan;
657         &joinchan($chan);
658
659         if (my $i = scalar @joinchan) {
660             &status("joinNextChan: $i chans to join.");
661         }
662
663         return;
664     }
665
666     # !scalar @joinchan:
667     my @c       = &getJoinChans();
668     if (exists $cache{joinTime} and scalar @c) {
669         my $delta       = time() - $cache{joinTime} - 5;
670         my $timestr     = &Time2String($delta);
671         my $rate        = sprintf("%.1f", $delta / @c);
672         delete $cache{joinTime};
673
674         &status("time taken to join all chans: $timestr; rate: $rate sec/join");
675     }
676
677     # chanserv check: global channels, in case we missed one.
678     foreach ( &ChanConfList("chanServ_ops") ) {
679         &chanServCheck($_);
680     }
681 }
682
683 # Usage: &getNickInChans($nick);
684 sub getNickInChans {
685     my ($nick) = @_;
686     my @array;
687
688     foreach (keys %channels) {
689         next unless (grep /^\Q$nick\E$/i, keys %{ $channels{$_}{''} });
690         push(@array, $_);
691     }
692
693     return @array;
694 }
695
696 # Usage: &getNicksInChan($chan);
697 sub getNicksInChan {
698     my ($chan) = @_;
699     my @array;
700
701     return keys %{ $channels{$chan}{''} };
702 }
703
704 sub IsNickInChan {
705     my ($nick,$chan) = @_;
706
707     $chan =~ tr/A-Z/a-z/;       # not lowercase unfortunately.
708
709     if ($chan =~ /^$/) {
710         &DEBUG("INIC: chan == NULL.");
711         return 0;
712     }
713
714     if (&validChan($chan) == 0) {
715         &ERROR("INIC: invalid channel $chan.");
716         return 0;
717     }
718
719     if (grep /^\Q$nick\E$/i, keys %{ $channels{$chan}{''} }) {
720         return 1;
721     } else {
722         foreach (keys %channels) {
723             next unless (/[A-Z]/);
724             &DEBUG("iNIC: hash channels contains mixed cased chan!!!");
725         }
726         return 0;
727     }
728 }
729
730 sub IsNickInAnyChan {
731     my ($nick) = @_;
732     my $chan;
733
734     foreach $chan (keys %channels) {
735         next unless (grep /^\Q$nick\E$/i, keys %{ $channels{$chan}{''}  });
736         return 1;
737     }
738     return 0;
739 }
740
741 # Usage: &validChan($chan);
742 sub validChan {
743     # todo: use $c instead?
744     my ($chan) = @_;
745
746     if (!defined $chan or $chan =~ /^\s*$/) {
747         return 0;
748     }
749
750     if (lc $chan ne $chan) {
751         &WARN("validChan: lc chan != chan. ($chan); fixing.");
752         $chan =~ tr/A-Z/a-z/;
753     }
754
755     # it's possible that this check creates the hash if empty.
756     if (defined $channels{$chan} or exists $channels{$chan}) {
757         if ($chan =~ /^_?default$/) {
758 #           &WARN("validC: chan cannot be _default! returning 0!");
759             return 0;
760         }
761
762         return 1;
763     } else {
764         return 0;
765     }
766 }
767
768 ###
769 # Usage: &delUserInfo($nick,@chans);
770 sub delUserInfo {
771     my ($nick,@chans) = @_;
772     my ($mode,$chan);
773
774     foreach $chan (@chans) {
775         foreach $mode (keys %{ $channels{$chan} }) {
776             # use grep here?
777             next unless (exists $channels{$chan}{$mode}{$nick});
778
779             delete $channels{$chan}{$mode}{$nick};
780         }
781     }
782 }
783
784 sub clearChanVars {
785     my ($chan) = @_;
786
787     delete $channels{$chan};
788 }
789
790 sub clearIRCVars {
791     undef %channels;
792     undef %floodjoin;
793
794     @joinchan           = &getJoinChans(1);
795     $cache{joinTime}    = time();
796 }
797
798 sub getJoinChans {
799     my($show)   = @_;
800     my @chans;
801     my @skip;
802
803     foreach (keys %chanconf) {
804         next if ($_ eq "_default");
805
806         my $val = $chanconf{$_}{autojoin};
807         my $skip = 0;
808
809         if (defined $val) {
810             $skip++ if ($val eq "0");
811         } else {
812             $skip++;
813         }
814
815         if ($skip) {
816             push(@skip, $_);
817             next;
818         }
819
820         push(@chans, $_);
821     }
822
823     my $str;
824     if (scalar @skip) {
825         $str = "channels not auto-joining: @skip (joining: @chans)";
826     } else {
827         $str = "auto-joining all chans: @chans";
828     }
829
830     &status("Chans: ".$str) if ($show);
831
832     return @chans;
833 }
834
835 sub closeDCC {
836 #    &DEBUG("closeDCC called.");
837     my $type;
838
839     foreach $type (keys %dcc) {
840         next if ($type ne uc($type));
841  
842         my $nick;
843         foreach $nick (keys %{ $dcc{$type} }) {
844             next unless (defined $nick);
845             &status("DCC CHAT: closing DCC $type to $nick.");
846             next unless (defined $dcc{$type}{$nick});
847
848             my $ref = $dcc{$type}{$nick};
849             &dccsay($nick, "bye bye, $nick") if ($type =~ /^chat$/i);
850             $dcc{$type}{$nick}->close();
851             delete $dcc{$type}{$nick};
852             &DEBUG("after close for $nick");
853         }
854         delete $dcc{$type};
855     }
856 }
857
858 sub joinfloodCheck {
859     my($who,$chan,$userhost) = @_;
860
861     return unless (&IsChanConf("joinfloodCheck"));
862
863     if (exists $netsplit{lc $who}) {    # netsplit join.
864         &DEBUG("joinfloodCheck: $who was in netsplit; not checking.");
865     }
866
867     if (exists $floodjoin{$chan}{$who}{Time}) {
868         &WARN("floodjoin{$chan}{$who} already exists?");
869     }
870
871     $floodjoin{$chan}{$who}{Time} = time();
872     $floodjoin{$chan}{$who}{Host} = $userhost;
873
874     ### Check...
875     foreach (keys %floodjoin) {
876         my $c = $_;
877         my $count = scalar keys %{ $floodjoin{$c} };
878         next unless ($count > 5);
879         &DEBUG("joinflood: count => $count");
880
881         my $time;
882         foreach (keys %{ $floodjoin{$c} }) {
883             my $t = $floodjoin{$c}{$_}{Time};
884             next unless (defined $t);
885
886             $time += $t;
887         }
888         &DEBUG("joinflood: time => $time");
889         $time /= $count;
890
891         &DEBUG("joinflood: new time => $time");
892     }
893
894     ### Clean it up.
895     my $delete = 0;
896     my $time = time();
897     foreach $chan (keys %floodjoin) {
898         foreach $who (keys %{ $floodjoin{$chan} }) {
899             my $t       = $floodjoin{$chan}{$who}{Time};
900             next unless (defined $t);
901
902             my $delta   = $time - $t;
903             next unless ($delta > 10);
904
905             delete $floodjoin{$chan}{$who};
906             $delete++;
907         }
908     }
909
910     &DEBUG("joinfloodCheck: $delete deleted.") if ($delete);
911 }
912
913 sub getHostMask {
914     my($n) = @_;
915
916     if (exists $nuh{$n}) {
917         return &makeHostMask($nuh{$n});
918     } else {
919         $cache{on_who_Hack} = 1;
920         $conn->who($n);
921     }
922 }
923
924 1;