From cb0a9eaf9fc390dc7bbb951251bddeadd8572c7d Mon Sep 17 00:00:00 2001 From: dusk Date: Fri, 4 Apr 2025 05:24:09 +0900 Subject: [PATCH] feat: implement system proxy commands --- PluralKit.Bot/CommandMeta/CommandTree.cs | 8 ++-- .../Context/ContextEntityArgumentsExt.cs | 8 ++++ .../Context/ContextParametersExt.cs | 8 ++++ PluralKit.Bot/CommandSystem/Parameters.cs | 3 ++ PluralKit.Bot/Commands/SystemEdit.cs | 48 +++++++++++-------- crates/command_definitions/src/system.rs | 11 +++++ crates/command_parser/src/parameter.rs | 9 ++++ crates/command_parser/src/token.rs | 12 +++-- crates/commands/src/bin/write_cs_glue.rs | 12 ++++- crates/commands/src/commands.udl | 1 + crates/commands/src/lib.rs | 2 + 11 files changed, 93 insertions(+), 29 deletions(-) diff --git a/PluralKit.Bot/CommandMeta/CommandTree.cs b/PluralKit.Bot/CommandMeta/CommandTree.cs index 38e49001..17258e55 100644 --- a/PluralKit.Bot/CommandMeta/CommandTree.cs +++ b/PluralKit.Bot/CommandMeta/CommandTree.cs @@ -96,6 +96,10 @@ public partial class CommandTree Commands.SystemClearBanner(var param, var flags) => ctx.Execute(SystemBannerImage, m => m.ClearBannerImage(ctx, ctx.System, flags.yes)), Commands.SystemChangeBanner(var param, _) => ctx.Execute(SystemBannerImage, m => m.ChangeBannerImage(ctx, ctx.System, param.banner)), Commands.SystemDelete(_, var flags) => ctx.Execute(SystemDelete, m => m.Delete(ctx, ctx.System, flags.no_export)), + Commands.SystemShowProxyCurrent(_, _) => ctx.Execute(SystemProxy, m => m.ShowSystemProxy(ctx, ctx.Guild)), + Commands.SystemShowProxy(var param, _) => ctx.Execute(SystemProxy, m => m.ShowSystemProxy(ctx, param.target)), + Commands.SystemToggleProxyCurrent(var param, _) => ctx.Execute(SystemProxy, m => m.ToggleSystemProxy(ctx, ctx.Guild, param.toggle)), + Commands.SystemToggleProxy(var param, _) => ctx.Execute(SystemProxy, m => m.ToggleSystemProxy(ctx, param.target, param.toggle)), _ => // this should only ever occur when deving if commands are not implemented... ctx.Reply( @@ -167,8 +171,6 @@ public partial class CommandTree if (ctx.Match("proxy")) if (ctx.Match("debug")) return ctx.Execute(ProxyCheck, m => m.MessageProxyCheck(ctx)); - else - return ctx.Execute(SystemProxy, m => m.SystemProxy(ctx)); if (ctx.Match("invite")) return ctx.Execute(Invite, m => m.Invite(ctx)); if (ctx.Match("mn")) return ctx.Execute(null, m => m.Mn(ctx)); if (ctx.Match("fire")) return ctx.Execute(null, m => m.Fire(ctx)); @@ -295,8 +297,6 @@ public partial class CommandTree // todo: these aren't deprecated but also shouldn't be here else if (ctx.Match("webhook", "hook")) await ctx.Execute(null, m => m.SystemWebhook(ctx)); - else if (ctx.Match("proxy")) - await ctx.Execute(SystemProxy, m => m.SystemProxy(ctx)); // finally, parse commands that *can* take a system target else diff --git a/PluralKit.Bot/CommandSystem/Context/ContextEntityArgumentsExt.cs b/PluralKit.Bot/CommandSystem/Context/ContextEntityArgumentsExt.cs index 6eba5039..b1cddf17 100644 --- a/PluralKit.Bot/CommandSystem/Context/ContextEntityArgumentsExt.cs +++ b/PluralKit.Bot/CommandSystem/Context/ContextEntityArgumentsExt.cs @@ -213,6 +213,14 @@ public static class ContextEntityArgumentsExt return channel; } + public static async Task ParseGuild(this Context ctx, string input) + { + if (!ulong.TryParse(input, out var id)) + return null; + + return await ctx.Rest.GetGuildOrNull(id); + } + public static async Task MatchGuild(this Context ctx) { if (!ulong.TryParse(ctx.PeekArgument(), out var id)) diff --git a/PluralKit.Bot/CommandSystem/Context/ContextParametersExt.cs b/PluralKit.Bot/CommandSystem/Context/ContextParametersExt.cs index cf2fa6c7..fd67ad76 100644 --- a/PluralKit.Bot/CommandSystem/Context/ContextParametersExt.cs +++ b/PluralKit.Bot/CommandSystem/Context/ContextParametersExt.cs @@ -59,4 +59,12 @@ public static class ContextParametersExt param => (param as Parameter.Avatar)?.avatar ); } + + public static async Task ParamResolveGuild(this Context ctx, string param_name) + { + return await ctx.Parameters.ResolveParameter( + ctx, param_name, + param => (param as Parameter.GuildRef)?.guild + ); + } } \ No newline at end of file diff --git a/PluralKit.Bot/CommandSystem/Parameters.cs b/PluralKit.Bot/CommandSystem/Parameters.cs index 48aaa583..6846938e 100644 --- a/PluralKit.Bot/CommandSystem/Parameters.cs +++ b/PluralKit.Bot/CommandSystem/Parameters.cs @@ -10,6 +10,7 @@ public abstract record Parameter() { public record MemberRef(PKMember member): Parameter; public record SystemRef(PKSystem system): Parameter; + public record GuildRef(Guild guild): Parameter; public record MemberPrivacyTarget(MemberPrivacySubject target): Parameter; public record PrivacyLevel(string level): Parameter; public record Toggle(bool value): Parameter; @@ -83,6 +84,8 @@ public class Parameters return new Parameter.Opaque(opaque.raw); case uniffi.commands.Parameter.Avatar avatar: return new Parameter.Avatar(await ctx.GetUserPfp(avatar.avatar) ?? ctx.ParseImage(avatar.avatar)); + case uniffi.commands.Parameter.GuildRef guildRef: + return new Parameter.GuildRef(await ctx.ParseGuild(guildRef.guild) ?? throw new PKError($"Guild {guildRef.guild} not found")); } return null; } diff --git a/PluralKit.Bot/Commands/SystemEdit.cs b/PluralKit.Bot/Commands/SystemEdit.cs index 8fe73c77..f162ee9b 100644 --- a/PluralKit.Bot/Commands/SystemEdit.cs +++ b/PluralKit.Bot/Commands/SystemEdit.cs @@ -830,11 +830,32 @@ public class SystemEdit await ctx.Repository.DeleteSystem(target.Id); } - public async Task SystemProxy(Context ctx) + public async Task ToggleSystemProxy(Context ctx, Guild guildArg, bool newValue) { ctx.CheckSystem(); - var guild = await ctx.MatchGuild() ?? ctx.Guild ?? + var guild = guildArg ?? + throw new PKError("You must run this command in a server or pass a server ID."); + + string serverText; + if (guild.Id == ctx.Guild?.Id) + serverText = $"this server ({guild.Name.EscapeMarkdown()})"; + else + serverText = $"the server {guild.Name.EscapeMarkdown()}"; + + await ctx.Repository.UpdateSystemGuild(ctx.System.Id, guild.Id, new SystemGuildPatch { ProxyEnabled = newValue }); + + if (newValue) + await ctx.Reply($"Message proxying in {serverText} is now **enabled** for your system."); + else + await ctx.Reply($"Message proxying in {serverText} is now **disabled** for your system."); + } + + public async Task ShowSystemProxy(Context ctx, Guild guildArg) + { + ctx.CheckSystem(); + + var guild = guildArg ?? throw new PKError("You must run this command in a server or pass a server ID."); var gs = await ctx.Repository.GetSystemGuild(guild.Id, ctx.System.Id); @@ -845,25 +866,12 @@ public class SystemEdit else serverText = $"the server {guild.Name.EscapeMarkdown()}"; - if (!ctx.HasNext()) - { - if (gs.ProxyEnabled) - await ctx.Reply( - $"Proxying in {serverText} is currently **enabled** for your system. To disable it, type `{ctx.DefaultPrefix}system proxy off`."); - else - await ctx.Reply( - $"Proxying in {serverText} is currently **disabled** for your system. To enable it, type `{ctx.DefaultPrefix}system proxy on`."); - return; - } - - var newValue = ctx.MatchToggle(); - - await ctx.Repository.UpdateSystemGuild(ctx.System.Id, guild.Id, new SystemGuildPatch { ProxyEnabled = newValue }); - - if (newValue) - await ctx.Reply($"Message proxying in {serverText} is now **enabled** for your system."); + if (gs.ProxyEnabled) + await ctx.Reply( + $"Proxying in {serverText} is currently **enabled** for your system. To disable it, type `{ctx.DefaultPrefix}system proxy off`."); else - await ctx.Reply($"Message proxying in {serverText} is now **disabled** for your system."); + await ctx.Reply( + $"Proxying in {serverText} is currently **disabled** for your system. To enable it, type `{ctx.DefaultPrefix}system proxy on`."); } public async Task SystemPrivacy(Context ctx, PKSystem target) diff --git a/crates/command_definitions/src/system.rs b/crates/command_definitions/src/system.rs index 83750af8..59b26ecc 100644 --- a/crates/command_definitions/src/system.rs +++ b/crates/command_definitions/src/system.rs @@ -200,6 +200,16 @@ pub fn edit() -> impl Iterator { .help("Deletes the system"), ); + let system_proxy = tokens!(system, "proxy"); + let system_proxy_cmd = [ + command!(system_proxy => "system_show_proxy_current").help("Shows your system's proxy setting for the guild you are in"), + command!(system_proxy, Toggle => "system_toggle_proxy_current") + .help("Toggle your system's proxy for the guild you are in"), + command!(system_proxy, GuildRef => "system_show_proxy").help("Shows your system's proxy setting for a guild"), + command!(system_proxy, GuildRef, Toggle => "system_toggle_proxy") + .help("Toggle your system's proxy for a guild"), + ].into_iter(); + system_new_cmd .chain(system_name_self_cmd) .chain(system_server_name_self_cmd) @@ -212,6 +222,7 @@ pub fn edit() -> impl Iterator { .chain(system_server_avatar_self_cmd) .chain(system_banner_self_cmd) .chain(system_delete) + .chain(system_proxy_cmd) .chain(system_name_cmd) .chain(system_server_name_cmd) .chain(system_description_cmd) diff --git a/crates/command_parser/src/parameter.rs b/crates/command_parser/src/parameter.rs index 14e79437..dde6b535 100644 --- a/crates/command_parser/src/parameter.rs +++ b/crates/command_parser/src/parameter.rs @@ -12,6 +12,7 @@ pub enum ParameterValue { OpaqueString(String), MemberRef(String), SystemRef(String), + GuildRef(String), MemberPrivacyTarget(String), PrivacyLevel(String), Toggle(bool), @@ -42,6 +43,7 @@ impl Display for Parameter { } ParameterKind::MemberRef => write!(f, ""), ParameterKind::SystemRef => write!(f, ""), + ParameterKind::GuildRef => write!(f, ""), ParameterKind::MemberPrivacyTarget => write!(f, ""), ParameterKind::PrivacyLevel => write!(f, "[privacy level]"), ParameterKind::Toggle => write!(f, "on/off"), @@ -74,6 +76,7 @@ pub enum ParameterKind { OpaqueStringRemainder, MemberRef, SystemRef, + GuildRef, MemberPrivacyTarget, PrivacyLevel, Toggle, @@ -87,6 +90,7 @@ impl ParameterKind { ParameterKind::OpaqueStringRemainder => "string", ParameterKind::MemberRef => "target", ParameterKind::SystemRef => "target", + ParameterKind::GuildRef => "target", ParameterKind::MemberPrivacyTarget => "member_privacy_target", ParameterKind::PrivacyLevel => "privacy_level", ParameterKind::Toggle => "toggle", @@ -114,8 +118,13 @@ impl ParameterKind { Toggle::from_str(input).map(|t| ParameterValue::Toggle(t.into())) } ParameterKind::Avatar => Ok(ParameterValue::Avatar(input.into())), + ParameterKind::GuildRef => Ok(ParameterValue::GuildRef(input.into())), } } + + pub(crate) fn skip_if_cant_match(&self) -> bool { + matches!(self, ParameterKind::Toggle) + } } pub enum MemberPrivacyTargetKind { diff --git a/crates/command_parser/src/token.rs b/crates/command_parser/src/token.rs index 996ee017..863cb936 100644 --- a/crates/command_parser/src/token.rs +++ b/crates/command_parser/src/token.rs @@ -67,9 +67,15 @@ impl Token { name: param.name().into(), value: matched, }, - Err(err) => TokenMatchResult::ParameterMatchError { - input: input.into(), - msg: err, + Err(err) => { + if param.kind().skip_if_cant_match() { + return None; + } else { + TokenMatchResult::ParameterMatchError { + input: input.into(), + msg: err, + } + } }, }), // don't add a _ match here! diff --git a/crates/commands/src/bin/write_cs_glue.rs b/crates/commands/src/bin/write_cs_glue.rs index cd69b3b1..dda4e1b3 100644 --- a/crates/commands/src/bin/write_cs_glue.rs +++ b/crates/commands/src/bin/write_cs_glue.rs @@ -1,7 +1,8 @@ use std::{env, fmt::Write, fs, path::PathBuf, str::FromStr}; use command_parser::{ - command, parameter::{Parameter, ParameterKind}, token::Token + parameter::{Parameter, ParameterKind}, + token::Token, }; fn main() -> Result<(), Box> { @@ -16,6 +17,7 @@ fn main() -> Result<(), Box> { writeln!(&mut glue, "#nullable enable\n")?; writeln!(&mut glue, "using PluralKit.Core;\n")?; + writeln!(&mut glue, "using Myriad.Types;")?; writeln!(&mut glue, "namespace PluralKit.Bot;\n")?; let mut record_fields = String::new(); @@ -114,7 +116,11 @@ fn main() -> Result<(), Box> { } } let mut command_reply_format = String::new(); - if command.flags.iter().any(|flag| flag.get_name() == "plaintext") { + if command + .flags + .iter() + .any(|flag| flag.get_name() == "plaintext") + { writeln!( &mut command_reply_format, r#"if (plaintext) return ReplyFormat.Plaintext;"#, @@ -166,6 +172,7 @@ fn get_param_ty(kind: ParameterKind) -> &'static str { ParameterKind::PrivacyLevel => "string", ParameterKind::Toggle => "bool", ParameterKind::Avatar => "ParsedImage", + ParameterKind::GuildRef => "Guild", } } @@ -178,6 +185,7 @@ fn get_param_param_ty(kind: ParameterKind) -> &'static str { ParameterKind::PrivacyLevel => "PrivacyLevel", ParameterKind::Toggle => "Toggle", ParameterKind::Avatar => "Avatar", + ParameterKind::GuildRef => "Guild", } } diff --git a/crates/commands/src/commands.udl b/crates/commands/src/commands.udl index 5ab50c63..1b9f9ff1 100644 --- a/crates/commands/src/commands.udl +++ b/crates/commands/src/commands.udl @@ -10,6 +10,7 @@ interface CommandResult { interface Parameter { MemberRef(string member); SystemRef(string system); + GuildRef(string guild); MemberPrivacyTarget(string target); PrivacyLevel(string level); OpaqueString(string raw); diff --git a/crates/commands/src/lib.rs b/crates/commands/src/lib.rs index 0af12856..0cbd975a 100644 --- a/crates/commands/src/lib.rs +++ b/crates/commands/src/lib.rs @@ -24,6 +24,7 @@ pub enum CommandResult { pub enum Parameter { MemberRef { member: String }, SystemRef { system: String }, + GuildRef { guild: String }, MemberPrivacyTarget { target: String }, PrivacyLevel { level: String }, OpaqueString { raw: String }, @@ -41,6 +42,7 @@ impl From for Parameter { ParameterValue::OpaqueString(raw) => Self::OpaqueString { raw }, ParameterValue::Toggle(toggle) => Self::Toggle { toggle }, ParameterValue::Avatar(avatar) => Self::Avatar { avatar }, + ParameterValue::GuildRef(guild) => Self::GuildRef { guild }, } } }