feat(bot): add new guild settings command

This commit is contained in:
Iris System 2024-11-10 15:46:36 +13:00
parent f0436332c0
commit 0473bd8f01
15 changed files with 179 additions and 15 deletions

View file

@ -105,10 +105,12 @@ public partial class CommandTree
public static Command LogEnable = new Command("log enable", "log enable all|<channel> [channel 2] [channel 3...]", "Enables message logging in certain channels");
public static Command LogDisable = new Command("log disable", "log disable all|<channel> [channel 2] [channel 3...]", "Disables message logging in certain channels");
public static Command LogShow = new Command("log show", "log show", "Displays the current list of channels where logging is disabled");
public static Command LogClean = new Command("logclean", "logclean [on|off]", "Toggles whether to clean up other bots' log channels");
public static Command BlacklistShow = new Command("blacklist show", "blacklist show", "Displays the current proxy blacklist");
public static Command BlacklistAdd = new Command("blacklist add", "blacklist add all|<channel> [channel 2] [channel 3...]", "Adds certain channels to the proxy blacklist");
public static Command BlacklistRemove = new Command("blacklist remove", "blacklist remove all|<channel> [channel 2] [channel 3...]", "Removes certain channels from the proxy blacklist");
public static Command ServerConfigLogClean = new Command("serverconfig logclean", "serverconfig logclean [on|off]", "Toggles whether to clean up other bots' log channels");
public static Command ServerConfigInvalidCommandResponse = new Command("serverconfig invalidcommanderror", "serverconfig invalidcommanderror [on|off]", "Sets whether to show an error message when an unknown command is sent");
public static Command ServerConfigRequireSystemTag = new Command("serverconfig requiretag", "serverconfig requiretag [on|off]", "Sets whether server users are required to have a system tag on proxied messages");
public static Command Invite = new Command("invite", "invite", "Gets a link to invite PluralKit to other servers");
public static Command PermCheck = new Command("permcheck", "permcheck <guild>", "Checks whether a server's permission setup is correct");
public static Command Admin = new Command("admin", "admin", "Super secret admin commands (sshhhh)");
@ -151,6 +153,11 @@ public partial class CommandTree
ConfigProxySwitch, ConfigNameFormat
};
public static Command[] ServerConfigCommands =
{
ServerConfigLogClean, ServerConfigInvalidCommandResponse, ServerConfigRequireSystemTag
};
public static Command[] AutoproxyCommands =
{
AutoproxyOff, AutoproxyFront, AutoproxyLatch, AutoproxyMember

View file

@ -20,6 +20,8 @@ public partial class CommandTree
return HandleAutoproxyCommand(ctx);
if (ctx.Match("config", "cfg", "configure"))
return HandleConfigCommand(ctx);
if (ctx.Match("serverconfig", "guildconfig", "scfg"))
return HandleServerConfigCommand(ctx);
if (ctx.Match("list", "find", "members", "search", "query", "l", "f", "fd", "ls"))
return ctx.Execute<SystemList>(SystemList, m => m.MemberList(ctx, ctx.System));
if (ctx.Match("link"))
@ -65,7 +67,7 @@ public partial class CommandTree
return PrintCommandList(ctx, "message logging", LogCommands);
else return PrintCommandExpectedError(ctx, LogCommands);
if (ctx.Match("logclean"))
return ctx.Execute<ServerConfig>(LogClean, m => m.SetLogCleanup(ctx));
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.SetBlacklisted(ctx, true));
@ -108,6 +110,10 @@ public partial class CommandTree
if (ctx.Match("dashboard", "dash"))
return ctx.Execute<Help>(Dashboard, m => m.Dashboard(ctx));
// don't send an "invalid command" response if the guild has those turned off
if (ctx.GuildConfig != null && ctx.GuildConfig!.InvalidCommandResponseEnabled != true)
return Task.CompletedTask;
// remove compiler warning
return ctx.Reply(
$"{Emojis.Error} Unknown command {ctx.PeekArgument().AsCode()}. For a list of possible commands, see <https://pluralkit.me/commands>.");
@ -534,6 +540,11 @@ public partial class CommandTree
case "cfg":
await PrintCommandList(ctx, "settings", ConfigCommands);
break;
case "serverconfig":
case "guildconfig":
case "scfg":
await PrintCommandList(ctx, "server settings", ServerConfigCommands);
break;
case "autoproxy":
case "ap":
await PrintCommandList(ctx, "autoproxy", AutoproxyCommands);
@ -604,4 +615,20 @@ public partial class CommandTree
// todo: maybe add the list of configuration keys here?
return ctx.Reply($"{Emojis.Error} Could not find a setting with that name. Please see `pk;commands config` for the list of possible config settings.");
}
private Task HandleServerConfigCommand(Context ctx)
{
if (!ctx.HasNext())
return ctx.Execute<ServerConfig>(null, m => m.ShowConfig(ctx));
if (ctx.MatchMultiple(new[] { "log" }, new[] { "cleanup", "clean" }) || ctx.Match("logclean"))
return ctx.Execute<ServerConfig>(null, m => m.SetLogCleanup(ctx));
if (ctx.MatchMultiple(new[] { "invalid", "unknown" }, new[] { "command" }, new[] { "error", "response" }) || ctx.Match("invalidcommanderror", "unknowncommanderror"))
return ctx.Execute<ServerConfig>(null, m => m.InvalidCommandResponse(ctx));
if (ctx.MatchMultiple(new[] { "require", "enforce" }, new[] { "tag", "systemtag" }) || ctx.Match("requiretag", "enforcetag"))
return ctx.Execute<ServerConfig>(null, m => m.RequireSystemTag(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 `pk;commands serverconfig` for the list of possible config settings.");
}
}

View file

@ -29,11 +29,13 @@ public class Context
private Command? _currentCommand;
public Context(ILifetimeScope provider, int shardId, Guild? guild, Channel channel, MessageCreateEvent message,
int commandParseOffset, PKSystem senderSystem, SystemConfig config)
int commandParseOffset, PKSystem senderSystem, SystemConfig config,
GuildConfig? guildConfig)
{
Message = (Message)message;
ShardId = shardId;
Guild = guild;
GuildConfig = guildConfig;
Channel = channel;
System = senderSystem;
Config = config;
@ -59,6 +61,7 @@ public class Context
public readonly Message Message;
public readonly Guild Guild;
public readonly GuildConfig? GuildConfig;
public readonly int ShardId;
public readonly Cluster Cluster;

View file

@ -18,6 +18,70 @@ public class ServerConfig
_cache = cache;
}
private record PaginatedConfigItem(string Key, string Description, string? CurrentValue, string DefaultValue);
private string EnabledDisabled(bool value) => value ? "enabled" : "disabled";
public async Task ShowConfig(Context ctx)
{
await ctx.CheckGuildContext().CheckAuthorPermission(PermissionSet.ManageGuild, "Manage Server");
var items = new List<PaginatedConfigItem>();
// TODO: move log channel / blacklist into here
items.Add(new(
"log cleanup",
"Whether to clean up other bots' log channels",
EnabledDisabled(ctx.GuildConfig!.LogCleanupEnabled),
"disabled"
));
items.Add(new(
"invalid command error",
"Whether to show an error message when an unknown command is sent",
EnabledDisabled(ctx.GuildConfig!.InvalidCommandResponseEnabled),
"enabled"
));
items.Add(new(
"require tag",
"Whether server users are required to have a system tag on proxied messages",
EnabledDisabled(ctx.GuildConfig!.RequireSystemTag),
"disabled"
));
await ctx.Paginate<PaginatedConfigItem>(
items.ToAsyncEnumerable(),
items.Count,
10,
"Current settings for this server",
null,
(eb, l) =>
{
var description = new StringBuilder();
foreach (var item in l)
{
description.Append(item.Key.AsCode());
description.Append($" **({item.CurrentValue ?? item.DefaultValue})**");
if (item.CurrentValue != null && item.CurrentValue != item.DefaultValue)
description.Append("\ud83d\udd39");
description.AppendLine();
description.Append(item.Description);
description.AppendLine();
description.AppendLine();
}
eb.Description(description.ToString());
// using *large* blue diamond here since it's easier to see in the small footer
eb.Footer(new("\U0001f537 means this setting was changed. Type `pk;serverconfig <setting name> clear` to reset it to the default."));
return Task.CompletedTask;
}
);
}
public async Task SetLogChannel(Context ctx)
{
await ctx.CheckGuildContext().CheckAuthorPermission(PermissionSet.ManageGuild, "Manage Server");
@ -246,20 +310,16 @@ public class ServerConfig
}
await ctx.CheckGuildContext().CheckAuthorPermission(PermissionSet.ManageGuild, "Manage Server");
var guild = await ctx.Repository.GetGuild(ctx.Guild.Id);
bool? newValue = ctx.MatchToggleOrNull();
if (newValue == null)
{
var guildCfg = await ctx.Repository.GetGuild(ctx.Guild.Id);
if (guildCfg.LogCleanupEnabled)
if (ctx.GuildConfig!.LogCleanupEnabled)
eb.Description(
"Log cleanup is currently **on** for this server. To disable it, type `pk;logclean off`.");
"Log cleanup is currently **on** for this server. To disable it, type `pk;serverconfig logclean off`.");
else
eb.Description(
"Log cleanup is currently **off** for this server. To enable it, type `pk;logclean on`.");
"Log cleanup is currently **off** for this server. To enable it, type `pk;serverconfig logclean on`.");
await ctx.Reply(embed: eb.Build());
return;
}
@ -272,4 +332,36 @@ public class ServerConfig
else
await ctx.Reply($"{Emojis.Success} Log cleanup has been **disabled** for this server.");
}
public async Task InvalidCommandResponse(Context ctx)
{
await ctx.CheckGuildContext().CheckAuthorPermission(PermissionSet.ManageGuild, "Manage Server");
if (!ctx.HasNext())
{
var msg = $"Error responses for unknown/invalid commands are currently **{EnabledDisabled(ctx.GuildConfig!.InvalidCommandResponseEnabled)}**.";
await ctx.Reply(msg);
return;
}
var newVal = ctx.MatchToggle(false);
await ctx.Repository.UpdateGuild(ctx.Guild.Id, new() { InvalidCommandResponseEnabled = newVal });
await ctx.Reply($"Error responses for unknown/invalid commands are now {EnabledDisabled(newVal)}.");
}
public async Task RequireSystemTag(Context ctx)
{
await ctx.CheckGuildContext().CheckAuthorPermission(PermissionSet.ManageGuild, "Manage Server");
if (!ctx.HasNext())
{
var msg = $"System tags are currently **{(ctx.GuildConfig!.RequireSystemTag ? "required" : "not required")}** for PluralKit users in this server.";
await ctx.Reply(msg);
return;
}
var newVal = ctx.MatchToggle(false);
await ctx.Repository.UpdateGuild(ctx.Guild.Id, new() { RequireSystemTag = newVal });
await ctx.Reply($"System tags are now **{(newVal ? "required" : "not required")}** for PluralKit users in this server.");
}
}

View file

@ -129,7 +129,9 @@ public class MessageCreated: IEventHandler<MessageCreateEvent>
{
var system = await _repo.GetSystemByAccount(evt.Author.Id);
var config = system != null ? await _repo.GetSystemConfig(system.Id) : null;
await _tree.ExecuteCommand(new Context(_services, shardId, guild, channel, evt, cmdStart, system, config));
var guildConfig = guild != null ? await _repo.GetGuild(guild.Id) : null;
await _tree.ExecuteCommand(new Context(_services, shardId, guild, channel, evt, cmdStart, system, config, guildConfig));
}
catch (PKError)
{

View file

@ -123,6 +123,15 @@ public class ProxyService
return "PluralKit cannot proxy messages over 2000 characters in length.";
}
if (ctx.RequireSystemTag)
{
var tag = ctx.SystemGuildTag ?? ctx.SystemTag;
if (tag == null)
return "This server requires PluralKit users to have a system tag, but you do not have one set.";
if (!ctx.TagEnabled)
return "This server requires PluralKit users to have a system tag, but your system tag is disabled in this server.";
}
var guild = await _cache.GetGuild(channel.GuildId.Value);
var fileSizeLimit = guild.FileSizeLimit();
var bytesThreshold = fileSizeLimit * 1024 * 1024;

View file

@ -18,6 +18,7 @@ public class MessageContext
public bool InBlacklist { get; }
public bool InLogBlacklist { get; }
public bool LogCleanupEnabled { get; }
public bool RequireSystemTag { get; }
public bool ProxyEnabled { get; }
public SwitchId? LastSwitch { get; }
public MemberId[] LastSwitchMembers { get; } = new MemberId[0];

View file

@ -25,6 +25,7 @@ create function message_context(account_id bigint, guild_id bigint, channel_id b
in_blacklist bool,
in_log_blacklist bool,
log_cleanup_enabled bool,
require_system_tag bool,
deny_bot_usage bool
)
@ -63,6 +64,7 @@ as $$
((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,
coalesce(servers.require_system_tag, false) as require_system_tag,
-- abuse_logs table
coalesce(abuse_logs.deny_bot_usage, false) as deny_bot_usage

View file

@ -0,0 +1,9 @@
-- database version 48
--
-- add guild settings for disabling "invalid command" responses &
-- enforcing the presence of system tags
alter table servers add column invalid_command_response_enabled bool not null default true;
alter table servers add column require_system_tag bool not null default false;
update info set schema_version = 48;

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

View file

@ -7,4 +7,6 @@ public class GuildConfig
public ulong[] LogBlacklist { get; }
public ulong[] Blacklist { get; }
public bool LogCleanupEnabled { get; }
public bool InvalidCommandResponseEnabled { get; }
public bool RequireSystemTag { get; }
}

View file

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

View file

@ -159,9 +159,11 @@ You can have a space after `pk;`, e.g. `pk;system` and `pk; system` will do the
- `pk;log disable <#channel> [#channel...]` - Disables logging messages posted in the given channel(s) (useful for staff channels and such).
- `pk;log enable <#channel> [#channel...]` - Re-enables logging messages posted in the given channel(s).
- `pk;log show` - Displays the current list of channels where logging is disabled.
- `pk;logclean <on|off>` - Enables or disables [log cleanup](/staff/compatibility/#log-cleanup).
- `pk;blacklist add <#channel> [#channel...]` - Adds the given channel(s) to the proxy blacklist (proxying will be disabled here)
- `pk;blacklist remove <#channel> [#channel...]` - Removes the given channel(s) from the proxy blacklist.
- `pk;serverconfig logclean [on|off]` - Enables or disables [log cleanup](/staff/compatibility/#log-cleanup).
- `pk;serverconfig invalidcommanderror [on|off]` - Sets whether to show an error message when an unknown command is sent
- `pk;serverconfig requiretag [on|off]` - Sets whether server users are required to have a system tag on proxied messages
## Utility
- `pk;message <message id|message link|reply>` - Looks up information about a proxied message by its message ID or link.

View file

@ -15,7 +15,7 @@ If your server uses an in-house bot for logging, you can use [the API](/api) to
Another solution is for PluralKit to automatically delete log messages from other bots when they get posted.
PluralKit supports this through the **log cleanup** feature. To enable it, use the following command:
pk;logclean on
pk;serverconfig logclean on
This requires you to have the *Manage Server* permission on the server.

View file

@ -28,7 +28,11 @@ You can also do the reverse operation by passing a Discord account ID (or a @men
Both commands output a system card, which includes a linked account list. These commands also work in PluralKit's DMs.
### System tags
A common rule on servers with PluralKit is to enforce system tags. System tags are a little snippet of text, a symbol, an emoji, etc, that's added to the webhook name of every message proxied by a system. A system tag will allow you to identify members that share a system at a glance. Note that this isn't enforced by the bot; this is simply a suggestion for a helpful server policy :slightly_smiling_face:
A common rule on servers with PluralKit is to enforce system tags. System tags are a little snippet of text, a symbol, an emoji, etc, that's added to the webhook name of every message proxied by a system. A system tag will allow you to identify members that share a system at a glance.
You can enforce system tags for all PluralKit users in your server with the following command:
pk;serverconfig requiretag on
## Blocking users
It's not possible to block specific PluralKit users. Discord webhooks don't count as 'real accounts', so there's no way to block them. PluralKit also can't control who gets to see a message, so there's also no way to implement user blocking on the bot's end. Sorry. :slightly_frowning_face: