gecko-dev/webtools/mozbot/BotModules/God.bm

342 lines
16 KiB
Plaintext

# -*- Mode: perl; indent-tabs-mode: nil -*-
################################
# God Module #
################################
package BotModules::God;
use vars qw(@ISA);
@ISA = qw(BotModules);
1;
# XXX should also do autovoice
sub Help {
my $self = shift;
my ($event) = @_;
my $answer = {
'' => 'A per-channel auto-opper.',
'ops' => 'Lists the autoop list for a channel. Syntax: \'ops in <channel>\'',
'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\'',
'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.',
'autoop' => 'Add someone to the autoop list for a channel. Only bot and channel admins can do this. Syntax: \'autoop <user> in <channel>\'',
'deautoop' => 'Remove someone from the autoop list for a channel. Only bot and channel admins can do this. Syntax: \'deautoop <user> in <channel>\'',
'enable' => 'Enable a module in a channel. Only bot and channel admins can do this. Syntax: \'enable <module> in <channel>\'',
'disable' => 'Disable a module in a channel. Only bot and channel admins can do this. Syntax: \'disable <module> in <channel>\'',
};
if ($self->isAdmin($event)) {
$answer->{'opme'} .= '. As an administrator, you can also say \'op me in <channel>\' or \'op me everywhere\' which will do the obvious things.';
$answer->{'promote'} = 'Add someone to the channel admin list for a channel. Only bot admins can do this. Syntax: \'promote <user> in <channel>\'',
$answer->{'demote'} = 'Remove someone from the channel admin list for a channel. Only bot admins can do this. Syntax: \'demote <user> in <channel>\'',
}
return $answer;
}
# RegisterConfig - Called when initialised, should call registerVariables
sub RegisterConfig {
my $self = shift;
$self->SUPER::RegisterConfig(@_);
$self->registerVariables(
# [ name, save?, settable? ]
['channelAdmins', 1, 1, {}],
['channelOps', 1, 1, {}],
['channelOpMasks', 1, 1, {}],
['kickLog', 1, 1, []],
['allowPrivateOpRequests', 1, 1, 1],
['maxInChannel', 1, 1, 4],
);
}
sub Told {
my $self = shift;
my ($event, $message) = @_;
if ($event->{'level'} == 1) {
if ($message =~ /^\s*(?:list\s+)?ops\s+(?:in\s+|for\s+)?(\S+)\s*\??$/osi) {
my $channel = lc($1);
$self->listOps($event, $channel);
} elsif ($message =~ /^\s*autoop\s+(\S+)\s+in\s+(\S+)\s*$/osi) {
if (($self->isChannelAdmin($event, $2)) or ($self->isAdmin($event))) {
my $channel = $2 eq 'everywhere' ? '' : lc($2);
$self->{'channelOps'}->{$channel} .= " $1";
$self->saveConfig();
$self->say($event, "$event->{'from'}: User '$1' added to the autoop list of channel '$2'.");
} else {
$self->say($event, "$event->{'from'}: Only channel administrators may add people to a channel's autoop list.");
}
} elsif ($message =~ /^\s*deautoop\s+(\S+)\s+in\s+(\S+)\s*$/osi) {
if (($self->isChannelAdmin($event, $2)) or ($self->isAdmin($event))) {
my $channel = $2 eq 'everywhere' ? '' : lc($2);
my %people = map { $_ => 1 } split(/ +/os, $self->{'channelOps'}->{$channel});
delete($people{$1}); # get rid of any mentions of that person
$self->{'channelOps'}->{$channel} = join(' ', keys(%people));
$self->saveConfig();
$self->say($event, "$event->{'from'}: User '$1' removed from the autoop list of channel '$2'.");
} else {
$self->say($event, "$event->{'from'}: Only channel administrators may remove people from a channel's autoop list.");
}
} elsif ($message =~ /^\s*add\s+mask\s+(\S+)\s+(?:in|to|for|from)\s+(\S+)\s*$/osi) {
if (($self->isChannelAdmin($event, $2)) or ($self->isAdmin($event))) {
my $channel = $2 eq 'everywhere' ? '' : lc($2);
$self->{'channelOpMasks'}->{$channel} .= " $1";
$self->saveConfig();
$self->say($event, "$event->{'from'}: Mask '$1' added to the autoop list of channel '$2'.");
} else {
$self->say($event, "$event->{'from'}: Only channel administrators may add masks to a channel's autoop list.");
}
} elsif ($message =~ /^\s*remove\s+mask\s+(\S+)\s+(?:in|from|for|to)\s+(\S+)\s*$/osi) {
if (($self->isChannelAdmin($event, $2)) or ($self->isAdmin($event))) {
my $channel = $2 eq 'everywhere' ? '' : lc($2);
my %people = map { $_ => 1 } split(/ +/os, $self->{'channelOpMasks'}->{$channel});
delete($people{$1}); # get rid of any mentions of that person
$self->{'channelOpMasks'}->{$channel} = join(' ', keys(%people));
$self->saveConfig();
$self->say($event, "$event->{'from'}: Mask '$1' removed from the autoop list of channel '$2'.");
} else {
$self->say($event, "$event->{'from'}: Only channel administrators may remove masks from a channel's autoop list.");
}
} elsif ($message =~ /^\s*promote\s+(\S+)\s+in\s+(\S+)\s*$/osi) {
if ($self->isAdmin($event)) {
$self->{'channelAdmins'}->{lc($2)} .= " $1";
$self->saveConfig();
$self->say($event, "$event->{'from'}: User '$1' promoted to channel administrator status in channel '$2'.");
} else {
$self->say($event, "$event->{'from'}: Only administrators may promote people to channel admin status.");
}
} elsif ($message =~ /^\s*demote\s+(\S+)\s+in\s+(\S+)\s*$/osi) {
if ($self->isAdmin($event)) {
my %people = map { $_ => 1 } split(/ +/os, $self->{'channelAdmins'}->{lc($2)});
delete($people{$1}); # get rid of any mentions of that person
$self->{'channelAdmins'}->{lc($2)} = join(' ', keys(%people));
$self->saveConfig();
$self->say($event, "$event->{'from'}: User '$1' removed from the channel administrator list of channel '$2'.");
} else {
$self->say($event, "$event->{'from'}: Only administrators may remove people's channel admin status.");
}
} elsif ($message =~ /^\s*enable\s+(\S+)\s+in\s+(\S+)\s*$/osi) {
if (($self->isAdmin($event)) or ($self->isChannelAdmin($event, $2))) {
my $module = $self->getModule($1);
if ($1) {
push(@{$module->{'channels'}}, lc($2));
$module->saveConfig();
$self->say($event, "$event->{'from'}: Module '$1' enabled in channel '$2'.");
} else {
$self->say($event, "$event->{'from'}: There is no module called '$1', sorry.");
}
} else {
$self->say($event, "$event->{'from'}: Only channel administrators may change a module's status.");
}
} elsif ($message =~ /^\s*disable\s+(\S+)\s+in\s+(\S+)\s*$/osi) {
if (($self->isAdmin($event)) or ($self->isChannelAdmin($event, $2))) {
my $module = $self->getModule($1);
if ($1) {
my %channels = map { $_ => 1 } @{$module->{'channels'}};
delete($channels{lc($2)}); # get rid of any mentions of that channel
@{$module->{'channels'}} = keys %channels;
$module->saveConfig();
$self->say($event, "$event->{'from'}: Module '$1' disabled in channel '$2'.");
} else {
$self->say($event, "$event->{'from'}: There is no module called '$1', sorry.");
}
} else {
$self->say($event, "$event->{'from'}: Only channel administrators may change a module's status.");
}
} elsif ($message =~ /^\s*(?:(?:(?:de)?autoop|promote|demote|enable|disable|add\s+mask|remove\s+mask)\s+(\S+)|(?:list\s+)?ops)\s*$/osi) {
$self->say($event, "$event->{'from'}: You have to give a channel, as in \'<command> <who> in <channel>\'.");
# XXX next two could be merged, maybe.
} elsif ($message =~ /^\s*op\s*meh?[!1.,\s]*(?:now\s+)?(?:please|(b+[iea]+t+c+h+))?\s*[.!1]*\s*$/osi) {
if ($event->{'channel'}) {
if ($event->{'userName'}) {
unless ($self->checkOpping($event, $event->{'channel'}, $event->{'from'}, $self->isAdmin($event))) {
if ($1) { # only true if they said bitch
$self->say($event, "$event->{'from'}: No way, beetch!");
} else {
$self->say($event, "$event->{'from'}: Sorry, you are not on my auto-op list.");
}
}
} else {
unless ($self->isMatchedByMask($event, $event->{'channel'}) and
$self->checkOpping($event, $event->{'channel'}, $event->{'from'})) {
$self->say($event, "$event->{'from'}: You haven't authenticated yet. See 'help auth' for details.");
}
}
} else {
$self->say($event, "$event->{'from'}: You have to use this command in public.");
}
} elsif ($message =~ /^\s*(?:please\s+)?op\s*me(?:\s+in\s+(\S+)|\s+everywhere)?[\s!1.]*\s*$/osi) {
if (($self->{'allowPrivateOpRequests'}) or ($self->isAdmin($event))) {
if ($1) {
$self->checkOpping($event, lc($1), $event->{'from'}, $self->isAdmin($event));
} else {
foreach (@{$self->{'channels'}}) {
$self->checkOpping($event, $_, $event->{'from'}, $self->isAdmin($event));
}
}
} else {
$self->say($event, "$event->{'from'}: Sorry, but no. Try \'help opme\' for details on commansyntax.");
}
} else {
my $parentResult = $self->SUPER::Told(@_);
return $parentResult < 2 ? 2 : $parentResult;
}
return 0; # we've dealt with it, no need to do anything ese.
} elsif ($event->{'level'} == 2) {
if (defined($event->{'God_channel'})) {
$event->{'God_channel_rights'} = $self->isChannelAdmin($event, $event->{'God_channel'});
}
}
return $self->SUPER::Told(@_);
}
# SpottedJoin - Called when someone joins a channel
sub SpottedJoin {
my $self = shift;
my ($event, $channel, $who) = @_;
$self->checkOpping(@_, 0);
return $self->SUPER::SpottedJoin(@_); # this should not stop anything else happening
}
# do all channels when someone authenticates
sub Authed {
my $self = shift;
my ($event, $who) = @_;
foreach (@{$self->{'channels'}}) {
$self->checkOpping($event, $_, $who, 0);
}
return $self->SUPER::Authed(@_); # this should not stop anything else happening
}
# check is someone is in the opping.
sub checkOpping {
my $self = shift;
my ($event, $channel, $who, $override) = @_;
if (($self->isAutoopped($event, $channel)) or ($self->isChannelAdmin($event, $channel)) or ($override)) {
$self->mode($event, $channel, '+o', $who);
return 1;
}
return 0;
}
sub isChannelAdmin {
my $self = shift;
my ($event, $channel) = @_;
return (($event->{'userName'}) and
(defined($self->{'channelAdmins'}->{$channel})) and
($self->{'channelAdmins'}->{$channel} =~ /^(|.*\s+)$event->{'userName'}(\s+.*|)$/s));
}
sub isAutoopped {
my $self = shift;
my ($event, $channel) = @_;
return ((($event->{'userName'}) and
(defined($self->{'channelOps'}->{$channel})) and
(($self->{'channelOps'}->{$channel} =~ /^(|.*\s+)$event->{'userName'}(\s+.*|)$/s) or
($self->{'channelOps'}->{''} =~ /^(|.*\s+)$event->{'userName'}(\s+.*|)$/s))) or
($self->isMatchedByMask($event, $channel)));
}
# grrrr -- this insecure feature is here by popular demand
sub isMatchedByMask {
my $self = shift;
my ($event, $channel) = @_;
my $masks;
$masks .= $self->{'channelOpMasks'}->{$channel} if defined($self->{'channelOpMasks'}->{$channel});
$masks .= ' '.$self->{'channelOpMasks'}->{''} if defined($self->{'channelOpMasks'}->{''});
if (defined($masks)) {
my @masks = split(/ +/os, $masks);
my $user = $event->{'user'};
foreach my $regexp (@masks) {
my $pattern;
if ($regexp =~ m/ ^ # start at the start
([^!@]+) # nick part
\! # nick-username delimiter
([^!@]+) # username part
\@ # username-host delimiter
([^!@]+) # host part
$ # end at the end
/osx) {
my $nick = $1;
my $user = $2;
my $host = $3;
# This was entered as an IRC hostmask so we need to
# translate it into a regular expression.
foreach ($nick, $user, $host) {
$_ = quotemeta($_); # escape regular expression magic
s/\\\*/.*/gos; # translate "*" into regexp equivalent
}
# If we don't match the first part of the host-mask
# (the user's nick) then we should not op them; we
# should just skip to the next mask.
next unless $event->{'from'} =~ m/^$nick$/i;
# ok, create hostmask regexp
$pattern = "^$user\@$host\$";
} else {
# this was entered as a regexp, check it is valid.
$pattern = $self->sanitizeRegexp($regexp);
}
if (($pattern =~ /[^\s.*+]/os) # pattern is non-trivial
and ($user =~ /$pattern/si)) { # pattern matches user
return 1; # op user (so insecure, sigh)
}
}
}
return 0;
}
sub Kicked {
my $self = shift;
my ($event, $channel) = @_;
push(@{$self->{'kickLog'}}, "$event->{'from'} kicked us from $channel"); # XXX karma or something... ;-)
return $self->SUPER::Kicked(@_);
}
sub getList {
my $self = shift;
my ($channel, $list) = @_;
my $data;
my @list;
$data = defined($self->{$list}->{$channel}) ? $self->{$list}->{$channel} : '';
$data .= defined($self->{$list}->{''}) ? ' '.$self->{$list}->{''} : '';
if ($data =~ /^\s*$/os) {
@list = ('(none)');
} else {
@list = sort(split(/\s+/os, $data));
while ((@list) and ($list[0] =~ /^\s*$/)) { shift @list; }
}
return @list;
}
sub listOps {
my $self = shift;
my ($event, $channel) = @_;
my @admins = $self->getList($channel, 'channelAdmins');
my @ops = $self->getList($channel, 'channelOps');
my @masks = $self->getList($channel, 'channelOpMasks');
local $" = ' ';
my @output = ();
push(@output, "$channel admins: @admins");
push(@output, "$channel ops: @ops");
if (@masks > 2) {
push(@output, "$channel autoop masks:");
foreach (@masks) {
push(@output, " $_");
}
} else {
push(@output, "$channel autoop masks: @masks");
}
if (scalar(@output) > $self->{'maxInChannel'}) {
foreach (@output) {
$self->directSay($event, $_);
}
$self->channelSay($event, "$event->{'from'}: long list /msg'ed");
} else {
foreach (@output) {
$self->say($event, "$event->{'from'}: $_");
}
}
}