1 # -*- Mode: perl; indent-tabs-mode: nil -*-
2 ################################
4 ################################
6 package BotModules::God;
11 # XXX should also do autovoice
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>\'',
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>\'',
34 # RegisterConfig - Called when initialised, should call registerVariables
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],
51 my ($event, $message) = @_;
52 if ($event->{'level'} == 1) {
53 if ($message =~ /^\s*(?:list\s+)?ops\s+(?:in\s+|for\s+)?(\S+)\s*\??$/osi) {
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";
61 $self->say($event, "$event->{'from'}: User '$1' added to the autoop list of channel '$2'.");
63 $self->say($event, "$event->{'from'}: Only channel administrators may add people to a channel's autoop list.");
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));
72 $self->say($event, "$event->{'from'}: User '$1' removed from the autoop list of channel '$2'.");
74 $self->say($event, "$event->{'from'}: Only channel administrators may remove people from a channel's autoop list.");
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";
81 $self->say($event, "$event->{'from'}: Mask '$1' added to the autoop list of channel '$2'.");
83 $self->say($event, "$event->{'from'}: Only channel administrators may add masks to a channel's autoop list.");
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));
92 $self->say($event, "$event->{'from'}: Mask '$1' removed from the autoop list of channel '$2'.");
94 $self->say($event, "$event->{'from'}: Only channel administrators may remove masks from a channel's autoop list.");
96 } elsif ($message =~ /^\s*promote\s+(\S+)\s+in\s+(\S+)\s*$/osi) {
97 if ($self->isAdmin($event)) {
98 $self->{'channelAdmins'}->{lc($2)} .= " $1";
100 $self->say($event, "$event->{'from'}: User '$1' promoted to channel administrator status in channel '$2'.");
102 $self->say($event, "$event->{'from'}: Only administrators may promote people to channel admin status.");
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));
110 $self->say($event, "$event->{'from'}: User '$1' removed from the channel administrator list of channel '$2'.");
112 $self->say($event, "$event->{'from'}: Only administrators may remove people's channel admin status.");
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);
118 push(@{$module->{'channels'}}, lc($2));
119 $module->saveConfig();
120 $self->say($event, "$event->{'from'}: Module '$1' enabled in channel '$2'.");
122 $self->say($event, "$event->{'from'}: There is no module called '$1', sorry.");
125 $self->say($event, "$event->{'from'}: Only channel administrators may change a module's status.");
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);
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'.");
137 $self->say($event, "$event->{'from'}: There is no module called '$1', sorry.");
140 $self->say($event, "$event->{'from'}: Only channel administrators may change a module's status.");
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>\'.");
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!");
153 $self->say($event, "$event->{'from'}: Sorry, you are not on my auto-op list.");
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.");
163 $self->say($event, "$event->{'from'}: You have to use this command in public.");
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))) {
168 $self->checkOpping($event, lc($1), $event->{'from'}, $self->isAdmin($event));
170 foreach (@{$self->{'channels'}}) {
171 $self->checkOpping($event, $_, $event->{'from'}, $self->isAdmin($event));
175 $self->say($event, "$event->{'from'}: Sorry, but no. Try \'help opme\' for details on commansyntax.");
178 my $parentResult = $self->SUPER::Told(@_);
179 return $parentResult < 2 ? 2 : $parentResult;
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'});
187 return $self->SUPER::Told(@_);
190 # SpottedJoin - Called when someone joins a channel
193 my ($event, $channel, $who) = @_;
194 $self->checkOpping(@_, 0);
195 return $self->SUPER::SpottedJoin(@_); # this should not stop anything else happening
198 # do all channels when someone authenticates
201 my ($event, $who) = @_;
202 foreach (@{$self->{'channels'}}) {
203 $self->checkOpping($event, $_, $who, 0);
205 return $self->SUPER::Authed(@_); # this should not stop anything else happening
208 # check is someone is in the opping.
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);
221 my ($event, $channel) = @_;
222 return (($event->{'userName'}) and
223 (defined($self->{'channelAdmins'}->{$channel})) and
224 ($self->{'channelAdmins'}->{$channel} =~ /^(|.*\s+)$event->{'userName'}(\s+.*|)$/s));
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)));
237 # grrrr -- this insecure feature is here by popular demand
238 sub isMatchedByMask {
240 my ($event, $channel) = @_;
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) {
249 if ($regexp =~ m/ ^ # start at the start
251 \! # nick-username delimiter
252 ([^!@]+) # username part
253 \@ # username-host delimiter
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
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;
274 # ok, create hostmask regexp
275 $pattern = "^$user\@$host\$";
277 # this was entered as a regexp, check it is valid.
278 $pattern = $self->sanitizeRegexp($regexp);
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)
291 my ($event, $channel) = @_;
292 push(@{$self->{'kickLog'}}, "$event->{'from'} kicked us from $channel"); # XXX karma or something... ;-)
293 return $self->SUPER::Kicked(@_);
298 my ($channel, $list) = @_;
301 $data = defined($self->{$list}->{$channel}) ? $self->{$list}->{$channel} : '';
302 $data .= defined($self->{$list}->{''}) ? ' '.$self->{$list}->{''} : '';
303 if ($data =~ /^\s*$/os) {
306 @list = sort(split(/\s+/os, $data));
307 while ((@list) and ($list[0] =~ /^\s*$/)) { shift @list; }
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');
321 push(@output, "$channel admins: @admins");
322 push(@output, "$channel ops: @ops");
324 push(@output, "$channel autoop masks:");
326 push(@output, " $_");
329 push(@output, "$channel autoop masks: @masks");
331 if (scalar(@output) > $self->{'maxInChannel'}) {
333 $self->directSay($event, $_);
335 $self->channelSay($event, "$event->{'from'}: long list /msg'ed");
338 $self->say($event, "$event->{'from'}: $_");