diff --git a/doc/Admin.md b/doc/Admin.md index 36f0d78d..59135aaa 100644 --- a/doc/Admin.md +++ b/doc/Admin.md @@ -498,18 +498,20 @@ Bans or mutes a user. If the argument is a nick instead of a hostmask, it will d The argument can be a comma-separated list of multiple nicks or masks. Usages: -- `ban [channel [timeout]]` -- `mute [channel [timeout]]` +- `ban [timeout (default: 24h) [reason]] [-c ] [-t ] [-r ]` +- `mute [timeout (default: 24h) [reason]] [-c ] [-t ] [-r ]` If `timeout` is omitted, PBot will ban the user for 24 hours. Timeout can be specified as an relative time in English; for instance, `5 minutes`, `1 month and 2 weeks`, `next thursday`, `friday after next`, `forever` and such. +If a ban already exists, you may update the timeout or reason at any time. + ### unban/unmute Unbans or unmutes a user. If the argument is a nick instead of a hostmask, it will find all bans that match any of that nick's hostmasks or NickServ accounts and unban them. The argument can be a comma-separated list of multiple nicks or masks. If the argument is `*` then all bans/mutes for the channel will be removed. Usages: -- `unban [channel]` -- `unmute [channel]` +- `unban [channel]` +- `unmute [channel]` ### checkban The `checkban` command displays information about an entry in PBot's internal banlist. PBot's internal banlist @@ -523,7 +525,7 @@ If the `[channel]` option is omitted, the channel in which the command is invoke Example: checkban loser!*@* - loser!*@* banned in #c on Tue Aug 31 06:41:24 2021 PDT (14d15h ago) by candide!~pbot3@about/c/bot/candide for chat-flooding (2h remaining) + loser!*@* banned in #c on Tue Aug 31 06:41:24 2021 PDT (14d15h ago) by candide!~pbot3@about/c/bot/candide because chat-flooding (2h remaining) ### checkmute The `checkmute` command is identical to the [`checkban`](#checkban) command, except for mutes instead of bans. @@ -538,8 +540,8 @@ Usage: `invite [channel] ` ### kick Removes a user from the channel. `` can be a comma-separated list of multiple users, optionally containing wildcards. If `[reason]` is omitted, a random insult will be used. -Usage from channel: `kick [reason]` -From private message: `kick [reason]` +Usage from channel: `kick [reason]` +From private message: `kick [reason]` ## Applet-management Note that applets are "reloaded" each time they are executed. There is no need to `refresh` after editing an applet. diff --git a/lib/PBot/Core/BanList.pm b/lib/PBot/Core/BanList.pm index 3cd4018e..e3c32bfa 100644 --- a/lib/PBot/Core/BanList.pm +++ b/lib/PBot/Core/BanList.pm @@ -82,8 +82,8 @@ sub checkban($self, $channel, $mode, $mask) { $result .= "on $date ($ago) "; } - $result .= "by $data->{owner} " if defined $data->{owner}; - $result .= "for $data->{reason} " if defined $data->{reason}; + $result .= "by $data->{owner} " if defined $data->{owner}; + $result .= "because $data->{reason} " if defined $data->{reason}; if (exists $data->{timeout} and $data->{timeout} > 0) { my $duration = concise duration($data->{timeout} - gettimeofday); diff --git a/lib/PBot/Core/Commands/BanList.pm b/lib/PBot/Core/Commands/BanList.pm index 89d8aedc..fd736659 100644 --- a/lib/PBot/Core/Commands/BanList.pm +++ b/lib/PBot/Core/Commands/BanList.pm @@ -17,15 +17,14 @@ use Time::Duration; use POSIX qw/strftime/; sub initialize($self, %conf) { - $self->{pbot}->{commands}->register(sub { $self->cmd_banlist(@_) }, "banlist", 0); - $self->{pbot}->{commands}->register(sub { $self->cmd_checkban(@_) }, "checkban", 0); - $self->{pbot}->{commands}->register(sub { $self->cmd_checkmute(@_) }, "checkmute", 0); + $self->{pbot}->{commands}->register(sub { $self->cmd_banlist(@_) }, "banlist", 0); + $self->{pbot}->{commands}->register(sub { $self->cmd_checkban(@_) }, "checkban", 0); + $self->{pbot}->{commands}->register(sub { $self->cmd_checkmute(@_) }, "checkmute", 0); $self->{pbot}->{commands}->register(sub { $self->cmd_unbanme(@_) }, "unbanme", 0); $self->{pbot}->{commands}->register(sub { $self->cmd_ban_exempt(@_) }, "ban-exempt", 1); # add capability to admin group $self->{pbot}->{capabilities}->add('admin', 'can-ban-exempt', 1); - } sub cmd_banlist($self, $context) { @@ -48,8 +47,8 @@ sub cmd_banlist($self, $context) { $result .= "on $date ($ago) "; } - $result .= "by $data->{owner} " if defined $data->{owner}; - $result .= "for $data->{reason} " if defined $data->{reason}; + $result .= "by $data->{owner} " if defined $data->{owner}; + $result .= "because $data->{reason} " if defined $data->{reason}; if (defined $data->{timeout} and $data->{timeout} > 0) { my $duration = concise duration($data->{timeout} - gettimeofday); $result .= "($duration remaining)"; @@ -73,8 +72,8 @@ sub cmd_banlist($self, $context) { $result .= "on $date ($ago) "; } - $result .= "by $data->{owner} " if defined $data->{owner}; - $result .= "for $data->{reason} " if defined $data->{reason}; + $result .= "by $data->{owner} " if defined $data->{owner}; + $result .= "because $data->{reason} " if defined $data->{reason}; if (defined $data->{timeout} and $data->{timeout} > 0) { my $duration = concise duration($data->{timeout} - gettimeofday); $result .= "($duration remaining)"; diff --git a/lib/PBot/Core/Commands/ChanOp.pm b/lib/PBot/Core/Commands/ChanOp.pm index 86375cff..d37f969b 100644 --- a/lib/PBot/Core/Commands/ChanOp.pm +++ b/lib/PBot/Core/Commands/ChanOp.pm @@ -337,81 +337,7 @@ sub cmd_mode($self, $context) { } sub cmd_ban($self, $context) { - my ($target, $channel, $length) = $self->{pbot}->{interpreter}->split_args($context->{arglist}, 3); - - $channel = '' if not defined $channel; - $length = '' if not defined $length; - - if (not defined $context->{from}) { - $self->{pbot}->{logger}->log("Command missing ~from parameter!\n"); - return ""; - } - - if ($channel !~ m/^#/) { - $length = "$channel $length"; - $length = undef if $length eq ' '; - $channel = exists $context->{admin_channel_override} ? $context->{admin_channel_override} : $context->{from}; - } - - if (not defined $channel or not length $channel) { - $channel = exists $context->{admin_channel_override} ? $context->{admin_channel_override} : $context->{from}; - } - - if (not defined $target) { return "Usage: ban [channel [timeout (default: 24 hours)]]"; } - - my $no_length = 0; - if (not defined $length) { - # TODO: user account length override - $length = $self->{pbot}->{registry}->get_value($channel, 'default_ban_timeout', 0, $context) - // $self->{pbot}->{registry}->get_value('general', 'default_ban_timeout', 0, $context) // 60 * 60 * 24; # 24 hours - $no_length = 1; - } else { - my $error; - ($length, $error) = $self->{pbot}->{parsedate}->parsedate($length); - return $error if defined $error; - } - - $channel = lc $channel; - $target = lc $target; - - my $botnick = $self->{pbot}->{registry}->get_value('irc', 'botnick'); - return "I don't think so." if $target =~ /^\Q$botnick\E!/i; - - my $result = ''; - my $sep = ''; - my @targets = split /,/, $target; - my $immediately = @targets > 1 ? 0 : 1; - my $duration; - - foreach my $t (@targets) { - my $mask = lc $self->{pbot}->{banlist}->nick_to_banmask($t); - - my $timeout = $self->{pbot}->{banlist}->{banlist}->get_data($channel, $mask, 'timeout') // 0; - - if ($no_length && $timeout > 0) { - my $d = duration($timeout - gettimeofday); - $result .= "$sep$mask has $d remaining on their $channel ban"; - $sep = '; '; - } else { - $self->{pbot}->{banlist}->ban_user_timed($channel, 'b', $mask, $length, $context->{hostmask}, undef, $immediately); - $duration = $length > 0 ? duration $length : 'all eternity'; - if ($immediately) { - $result .= "$sep$mask banned in $channel for $duration"; - $sep = '; '; - } else { - $result .= "$sep$mask"; - $sep = ', '; - } - } - } - - if (not $immediately) { - $result .= " banned in $channel for $duration"; - $self->{pbot}->{banlist}->flush_ban_queue; - } - - $result = "/msg $context->{nick} $result" if $result !~ m/remaining on their/; - return $result; + return do_ban_or_mute($self, $context, 'ban'); } sub cmd_unban($self, $context) { @@ -428,7 +354,7 @@ sub cmd_unban($self, $context) { $channel = $temp; } - if (not defined $target) { return "Usage: unban [channel [false value to use unban queue]]"; } + if (not defined $target) { return "Usage: unban [channel [false value to use unban queue]]"; } if (not defined $channel) { $channel = exists $context->{admin_channel_override} ? $context->{admin_channel_override} : $context->{from}; @@ -436,7 +362,7 @@ sub cmd_unban($self, $context) { $immediately = 1 if not defined $immediately; - return "Usage for /msg: unban [false value to use unban queue]" if $channel !~ /^#/; + return "Usage: unban [false value to use unban queue]" if $channel !~ /^#/; my @targets = split /,/, $target; $immediately = 0 if @targets > 1; @@ -465,83 +391,7 @@ sub cmd_unban($self, $context) { } sub cmd_mute($self, $context) { - my ($target, $channel, $length) = $self->{pbot}->{interpreter}->split_args($context->{arglist}, 3); - - $channel = '' if not defined $channel; - - if (not defined $context->{from}) { - $self->{pbot}->{logger}->log("Command missing ~from parameter!\n"); - return ""; - } - - if (not length $channel and $context->{from} !~ m/^#/) { - return "Usage from private message: mute [timeout (default: 24 hours)]"; - } - - if ($channel !~ m/^#/) { - $length = $channel . ' ' . (defined $length ? $length : ''); - $length = undef if $length eq ' '; - $channel = exists $context->{admin_channel_override} ? $context->{admin_channel_override} : $context->{from}; - } - - $channel = exists $context->{admin_channel_override} ? $context->{admin_channel_override} : $context->{from} if not defined $channel; - - if ($channel !~ m/^#/) { return "Please specify a channel."; } - - if (not defined $target) { return "Usage: mute [channel [timeout (default: 24 hours)]]"; } - - my $no_length = 0; - if (not defined $length) { - $length = $self->{pbot}->{registry}->get_value($channel, 'default_mute_timeout', 0, $context) - // $self->{pbot}->{registry}->get_value('general', 'default_mute_timeout', 0, $context) // 60 * 60 * 24; # 24 hours - $no_length = 1; - } else { - my $error; - ($length, $error) = $self->{pbot}->{parsedate}->parsedate($length); - return $error if defined $error; - } - - $channel = lc $channel; - $target = lc $target; - - my $botnick = $self->{pbot}->{registry}->get_value('irc', 'botnick'); - return "I don't think so." if $target =~ /^\Q$botnick\E!/i; - - my $result = ''; - my $sep = ''; - my @targets = split /,/, $target; - my $immediately = @targets > 1 ? 0 : 1; - my $duration; - - foreach my $t (@targets) { - my $mask = lc $self->{pbot}->{banlist}->nick_to_banmask($t); - - my $timeout = $self->{pbot}->{banlist}->{quietlist}->get_data($channel, $mask, 'timeout') // 0; - - if ($no_length && $timeout > 0) { - my $d = duration($timeout - gettimeofday); - $result .= "$sep$mask has $d remaining on their $channel mute"; - $sep = '; '; - } else { - $self->{pbot}->{banlist}->ban_user_timed($channel, 'q', $t, $length, $context->{hostmask}, undef, $immediately); - $duration = $length > 0 ? duration $length : 'all eternity'; - if ($immediately) { - $result .= "$sep$mask muted in $channel for $duration"; - $sep = '; '; - } else { - $result .= "$sep$mask"; - $sep = ', '; - } - } - } - - if (not $immediately) { - $result .= " muted in $channel for $duration"; - $self->{pbot}->{banlist}->flush_ban_queue; - } - - $result = "/msg $context->{nick} $result" if $result !~ m/remaining on their/; - return $result; + return do_ban_or_mute($self, $context, 'mute'); } sub cmd_unmute($self, $context) { @@ -558,12 +408,12 @@ sub cmd_unmute($self, $context) { $channel = $temp; } - if (not defined $target) { return "Usage: unmute [channel [false value to use unban queue]]"; } + if (not defined $target) { return "Usage: unmute [channel [false value to use unban queue]]"; } $channel = exists $context->{admin_channel_override} ? $context->{admin_channel_override} : $context->{from} if not defined $channel; $immediately = 1 if not defined $immediately; - return "Usage for /msg: unmute [false value to use unban queue]" if $channel !~ /^#/; + return "Usage for /msg: unmute [false value to use unban queue]" if $channel !~ /^#/; my @targets = split /,/, $target; $immediately = 0 if @targets > 1; @@ -597,13 +447,13 @@ sub cmd_kick($self, $context) { if (not $context->{from} =~ /^#/) { # used in private message - if (not $arguments =~ s/^(^#\S+) (\S+)\s*//) { return "Usage from private message: kick [reason]; may include wildcards"; } + if (not $arguments =~ s/^(^#\S+) (\S+)\s*//) { return "Usage from private message: kick [reason]; may include wildcards"; } ($channel, $victim) = ($1, $2); } else { # used in channel if ($arguments =~ s/^(#\S+)\s+(\S+)\s*//) { ($channel, $victim) = ($1, $2); } elsif ($arguments =~ s/^(\S+)\s*//) { ($victim, $channel) = ($1, exists $context->{admin_channel_override} ? $context->{admin_channel_override} : $context->{from}); } - else { return "Usage: kick [channel] [reason]; may include wildcards"; } + else { return "Usage: kick [channel] [reason]; may include wildcards"; } } $reason = $arguments; @@ -672,4 +522,131 @@ sub cmd_kick($self, $context) { return ""; } +sub do_ban_or_mute($self, $context, $mode) { + my ($target, $channel, $length, $reason); + + my $usage = "usage: $mode [timeout (default: 24h) [reason]] [-c ] [-t ] [-r ]"; + + my %opts = ( + channel => \$channel, + timeout => \$length, + reason => \$reason, + ); + + my ($opt_args, $opt_error) = $self->{pbot}->{interpreter}->getopt( + $context->{arguments}, + \%opts, + ['bundling'], + 'channel|c=s', + 'timeout|t=s', + 'reason|r=s', + ); + + return "$opt_error -- $usage" if defined $opt_error; + + $target = shift @$opt_args; + $length = shift @$opt_args if not defined $length; + $reason = "@$opt_args" if not defined $reason; + + $channel = '' if not defined $channel; + $length = '' if not defined $length; + + $reason = undef if not length $reason; + + if (not defined $target) { + return $usage; + } + + if (not length $channel) { + $channel = exists $context->{admin_channel_override} ? $context->{admin_channel_override} : $context->{from}; + } + + if ($channel eq $context->{nick}) { + return "Channel argument is required in private-message; $usage"; + } + + if ($channel !~ m/^#/) { + return "Bad channel '$channel'; $usage"; + } + + my $error; + ($length, $error) = $self->{pbot}->{parsedate}->parsedate($length); + return $error if defined $error; + + $channel = lc $channel; + $target = lc $target; + + my $result = ''; + my $sep = ''; + my @targets = split /,/, $target; + my $immediately = @targets > 1 ? 0 : 1; + my $duration; + + foreach my $t (@targets) { + my $mask = lc $self->{pbot}->{banlist}->nick_to_banmask($t); + + my $ban = $self->{pbot}->{banlist}->{banlist}->get_data($channel, $mask); + + if (defined $ban) { + my $save = 0; + + if (defined $reason) { + $ban->{reason} = $reason; + $save = 1; + } + + if ($length) { + $ban->{timeout} = gettimeofday + $length; + $save = 1; + } + + $self->{pbot}->{banlist}->{banlist}->save if $save; + + $result .= "$sep$mask "; + + if (not $save) { + $result .= 'is already '; + } + + $result .= ($mode eq 'ban' ? 'banned' : 'muted') . " in $channel"; + + if (defined $ban->{reason}) { + $result .= " because $ban->{reason}"; + } + + if ($ban->{timeout} > 0) { + my $d = duration($ban->{timeout} - gettimeofday); + $result .= " ($d remaining)"; + } + + $sep = '; '; + } else { + if (not $length) { + # TODO: user account length override + $length = $self->{pbot}->{registry}->get_value($channel, "default_${mode}_timeout", 0, $context) + // $self->{pbot}->{registry}->get_value('general', "default_${mode}_timeout", 0, $context) // 60 * 60 * 24; # 24 hours + } + + $self->{pbot}->{banlist}->ban_user_timed($channel, $mode eq 'ban' ? 'b' : 'q', $mask, $length, $context->{hostmask}, $reason, $immediately); + $duration = $length > 0 ? duration $length : 'all eternity'; + if ($immediately) { + $result .= "$sep$mask " . ($mode eq 'ban' ? 'banned' : 'muted') . " in $channel ($duration)"; + $result .= " because $reason" if defined $reason; + $sep = '; '; + } else { + $result .= "$sep$mask"; + $sep = ', '; + } + } + } + + if (not $immediately) { + $result .= ($mode eq 'ban' ? ' banned' : ' muted') . " in $channel for $duration"; + $self->{pbot}->{banlist}->flush_ban_queue; + } + + $result = "/msg $context->{nick} $result" if $result !~ m/remaining/; + return $result; +} + 1; diff --git a/lib/PBot/VERSION.pm b/lib/PBot/VERSION.pm index 520e8cc7..79ba2841 100644 --- a/lib/PBot/VERSION.pm +++ b/lib/PBot/VERSION.pm @@ -25,7 +25,7 @@ use PBot::Imports; # These are set by the /misc/update_version script use constant { BUILD_NAME => "PBot", - BUILD_REVISION => 4655, + BUILD_REVISION => 4656, BUILD_DATE => "2023-05-04", };