]> git.donarmstrong.com Git - infobot.git/blob - src/DynaConfig.pl
4bdcf99d86e2fb237794110d19488cc3987c6877
[infobot.git] / src / DynaConfig.pl
1 #
2 # DynaConfig.pl: Read/Write configuration files dynamically.
3 #        Author: dms
4 #       Version: v0.1 (20010120)
5 #       Created: 20010119
6 #          NOTE: Merged from User.pl
7 #
8
9 use strict;
10
11 use vars qw(%chanconf %cache %bans %channels %nuh %users %ignore
12         %talkWho %dcc %mask);
13 use vars qw($utime_userfile $ucount_userfile $utime_chanfile $who
14         $ucount_chanfile $userHandle $chan $msgType $talkchannel
15         $ident $bot_state_dir $talkWho $flag_quit $wtime_userfile
16         $wcount_userfile $wtime_chanfile $nuh $message);
17
18 #####
19 ##### USERFILE CONFIGURATION READER/WRITER
20 #####
21
22 sub readUserFile {
23     my $f = "$bot_state_dir/infobot.users";
24     if (! -e $f and -e "$bot_state_dir/blootbot.users") {
25          $f = "$bot_state_dir/blootbot.users";
26     }
27
28     if (! -f $f) {
29         &DEBUG("userfile not found; new fresh run detected.");
30         return;
31     }
32
33     if ( -f $f and -f "$f~") {
34         my $s1 = -s $f;
35         my $s2 = -s "$f~";
36
37         if ($s2 > $s1*3) {
38             &FIXME("rUF: backup file bigger than current file.");
39         }
40     }
41
42     if (!open IN, $f) {
43         &ERROR("Cannot read userfile ($f): $!");
44         &closeLog();
45         exit 1;
46     }
47
48     undef %users;       # clear on reload.
49     undef %bans;        # reset.
50     undef %ignore;      # reset.
51
52     my $ver = <IN>;
53     if ($ver !~ /^#v1/) {
54         &ERROR("old or invalid user file found.");
55         &closeLog();
56         exit 1; # correct?
57     }
58
59     my $nick;
60     my $type;
61     while (<IN>) {
62         chop;
63
64         next if /^$/;
65         next if /^#/;
66
67         if (/^--(\S+)[\s\t]+(.*)$/) {           # user: middle entry.
68             my ($what,$val) = ($1,$2);
69
70             if (!defined $val or $val eq '') {
71                 &WARN("$what: val == NULL.");
72                 next;
73             }
74
75             if (!defined $nick) {
76                 &WARN("DynaConfig: invalid line: $_");
77                 next;
78             }
79
80             # nice little hack.
81             if ($what eq 'HOSTS') {
82                 $users{$nick}{$what}{$val} = 1;
83             } else {
84                 $users{$nick}{$what} = $val;
85             }
86
87         } elsif (/^(\S+)$/) {                   # user: start entry.
88             $nick       = $1;
89
90         } elsif (/^::(\S+) ignore$/) {          # ignore: start entry.
91             $chan       = $1;
92             $type       = 'ignore';
93
94         } elsif (/^- (\S+):\+(\d+):\+(\d+):(\S+):(.*)$/ and $type eq 'ignore') {
95             ### ignore: middle entry.
96             my $mask = $1;
97             my(@array) = ($2,$3,$4,$5);
98             ### DEBUG purposes only!
99             if ($mask !~ /^$mask{nuh}$/) {
100                 &WARN("ignore: mask $mask is invalid.");
101                 next;
102             }
103             $ignore{$chan}{$mask} = \@array;
104
105         } elsif (/^::(\S+) bans$/) {            # bans: start entry.
106             $chan       = $1;
107             $type       = 'bans';
108
109         } elsif (/^- (\S+):\+(\d+):\+(\d+):(\d+):(\S+):(.*)$/ and $type eq 'bans') {
110             ### bans: middle entry.
111             # $btime, $atime, $count, $whoby, $reason.
112             my(@array) = ($2,$3,$4,$5,$6);
113             $bans{$chan}{$1} = \@array;
114
115         } else {                                # unknown.
116             &WARN("unknown line: $_");
117         }
118     }
119     close IN;
120
121     &status( sprintf("USERFILE: Loaded: %d users, %d bans, %d ignore",
122                 scalar(keys %users)-1,
123                 scalar(keys %bans),             # ??
124                 scalar(keys %ignore),           # ??
125         )
126     );
127 }
128
129 sub writeUserFile {
130     if (!scalar keys %users) {
131         &DEBUG("wUF: nothing to write.");
132         return;
133     }
134
135     if (!open OUT,">$bot_state_dir/infobot.users") {
136         &ERROR("Cannot write userfile ($bot_state_dir/infobot.users): $!");
137         return;
138     }
139
140     my $time            = scalar(gmtime);
141
142     print OUT "#v1: infobot -- $ident -- written $time\n\n";
143
144     ### USER LIST.
145     my $cusers  = 0;
146     foreach (sort keys %users) {
147         my $user = $_;
148         $cusers++;
149         my $count = scalar keys %{ $users{$user} };
150         if (!$count) {
151             &WARN("user $user has no other attributes; skipping.");
152             next;
153         }
154
155         print OUT "$user\n";
156
157         foreach (sort keys %{ $users{$user} }) {
158             my $what    = $_;
159             my $val     = $users{$user}{$_};
160
161             if (ref($val) eq 'HASH') {
162                 foreach (sort keys %{ $users{$user}{$_} }) {
163                     print OUT "--$what\t\t$_\n";
164                 }
165
166             } elsif ($_ eq 'FLAGS') {
167                 print OUT "--$_\t\t" . join('', sort split('', $val)) . "\n";
168             } else {
169                 print OUT "--$_\t\t$val\n";
170             }
171         }
172         print OUT "\n";
173     }
174
175     ### BAN LIST.
176     my $cbans   = 0;
177     foreach (keys %bans) {
178         my $chan = $_;
179         $cbans++;
180
181         my $count = scalar keys %{ $bans{$chan} };
182         if (!$count) {
183             &WARN("bans: chan $chan has no other attributes; skipping.");
184             next;
185         }
186
187         print OUT "::$chan bans\n";
188         foreach (keys %{ $bans{$chan} }) {
189 # format: bans: mask expire time-added count who-added reason
190             my @array = @{ $bans{$chan}{$_} };
191             if (scalar @array != 5) {
192                 &WARN("bans: $chan/$_ is corrupted.");
193                 next;
194             }
195
196             printf OUT "- %s:+%d:+%d:%d:%s:%s\n", $_, @array;
197         }
198     }
199     print OUT "\n" if ($cbans);
200
201     ### IGNORE LIST.
202     my $cignore = 0;
203     foreach (keys %ignore) {
204         my $chan = $_;
205         $cignore++;
206
207         my $count = scalar keys %{ $ignore{$chan} };
208         if (!$count) {
209             &WARN("ignore: chan $chan has no other attributes; skipping.");
210             next;
211         }
212
213         ### TODO: use hash instead of array for flexibility?
214         print OUT "::$chan ignore\n";
215         foreach (keys %{ $ignore{$chan} }) {
216 # format: ignore: mask expire time-added who-added reason
217             my @array = @{ $ignore{$chan}{$_} };
218             if (scalar @array != 4) {
219                 &WARN("ignore: $chan/$_ is corrupted.");
220                 next;
221             }
222
223             printf OUT "- %s:+%d:+%d:%s:%s\n", $_, @array;
224         }
225     }
226
227     close OUT;
228
229     $wtime_userfile = time();
230     &status("--- Saved USERFILE ($cusers users; $cbans bans; $cignore ignore) at $time");
231     if (defined $msgType and $msgType =~ /^chat$/) {
232         &performStrictReply("--- Writing user file...");
233     }
234 }
235
236 #####
237 ##### CHANNEL CONFIGURATION READER/WRITER
238 #####
239
240 sub readChanFile {
241     my $f = "$bot_state_dir/infobot.chan";
242     if (-e "$bot_state_dir/infobot.chan" and -e "$bot_state_dir/blootbot.chan") {
243          $f = "$bot_state_dir/blootbot.chan";
244     }
245     if ( -f $f and -f "$f~") {
246         my $s1 = -s $f;
247         my $s2 = -s "$f~";
248
249         if ($s2 > $s1*3) {
250             &FIXME("rCF: backup file bigger than current file.");
251         }
252     }
253
254     if (!open IN, $f) {
255         &ERROR("Cannot read chanfile ($f): $!");
256         return;
257     }
258
259     undef %chanconf;    # reset.
260
261     $_ = <IN>;          # version string.
262
263     my $chan;
264     while (<IN>) {
265         chop;
266
267         next if /^\s*$/;
268         next if /^\// or /^\;/; # / or ; are comment lines.
269
270         if (/^(\S+)\s*$/) {
271             $chan       = $1;
272             next;
273         }
274         next unless (defined $chan);
275
276         if (/^[\s\t]+\+(\S+)$/) {               # bool, true.
277             $chanconf{$chan}{$1} = 1;
278
279         } elsif (/^[\s\t]+\-(\S+)$/) {          # bool, false.
280             # although this is supported in run-time configuration.
281             $chanconf{$chan}{$1} = 0;
282
283         } elsif (/^[\s\t]+(\S+)[\s\t]+(.*)$/) {# what = val.
284             $chanconf{$chan}{$1} = $2;
285
286         } else {
287             &WARN("unknown line: $_") unless (/^#/);
288         }
289     }
290     close IN;
291
292     # verify configuration
293     ### TODO: check against valid params.
294     foreach $chan (keys %chanconf) {
295         foreach (keys %{ $chanconf{$chan} }) {
296             next unless /^[+-]/;
297
298             &WARN("invalid param: chanconf{$chan}{$_}; removing.");
299             delete $chanconf{$chan}{$_};
300             undef $chanconf{$chan}{$_};
301         }
302     }
303
304     &status("CHANFILE: Loaded: ".(scalar(keys %chanconf)-1)." chans");
305 }
306
307 sub writeChanFile {
308     if (!scalar keys %chanconf) {
309         &DEBUG("wCF: nothing to write.");
310         return;
311     }
312
313     if (!open OUT,">$bot_state_dir/infobot.chan") {
314         &ERROR("Cannot write chanfile ($bot_state_dir/infobot.chan): $!");
315         return;
316     }
317
318     my $time            = scalar(gmtime);
319     print OUT "#v1: infobot -- $ident -- written $time\n\n";
320
321     if ($flag_quit) {
322
323         ### Process 1: if defined in _default, remove same definition
324         ###             from non-default channels.
325         foreach (keys %{ $chanconf{_default} }) {
326             my $opt     = $_;
327             my $val     = $chanconf{_default}{$opt};
328             my @chans;
329
330             foreach (keys %chanconf) {
331                 $chan = $_;
332
333                 next if ($chan eq "_default");
334                 next unless (exists $chanconf{$chan}{$opt});
335                 next unless ($val eq $chanconf{$chan}{$opt});
336
337                 push(@chans,$chan);
338                 delete $chanconf{$chan}{$opt};
339             }
340
341             if (scalar @chans) {
342                 &DEBUG("Removed config $opt to @chans since it's defiend in '_default'");
343             }
344         }
345
346         ### Process 2: if defined in all chans but _default, set in
347         ###             _default and remove all others.
348         my (%optsval, %opts);
349         foreach (keys %chanconf) {
350             $chan = $_;
351             next if ($chan eq "_default");
352             my $opt;
353
354             foreach (keys %{ $chanconf{$chan} }) {
355                 $opt = $_;
356                 if (exists $optsval{$opt} and $optsval{$opt} eq $chanconf{$chan}{$opt}) {
357                     $opts{$opt}++;
358                     next;
359                 }
360                 $optsval{$opt}  = $chanconf{$chan}{$opt};
361                 $opts{$opt}     = 1;
362             }
363         }
364
365         foreach (keys %opts) {
366             next unless ($opts{$_} > 2);
367             &DEBUG("  opts{$_} => $opts{$_}");
368         }
369
370         ### other optimizations are in UserDCC.pl
371     }
372
373     ### lets do it...
374     foreach (sort keys %chanconf) {
375         $chan   = $_;
376
377         print OUT "$chan\n";
378
379         foreach (sort keys %{ $chanconf{$chan} }) {
380             my $val = $chanconf{$chan}{$_};
381
382             if ($val =~ /^0$/) {                # bool, false.
383                 print OUT "    -$_\n";
384
385             } elsif ($val =~ /^1$/) {           # bool, true.
386                 print OUT "    +$_\n";
387
388             } else {                            # what = val.
389                 print OUT "    $_ $val\n";
390
391             }
392
393         }
394         print OUT "\n";
395     }
396
397     close OUT;
398
399     $wtime_chanfile = time();
400     &status("--- Saved CHANFILE (".scalar(keys %chanconf).
401                 " chans) at $time");
402
403     if (defined $msgType and $msgType =~ /^chat$/) {
404         &performStrictReply("--- Writing chan file...");
405     }
406 }
407
408 #####
409 ##### USER COMMANDS.
410 #####
411
412 # TODO: support multiple flags.
413 # TODO: return all flags for opers
414 sub IsFlag {
415     my $flags = shift;
416     my ($ret, $f, $o) = '';
417
418     &verifyUser($who, $nuh);
419
420     foreach $f (split //, $users{$userHandle}{FLAGS}) {
421         foreach $o ( split //, $flags ) {
422             next unless ($f eq $o);
423
424             $ret = $f;
425             last;
426         }
427     }
428
429     $ret;
430 }
431
432 sub verifyUser {
433     my ($nick, $lnuh) = @_;
434     my ($user, $m);
435
436     if ($userHandle = $dcc{'CHATvrfy'}{$who}) {
437         &VERB("vUser: cached auth for $who.",2);
438         return $userHandle;
439     }
440
441     $userHandle = '';
442
443     foreach $user (keys %users) {
444         next if ($user eq "_default");
445
446         foreach $m (keys %{ $users{$user}{HOSTS} }) {
447             $m =~ s/\?/./g;
448             $m =~ s/\*/.*?/g;
449             $m =~ s/([\@\(\)\[\]])/\\$1/g;
450
451             next unless ($lnuh =~ /^$m$/i);
452
453             if ($user !~ /^\Q$nick\E$/i and !exists $cache{VUSERWARN}{$user}) {
454                 &status("vU: host matched but diff nick ($nick != $user).");
455                 $cache{VUSERWARN}{$user} = 1;
456             }
457
458             $userHandle = $user;
459             last;
460         }
461
462         last if ($userHandle ne '');
463
464         if ($user =~ /^\Q$nick\E$/i and !exists $cache{VUSERWARN}{$user}) {
465             &status("vU: nick matched but host is not in list ($lnuh).");
466             $cache{VUSERWARN}{$user} = 1;
467         }
468     }
469
470     $userHandle ||= "_default";
471     # what's talkchannel for?
472     $talkWho{$talkchannel} = $who if (defined $talkchannel);
473     $talkWho = $who;
474
475     return $userHandle;
476 }
477
478 sub ckpasswd {
479     # returns true if arg1 encrypts to arg2
480     my ($plain, $encrypted) = @_;
481     if ($encrypted eq '') {
482         ($plain, $encrypted) = split(/\s+/, $plain, 2);
483     }
484     return 0 unless ($plain ne '' and $encrypted ne '');
485
486     # MD5 // DES. Bobby Billingsley++.
487     my $salt;
488     if ($encrypted =~ /^(\S{2})/ and length $encrypted == 13) {
489         $salt = $1;
490     } elsif ($encrypted =~ /^\$\d\$(\w\w)\$/) {
491         $salt = $1;
492     } else {
493         &DEBUG("unknown salt from $encrypted.");
494         return 0;
495     }
496
497     return ($encrypted eq crypt($plain, $salt));
498 }
499
500 # mainly for dcc chat... hrm.
501 sub hasFlag {
502     my ($flag) = @_;
503
504     if (&IsFlag($flag) eq $flag) {
505         return 1;
506     } else {
507         &status("DCC CHAT: <$who> $message -- not enough flags.");
508         &performStrictReply("error: you do not have enough flags for that. ($flag required)");
509         return 0;
510     }
511 }
512
513 # expire is time in minutes
514 sub ignoreAdd {
515     my($mask,$chan,$expire,$comment) = @_;
516
517     $chan       ||= '*';        # global if undefined.
518     $comment    ||= '';         # optional.
519     $expire     ||= 0;          # permament.
520     my $count   ||= 0;
521
522     if ($expire > 0) {
523         $expire         = ($expire*60) + time();
524     } else {
525         $expire         = 0;
526     }
527
528     my $exist   = 0;
529     $exist++ if (exists $ignore{$chan}{$mask});
530
531     $ignore{$chan}{$mask} = [$expire, time(), $who, $comment];
532
533     # TODO: improve this.
534     if ($expire == 0) {
535         &status("ignore: Added $mask for $chan to NEVER expire, by $who, for $comment");
536     } else {
537         &status("ignore: Added $mask for $chan to expire $expire mins, by $who, for $comment");
538     }
539
540     if ($exist) {
541         $utime_userfile = time();
542         $ucount_userfile++;
543
544         return 2;
545     } else {
546         return 1;
547     }
548 }
549
550 sub ignoreDel {
551     my($mask)   = @_;
552     my @match;
553
554     ### TODO: support wildcards.
555     foreach (keys %ignore) {
556         my $chan = $_;
557
558         foreach (grep /^\Q$mask\E$/i, keys %{ $ignore{$chan} }) {
559             delete $ignore{$chan}{$mask};
560             push(@match,$chan);
561         }
562
563         &DEBUG("iD: scalar => ".scalar(keys %{ $ignore{$chan} }) );
564     }
565
566     if (scalar @match) {
567         $utime_userfile = time();
568         $ucount_userfile++;
569     }
570
571     return @match;
572 }
573
574 sub userAdd {
575     my($nick,$mask)     = @_;
576
577     if (exists $users{$nick}) {
578         return 0;
579     }
580
581     $utime_userfile = time();
582     $ucount_userfile++;
583
584     if (defined $mask and $mask !~ /^\s*$/) {
585         &DEBUG("userAdd: mask => $mask");
586         $users{$nick}{HOSTS}{$mask} = 1;
587     }
588
589     $users{$nick}{FLAGS}        ||= $users{_default}{FLAGS};
590
591     return 1;
592 }
593
594 sub userDel {
595     my($nick)   = @_;
596
597     if (!exists $users{$nick}) {
598         return 0;
599     }
600
601     $utime_userfile = time();
602     $ucount_userfile++;
603
604     delete $users{$nick};
605
606     return 1;
607 }
608
609 sub banAdd {
610     my($mask,$chan,$expire,$reason) = @_;
611
612     $chan       ||= '*';
613     $expire     ||= 0;
614
615     if ($expire > 0) {
616         $expire         = $expire*60 + time();
617     }
618
619     my $exist   = 1;
620     $exist++ if (exists $bans{$chan}{$mask} or
621                 exists $bans{'*'}{$mask});
622     $bans{$chan}{$mask} = [$expire, time(), 0, $who, $reason];
623
624     my @chans   = ($chan eq '*') ? keys %channels : $chan;
625     my $m       = $mask;
626     $m          =~ s/\?/\\./g;
627     $m          =~ s/\*/\\S*/g;
628     foreach (@chans) {
629         my $chan = $_;
630         foreach (keys %{ $channels{$chan}{''} }) {
631             next unless (exists $nuh{lc $_});
632             next unless ($nuh{lc $_} =~ /^$m$/i);
633             &FIXME("nuh{$_} =~ /$m/");
634         }
635     }
636
637     if ($exist == 1) {
638         $utime_userfile = time();
639         $ucount_userfile++;
640     }
641
642     return $exist;
643 }
644
645 sub banDel {
646     my($mask)   = @_;
647     my @match;
648
649     foreach (keys %bans) {
650         my $chan        = $_;
651
652         foreach (grep /^\Q$mask\E$/i, keys %{ $bans{$chan} }) {
653             delete $bans{$chan}{$_};
654             push(@match, $chan);
655         }
656
657         &DEBUG("bans: scalar => ".scalar(keys %{ $bans{$chan} }) );
658     }
659
660     if (scalar @match) {
661         $utime_userfile = time();
662         $ucount_userfile++;
663     }
664
665     return @match;
666 }
667
668 sub IsUser {
669     my($user) = @_;
670
671     if ( &getUser($user) ) {
672         return 1;
673     } else {
674         return 0;
675     }
676 }
677
678 sub getUser {
679     my($user) = @_;
680
681     if (!defined $user) {
682         &WARN("getUser: user == NULL.");
683         return;
684     }
685
686     if (my @retval = grep /^\Q$user\E$/i, keys %users) {
687         if ($retval[0] ne $user) {
688             &WARN("getUser: retval[0] ne user ($retval[0] ne $user)");
689         }
690         my $count = scalar keys %{ $users{$retval[0]} };
691         &DEBUG("count => $count.");
692
693         return $retval[0];
694     } else {
695         return;
696     }
697 }
698
699 sub chanSet {
700     my($cmd, $chan, $what, $val) = @_;
701
702     if ($cmd eq "+chan") {
703         if (exists $chanconf{$chan}) {
704             &performStrictReply("chan $chan already exists.");
705             return;
706         }
707         $chanconf{$chan}{_time_added}   = time();
708         $chanconf{$chan}{autojoin}      = $conn->nick();
709
710         &performStrictReply("Joining $chan...");
711         &joinchan($chan);
712
713         return;
714     }
715
716     if (!exists $chanconf{$chan}) {
717         &performStrictReply("no such channel $chan");
718         return;
719     }
720
721     my $update  = 0;
722
723     if (defined $what and $what =~ s/^([+-])(\S+)/$2/) {
724         ### ".chanset +blah"
725         ### ".chanset +blah 10"         -- error.
726
727         my $set = ($1 eq "+") ? 1 : 0;
728         my $was         = $chanconf{$chan}{$what};
729
730         if ($set) {                     # add/set.
731             if (defined $was and $was eq '1') {
732                 &performStrictReply("setting $what for $chan already 1.");
733                 return;
734             }
735
736             $val        = 1;
737
738         } else {                        # delete/unset.
739             if (!defined $was) {
740                 &performStrictReply("setting $what for $chan is not set.");
741                 return;
742             }
743
744             $val        = 0;
745         }
746
747         # alter for cosmetic (print out) reasons only.
748         $was    = (defined $was) ? "; was '$was'" : '';
749
750         if ($val eq '0') {
751             &performStrictReply("Unsetting $what for $chan$was.");
752             delete $chanconf{$chan}{$what};
753             delete $cache{ircTextCounters} if $what eq 'ircTextCounters';
754         } else {
755             &performStrictReply("Setting $what for $chan to '$val'$was.");
756             $chanconf{$chan}{$what}     = $val;
757             delete $cache{ircTextCounters} if $what eq 'ircTextCounters';
758         }
759
760         $update++;
761
762     } elsif (defined $val) {
763         ### ".chanset blah testing"
764
765         my $was = $chanconf{$chan}{$what};
766         if (defined $was and $was eq $val) {
767             &performStrictReply("setting $what for $chan already '$val'.");
768             return;
769         }
770         $was    = ($was) ? "; was '$was'" : '';
771         &performStrictReply("Setting $what for $chan to '$val'$was.");
772
773         $chanconf{$chan}{$what} = $val;
774         delete $cache{ircTextCounters} if $what eq 'ircTextCounters';
775
776         $update++;
777
778     } else {                            # read only.
779         ### ".chanset"
780         ### ".chanset blah"
781
782         if (!defined $what) {
783             &WARN("chanset/DC: what == undefine.");
784             return;
785         }
786
787         if (exists $chanconf{$chan}{$what}) {
788             &performStrictReply("$what for $chan is '$chanconf{$chan}{$what}'");
789         } else {
790             &performStrictReply("$what for $chan is not set.");
791         }
792     }
793
794     if ($update) {
795         $utime_chanfile = time();
796         $ucount_chanfile++;
797     }
798
799     return;
800 }
801
802 sub rehashConfVars {
803     # this is an attempt to fix where an option is enabled but the module
804     # has been not loaded. it also can be used for other things.
805
806     foreach (keys %{ $cache{confvars} }) {
807         my $i = $cache{confvars}{$_};
808         &DEBUG("rehashConfVars: _ => $_");
809
810         if (/^news$/ and $i) {
811             &loadMyModule('News');
812             delete $cache{confvars}{$_};
813         }
814
815         if (/^uptime$/ and $i) {
816             &loadMyModule('Uptime');
817             delete $cache{confvars}{$_};
818         }
819
820         if (/^rootwarn$/i and $i) {
821             &loadMyModule('RootWarn');
822             delete $cache{confvars}{$_};
823         }
824
825         if (/^onjoin$/i and $i) {
826             &loadMyModule('OnJoin');
827             delete $cache{confvars}{$_};
828         }
829     }
830
831     &DEBUG("end of rehashConfVars");
832
833     delete $cache{confvars};
834 }
835
836 my @regFlagsUser = (
837         # possible chars to include in FLAG
838         'A',    # bot administration over /msg
839                         # default is only via DCC CHAT
840         'O',    # dynamic ops (as on channel). (automatic +o)
841         'T',    # add topics.
842         'a',    # ask/request factoid.
843         'm',    # modify factoid. (includes renaming)
844         'n',    # bot owner, can 'reload'
845         'o',    # master of bot (automatic +amrt)
846                         # can search on factoid strings shorter than 2 chars
847                         # can tell bot to join new channels
848                         # can [un]lock factoids
849         'r',    # remove factoid.
850         't',    # teach/add factoid.
851         's',    # Bypass +silent on channels
852 );
853
854 1;
855
856 # vim:ts=4:sw=4:expandtab:tw=80