This commit is contained in:
Petal Ladenson 2026-01-19 12:29:08 +00:00 committed by GitHub
commit 5fcbd1397f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 182 additions and 29 deletions

View file

@ -106,9 +106,12 @@ public partial class CommandTree
public static Command LogEnable = new Command("serverconfig log blacklist remove", "serverconfig log blacklist remove all|<channel> [channel 2] [channel 3...]", "Enables message logging in certain channels");
public static Command LogDisable = new Command("serverconfig log blacklist add", "serverconfig log blacklist add all|<channel> [channel 2] [channel 3...]", "Disables message logging in certain channels");
public static Command LogShow = new Command("serverconfig log blacklist", "serverconfig log blacklist", "Displays the current list of channels where logging is disabled");
public static Command BlacklistShow = new Command("serverconfig proxy blacklist", "serverconfig proxy blacklist", "Displays the current list of channels where message proxying is disabled");
public static Command BlacklistAdd = new Command("serverconfig proxy blacklist add", "serverconfig proxy blacklist add all|<channel> [channel 2] [channel 3...]", "Disables message proxying in certain channels");
public static Command BlacklistRemove = new Command("serverconfig proxy blacklist remove", "serverconfig proxy blacklist remove all|<channel> [channel 2] [channel 3...]", "Enables message proxying in certain channels");
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> [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> [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> [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> [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,
BlacklistShow, BlacklistAdd, BlacklistRemove
ProxyBlacklistShow, ProxyBlacklistAdd, ProxyBlacklistRemove,
CommandBlacklistShow, CommandBlacklistAdd, CommandBlacklistRemove
};
public static Command[] AutoproxyCommands =
@ -170,5 +174,5 @@ public partial class CommandTree
public static Command[] LogCommands = { LogChannel, LogChannelClear, LogEnable, LogDisable, LogShow };
public static Command[] BlacklistCommands = { BlacklistAdd, BlacklistRemove, BlacklistShow };
public static Command[] ProxyBlacklistCommands = { ProxyBlacklistAdd, ProxyBlacklistRemove, ProxyBlacklistShow };
}

View file

@ -69,11 +69,11 @@ public partial class CommandTree
return ctx.Execute<ServerConfig>(ServerConfigLogClean, m => m.SetLogCleanup(ctx), true);
if (ctx.Match("blacklist", "bl"))
if (ctx.Match("enable", "on", "add", "deny"))
return ctx.Execute<ServerConfig>(BlacklistAdd, m => m.SetProxyBlacklisted(ctx, true), true);
return ctx.Execute<ServerConfig>(ProxyBlacklistAdd, m => m.SetProxyBlacklisted(ctx, true), true);
else if (ctx.Match("disable", "off", "remove", "allow"))
return ctx.Execute<ServerConfig>(BlacklistRemove, m => m.SetProxyBlacklisted(ctx, false), true);
return ctx.Execute<ServerConfig>(ProxyBlacklistRemove, m => m.SetProxyBlacklisted(ctx, false), true);
else if (ctx.Match("list", "show"))
return ctx.Execute<ServerConfig>(BlacklistShow, m => m.ShowProxyBlacklisted(ctx), true);
return ctx.Execute<ServerConfig>(ProxyBlacklistShow, m => m.ShowProxyBlacklisted(ctx), true);
else
return ctx.Reply($"{Emojis.Warn} Blacklist commands have moved to `{ctx.DefaultPrefix}serverconfig`.");
if (ctx.Match("proxy"))
@ -530,7 +530,7 @@ public partial class CommandTree
break;
case "blacklist":
case "bl":
await PrintCommandList(ctx, "channel blacklisting", BlacklistCommands);
await PrintCommandList(ctx, "channel proxy blacklisting", ProxyBlacklistCommands);
break;
case "config":
case "cfg":
@ -642,6 +642,15 @@ public partial class CommandTree
else
return ctx.Execute<ServerConfig>(null, m => m.ShowProxyBlacklisted(ctx));
}
if (ctx.MatchMultiple(new[] { "command" }, new[] { "blacklist" }))
{
if (ctx.Match("enable", "on", "add", "deny"))
return ctx.Execute<ServerConfig>(null, m => m.SetCommandBlacklisted(ctx, true));
else if (ctx.Match("disable", "off", "remove", "allow"))
return ctx.Execute<ServerConfig>(null, m => m.SetCommandBlacklisted(ctx, false));
else
return ctx.Execute<ServerConfig>(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.");

View file

@ -73,10 +73,17 @@ public class ServerConfig
items.Add(new(
"proxy blacklist",
"Channels where message proxying is disabled",
ChannelListMessage(ctx.GuildConfig!.Blacklist.Length, "proxy blacklist"),
ChannelListMessage(ctx.GuildConfig!.ProxyBlacklist.Length, "proxy blacklist"),
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<PaginatedConfigItem>(
items.ToAsyncEnumerable(),
items.Count,
@ -197,7 +204,7 @@ public class ServerConfig
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.Blacklist
var channels = (await Task.WhenAll(blacklist.ProxyBlacklist
.Select(id => _cache.TryGetChannel(ctx.Guild.Id, id))))
.Where(c => c != null)
.OrderBy(c => c.Position)
@ -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<string> 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) =>
{
@ -314,16 +371,49 @@ public class ServerConfig
var guild = await ctx.Repository.GetGuild(ctx.Guild.Id);
var blacklist = guild.Blacklist.ToHashSet();
var blacklist = guild.ProxyBlacklist.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 { Blacklist = blacklist.ToArray() });
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<Channel>();
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)

View file

@ -140,7 +140,22 @@ public class MessageCreated: IEventHandler<MessageCreateEvent>
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)
{

View file

@ -184,7 +184,7 @@ public class ProxyService
throw new ProxyChecksFailedException("This message is the initial message in a forum post, which PluralKit is unable to proxy correctly.");
// Make sure proxying is enabled here
if (ctx.InBlacklist)
if (ctx.InProxyBlacklist)
throw new ProxyChecksFailedException(
"Proxying was disabled in this channel by a server administrator (via the proxy blacklist).");
@ -290,7 +290,7 @@ public class ProxyService
await _repo.GetMessageContext(msg.Sender, msg.Guild!.Value, rootChannel.Id, msg.Channel);
// Make sure proxying is enabled here
if (ctx.InBlacklist)
if (ctx.InProxyBlacklist)
throw new ProxyChecksFailedException(
"Proxying was disabled in this channel by a server administrator (via the proxy blacklist).");

View file

@ -32,7 +32,8 @@ public class MessageContext
public Instant? LastSwitchTimestamp { get; }
public ulong? LogChannel { get; }
public bool InBlacklist { get; }
public bool InProxyBlacklist { get; }
public bool InCommandBlacklist { get; }
public bool InLogBlacklist { get; }
public bool LogCleanupEnabled { get; }
public bool RequireSystemTag { get; }

View file

@ -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 = 52;
private const int TargetSchemaVersion = 55;
private readonly ILogger _logger;
public DatabaseMigrator(ILogger logger)

View file

@ -5,7 +5,8 @@ public class GuildConfig
public ulong Id { get; }
public ulong? LogChannel { get; }
public ulong[] LogBlacklist { get; }
public ulong[] Blacklist { get; }
public ulong[] ProxyBlacklist { get; }
public ulong[] CommandBlacklist { get; }
public bool LogCleanupEnabled { get; }
public bool InvalidCommandResponseEnabled { get; }
public bool RequireSystemTag { get; }

View file

@ -6,7 +6,8 @@ public class GuildPatch: PatchObject
{
public Partial<ulong?> LogChannel { get; set; }
public Partial<ulong[]> LogBlacklist { get; set; }
public Partial<ulong[]> Blacklist { get; set; }
public Partial<ulong[]> ProxyBlacklist { get; set; }
public Partial<ulong[]> CommandBlacklist { get; set; }
public Partial<bool> LogCleanupEnabled { get; set; }
public Partial<bool> InvalidCommandResponseEnabled { get; set; }
public Partial<bool> RequireSystemTag { get; set; }
@ -15,7 +16,8 @@ public class GuildPatch: PatchObject
public override Query Apply(Query q) => q.ApplyPatch(wrapper => wrapper
.With("log_channel", LogChannel)
.With("log_blacklist", LogBlacklist)
.With("blacklist", Blacklist)
.With("proxy_blacklist", ProxyBlacklist)
.With("command_blacklist", CommandBlacklist)
.With("log_cleanup_enabled", LogCleanupEnabled)
.With("invalid_command_response_enabled", InvalidCommandResponseEnabled)
.With("require_system_tag", RequireSystemTag)

View file

@ -23,7 +23,8 @@ create function message_context(account_id bigint, guild_id bigint, channel_id b
last_switch_timestamp timestamp,
log_channel bigint,
in_blacklist bool,
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,
((channel_id = any (servers.blacklist))
or (thread_id = any (servers.blacklist))) as in_blacklist,
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,

View file

@ -0,0 +1,6 @@
-- database version 54
-- rename blacklist column to proxy blacklist
alter table servers rename column blacklist to proxy_blacklist;
update info set schema_version = 54;

View file

@ -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;

View file

@ -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> [channel 2] [channel 3...]` - Adds certain channels to the proxy blacklist
- `pk;serverconfig proxy blacklist remove all|<channel> [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> [channel 2] [channel 3...]` - Adds certain channels to the command blacklist
- `pk;serverconfig command blacklist remove all|<channel> [channel 2] [channel 3...]` - Removes certain channels from the command blacklist
## Utility
- `pk;message <message id|message link|reply>` - Looks up information about a proxied message by its message ID or link.

View file

@ -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.
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.