# -*- 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 \'', '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 in \' to add and \'remove mask in \' 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 in \'', 'deautoop' => 'Remove someone from the autoop list for a channel. Only bot and channel admins can do this. Syntax: \'deautoop in \'', 'enable' => 'Enable a module in a channel. Only bot and channel admins can do this. Syntax: \'enable in \'', 'disable' => 'Disable a module in a channel. Only bot and channel admins can do this. Syntax: \'disable in \'', }; if ($self->isAdmin($event)) { $answer->{'opme'} .= '. As an administrator, you can also say \'op me in \' 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 in \'', $answer->{'demote'} = 'Remove someone from the channel admin list for a channel. Only bot admins can do this. Syntax: \'demote in \'', } 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 \' in \'."); # 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'}: $_"); } } }