diff --git a/PluralKit.Bot/CommandMeta/CommandHelp.cs b/PluralKit.Bot/CommandMeta/CommandHelp.cs index 5f3314b7..d1b72a87 100644 --- a/PluralKit.Bot/CommandMeta/CommandHelp.cs +++ b/PluralKit.Bot/CommandMeta/CommandHelp.cs @@ -109,6 +109,9 @@ public partial class CommandTree public static Command ProxyBlacklistShow = new Command("serverconfig proxy blacklist", "serverconfig proxy blacklist", "Displays the current list of channels where message proxying is disabled"); public static Command ProxyBlacklistAdd = new Command("serverconfig proxy blacklist add", "serverconfig proxy blacklist add all| [channel 2] [channel 3...]", "Disables message proxying in certain channels"); public static Command ProxyBlacklistRemove = new Command("serverconfig proxy blacklist remove", "serverconfig proxy blacklist remove all| [channel 2] [channel 3...]", "Enables message proxying in certain channels"); + public static Command CommandBlacklistShow = new Command("serverconfig command blacklist", "serverconfig command blacklist", "Displays the current list of channels where running text commands is disabled"); + public static Command CommandBlacklistAdd = new Command("serverconfig command blacklist add", "serverconfig command blacklist add all| [channel 2] [channel 3...]", "Disables running text commands in certain channels"); + public static Command CommandBlacklistRemove = new Command("serverconfig command blacklist remove", "serverconfig command blacklist remove all| [channel 2] [channel 3...]", "Enables running text commands in certain channels"); public static Command ServerConfigLogClean = new Command("serverconfig log cleanup", "serverconfig log cleanup [on|off]", "Toggles whether to clean up other bots' log channels"); public static Command ServerConfigInvalidCommandResponse = new Command("serverconfig invalid command error", "serverconfig invalid command error [on|off]", "Sets whether to show an error message when an unknown command is sent"); public static Command ServerConfigRequireSystemTag = new Command("serverconfig require tag", "serverconfig require tag [on|off]", "Sets whether server users are required to have a system tag on proxied messages"); @@ -160,7 +163,8 @@ public partial class CommandTree ServerConfigLogClean, ServerConfigInvalidCommandResponse, ServerConfigRequireSystemTag, ServerConfigSuppressNotifications, LogChannel, LogChannelClear, LogShow, LogDisable, LogEnable, - ProxyBlacklistShow, ProxyBlacklistAdd, ProxyBlacklistRemove + ProxyBlacklistShow, ProxyBlacklistAdd, ProxyBlacklistRemove, + CommandBlacklistShow, CommandBlacklistAdd, CommandBlacklistRemove }; public static Command[] AutoproxyCommands = diff --git a/PluralKit.Bot/CommandMeta/CommandTree.cs b/PluralKit.Bot/CommandMeta/CommandTree.cs index 0a56c20f..392676f0 100644 --- a/PluralKit.Bot/CommandMeta/CommandTree.cs +++ b/PluralKit.Bot/CommandMeta/CommandTree.cs @@ -642,6 +642,15 @@ public partial class CommandTree else return ctx.Execute(null, m => m.ShowProxyBlacklisted(ctx)); } + if (ctx.MatchMultiple(new[] { "command" }, new[] { "blacklist" })) + { + if (ctx.Match("enable", "on", "add", "deny")) + return ctx.Execute(null, m => m.SetCommandBlacklisted(ctx, true)); + else if (ctx.Match("disable", "off", "remove", "allow")) + return ctx.Execute(null, m => m.SetCommandBlacklisted(ctx, false)); + else + return ctx.Execute(null, m => m.ShowCommandBlacklisted(ctx)); + } // todo: maybe add the list of configuration keys here? return ctx.Reply($"{Emojis.Error} Could not find a setting with that name. Please see `{ctx.DefaultPrefix}commands serverconfig` for the list of possible config settings."); diff --git a/PluralKit.Bot/Commands/ServerConfig.cs b/PluralKit.Bot/Commands/ServerConfig.cs index 0219c8c6..c3f53b59 100644 --- a/PluralKit.Bot/Commands/ServerConfig.cs +++ b/PluralKit.Bot/Commands/ServerConfig.cs @@ -77,6 +77,13 @@ public class ServerConfig ChannelListMessage(0, "proxy blacklist") )); + items.Add(new( + "command blacklist", + "Channels where running text commands is disabled", + ChannelListMessage(ctx.GuildConfig!.CommandBlacklist.Length, "command blacklist"), + ChannelListMessage(0, "command blacklist") + )); + await ctx.Paginate( items.ToAsyncEnumerable(), items.Count, @@ -210,7 +217,57 @@ public class ServerConfig } await ctx.Paginate(channels.ToAsyncEnumerable(), channels.Count, 25, - $"Blacklisted channels for {ctx.Guild.Name}", + $"Blacklisted channels for proxying in {ctx.Guild.Name}", + null, + async (eb, l) => + { + async Task CategoryName(ulong? id) => + id != null ? (await _cache.GetChannel(ctx.Guild.Id, id.Value)).Name : "(no category)"; + + ulong? lastCategory = null; + + var fieldValue = new StringBuilder(); + foreach (var channel in l) + { + if (lastCategory != channel!.ParentId && fieldValue.Length > 0) + { + eb.Field(new Embed.Field(await CategoryName(lastCategory), fieldValue.ToString())); + fieldValue.Clear(); + } + else + { + fieldValue.Append("\n"); + } + + fieldValue.Append(channel.Mention()); + lastCategory = channel.ParentId; + } + + eb.Field(new Embed.Field(await CategoryName(lastCategory), fieldValue.ToString())); + }); + } + + public async Task ShowCommandBlacklisted(Context ctx) + { + await ctx.CheckGuildContext().CheckAuthorPermission(PermissionSet.ManageGuild, "Manage Server"); + + var blacklist = await ctx.Repository.GetGuild(ctx.Guild.Id); + + // Resolve all channels from the cache and order by position + var channels = (await Task.WhenAll(blacklist.CommandBlacklist + .Select(id => _cache.TryGetChannel(ctx.Guild.Id, id)))) + .Where(c => c != null) + .OrderBy(c => c.Position) + .ToList(); + + if (channels.Count == 0) + { + await ctx.Reply("This server has no channels where running text commands is disabled."); + return; + } + + await ctx.Paginate(channels.ToAsyncEnumerable(), channels.Count, 25, + $"Blacklisted channels for running text commands in {ctx.Guild.Name}", null, async (eb, l) => { @@ -323,7 +380,40 @@ public class ServerConfig await ctx.Repository.UpdateGuild(ctx.Guild.Id, new GuildPatch { ProxyBlacklist = blacklist.ToArray() }); await ctx.Reply( - $"{Emojis.Success} Channels {(shouldAdd ? "added to" : "removed from")} the proxy blacklist."); + $"{Emojis.Success} Channel(s) {(shouldAdd ? "added to" : "removed from")} the proxy blacklist."); + } + + public async Task SetCommandBlacklisted(Context ctx, bool shouldAdd) + { + await ctx.CheckGuildContext().CheckAuthorPermission(PermissionSet.ManageGuild, "Manage Server"); + + var affectedChannels = new List(); + if (ctx.Match("all")) + affectedChannels = (await _cache.GetGuildChannels(ctx.Guild.Id)) + // All the channel types you can proxy in + .Where(x => DiscordUtils.IsValidGuildChannel(x)).ToList(); + else if (!ctx.HasNext()) throw new PKSyntaxError("You must pass one or more #channels."); + else + while (ctx.HasNext()) + { + var channelString = ctx.PeekArgument(); + var channel = await ctx.MatchChannel(); + if (channel == null || channel.GuildId != ctx.Guild.Id) throw Errors.ChannelNotFound(channelString); + affectedChannels.Add(channel); + } + + var guild = await ctx.Repository.GetGuild(ctx.Guild.Id); + + var blacklist = guild.CommandBlacklist.ToHashSet(); + if (shouldAdd) + blacklist.UnionWith(affectedChannels.Select(c => c.Id)); + else + blacklist.ExceptWith(affectedChannels.Select(c => c.Id)); + + await ctx.Repository.UpdateGuild(ctx.Guild.Id, new GuildPatch { CommandBlacklist = blacklist.ToArray() }); + + await ctx.Reply( + $"{Emojis.Success} Channel(s) {(shouldAdd ? "added to" : "removed from")} the command blacklist."); } public async Task SetLogBlacklisted(Context ctx, bool shouldAdd) diff --git a/PluralKit.Bot/Handlers/MessageCreated.cs b/PluralKit.Bot/Handlers/MessageCreated.cs index a14d024b..387d8659 100644 --- a/PluralKit.Bot/Handlers/MessageCreated.cs +++ b/PluralKit.Bot/Handlers/MessageCreated.cs @@ -140,7 +140,22 @@ public class MessageCreated: IEventHandler var config = system != null ? await _repo.GetSystemConfig(system.Id) : null; var guildConfig = guild != null ? await _repo.GetGuild(guild.Id) : null; - await _tree.ExecuteCommand(new Context(_services, shardId, guild, channel, evt, cmdStart, system, config, guildConfig, _config.Prefixes ?? BotConfig.DefaultPrefixes)); + var ctx = new Context(_services, shardId, guild, channel, evt, cmdStart, system, config, guildConfig, _config.Prefixes ?? BotConfig.DefaultPrefixes); + + // If we're in a guild we need to check if this channel is on the command blacklist + if (guild is not null) + { + // If we're in a thread we want to check the root channel and the thread + var rootChannel = channel.IsThread() ? await _rest.GetChannelOrNull(channel.ParentId!.Value) : channel; + var msgCtx = await _repo.GetMessageContext(evt.Author.Id, guild.Id, rootChannel.Id, channel.Id != rootChannel.Id ? channel.Id : default); + // If the channel is in the command blacklist, then check if author has Manage Server + // If they do, we let the command run regardless + // If they don't a PK Error gets thrown here and caught in the catch + if (msgCtx.InCommandBlacklist) + await ctx.CheckAuthorPermission(PermissionSet.ManageGuild, "Manage Server"); ; + } + + await _tree.ExecuteCommand(ctx); } catch (PKError) { diff --git a/PluralKit.Core/Database/Functions/MessageContext.cs b/PluralKit.Core/Database/Functions/MessageContext.cs index 876dabc2..99b6b94a 100644 --- a/PluralKit.Core/Database/Functions/MessageContext.cs +++ b/PluralKit.Core/Database/Functions/MessageContext.cs @@ -33,6 +33,7 @@ public class MessageContext public ulong? LogChannel { get; } public bool InProxyBlacklist { get; } + public bool InCommandBlacklist { get; } public bool InLogBlacklist { get; } public bool LogCleanupEnabled { get; } public bool RequireSystemTag { get; } diff --git a/PluralKit.Core/Database/Utils/DatabaseMigrator.cs b/PluralKit.Core/Database/Utils/DatabaseMigrator.cs index 1161cf31..9886c21a 100644 --- a/PluralKit.Core/Database/Utils/DatabaseMigrator.cs +++ b/PluralKit.Core/Database/Utils/DatabaseMigrator.cs @@ -9,7 +9,7 @@ namespace PluralKit.Core; internal class DatabaseMigrator { private const string RootPath = "PluralKit.Core.Database"; // "resource path" root for SQL files - private const int TargetSchemaVersion = 54; + private const int TargetSchemaVersion = 55; private readonly ILogger _logger; public DatabaseMigrator(ILogger logger) diff --git a/PluralKit.Core/Models/GuildConfig.cs b/PluralKit.Core/Models/GuildConfig.cs index 9e6cf6a6..71a4370b 100644 --- a/PluralKit.Core/Models/GuildConfig.cs +++ b/PluralKit.Core/Models/GuildConfig.cs @@ -6,6 +6,7 @@ public class GuildConfig public ulong? LogChannel { get; } public ulong[] LogBlacklist { get; } public ulong[] ProxyBlacklist { get; } + public ulong[] CommandBlacklist { get; } public bool LogCleanupEnabled { get; } public bool InvalidCommandResponseEnabled { get; } public bool RequireSystemTag { get; } diff --git a/PluralKit.Core/Models/Patch/GuildPatch.cs b/PluralKit.Core/Models/Patch/GuildPatch.cs index f814e909..9a54fc97 100644 --- a/PluralKit.Core/Models/Patch/GuildPatch.cs +++ b/PluralKit.Core/Models/Patch/GuildPatch.cs @@ -7,6 +7,7 @@ public class GuildPatch: PatchObject public Partial LogChannel { get; set; } public Partial LogBlacklist { get; set; } public Partial ProxyBlacklist { get; set; } + public Partial CommandBlacklist { get; set; } public Partial LogCleanupEnabled { get; set; } public Partial InvalidCommandResponseEnabled { get; set; } public Partial RequireSystemTag { get; set; } @@ -16,6 +17,7 @@ public class GuildPatch: PatchObject .With("log_channel", LogChannel) .With("log_blacklist", LogBlacklist) .With("proxy_blacklist", ProxyBlacklist) + .With("command_blacklist", CommandBlacklist) .With("log_cleanup_enabled", LogCleanupEnabled) .With("invalid_command_response_enabled", InvalidCommandResponseEnabled) .With("require_system_tag", RequireSystemTag) diff --git a/crates/migrate/data/functions.sql b/crates/migrate/data/functions.sql index 6ec03ee8..8e21d1b3 100644 --- a/crates/migrate/data/functions.sql +++ b/crates/migrate/data/functions.sql @@ -24,6 +24,7 @@ create function message_context(account_id bigint, guild_id bigint, channel_id b log_channel bigint, in_proxy_blacklist bool, + in_command_blacklist bool, in_log_blacklist bool, log_cleanup_enabled bool, require_system_tag bool, @@ -61,9 +62,11 @@ as $$ system_last_switch.timestamp as last_switch_timestamp, -- servers table - servers.log_channel as log_channel, + servers.log_channel as log_channel, ((channel_id = any (servers.proxy_blacklist)) or (thread_id = any (servers.proxy_blacklist))) as in_proxy_blacklist, + ((channel_id = any (servers.command_blacklist)) + or (thread_id = any (servers.command_blacklist))) as in_command_blacklist, ((channel_id = any (servers.log_blacklist)) or (thread_id = any (servers.log_blacklist))) as in_log_blacklist, coalesce(servers.log_cleanup_enabled, false) as log_cleanup_enabled, diff --git a/crates/migrate/data/migrations/55.sql b/crates/migrate/data/migrations/55.sql new file mode 100644 index 00000000..87e26416 --- /dev/null +++ b/crates/migrate/data/migrations/55.sql @@ -0,0 +1,6 @@ +-- database version 55 +-- add command blacklist option for servers + +alter table servers add column command_blacklist bigint[] not null default array[]::bigint[]; + +update info set schema_version = 55; \ No newline at end of file diff --git a/docs/content/command-list.md b/docs/content/command-list.md index 80abf707..dfd25a89 100644 --- a/docs/content/command-list.md +++ b/docs/content/command-list.md @@ -167,6 +167,9 @@ You can have a space after `pk;`, e.g. `pk;system` and `pk; system` will do the - `pk;serverconfig proxy blacklist` - Displays the current proxy blacklist - `pk;serverconfig proxy blacklist add all| [channel 2] [channel 3...]` - Adds certain channels to the proxy blacklist - `pk;serverconfig proxy blacklist remove all| [channel 2] [channel 3...]` - Removes certain channels from the proxy blacklist +- `pk;serverconfig command blacklist` - Displays the current command blacklist +- `pk;serverconfig command blacklist add all| [channel 2] [channel 3...]` - Adds certain channels to the command blacklist +- `pk;serverconfig command blacklist remove all| [channel 2] [channel 3...]` - Removes certain channels from the command blacklist ## Utility - `pk;message ` - Looks up information about a proxied message by its message ID or link. diff --git a/docs/content/staff/disabling.md b/docs/content/staff/disabling.md index c585ffc4..07c1110b 100644 --- a/docs/content/staff/disabling.md +++ b/docs/content/staff/disabling.md @@ -1,4 +1,7 @@ -# Disabling proxying in a channel +# Disabling bot functionality +You can use the blacklist commands to disable proxying or text commands in some channels of your server. [You can also disable PluralKit by taking away its permissions.](/staff/permissions) + +## Disabling proxying in a channel It's possible to block a channel from being used for proxying. To do so, use the `pk;serverconfig proxy blacklist` command. For example: pk;serverconfig proxy blacklist add #admin-channel #mod-channel #welcome @@ -6,4 +9,14 @@ It's possible to block a channel from being used for proxying. To do so, use the pk;serverconfig proxy blacklist remove #general-two pk;serverconfig proxy blacklist remove all -This requires you to have the *Manage Server* permission on the server. \ No newline at end of file +This requires you to have the *Manage Server* permission on the server. + +## Disabling commands in a channel +It's possible to block a channel from being used for text commands. To do so, use the `pk;serverconfig command blacklist` command. For example: + + pk;serverconfig command blacklist add #admin-channel #mod-channel #welcome + pk;serverconfig command blacklist add all + pk;serverconfig command blacklist remove #general-two + pk;serverconfig command blacklist remove all + +This requires you to have the *Manage Server* permission on the server. If you have the *Manage Server* permission on the server you **will not be affected by the command blacklist** and will always be able to run commands. \ No newline at end of file