]> git.donarmstrong.com Git - infobot.git/blob - src/Modules/UserDCC.pl
clearer, and wrap fixes
[infobot.git] / src / Modules / UserDCC.pl
1 #
2 #  UserDCC.pl: User Commands, DCC CHAT.
3 #      Author: dms
4 #     Version: v0.2 (20010119)
5 #     Created: 20000707 (from UserExtra.pl)
6 #
7
8 use strict;
9
10 use vars qw(%users %ignore %sched %bans %mask %cache %channels %param
11         %chanconf %dcc);
12 use vars qw($who $chan $message $msgType $user $chnick $conn $ident
13         $verifyUser $ucount_userfile $utime_userfile $lobotomized
14         $utime_chanfile $ucount_chanfile);
15 use vars qw(@backlog);
16
17 sub userDCC {
18     # hrm...
19     $message =~ s/\s+$//;
20
21     ### for all users.
22     # quit.
23     if ($message =~ /^(exit|quit)$/i) {
24         # do ircII clients support remote close? if so, cool!
25         &FIXME("userDCC: quit called.");
26         &dcc_close($who);
27         &status("userDCC: after dcc_close!");
28
29         return;
30     }
31
32     # who.
33     if ($message =~ /^who$/) {
34         my $count = scalar(keys %{ $dcc{'CHAT'} });
35         my $dccCHAT = $message;
36
37         &pSReply("Start of who ($count users).");
38         foreach (keys %{ $dcc{'CHAT'} }) {
39             &pSReply("=> $_");
40         }
41         &pSReply("End of who.");
42
43         return;
44     }
45
46     ### for those users with enough flags.
47
48     if ($message =~ /^tellme(\s+(.*))?$/i) {
49         my $args = $2;
50         if ($args =~ /^\s*$/) {
51             &help("tellme");
52             return;
53         }
54
55         my $result = &doQuestion($args);
56         &pSReply($result);
57
58         return;
59     }
60
61     # 4op.
62     if ($message =~ /^4op(\s+($mask{chan}))?$/i) {
63         return unless (&hasFlag("o"));
64
65         my $chan = $2;
66
67         if ($chan eq "") {
68             &help("4op");
69             return;
70         }
71
72         if (!$channels{$chan}{'o'}{$ident}) {
73             &msg($who, "i don't have ops on $chan to do that.");
74             return;
75         }
76
77         # on non-4mode(<4) servers, this may be exploited.
78         if ($channels{$chan}{'o'}{$who}) {
79             rawout("MODE $chan -o+o-o+o". (" $who" x 4));
80         } else {
81             rawout("MODE $chan +o-o+o-o". (" $who" x 4));
82         }
83
84         return;
85     }
86
87     # backlog.
88     if ($message =~ /^backlog(\s+(.*))?$/i) {
89         return unless (&hasFlag("o"));
90         return unless (&hasParam("backlog"));
91         my $num = $2;
92         my $max = $param{'backlog'};
93
94         if (!defined $num) {
95             &help("backlog");
96             return;
97         } elsif ($num !~ /^\d+/) {
98             &msg($who, "error: argument is not positive integer.");
99             return;
100         } elsif ($num > $max or $num < 0) {
101             &msg($who, "error: argument is out of range (max $max).");
102             return;
103         }
104
105         &msg($who, "Start of backlog...");
106         for (0..$num-1) {
107             sleep 1 if ($_ % 4 == 0 and $_ != 0);
108             $conn->privmsg($who, "[".($_+1)."]: $backlog[$max-$num+$_]");
109         }
110         &msg($who, "End of backlog.");
111
112         return;
113     }
114
115     # dump variables.
116     if ($message =~ /^dumpvars$/i) {
117         return unless (&hasFlag("o"));
118         return unless (&IsParam("dumpvars"));
119
120         &status("Dumping all variables...");
121         &dumpallvars();
122
123         return;
124     }
125
126     # dump variables ][.
127     if ($message =~ /^symdump$/i) {
128         return unless (&hasFlag("o"));
129         return unless (&IsParam("symdump"));
130
131         &status("Dumping all variables...");
132         &symdumpAllFile();
133
134         return;
135     }
136
137     # kick.
138     if ($message =~ /^kick(\s+(.*?))$/) {
139         return unless (&hasFlag("o"));
140
141         my $arg = $2;
142
143         if ($arg eq "") {
144             &help("kick");
145             return;
146         }
147         my @args = split(/\s+/, $arg);
148         my ($nick,$chan,$reason) = @args;
149
150         if (&validChan($chan) == 0) {
151             &msg($who,"error: invalid channel \002$chan\002");
152             return;
153         }
154
155         if (&IsNickInChan($nick,$chan) == 0) {
156             &msg($who,"$nick is not in $chan.");
157             return;
158         }
159
160         &kick($nick,$chan,$reason);
161
162         return;
163     }
164
165     # mode.
166     if ($message =~ /^mode(\s+(.*))?$/) {
167         return unless (&hasFlag("n"));
168         my ($chan,$mode) = split /\s+/,$2,2;
169
170         if ($chan eq "") {
171             &help("mode");
172             return;
173         }
174
175         if (&validChan($chan) == 0) {
176             &msg($who,"error: invalid channel \002$chan\002");
177             return;
178         }
179
180         if (!$channels{$chan}{o}{$ident}) {
181             &msg($who,"error: don't have ops on \002$chan\002");
182             return;
183         }
184
185         &mode($chan, $mode);
186
187         return;
188     }
189
190     # part.
191     if ($message =~ /^part(\s+(\S+))?$/i) {
192         return unless (&hasFlag("o"));
193         my $jchan = $2;
194
195         if ($jchan !~ /^$mask{chan}$/) {
196             &msg($who, "error, invalid chan.");
197             &help("part");
198             return;
199         }
200
201         if (!&validChan($jchan)) {
202             &msg($who, "error, I'm not on that chan.");
203             return;
204         }
205
206         &msg($jchan, "Leaving. (courtesy of $who).");
207         &part($jchan);
208         return;
209     }
210
211     # lobotomy. sometimes we want the bot to be _QUIET_.
212     if ($message =~ /^(lobotomy|bequiet)$/i) {
213         return unless (&hasFlag("o"));
214
215         if ($lobotomized) {
216             &performReply("i'm already lobotomized");
217         } else {
218             &performReply("i have been lobotomized");
219             $lobotomized = 1;
220         }
221
222         return;
223     }
224
225     # unlobotomy.
226     if ($message =~ /^(unlobotomy|benoisy)$/i) {
227         return unless (&hasFlag("o"));
228
229         if ($lobotomized) {
230             &performReply("i have been unlobotomized, woohoo");
231             $lobotomized = 0;
232             delete $cache{lobotomy};
233 #           undef $cache{lobotomy};     # ??
234         } else {
235             &performReply("i'm not lobotomized");
236         }
237
238         return;
239     }
240
241     # op.
242     if ($message =~ /^op(\s+(.*))?$/i) {
243         return unless (&hasFlag("o"));
244         my ($opee) = lc $2;
245         my @chans;
246
247         if ($opee =~ / /) {
248             if ($opee =~ /^(\S+)\s+(\S+)$/) {
249                 $opee  = $1;
250                 @chans = ($2);
251                 if (!&validChan($2)) {
252                     &msg($who,"error: invalid chan ($2).");
253                     return;
254                 }
255             } else {
256                 &msg($who,"error: invalid params.");
257                 return;
258             }
259         } else {
260             @chans = keys %channels;
261         }
262
263         my $found = 0;
264         my $op = 0;
265         foreach (@chans) {
266             next unless (&IsNickInChan($opee,$_));
267             $found++;
268             if ($channels{$_}{'o'}{$opee}) {
269                 &pSReply("op: $opee already has ops on $_");
270                 next;
271             }
272             $op++;
273
274             &pSReply("opping $opee on $_");
275             &op($_, $opee);
276         }
277
278         if ($found != $op) {
279             &pSReply("op: opped on all possible channels.");
280         } else {
281             &DEBUG("op: found => '$found'.");
282             &DEBUG("op:    op => '$op'.");
283         }
284
285         return;
286     }
287
288     # deop.
289     if ($message =~ /^deop(\s+(.*))?$/i) {
290         return unless (&hasFlag("o"));
291         my ($opee) = lc $2;
292         my @chans;
293
294         if ($opee =~ / /) {
295             if ($opee =~ /^(\S+)\s+(\S+)$/) {
296                 $opee  = $1;
297                 @chans = ($2);
298                 if (!&validChan($2)) {
299                     &msg($who,"error: invalid chan ($2).");
300                     return;
301                 }
302             } else {
303                 &msg($who,"error: invalid params.");
304                 return;
305             }
306         } else {
307             @chans = keys %channels;
308         }
309
310         my $found = 0;
311         my $op = 0;
312         foreach (@chans) {
313             next unless (&IsNickInChan($opee,$_));
314             $found++;
315             if (!exists $channels{$_}{'o'}{$opee}) {
316                 &status("deop: $opee already has no ops on $_");
317                 next;
318             }
319             $op++;
320
321             &status("deopping $opee on $_ at ${who}'s request");
322             &deop($_, $opee);
323         }
324
325         if ($found != $op) {
326             &status("deop: deopped on all possible channels.");
327         } else {
328             &DEBUG("deop: found => '$found'.");
329             &DEBUG("deop: op => '$op'.");
330         }
331
332         return;
333     }
334
335     # say.
336     if ($message =~ s/^say\s+(\S+)\s+(.*)//) {
337         return unless (&hasFlag("o"));
338         my ($chan,$msg) = (lc $1, $2);
339
340         &DEBUG("chan => '$1', msg => '$msg'.");
341
342         # TODO: add nick destination.
343         if (&validChan($chan)) {
344             &msg($chan, $msg);
345         } else {
346             &msg($who,"i'm not on \002$chan\002, sorry.");
347         }
348
349         return;
350     }
351
352     # die.
353     if ($message =~ /^die$/) {
354         return unless (&hasFlag("n"));
355
356         &doExit();
357
358         &status("Dying by $who\'s request");
359         exit 0;
360     }
361
362     # global factoid substitution.
363     if ($message =~ m|^s([/,#])(.+?)\1(.*?)\1;?\s*$|) {
364         my ($delim,$op,$np) = ($1, $2, $3);
365         return unless (&hasFlag("n"));
366         ### TODO: support flags to do full-on global.
367
368         # incorrect format.
369         if ($np =~ /$delim/) {
370             &performReply("looks like you used the delimiter too many times. You may want to use a different delimiter, like ':' or '#'.");
371             return;
372         }
373
374         ### TODO: fix up $op to support mysql/sqlite/pgsql
375         ### TODO: => add db/sql specific function to fix this.
376         my @list = &searchTable("factoids", "factoid_key",
377                         "factoid_value", $op);
378
379         if (!scalar @list) {
380             &performReply("Expression didn't match anything.");
381             return;
382         }
383
384         if (scalar @list > 100) {
385             &performReply("regex found more than 100 matches... not doing.");
386             return;
387         }
388
389         &status("gsubst: going to alter ".scalar(@list)." factoids.");
390         &performReply("going to alter ".scalar(@list)." factoids.");
391
392         my $error = 0;
393         foreach (@list) {
394             my $faqtoid = $_;
395
396             next if (&IsLocked($faqtoid) == 1);
397             my $result = &getFactoid($faqtoid);
398             my $was = $result;
399             &DEBUG("was($faqtoid) => '$was'.");
400
401             # global global
402             # we could support global local (once off).
403             if ($result =~ s/\Q$op/$np/gi) {
404                 if (length $result > $param{'maxDataSize'}) {
405                     &performReply("that's too long (or was long)");
406                     return;
407                 }
408                 &setFactInfo($faqtoid, "factoid_value", $result);
409                 &status("update: '$faqtoid' =is=> '$result'; was '$was'");
410             } else {
411                 &WARN("subst: that's weird... thought we found the string ($op) in '$faqtoid'.");
412                 $error++;
413             }
414         }
415
416         if ($error) {
417             &ERROR("Some warnings/errors?");
418         }
419
420         &performReply("Ok... did s/$op/$np/ for ".
421                                 (scalar(@list) - $error)." factoids");
422
423         return;
424     }
425
426     # jump.
427     if ($message =~ /^jump(\s+(\S+))?$/i) {
428         return unless (&hasFlag("n"));
429
430         if ($2 eq "") {
431             &help("jump");
432             return;
433         }
434
435         my ($server,$port);
436         if ($2 =~ /^(\S+)(:(\d+))?$/) {
437             $server = $1;
438             $port   = $3 || 6667;
439         } else {
440             &msg($who,"invalid format.");
441             return;
442         }
443
444         &status("jumping servers... $server...");
445         $conn->quit("jumping to $server");
446
447         if (&irc($server,$port) == 0) {
448             &ircloop();
449         }
450     }
451
452     # reload.
453     if ($message =~ /^reload$/i) {
454         return unless (&hasFlag("n"));
455
456         &status("USER reload $who");
457         &pSReply("reloading...");
458         &reloadAllModules();
459         &pSReply("reloaded.");
460
461         return;
462     }
463
464     # reset.
465     if ($message =~ /^reset$/i) {
466         return unless (&hasFlag("n"));
467
468         &msg($who,"resetting...");
469         my @done;
470         foreach ( keys %channels, keys %chanconf ) {
471             my $c = $_;
472             next if (grep /^\Q$c\E$/i, @done);
473
474             &part($_);
475
476             push(@done, $_);
477             sleep 1;
478         }
479         &DEBUG("before clearircvars");
480         &clearIRCVars();
481         &DEBUG("before joinnextchan");
482         &joinNextChan();
483         &DEBUG("after joinnextchan");
484
485         &status("USER reset $who");
486         &msg($who,"reset complete");
487
488         return;
489     }
490
491     # rehash.
492     if ($message =~ /^rehash$/) {
493         return unless (&hasFlag("n"));
494
495         &msg($who,"rehashing...");
496         &restart("REHASH");
497         &status("USER rehash $who");
498         &msg($who,"rehashed");
499
500         return;
501     }
502
503     #####
504     ##### USER//CHAN SPECIFIC CONFIGURATION COMMANDS
505     #####
506
507     if ($message =~ /^chaninfo(\s+(.*))?$/) {
508         my @args = split /[\s\t]+/, $2; # hrm.
509
510         if (scalar @args != 1) {
511             &help("chaninfo");
512             return;
513         }
514
515         if (!exists $chanconf{$args[0]}) {
516             &pSReply("no such channel $args[0]");
517             return;
518         }
519
520         &pSReply("showing channel conf.");
521         foreach (sort keys %{ $chanconf{$args[0]} }) {
522             &pSReply("$chan: $_ => $chanconf{$args[0]}{$_}");
523         }
524         &pSReply("End of chaninfo.");
525
526         return;
527     }
528
529     # +chan.
530     if ($message =~ /^(chanset|\+chan)(\s+(.*?))?$/) {
531         my $cmd         = $1;
532         my $args        = $3;
533         my $no_chan     = 0;
534
535         if (!defined $args) {
536             &help($cmd);
537             return;
538         }
539
540         my @chans;
541         while ($args =~ s/^($mask{chan})\s*//) {
542             push(@chans, lc($1));
543         }
544
545         if (!scalar @chans) {
546             push(@chans, "_default");
547             $no_chan    = 1;
548         }
549
550         my($what,$val) = split /[\s\t]+/, $args, 2;
551
552         ### TODO: "cannot set values without +m".
553         return unless (&hasFlag("n"));
554
555         # READ ONLY.
556         if (defined $what and $what !~ /^[-+]/ and !defined $val and $no_chan) {
557             &pSReply("Showing $what values on all channels...");
558
559             my %vals;
560             foreach (keys %chanconf) {
561                 my $val;
562                 if (defined $chanconf{$_}{$what}) {
563                     $val = $chanconf{$_}{$what};
564                 } else {
565                     $val = "NOT-SET";
566                 }
567                 $vals{$val}{$_} = 1;
568             }
569
570             foreach (keys %vals) {
571                 &pSReply("  $what = $_: ".join(' ', keys %{ $vals{$_} } ) );
572             }
573
574             &pSReply("End of list.");
575
576             return;
577         }
578
579         ### TODO: move to UserDCC again.
580         if ($cmd eq "chanset" and !defined $what) {
581             &DEBUG("showing channel conf.");
582
583             foreach $chan (@chans) {
584                 if ($chan eq '_default') {
585                     &pSReply("Default channel settings");
586                 } else {
587                     &pSReply("chan: $chan (see _default also)");
588                 }
589                 my @items;
590                 my $str = "";
591                 foreach (sort keys %{ $chanconf{$chan} }) {
592                     my $newstr = join(', ', @items);
593                     ### TODO: make length use channel line limit?
594                     if (length $newstr > 370) {
595                         &pSReply(" $str");
596                         @items = ();
597                     }
598                     $str = $newstr;
599                     push(@items, "$_ => $chanconf{$chan}{$_}");
600                 }
601                 &pSReply(" $str") if (@items);
602             }
603             return;
604         }
605
606         $cache{confvars}{$what} = $val;
607         &rehashConfVars();
608
609         foreach (@chans) {
610             &chanSet($cmd, $_, $what, $val);
611         }
612
613         return;
614     }
615
616     if ($message =~ /^(chanunset|\-chan)(\s+(.*))?$/) {
617         return unless (&hasFlag("n"));
618         my $args        = $3;
619         my $no_chan     = 0;
620
621         if (!defined $args) {
622             &help("chanunset");
623             return;
624         }
625
626         my ($chan);
627         my $delete      = 0;
628         if ($args =~ s/^(\-)?($mask{chan})\s*//) {
629             $chan       = $2;
630             $delete     = ($1) ? 1 : 0;
631         } else {
632             &VERB("no chan arg; setting to default.",2);
633             $chan       = "_default";
634             $no_chan    = 1;
635         }
636
637         if (!exists $chanconf{$chan}) {
638             &pSReply("no such channel $chan");
639             return;
640         }
641
642         if ($args ne "") {
643
644             if (!&getChanConf($args,$chan)) {
645                 &pSReply("$args does not exist for $chan");
646                 return;
647             }
648
649             my @chans = &ChanConfList($args);
650             &DEBUG("scalar chans => ".scalar(@chans) );
651             if (scalar @chans == 1 and $chans[0] eq "_default" and !$no_chan) {
652                 &psReply("ok, $args was set only for _default; unsetting for _defaul but setting for other chans.");
653
654                 my $val = $chanconf{$_}{_default};
655                 foreach (keys %chanconf) {
656                     $chanconf{$_}{$args} = $val;
657                 }
658                 delete $chanconf{_default}{$args};
659                 $cache{confvars}{$args} = 0;
660                 &rehashConfVars();
661
662                 return;
663             }
664
665             if ($no_chan and !exists($chanconf{_default}{$args})) {
666                 &pSReply("ok, $args for _default does not exist, removing from all chans.");
667
668                 foreach (keys %chanconf) {
669                     next unless (exists $chanconf{$_}{$args});
670                     &DEBUG("delete chanconf{$_}{$args};");
671                     delete $chanconf{$_}{$args};
672                 }
673                 $cache{confvars}{$args} = 0;
674                 &rehashConfVars();
675
676                 return;
677             }
678
679             &pSReply("Unsetting channel ($chan) option $args. (was $chanconf{$chan}{$args})");
680             delete $chanconf{$chan}{$args};
681
682             return;
683         }
684
685         if ($delete) {
686             &pSReply("Deleting channel $chan for sure!");
687             $utime_chanfile = time();
688             $ucount_chanfile++;
689
690             &part($chan);
691             &pSReply("Leaving $chan...");
692
693             delete $chanconf{$chan};
694         } else {
695             &pSReply("Prefix channel with '-' to delete for sure.");
696         }
697
698         return;
699     }
700
701     if ($message =~ /^newpass(\s+(.*))?$/) {
702         my(@args) = split /[\s\t]+/, $2 || '';
703
704         if (scalar @args != 1) {
705             &help("newpass");
706             return;
707         }
708
709         my $u           = &getUser($who);
710         my $crypt       = &mkcrypt($args[0]);
711
712         &pSReply("Set your passwd to '$crypt'");
713         $users{$u}{PASS} = $crypt;
714
715         $utime_userfile = time();
716         $ucount_userfile++;
717
718         return;
719     }
720
721     if ($message =~ /^chpass(\s+(.*))?$/) {
722         my(@args) = split /[\s\t]+/, $2 || '';
723
724         if (!scalar @args) {
725             &help("chpass");
726             return;
727         }
728
729         if (!&IsUser($args[0])) {
730             &pSReply("user $args[0] is not valid.");
731             return;
732         }
733
734         my $u = &getUser($args[0]);
735         if (!defined $u) {
736             &pSReply("Internal error, u = NULL.");
737             return;
738         }
739
740         if (scalar @args == 1) {        # del pass.
741             if (!&IsFlag("n") and $who !~ /^\Q$verifyUser\E$/i) {
742                 &pSReply("cannot remove passwd of others.");
743                 return;
744             }
745
746             if (!exists $users{$u}{PASS}) {
747                 &pSReply("$u does not have pass set anyway.");
748                 return;
749             }
750
751             &pSReply("Deleted pass from $u.");
752
753             $utime_userfile = time();
754             $ucount_userfile++;
755
756             delete $users{$u}{PASS};
757
758             return;
759         }
760
761         my $crypt       = &mkcrypt($args[1]);
762         &pSReply("Set $u's passwd to '$crypt'");
763         $users{$u}{PASS} = $crypt;
764
765         $utime_userfile = time();
766         $ucount_userfile++;
767
768         return;
769     }
770
771     if ($message =~ /^chattr(\s+(.*))?$/) {
772         my(@args) = split /[\s\t]+/, $2 || '';
773
774         if (!scalar @args) {
775             &help("chattr");
776             return;
777         }
778
779         my $chflag;
780         my $user;
781         if ($args[0] =~ /^$mask{nick}$/i) {     # <nick>
782             $user       = &getUser($args[0]);
783             $chflag     = $args[1];
784         } else {                                # <flags>
785             $user       = &getUser($who);
786             &DEBUG("user $who... nope.") unless (defined $user);
787             $user       = &getUser($verifyUser);
788             $chflag     = $args[0];
789         }
790
791         if (!defined $user) {
792             &pSReply("user does not exist.");
793             return;
794         }
795
796         my $flags = $users{$user}{FLAGS};
797         if (!defined $chflag) {
798             &pSReply("Flags for $user: $flags");
799             return;
800         }
801
802         &DEBUG("who => $who");
803         &DEBUG("verifyUser => $verifyUser");
804         if (!&IsFlag("n") and $who !~ /^\Q$verifyUser\E$/i) {
805             &pSReply("cannto change attributes of others.");
806             return "REPLY";
807         }
808
809         my $state;
810         my $change      = 0;
811         foreach (split //, $chflag) {
812             if ($_ eq "+") { $state = 1; next; }
813             if ($_ eq "-") { $state = 0; next; }
814
815             if (!defined $state) {
816                 &pSReply("no initial + or - was found in attr.");
817                 return;
818             }
819
820             if ($state) {
821                 next if ($flags =~ /\Q$_\E/);
822                 $flags .= $_;
823             } else {
824                 if (&IsParam("owner")
825                         and $param{owner} =~ /^\Q$user\E$/i
826                         and $flags =~ /[nmo]/
827                 ) {
828                     &pSReply("not removing flag $_ for $user.");
829                     next;
830                 }
831                 next unless ($flags =~ s/\Q$_\E//);
832             }
833
834             $change++;
835         }
836
837         if ($change) {
838             $utime_userfile = time();
839             $ucount_userfile++;
840             &pSReply("Current flags: $flags");
841             $users{$user}{FLAGS} = $flags;
842         } else {
843             &pSReply("No flags changed: $flags");
844         }
845
846         return;
847     }
848
849     if ($message =~ /^chnick(\s+(.*))?$/) {
850         my(@args) = split /[\s\t]+/, $2 || '';
851
852         if ($who eq "_default") {
853             &WARN("$who or verifyuser tried to run chnick.");
854             return "REPLY";
855         }
856
857         if (!scalar @args or scalar @args > 2) {
858             &help("chnick");
859             return;
860         }
861
862         if (scalar @args == 1) {        # 1
863             $user       = &getUser($who);
864             &DEBUG("nope, not $who.") unless (defined $user);
865             $user       ||= &getUser($verifyUser);
866             $chnick     = $args[0];
867         } else {                        # 2
868             $user       = &getUser($args[0]);
869             $chnick     = $args[1];
870         }
871
872         if (!defined $user) {
873             &pSReply("user $who or $args[0] does not exist.");
874             return;
875         }
876
877         if ($user =~ /^\Q$chnick\E$/i) {
878             &pSReply("user == chnick. why should I do that?");
879             return;
880         }
881
882         if (&getUser($chnick)) {
883             &pSReply("user $chnick is already used!");
884             return;
885         }
886
887         if (!&IsFlag("n") and $who !~ /^\Q$verifyUser\E$/i) {
888             &pSReply("cannto change nick of others.");
889             return "REPLY" if ($who eq "_default");
890             return;
891         }
892
893         foreach (keys %{ $users{$user} }) {
894             $users{$chnick}{$_} = $users{$user}{$_};
895             delete $users{$user}{$_};
896         }
897         undef $users{$user};    # ???
898
899         $utime_userfile = time();
900         $ucount_userfile++;
901
902         &pSReply("Changed '$user' to '$chnick' successfully.");
903
904         return;
905     }
906
907     if ($message =~ /^([-+])host(\s+(.*))?$/) {
908         my $cmd         = $1."host";
909         my(@args)       = split /[\s\t]+/, $3 || '';
910         my $state       = ($1 eq "+") ? 1 : 0;
911
912         if (!scalar @args) {
913             &help($cmd);
914             return;
915         }
916
917         if ($who eq "_default") {
918             &WARN("$who or verifyuser tried to run $cmd.");
919             return "REPLY";
920         }
921
922         my ($user,$mask);
923         if ($args[0] =~ /^$mask{nick}$/i) {     # <nick>
924             return unless (&hasFlag("n"));
925             $user       = &getUser($args[0]);
926             $mask       = $args[1];
927         } else {                                # <mask>
928             # FIXME: who or verifyUser. (don't remember why)
929             $user       = &getUser($who);
930             $mask       = $args[0];
931         }
932
933         if (!defined $user) {
934             &pSReply("user $user does not exist.");
935             return;
936         }
937
938         if (!defined $mask) {
939             ### FIXME:
940             &pSReply("Hostmasks for $user: $users{$user}{HOSTS}");
941
942             return;
943         }
944
945         if (!&IsFlag("n") and $who !~ /^\Q$verifyUser\E$/i) {
946             &pSReply("cannto change masks of others.");
947             return;
948         }
949
950         if ($mask !~ /^$mask{nuh}$/) {
951             &pSReply("error: mask ($mask) is not a real hostmask.");
952             return;
953         }
954
955         my $count = scalar keys %{ $users{$user}{HOSTS} };
956
957         if ($state) {                           # add.
958             if (exists $users{$user}{HOSTS}{$mask}) {
959                 &pSReply("mask $mask already exists.");
960                 return;
961             }
962
963             ### TODO: override support.
964             $users{$user}{HOSTS}{$mask} = 1;
965
966             &pSReply("Added $mask to list of masks.");
967
968         } else {                                # delete.
969
970             if (!exists $users{$user}{HOSTS}{$mask}) {
971                 &pSReply("mask $mask does not exist.");
972                 return;
973             }
974
975             ### TODO: wildcard support. ?
976             delete $users{$user}{HOSTS}{$mask};
977
978             if (scalar keys %{ $users{$user}{HOSTS} } != $count) {
979                 &pSReply("Removed $mask from list of masks.");
980             } else {
981                 &pSReply("error: could not find $mask in list of masks.");
982                 return;
983             }
984         }
985
986         $utime_userfile = time();
987         $ucount_userfile++;
988
989         return;
990     }
991
992     if ($message =~ /^([-+])ban(\s+(.*))?$/) {
993         my $cmd         = $1."ban";
994         my $flatarg     = $3;
995         my(@args)       = split /[\s\t]+/, $3 || '';
996         my $state       = ($1 eq "+") ? 1 : 0;
997
998         if (!scalar @args) {
999             &help($cmd);
1000             return;
1001         }
1002
1003         my($mask,$chan,$time,$reason);
1004
1005         if ($flatarg =~ s/^($mask{nuh})\s*//) {
1006             $mask = $1;
1007         } else {
1008             &DEBUG("arg does not contain nuh mask?");
1009         }
1010
1011         if ($flatarg =~ s/^($mask{chan})\s*//) {
1012             $chan = $1;
1013         } else {
1014             $chan = "*";        # _default instead?
1015         }
1016
1017         if ($state == 0) {              # delete.
1018             my @c = &banDel($mask);
1019
1020             foreach (@c) {
1021                 &unban($mask, $_);
1022             }
1023
1024             if (@c) {
1025                 &pSReply("Removed $mask from chans: @c");
1026             } else {
1027                 &pSReply("$mask was not found in ban list.");
1028             }
1029
1030             return;
1031         }
1032
1033         ###
1034         # add ban.
1035         ###
1036
1037         # time.
1038         if ($flatarg =~ s/^(\d+)\s*//) {
1039             $time = $1;
1040             &DEBUG("time = $time.");
1041             if ($time < 0) {
1042                 &pSReply("error: time cannot be negatime?");
1043                 return;
1044             }
1045         } else {
1046             $time = 0;
1047         }
1048
1049         if ($flatarg =~ s/^(.*)$//) {   # need length?
1050             $reason     = $1;
1051         }
1052
1053         if (!&IsFlag("n") and $who !~ /^\Q$verifyUser\E$/i) {
1054             &pSReply("cannto change masks of others.");
1055             return;
1056         }
1057
1058         if ($mask !~ /^$mask{nuh}$/) {
1059             &pSReply("error: mask ($mask) is not a real hostmask.");
1060             return;
1061         }
1062
1063         if ( &banAdd($mask,$chan,$time,$reason) == 2) {
1064             &pSReply("ban already exists; overwriting.");
1065         }
1066         &pSReply("Added $mask for $chan (time => $time, reason => $reason)");
1067
1068         return;
1069     }
1070
1071     if ($message =~ /^whois(\s+(.*))?$/) {
1072         my $arg = $2;
1073
1074         if (!defined $arg) {
1075             &help("whois");
1076             return;
1077         }
1078
1079         my $user = &getUser($arg);
1080         if (!defined $user) {
1081             &pSReply("whois: user $user does not exist.");
1082             return;
1083         }
1084
1085         ### TODO: better (eggdrop-like) output.
1086         &pSReply("user: $user");
1087         foreach (keys %{ $users{$user} }) {
1088             my $ref = ref $users{$user}{$_};
1089
1090             if ($ref eq "HASH") {
1091                 my $type = $_;
1092                 ### DOES NOT WORK???
1093                 foreach (keys %{ $users{$user}{$type} }) {
1094                     &pSReply("    $type => $_");
1095                 }
1096                 next;
1097             }
1098
1099             &pSReply("    $_ => $users{$user}{$_}");
1100         }
1101         &pSReply("End of USER whois.");
1102
1103         return;
1104     }
1105
1106     if ($message =~ /^bans(\s+(.*))?$/) {
1107         my $arg = $2;
1108
1109         if (defined $arg) {
1110             if ($arg ne "_default" and !&validChan($arg) ) {
1111                 &pSReply("error: chan $chan is invalid.");
1112                 return;
1113             }
1114         }
1115
1116         if (!scalar keys %bans) {
1117             &pSReply("Ban list is empty.");
1118             return;
1119         }
1120
1121         my $c;
1122         &pSReply("     mask: expire, time-added, count, who-by, reason");
1123         foreach $c (keys %bans) {
1124             next unless (!defined $arg or $arg =~ /^\Q$c\E$/i);
1125             &pSReply("  $c:");
1126
1127             foreach (keys %{ $bans{$c} }) {
1128                 my $val = $bans{$c}{$_};
1129
1130                 if (ref $val eq "ARRAY") {
1131                     my @array = @{ $val };
1132                     &pSReply("    $_: @array");
1133                 } else {
1134                     &DEBUG("unknown ban: $val");
1135                 }
1136             }
1137         }
1138         &pSReply("END of bans.");
1139
1140         return;
1141     }
1142
1143     if ($message =~ /^banlist(\s+(.*))?$/) {
1144         my $arg = $2;
1145
1146         if (defined $arg and $arg !~ /^$mask{chan}$/) {
1147             &pSReply("error: chan $chan is invalid.");
1148             return;
1149         }
1150
1151         &DEBUG("bans for global or arg => $arg.");
1152         foreach (keys %bans) {                  #CHANGE!!!
1153             &DEBUG("  $_ => $bans{$_}.");
1154         }
1155
1156         &DEBUG("End of bans.");
1157         &pSReply("END of bans.");
1158
1159         return;
1160     }
1161
1162     if ($message =~ /^save$/) {
1163         return unless (&hasFlag("o"));
1164
1165         &writeUserFile();
1166         &writeChanFile();
1167
1168         return;
1169     }
1170
1171     ### ALIASES.
1172     $message =~ s/^addignore/+ignore/;
1173     $message =~ s/^(del|un)ignore/-ignore/;
1174
1175     # ignore.
1176     if ($message =~ /^(\+|\-)ignore(\s+(.*))?$/i) {
1177         return unless (&hasFlag("o"));
1178         my $state       = ($1 eq "+") ? 1 : 0;
1179         my $str         = $1."ignore";
1180         my $args        = $3;
1181
1182         if (!$args) {
1183             &help($str);
1184             return;
1185         }
1186
1187         my($mask,$chan,$time,$comment);
1188
1189         # mask.
1190         if ($args =~ s/^($mask{nuh})\s*//) {
1191             $mask = $1;
1192         } else {
1193             &ERROR("no NUH mask?");
1194             return;
1195         }
1196
1197         if (!$state) {                  # delignore.
1198             if ( &ignoreDel($mask) ) {
1199                 &pSReply("ok, deleted ignores for $mask.");
1200             } else {
1201                 &pSReply("could not find $mask in ignore list.");
1202             }
1203             return;
1204         }
1205
1206         ###
1207         # addignore.
1208         ###
1209
1210         # chan.
1211         if ($args =~ s/^($mask{chan}|\*)\s*//) {
1212             $chan = $1;
1213         } else {
1214             $chan = "*";
1215         }
1216
1217         # time.
1218         if ($args =~ s/^(\d+)\s*//) {
1219             $time = $1; # time is in minutes
1220         } else {
1221             $time = 0;
1222         }
1223
1224         # time.
1225         if ($args) {
1226             $comment = $args;
1227         } else {
1228             $comment = "added by $who";
1229         }
1230
1231         if ( &ignoreAdd($mask, $chan, $time, $comment) > 1) {
1232             &pSReply("FIXME: $mask already in ignore list; written over anyway.");
1233         } else {
1234             &pSReply("added $mask to ignore list.");
1235         }
1236
1237         return;
1238     }
1239
1240     if ($message =~ /^ignore(\s+(.*))?$/) {
1241         my $arg = $2;
1242
1243         if (defined $arg) {
1244             if ($arg !~ /^$mask{chan}$/) {
1245                 &pSReply("error: chan $chan is invalid.");
1246                 return;
1247             }
1248
1249             if (!&validChan($arg)) {
1250                 &pSReply("error: chan $arg is invalid.");
1251                 return;
1252             }
1253
1254             &pSReply("Showing bans for $arg only.");
1255         }
1256
1257         if (!scalar keys %ignore) {
1258             &pSReply("Ignore list is empty.");
1259             return;
1260         }
1261
1262         ### TODO: proper (eggdrop-like) formatting.
1263         my $c;
1264         &pSReply("    mask: expire, time-added, who, comment");
1265         foreach $c (keys %ignore) {
1266             next unless (!defined $arg or $arg =~ /^\Q$c\E$/i);
1267             &pSReply("  $c:");
1268
1269             foreach (keys %{ $ignore{$c} }) {
1270                 my $ref = ref $ignore{$c}{$_};
1271                 if ($ref eq "ARRAY") {
1272                     my @array = @{ $ignore{$c}{$_} };
1273                     &pSReply("      $_: @array");
1274                 } else {
1275                     &DEBUG("unknown ignore line?");
1276                 }
1277             }
1278         }
1279         &pSReply("END of ignore.");
1280
1281         return;
1282     }
1283
1284     # adduser/deluser.
1285     if ($message =~ /^(\+|\-|add|del)user(\s+(.*))?$/i) {
1286         my $str         = $1;
1287         my $strstr      = $1."user";
1288         my @args        = split /\s+/, $3 || '';
1289         my $args        = $3;
1290         my $state       = ($str =~ /^(\+|add)$/) ? 1 : 0;
1291
1292         if (!scalar @args) {
1293             &help($strstr);
1294             return;
1295         }
1296
1297         if ($str eq "+") {
1298             if (scalar @args != 2) {
1299                 &pSReply(".+host requires hostmask argument.");
1300                 return;
1301             }
1302         } elsif (scalar @args != 1) {
1303             &pSReply("too many arguments.");
1304             return;
1305         }
1306
1307         if ($state) {                   # adduser.
1308             if (scalar @args == 1) {
1309                 $args[1]        = &getHostMask($args[0]);
1310                 &pSReply("Attemping to guess $args[0]'s hostmask...");
1311
1312                 # crude hack... crappy Net::IRC
1313                 $conn->schedule(5, sub {
1314         # hopefully this is right.
1315         my $nick = (keys %{ $cache{nuhInfo} })[0];
1316         if (!defined $nick) {
1317             &pSReply("couldn't get nuhinfo... adding user without a hostmask.");
1318             &userAdd($nick);
1319             return;
1320         }
1321
1322         my $mask = &makeHostMask( $cache{nuhInfo}{$nick}{NUH} );
1323
1324         if ( &userAdd($nick, $mask) ) { # success.
1325                 &pSReply("Added $nick with flags $users{$nick}{FLAGS}");
1326                 my @hosts = keys %{ $users{$nick}{HOSTS} };
1327                 &pSReply("hosts: @hosts");
1328         }
1329 });
1330                 return;
1331             }
1332
1333             &DEBUG("args => @args");
1334             if ( &userAdd(@args) ) {    # success.
1335                 &pSReply("Added $args[0] with flags $users{$args[0]}{FLAGS}");
1336                 my @hosts = keys %{ $users{$args[0]}{HOSTS} };
1337                 &pSReply("hosts: @hosts");
1338
1339             } else {                    # failure.
1340                 &pSReply("User $args[0] already exists");
1341             }
1342
1343         } else {                        # deluser.
1344
1345             if ( &userDel($args[0]) ) { # success.
1346                 &pSReply("Deleted $args[0] successfully.");
1347
1348             } else {                    # failure.
1349                 &pSReply("User $args[0] does not exist.");
1350             }
1351
1352         }
1353         return;
1354     }
1355
1356     if ($message =~ /^sched$/) {
1357         my @list;
1358         my @run;
1359
1360         my %time;
1361         foreach (keys %sched) {
1362             next unless (exists $sched{$_}{TIME});
1363             $time{ $sched{$_}{TIME}-time() }{$_} = 1;
1364             push(@list,$_);
1365
1366             next unless (exists $sched{$_}{RUNNING});
1367             push(@run,$_);
1368         }
1369
1370         my @time;
1371         foreach (sort { $a <=> $b } keys %time) {
1372             my $str = join(", ", sort keys %{ $time{$_} });
1373             &DEBUG("time => $_, str => $str");
1374             push(@time, "$str (".&Time2String($_).")");
1375         }
1376
1377         &pSReply( &formListReply(0, "Schedulers: ", @time ) );
1378         &pSReply( &formListReply(0, "Scheds to run: ", sort @list ) );
1379         &pSReply( &formListReply(0, "Scheds running(should not happen?) ", sort @run ) );
1380
1381         return;
1382     }
1383
1384     # quite a cool hack: reply in DCC CHAT.
1385     $msgType = "chat" if (exists $dcc{'CHAT'}{$who});
1386
1387     my $done = 0;
1388     $done++ if &parseCmdHook("main", $message);
1389     $done++ if &parseCmdHook("extra", $message);
1390     $done++ unless (&Modules());
1391
1392     if ($done) {
1393         &DEBUG("running non DCC CHAT command inside DCC CHAT!");
1394         return;
1395     }
1396
1397     return "REPLY";
1398 }
1399
1400 1;