]> git.somenet.org - irc/bugbot.git/blob - BotModules/God.bm
some old base
[irc/bugbot.git] / BotModules / God.bm
1 # -*- Mode: perl; indent-tabs-mode: nil -*-
2 ################################
3 # God Module                   #
4 ################################
5
6 package BotModules::God;
7 use vars qw(@ISA);
8 @ISA = qw(BotModules);
9 1;
10
11 # XXX should also do autovoice
12
13 sub Help {
14     my $self = shift;
15     my ($event) = @_;
16     my $answer = {
17         '' => 'A per-channel auto-opper.',
18         'ops' => 'Lists the autoop list for a channel. Syntax: \'ops in <channel>\'',
19         'opme' => 'Checks the autoop list, and ops the speaker if they are on the autoop list. Must be used in a channel. Syntax: \'op me\' or \'opme\'',
20         'mask' => 'Add or remove a regexp mask from a channel\'s autoop list. Only bot and channel admins can do this. USE OF THIS FEATURE IS HIGHLY DISCOURAGED AS IT IS VERY INSECURE!!! Syntax: \'add mask <user@host> in <channel>\' to add and \'remove mask <user@host> in <channel>\' to remove. The special word \'everywhere\' can be used instead of a channel name to add a mask that works in all channels.',
21         'autoop' => 'Add someone to the autoop list for a channel. Only bot and channel admins can do this. Syntax: \'autoop <user> in <channel>\'',
22         'deautoop' => 'Remove someone from the autoop list for a channel. Only bot and channel admins can do this. Syntax: \'deautoop <user> in <channel>\'',
23         'enable' => 'Enable a module in a channel. Only bot and channel admins can do this. Syntax: \'enable <module> in <channel>\'',
24         'disable' => 'Disable a module in a channel. Only bot and channel admins can do this. Syntax: \'disable <module> in <channel>\'',
25     };
26     if ($self->isAdmin($event)) {
27         $answer->{'opme'} .= '. As an administrator, you can also say \'op me in <channel>\' or \'op me everywhere\' which will do the obvious things.';
28         $answer->{'promote'} = 'Add someone to the channel admin list for a channel. Only bot admins can do this. Syntax: \'promote <user> in <channel>\'',
29         $answer->{'demote'} = 'Remove someone from the channel admin list for a channel. Only bot admins can do this. Syntax: \'demote <user> in <channel>\'',
30     }
31     return $answer;
32 }
33
34 # RegisterConfig - Called when initialised, should call registerVariables
35 sub RegisterConfig {
36     my $self = shift;
37     $self->SUPER::RegisterConfig(@_);
38     $self->registerVariables(
39       # [ name, save?, settable? ]
40         ['channelAdmins', 1, 1, {}],
41         ['channelOps', 1, 1, {}],
42         ['channelOpMasks', 1, 1, {}],
43         ['kickLog', 1, 1, []],
44         ['allowPrivateOpRequests', 1, 1, 1],
45         ['maxInChannel', 1, 1, 4],
46     );
47 }
48
49 sub Told {
50     my $self = shift;
51     my ($event, $message) = @_;
52     if ($event->{'level'} == 1) {
53         if ($message =~ /^\s*(?:list\s+)?ops\s+(?:in\s+|for\s+)?(\S+)\s*\??$/osi) {
54             my $channel = lc($1);
55             $self->listOps($event, $channel);
56         } elsif ($message =~ /^\s*autoop\s+(\S+)\s+in\s+(\S+)\s*$/osi) {
57             if (($self->isChannelAdmin($event, $2)) or ($self->isAdmin($event))) {
58                 my $channel = $2 eq 'everywhere' ? '' : lc($2);
59                 $self->{'channelOps'}->{$channel} .= " $1";
60                 $self->saveConfig();
61                 $self->say($event, "$event->{'from'}: User '$1' added to the autoop list of channel '$2'.");
62             } else {
63                 $self->say($event, "$event->{'from'}: Only channel administrators may add people to a channel's autoop list.");
64             }
65         } elsif ($message =~ /^\s*deautoop\s+(\S+)\s+in\s+(\S+)\s*$/osi) {
66             if (($self->isChannelAdmin($event, $2)) or ($self->isAdmin($event))) {
67                 my $channel = $2 eq 'everywhere' ? '' : lc($2);
68                 my %people = map { $_ => 1 } split(/ +/os, $self->{'channelOps'}->{$channel});
69                 delete($people{$1}); # get rid of any mentions of that person
70                 $self->{'channelOps'}->{$channel} = join(' ', keys(%people));
71                 $self->saveConfig();
72                 $self->say($event, "$event->{'from'}: User '$1' removed from the autoop list of channel '$2'.");
73             } else {
74                 $self->say($event, "$event->{'from'}: Only channel administrators may remove people from a channel's autoop list.");
75             }
76         } elsif ($message =~ /^\s*add\s+mask\s+(\S+)\s+(?:in|to|for|from)\s+(\S+)\s*$/osi) {
77             if (($self->isChannelAdmin($event, $2)) or ($self->isAdmin($event))) {
78                 my $channel = $2 eq 'everywhere' ? '' : lc($2);
79                 $self->{'channelOpMasks'}->{$channel} .= " $1";
80                 $self->saveConfig();
81                 $self->say($event, "$event->{'from'}: Mask '$1' added to the autoop list of channel '$2'.");
82             } else {
83                 $self->say($event, "$event->{'from'}: Only channel administrators may add masks to a channel's autoop list.");
84             }
85         } elsif ($message =~ /^\s*remove\s+mask\s+(\S+)\s+(?:in|from|for|to)\s+(\S+)\s*$/osi) {
86             if (($self->isChannelAdmin($event, $2)) or ($self->isAdmin($event))) {
87                 my $channel = $2 eq 'everywhere' ? '' : lc($2);
88                 my %people = map { $_ => 1 } split(/ +/os, $self->{'channelOpMasks'}->{$channel});
89                 delete($people{$1}); # get rid of any mentions of that person
90                 $self->{'channelOpMasks'}->{$channel} = join(' ', keys(%people));
91                 $self->saveConfig();
92                 $self->say($event, "$event->{'from'}: Mask '$1' removed from the autoop list of channel '$2'.");
93             } else {
94                 $self->say($event, "$event->{'from'}: Only channel administrators may remove masks from a channel's autoop list.");
95             }
96         } elsif ($message =~ /^\s*promote\s+(\S+)\s+in\s+(\S+)\s*$/osi) {
97             if ($self->isAdmin($event)) {
98                 $self->{'channelAdmins'}->{lc($2)} .= " $1";
99                 $self->saveConfig();
100                 $self->say($event, "$event->{'from'}: User '$1' promoted to channel administrator status in channel '$2'.");
101             } else {
102                 $self->say($event, "$event->{'from'}: Only administrators may promote people to channel admin status.");
103             }
104         } elsif ($message =~ /^\s*demote\s+(\S+)\s+in\s+(\S+)\s*$/osi) {
105             if ($self->isAdmin($event)) {
106                 my %people = map { $_ => 1 } split(/ +/os, $self->{'channelAdmins'}->{lc($2)});
107                 delete($people{$1}); # get rid of any mentions of that person
108                 $self->{'channelAdmins'}->{lc($2)} = join(' ', keys(%people));
109                 $self->saveConfig();
110                 $self->say($event, "$event->{'from'}: User '$1' removed from the channel administrator list of channel '$2'.");
111             } else {
112                 $self->say($event, "$event->{'from'}: Only administrators may remove people's channel admin status.");
113             }
114         } elsif ($message =~ /^\s*enable\s+(\S+)\s+in\s+(\S+)\s*$/osi) {
115             if (($self->isAdmin($event)) or ($self->isChannelAdmin($event, $2))) {
116                 my $module = $self->getModule($1);
117                 if ($1) {
118                     push(@{$module->{'channels'}}, lc($2));
119                     $module->saveConfig();
120                     $self->say($event, "$event->{'from'}: Module '$1' enabled in channel '$2'.");
121                 } else {
122                     $self->say($event, "$event->{'from'}: There is no module called '$1', sorry.");
123                 }
124             } else {
125                 $self->say($event, "$event->{'from'}: Only channel administrators may change a module's status.");
126             }
127         } elsif ($message =~ /^\s*disable\s+(\S+)\s+in\s+(\S+)\s*$/osi) {
128             if (($self->isAdmin($event)) or ($self->isChannelAdmin($event, $2))) {
129                 my $module = $self->getModule($1);
130                 if ($1) {
131                     my %channels = map { $_ => 1 } @{$module->{'channels'}};
132                     delete($channels{lc($2)}); # get rid of any mentions of that channel
133                     @{$module->{'channels'}} = keys %channels;
134                     $module->saveConfig();
135                     $self->say($event, "$event->{'from'}: Module '$1' disabled in channel '$2'.");
136                 } else {
137                     $self->say($event, "$event->{'from'}: There is no module called '$1', sorry.");
138                 }
139             } else {
140                 $self->say($event, "$event->{'from'}: Only channel administrators may change a module's status.");
141             }
142         } elsif ($message =~ /^\s*(?:(?:(?:de)?autoop|promote|demote|enable|disable|add\s+mask|remove\s+mask)\s+(\S+)|(?:list\s+)?ops)\s*$/osi) {
143                 $self->say($event, "$event->{'from'}: You have to give a channel, as in \'<command> <who> in <channel>\'.");
144
145         # XXX next two could be merged, maybe.
146         } elsif ($message =~ /^\s*op\s*meh?[!1.,\s]*(?:now\s+)?(?:please|(b+[iea]+t+c+h+))?\s*[.!1]*\s*$/osi) {
147             if ($event->{'channel'}) {
148                 if ($event->{'userName'}) {
149                     unless ($self->checkOpping($event, $event->{'channel'}, $event->{'from'}, $self->isAdmin($event))) {
150                         if ($1) { # only true if they said bitch
151                             $self->say($event, "$event->{'from'}: No way, beetch!");
152                         } else {
153                             $self->say($event, "$event->{'from'}: Sorry, you are not on my auto-op list.");
154                         }
155                     }
156                 } else {
157                     unless ($self->isMatchedByMask($event, $event->{'channel'}) and
158                             $self->checkOpping($event, $event->{'channel'}, $event->{'from'})) {
159                         $self->say($event, "$event->{'from'}: You haven't authenticated yet. See 'help auth' for details.");
160                     }
161                 }
162             } else {
163                 $self->say($event, "$event->{'from'}: You have to use this command in public.");
164             }
165         } elsif ($message =~ /^\s*(?:please\s+)?op\s*me(?:\s+in\s+(\S+)|\s+everywhere)?[\s!1.]*\s*$/osi) {
166             if (($self->{'allowPrivateOpRequests'}) or ($self->isAdmin($event))) {
167                 if ($1) {
168                     $self->checkOpping($event, lc($1), $event->{'from'}, $self->isAdmin($event));
169                 } else {
170                     foreach (@{$self->{'channels'}}) {
171                         $self->checkOpping($event, $_, $event->{'from'}, $self->isAdmin($event));
172                     }
173                }
174             } else {
175                $self->say($event, "$event->{'from'}: Sorry, but no. Try \'help opme\' for details on commansyntax.");
176             }
177         } else {
178             my $parentResult = $self->SUPER::Told(@_);
179             return $parentResult < 2 ? 2 : $parentResult;
180         }
181         return 0; # we've dealt with it, no need to do anything ese.
182     } elsif ($event->{'level'} == 2) {
183         if (defined($event->{'God_channel'})) {
184             $event->{'God_channel_rights'} = $self->isChannelAdmin($event, $event->{'God_channel'});
185         }
186     }
187     return $self->SUPER::Told(@_);
188 }
189
190 # SpottedJoin - Called when someone joins a channel
191 sub SpottedJoin {
192     my $self = shift;
193     my ($event, $channel, $who) = @_;
194     $self->checkOpping(@_, 0);
195     return $self->SUPER::SpottedJoin(@_); # this should not stop anything else happening
196 }
197
198 # do all channels when someone authenticates
199 sub Authed {
200     my $self = shift;
201     my ($event, $who) = @_;
202     foreach (@{$self->{'channels'}}) {
203         $self->checkOpping($event, $_, $who, 0);
204     }
205     return $self->SUPER::Authed(@_); # this should not stop anything else happening
206 }
207
208 # check is someone is in the opping.
209 sub checkOpping {
210     my $self = shift;
211     my ($event, $channel, $who, $override) = @_;
212     if (($self->isAutoopped($event, $channel)) or ($self->isChannelAdmin($event, $channel)) or ($override)) {
213         $self->mode($event, $channel, '+o', $who);
214         return 1;
215     }
216     return 0;
217 }
218
219 sub isChannelAdmin {
220     my $self = shift;
221     my ($event, $channel) = @_;
222     return (($event->{'userName'}) and 
223             (defined($self->{'channelAdmins'}->{$channel})) and 
224             ($self->{'channelAdmins'}->{$channel} =~ /^(|.*\s+)$event->{'userName'}(\s+.*|)$/s));
225 }
226
227 sub isAutoopped {
228     my $self = shift;
229     my ($event, $channel) = @_;
230     return ((($event->{'userName'}) and 
231              (defined($self->{'channelOps'}->{$channel})) and 
232              (($self->{'channelOps'}->{$channel} =~ /^(|.*\s+)$event->{'userName'}(\s+.*|)$/s) or
233               ($self->{'channelOps'}->{''} =~ /^(|.*\s+)$event->{'userName'}(\s+.*|)$/s))) or
234             ($self->isMatchedByMask($event, $channel)));
235 }
236
237 # grrrr -- this insecure feature is here by popular demand
238 sub isMatchedByMask {
239     my $self = shift;
240     my ($event, $channel) = @_;
241     my $masks;
242     $masks .= $self->{'channelOpMasks'}->{$channel} if defined($self->{'channelOpMasks'}->{$channel});
243     $masks .= ' '.$self->{'channelOpMasks'}->{''} if defined($self->{'channelOpMasks'}->{''});
244     if (defined($masks)) {
245         my @masks = split(/ +/os, $masks);
246         my $user = $event->{'user'};
247         foreach my $regexp (@masks) {
248             my $pattern;
249             if ($regexp =~ m/       ^ # start at the start
250                              ([^!@]+) # nick part
251                                    \! # nick-username delimiter
252                              ([^!@]+) # username part
253                                    \@ # username-host delimiter
254                              ([^!@]+) # host part
255                                     $ # end at the end
256                              /osx) {
257
258                 my $nick = $1;
259                 my $user = $2;
260                 my $host = $3;
261
262                 # This was entered as an IRC hostmask so we need to
263                 # translate it into a regular expression.
264                 foreach ($nick, $user, $host) {
265                     $_ = quotemeta($_); # escape regular expression magic
266                     s/\\\*/.*/gos; # translate "*" into regexp equivalent
267                 }
268
269                 # If we don't match the first part of the host-mask
270                 # (the user's nick) then we should not op them; we
271                 # should just skip to the next mask.
272                 next unless $event->{'from'} =~ m/^$nick$/i;
273
274                 # ok, create hostmask regexp
275                 $pattern = "^$user\@$host\$";
276             } else {
277                 # this was entered as a regexp, check it is valid.
278                 $pattern = $self->sanitizeRegexp($regexp);
279             }
280             if (($pattern =~ /[^\s.*+]/os) # pattern is non-trivial
281                 and ($user =~ /$pattern/si)) { # pattern matches user
282                 return 1; # op user (so insecure, sigh)
283             }
284         }
285     }
286     return 0;
287 }
288
289 sub Kicked {
290     my $self = shift;
291     my ($event, $channel) = @_;
292     push(@{$self->{'kickLog'}}, "$event->{'from'} kicked us from $channel"); # XXX karma or something... ;-)
293     return $self->SUPER::Kicked(@_);
294 }
295
296 sub getList {
297     my $self = shift;
298     my ($channel, $list) = @_;
299     my $data;
300     my @list;
301     $data = defined($self->{$list}->{$channel}) ? $self->{$list}->{$channel} : '';
302     $data .= defined($self->{$list}->{''}) ? ' '.$self->{$list}->{''} : '';
303     if ($data =~ /^\s*$/os) {
304         @list = ('(none)');
305     } else {
306         @list = sort(split(/\s+/os, $data));
307         while ((@list) and ($list[0] =~ /^\s*$/)) { shift @list; }
308     }
309     return @list;
310 }
311
312 sub listOps {
313     my $self = shift;
314     my ($event, $channel) = @_;
315     my @admins = $self->getList($channel, 'channelAdmins');
316     my @ops = $self->getList($channel, 'channelOps');
317     my @masks = $self->getList($channel, 'channelOpMasks');
318
319     local $" = ' ';
320     my @output = ();
321     push(@output, "$channel admins: @admins");
322     push(@output, "$channel ops: @ops");
323     if (@masks > 2) {
324         push(@output, "$channel autoop masks:");
325         foreach (@masks) {
326             push(@output, "  $_");            
327         }
328     } else {
329         push(@output, "$channel autoop masks: @masks");
330     }
331     if (scalar(@output) > $self->{'maxInChannel'}) {
332         foreach (@output) {
333             $self->directSay($event, $_);
334         }
335         $self->channelSay($event, "$event->{'from'}: long list /msg'ed");
336     } else {
337         foreach (@output) {
338             $self->say($event, "$event->{'from'}: $_");
339         }
340     }
341 }