# This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. package Plugins::RestrictedMod; use parent 'Plugins::Plugin'; # purpose: provides restricted moderation abilities to voiced users. # They are allowed to ban/mute/kick only users that are not admins, # whitelisted, or autoop/autovoice. This is useful for, e.g., IRCnet # configurations where +v users are recognized as "semi-trusted" in # order to provide assistance in combating heavy spam and drone traffic. use warnings; use strict; use feature 'unicode_strings'; use Storable qw/dclone/; sub initialize { my ($self, %conf) = @_; $self->{pbot}->{commands}->register(sub { $self->cmd_mod(@_) }, 'mod', 0); $self->{pbot}->{commands}->set_meta( 'mod', 'help', 'Provides restricted moderation abilities to voiced users. They can kick/ban/etc only users that are not admins, whitelisted, voiced or opped.' ); $self->{pbot}->{capabilities}->add('chanmod', 'can-mod', 1); $self->{pbot}->{capabilities}->add('chanmod', 'can-voice', 1); $self->{pbot}->{capabilities}->add('chanmod', 'can-devoice', 1); $self->{commands} = { 'help' => {subref => sub { $self->help(@_) }, help => "Provides help about this command. Usage: mod help ; see also: mod help list"}, 'list' => {subref => sub { $self->list(@_) }, help => "Lists available mod commands. Usage: mod list"}, 'kick' => {subref => sub { $self->kick(@_) }, help => "Kicks a nick from the channel. Usage: mod kick "}, 'ban' => {subref => sub { $self->ban(@_) }, help => "Bans a nick from the channel. Cannot be used to set a custom banmask. Usage: mod ban "}, 'mute' => {subref => sub { $self->mute(@_) }, help => "Mutes a nick in the channel. Usage: mod mute "}, 'unban' => {subref => sub { $self->unban(@_) }, help => "Removes bans set by moderators. Cannot remove any other types of bans. Usage: mod unban "}, 'unmute' => {subref => sub { $self->unmute(@_) }, help => "Removes mutes set by moderators. Cannot remove any other types of mutes. Usage: mod unmute "}, 'kb' => {subref => sub { $self->kb(@_) }, help => "Kickbans a nick from the channel. Cannot be used to set a custom banmask. Usage: mod kb "}, }; } sub unload { my ($self) = @_; $self->{pbot}->{commands}->unregister('mod'); $self->{pbot}->{capabilities}->remove('chanmod'); } sub help { my ($self, $context) = @_; my $command = $self->{pbot}->{interpreter}->shift_arg($context->{arglist}) // 'help'; if (exists $self->{commands}->{$command}) { return $self->{commands}->{$command}->{help}; } else { return "No such mod command '$command'. I can't help you with that."; } } sub list { my ($self, $context) = @_; return "Available mod commands: " . join ', ', sort keys %{$self->{commands}}; } sub generic_command { my ($self, $context, $command) = @_; my $channel = $context->{from}; if ($channel !~ m/^#/) { $channel = $self->{pbot}->{interpreter}->shift_arg($context->{arglist}); if (not defined $channel or $channel !~ /^#/) { return "Must specify channel from private message. Usage: mod $command "; } } return "I do not have OPs for this channel. I cannot do any moderation here." if not $self->{pbot}->{chanops}->can_gain_ops($channel); return "Voiced moderation is not enabled for this channel. Use `regset $channel.restrictedmod 1` to enable." if not $self->{pbot}->{registry}->get_value($channel, 'restrictedmod'); my $hostmask = $context->{hostmask}; my $user = $self->{pbot}->{users}->loggedin($channel, $hostmask) // {admin => 0, chanmod => 0}; my $voiced = $self->{pbot}->{nicklist}->get_meta($channel, $context->{nick}, '+v'); if (not $voiced and not $self->{pbot}->{capabilities}->userhas($user, 'admin') and not $self->{pbot}->{capabilities}->userhas($user, 'chanmod')) { return "You must be voiced (usermode +v) or have the admin or chanmod capability to use this command."; } my $target = $self->{pbot}->{interpreter}->shift_arg($context->{arglist}); return "Missing target. Usage: mod $command " if not defined $target; if ($command eq 'unban') { my $reason = $self->{pbot}->{banlist}->checkban($channel, 'b', $target); if ($reason =~ m/moderator ban/) { $self->{pbot}->{banlist}->unban_user($channel, 'b', $target, 1); return ""; } else { return "I don't think so. That ban was not set by a moderator."; } } elsif ($command eq 'unmute') { my $reason = $self->{pbot}->{banlist}->checkban($channel, 'q', $target); if ($reason =~ m/moderator mute/) { $self->{pbot}->{banlist}->unban_user($channel, 'q', $target, 1); return ""; } else { return "I don't think so. That mute was not set by a moderator."; } } my $target_nicklist; if (not $self->{pbot}->{nicklist}->is_present($channel, $target)) { return "$context->{nick}: I do not see anybody named $target in this channel."; } else { $target_nicklist = $self->{pbot}->{nicklist}->{nicklist}->{lc $channel}->{lc $target}; } my $target_user = $self->{pbot}->{users}->loggedin($channel, $target_nicklist->{hostmask}); if ( (defined $target_user and $target_user->{autoop} or $target_user->{autovoice}) or $target_nicklist->{'+v'} or $target_nicklist->{'+o'} or $self->{pbot}->{capabilities}->userhas($target_user, 'is-whitelisted')) { return "I don't think so."; } if ($command eq 'kick') { $self->{pbot}->{chanops}->add_op_command($channel, "kick $channel $target Have a nice day!"); $self->{pbot}->{chanops}->gain_ops($channel); } elsif ($command eq 'ban') { $self->{pbot}->{banlist}->ban_user_timed( $channel, 'b', $target, 3600 * 24, "$context->{nick}!$context->{user}\@$context->{host}", "doing something naughty (moderator ban)", 1 ); } elsif ($command eq 'mute') { $self->{pbot}->{banlist}->ban_user_timed( $channel, 'q', $target, 3600 * 24, "$context->{nick}!$context->{user}\@$context->{host}", "doing something naughty (moderator mute)", 1 ); } return ""; } sub kick { my ($self, $context) = @_; return $self->generic_command($context, 'kick'); } sub ban { my ($self, $context) = @_; return $self->generic_command($context, 'ban'); } sub mute { my ($self, $context) = @_; return $self->generic_command($context, 'mute'); } sub unban { my ($self, $context) = @_; return $self->generic_command($context, 'unban'); } sub unmute { my ($self, $context) = @_; return $self->generic_command($context, 'unmute'); } sub kb { my ($self, $context) = @_; my $result = $self->ban(dclone $context); # note: using copy of $context to preserve $context->{arglist} for $self->kick($context) return $result if length $result; return $self->kick($context); } sub cmd_mod { my ($self, $context) = @_; my $command = $self->{pbot}->{interpreter}->shift_arg($context->{arglist}) // ''; $command = lc $command; if (grep { $_ eq $command } keys %{$self->{commands}}) { return $self->{commands}->{$command}->{subref}->($context); } else { my $commands = join ', ', sort keys %{$self->{commands}}; if ($context->{from} !~ m/^#/) { return "Usage: mod [arguments]; commands are: $commands; see `mod help ` for more information."; } else { return "Usage: mod [arguments]; commands are: $commands; see `mod help ` for more information."; } } } 1;