From 3eece261fdba5cc0a55aa2004eeaa3ba57099490 Mon Sep 17 00:00:00 2001 From: dusk Date: Fri, 4 Apr 2025 06:14:17 +0900 Subject: [PATCH] feat: implement system privacy commands (yay system edit done) --- PluralKit.Bot/CommandMeta/CommandTree.cs | 5 +- .../CommandSystem/Context/ContextFlagsExt.cs | 2 +- .../Context/ContextParametersExt.cs | 10 +- PluralKit.Bot/CommandSystem/Parameters.cs | 16 ++- PluralKit.Bot/Commands/SystemEdit.cs | 117 ++++++++---------- crates/command_definitions/src/system.rs | 18 ++- crates/command_parser/src/parameter.rs | 54 ++++++++ crates/commands/src/bin/write_cs_glue.rs | 4 +- crates/commands/src/commands.udl | 1 + crates/commands/src/lib.rs | 2 + 10 files changed, 154 insertions(+), 75 deletions(-) diff --git a/PluralKit.Bot/CommandMeta/CommandTree.cs b/PluralKit.Bot/CommandMeta/CommandTree.cs index 17258e55..63539cca 100644 --- a/PluralKit.Bot/CommandMeta/CommandTree.cs +++ b/PluralKit.Bot/CommandMeta/CommandTree.cs @@ -100,6 +100,9 @@ public partial class CommandTree 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)), + Commands.SystemShowPrivacy(var param, _) => ctx.Execute(SystemPrivacy, m => m.ShowSystemPrivacy(ctx, ctx.System)), + Commands.SystemChangePrivacyAll(var param, _) => ctx.Execute(SystemPrivacy, m => m.ChangeSystemPrivacyAll(ctx, ctx.System, param.level)), + Commands.SystemChangePrivacy(var param, _) => ctx.Execute(SystemPrivacy, m => m.ChangeSystemPrivacy(ctx, ctx.System, param.privacy, param.level)), _ => // this should only ever occur when deving if commands are not implemented... ctx.Reply( @@ -363,8 +366,6 @@ public partial class CommandTree await ctx.CheckSystem(target).Execute(SystemFrontPercent, m => m.FrontPercent(ctx, system: target)); else if (ctx.Match("groups", "gs")) await ctx.CheckSystem(target).Execute(GroupList, g => g.ListSystemGroups(ctx, target)); - else if (ctx.Match("privacy")) - await ctx.CheckSystem(target).Execute(SystemPrivacy, m => m.SystemPrivacy(ctx, target)); else if (ctx.Match("id")) await ctx.CheckSystem(target).Execute(SystemId, m => m.DisplayId(ctx, target)); else if (ctx.Match("random", "rand", "r")) diff --git a/PluralKit.Bot/CommandSystem/Context/ContextFlagsExt.cs b/PluralKit.Bot/CommandSystem/Context/ContextFlagsExt.cs index 564a5f30..cf636322 100644 --- a/PluralKit.Bot/CommandSystem/Context/ContextFlagsExt.cs +++ b/PluralKit.Bot/CommandSystem/Context/ContextFlagsExt.cs @@ -36,7 +36,7 @@ public static class ContextFlagsExt ); } - public static async Task FlagResolvePrivacyLevel(this Context ctx, string param_name) + public static async Task FlagResolvePrivacyLevel(this Context ctx, string param_name) { return await ctx.Parameters.ResolveFlag( ctx, param_name, diff --git a/PluralKit.Bot/CommandSystem/Context/ContextParametersExt.cs b/PluralKit.Bot/CommandSystem/Context/ContextParametersExt.cs index fd67ad76..45efb250 100644 --- a/PluralKit.Bot/CommandSystem/Context/ContextParametersExt.cs +++ b/PluralKit.Bot/CommandSystem/Context/ContextParametersExt.cs @@ -36,7 +36,15 @@ public static class ContextParametersExt ); } - public static async Task ParamResolvePrivacyLevel(this Context ctx, string param_name) + public static async Task ParamResolveSystemPrivacyTarget(this Context ctx, string param_name) + { + return await ctx.Parameters.ResolveParameter( + ctx, param_name, + param => (param as Parameter.SystemPrivacyTarget)?.target + ); + } + + public static async Task ParamResolvePrivacyLevel(this Context ctx, string param_name) { return await ctx.Parameters.ResolveParameter( ctx, param_name, diff --git a/PluralKit.Bot/CommandSystem/Parameters.cs b/PluralKit.Bot/CommandSystem/Parameters.cs index 6846938e..28656ce6 100644 --- a/PluralKit.Bot/CommandSystem/Parameters.cs +++ b/PluralKit.Bot/CommandSystem/Parameters.cs @@ -1,4 +1,3 @@ -using System.Diagnostics; using Myriad.Types; using PluralKit.Core; using uniffi.commands; @@ -12,7 +11,8 @@ public abstract record 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 SystemPrivacyTarget(SystemPrivacySubject target): Parameter; + public record PrivacyLevel(Core.PrivacyLevel level): Parameter; public record Toggle(bool value): Parameter; public record Opaque(string value): Parameter; public record Avatar(ParsedImage avatar): Parameter; @@ -72,12 +72,16 @@ public class Parameters ); case uniffi.commands.Parameter.MemberPrivacyTarget memberPrivacyTarget: // this should never really fail... - // todo: we shouldn't have *three* different MemberPrivacyTarget types (rust, ffi, c#) syncing the cases will be annoying... - if (!MemberPrivacyUtils.TryParseMemberPrivacy(memberPrivacyTarget.target, out var target)) + if (!MemberPrivacyUtils.TryParseMemberPrivacy(memberPrivacyTarget.target, out var memberPrivacy)) throw new PKError($"Invalid member privacy target {memberPrivacyTarget.target}"); - return new Parameter.MemberPrivacyTarget(target); + return new Parameter.MemberPrivacyTarget(memberPrivacy); + case uniffi.commands.Parameter.SystemPrivacyTarget systemPrivacyTarget: + // this should never really fail... + if (!SystemPrivacyUtils.TryParseSystemPrivacy(systemPrivacyTarget.target, out var systemPrivacy)) + throw new PKError($"Invalid system privacy target {systemPrivacyTarget.target}"); + return new Parameter.SystemPrivacyTarget(systemPrivacy); case uniffi.commands.Parameter.PrivacyLevel privacyLevel: - return new Parameter.PrivacyLevel(privacyLevel.level); + return new Parameter.PrivacyLevel(privacyLevel.level == "public" ? PrivacyLevel.Public : privacyLevel.level == "private" ? PrivacyLevel.Private : throw new PKError($"Invalid privacy level {privacyLevel.level}")); case uniffi.commands.Parameter.Toggle toggle: return new Parameter.Toggle(toggle.toggle); case uniffi.commands.Parameter.OpaqueString opaque: diff --git a/PluralKit.Bot/Commands/SystemEdit.cs b/PluralKit.Bot/Commands/SystemEdit.cs index f162ee9b..3e72def3 100644 --- a/PluralKit.Bot/Commands/SystemEdit.cs +++ b/PluralKit.Bot/Commands/SystemEdit.cs @@ -874,79 +874,72 @@ public class SystemEdit $"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) + public async Task ShowSystemPrivacy(Context ctx, PKSystem target) { ctx.CheckSystem().CheckOwnSystem(target); - Task PrintEmbed() + var eb = new EmbedBuilder() + .Title("Current privacy settings for your system") + .Field(new Embed.Field("Name", target.NamePrivacy.Explanation())) + .Field(new Embed.Field("Avatar", target.AvatarPrivacy.Explanation())) + .Field(new Embed.Field("Description", target.DescriptionPrivacy.Explanation())) + .Field(new Embed.Field("Banner", target.BannerPrivacy.Explanation())) + .Field(new Embed.Field("Pronouns", target.PronounPrivacy.Explanation())) + .Field(new Embed.Field("Member list", target.MemberListPrivacy.Explanation())) + .Field(new Embed.Field("Group list", target.GroupListPrivacy.Explanation())) + .Field(new Embed.Field("Current fronter(s)", target.FrontPrivacy.Explanation())) + .Field(new Embed.Field("Front/switch history", target.FrontHistoryPrivacy.Explanation())) + .Description( + $"To edit privacy settings, use the command:\n`{ctx.DefaultPrefix}system privacy `\n\n- `subject` is one of `name`, `avatar`, `description`, `banner`, `pronouns`, `list`, `front`, `fronthistory`, `groups`, or `all` \n- `level` is either `public` or `private`."); + await ctx.Reply(embed: eb.Build()); + } + + public async Task ChangeSystemPrivacy(Context ctx, PKSystem target, SystemPrivacySubject subject, PrivacyLevel level) + { + ctx.CheckSystem().CheckOwnSystem(target); + + await ctx.Repository.UpdateSystem(target.Id, new SystemPatch().WithPrivacy(subject, level)); + + var levelExplanation = level switch { - var eb = new EmbedBuilder() - .Title("Current privacy settings for your system") - .Field(new Embed.Field("Name", target.NamePrivacy.Explanation())) - .Field(new Embed.Field("Avatar", target.AvatarPrivacy.Explanation())) - .Field(new Embed.Field("Description", target.DescriptionPrivacy.Explanation())) - .Field(new Embed.Field("Banner", target.BannerPrivacy.Explanation())) - .Field(new Embed.Field("Pronouns", target.PronounPrivacy.Explanation())) - .Field(new Embed.Field("Member list", target.MemberListPrivacy.Explanation())) - .Field(new Embed.Field("Group list", target.GroupListPrivacy.Explanation())) - .Field(new Embed.Field("Current fronter(s)", target.FrontPrivacy.Explanation())) - .Field(new Embed.Field("Front/switch history", target.FrontHistoryPrivacy.Explanation())) - .Description( - $"To edit privacy settings, use the command:\n`{ctx.DefaultPrefix}system privacy `\n\n- `subject` is one of `name`, `avatar`, `description`, `banner`, `pronouns`, `list`, `front`, `fronthistory`, `groups`, or `all` \n- `level` is either `public` or `private`."); - return ctx.Reply(embed: eb.Build()); - } + PrivacyLevel.Public => "be able to query", + PrivacyLevel.Private => "*not* be able to query", + _ => "" + }; - async Task SetLevel(SystemPrivacySubject subject, PrivacyLevel level) + var subjectStr = subject switch { - await ctx.Repository.UpdateSystem(target.Id, new SystemPatch().WithPrivacy(subject, level)); + SystemPrivacySubject.Name => "name", + SystemPrivacySubject.Avatar => "avatar", + SystemPrivacySubject.Description => "description", + SystemPrivacySubject.Banner => "banner", + SystemPrivacySubject.Pronouns => "pronouns", + SystemPrivacySubject.Front => "front", + SystemPrivacySubject.FrontHistory => "front history", + SystemPrivacySubject.MemberList => "member list", + SystemPrivacySubject.GroupList => "group list", + _ => "" + }; - var levelExplanation = level switch - { - PrivacyLevel.Public => "be able to query", - PrivacyLevel.Private => "*not* be able to query", - _ => "" - }; + var msg = $"System {subjectStr} privacy has been set to **{level.LevelName()}**. Other accounts will now {levelExplanation} your system {subjectStr}."; + await ctx.Reply($"{Emojis.Success} {msg}"); + } - var subjectStr = subject switch - { - SystemPrivacySubject.Name => "name", - SystemPrivacySubject.Avatar => "avatar", - SystemPrivacySubject.Description => "description", - SystemPrivacySubject.Banner => "banner", - SystemPrivacySubject.Pronouns => "pronouns", - SystemPrivacySubject.Front => "front", - SystemPrivacySubject.FrontHistory => "front history", - SystemPrivacySubject.MemberList => "member list", - SystemPrivacySubject.GroupList => "group list", - _ => "" - }; + public async Task ChangeSystemPrivacyAll(Context ctx, PKSystem target, PrivacyLevel level) + { + ctx.CheckSystem().CheckOwnSystem(target); - var msg = - $"System {subjectStr} privacy has been set to **{level.LevelName()}**. Other accounts will now {levelExplanation} your system {subjectStr}."; - await ctx.Reply($"{Emojis.Success} {msg}"); - } + await ctx.Repository.UpdateSystem(target.Id, new SystemPatch().WithAllPrivacy(level)); - async Task SetAll(PrivacyLevel level) + var msg = level switch { - await ctx.Repository.UpdateSystem(target.Id, new SystemPatch().WithAllPrivacy(level)); + PrivacyLevel.Private => + $"All system privacy settings have been set to **{level.LevelName()}**. Other accounts will now not be able to view your member list, group list, front history, or system description.", + PrivacyLevel.Public => + $"All system privacy settings have been set to **{level.LevelName()}**. Other accounts will now be able to view everything.", + _ => "" + }; - var msg = level switch - { - PrivacyLevel.Private => - $"All system privacy settings have been set to **{level.LevelName()}**. Other accounts will now not be able to view your member list, group list, front history, or system description.", - PrivacyLevel.Public => - $"All system privacy settings have been set to **{level.LevelName()}**. Other accounts will now be able to view everything.", - _ => "" - }; - - await ctx.Reply($"{Emojis.Success} {msg}"); - } - - if (!ctx.HasNext()) - await PrintEmbed(); - else if (ctx.Match("all")) - await SetAll(ctx.PopPrivacyLevel()); - else - await SetLevel(ctx.PopSystemPrivacySubject(), ctx.PopPrivacyLevel()); + await ctx.Reply($"{Emojis.Success} {msg}"); } } \ No newline at end of file diff --git a/crates/command_definitions/src/system.rs b/crates/command_definitions/src/system.rs index 59b26ecc..a4fe0037 100644 --- a/crates/command_definitions/src/system.rs +++ b/crates/command_definitions/src/system.rs @@ -202,12 +202,25 @@ pub fn edit() -> impl Iterator { 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 => "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 => "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(); + + let system_privacy = tokens!(system, ("privacy", ["priv"])); + let system_privacy_cmd = [ + command!(system_privacy => "system_show_privacy") + .help("Shows your system's privacy settings"), + command!(system_privacy, ("all", ["a"]), ("level", PrivacyLevel) => "system_change_privacy_all") + .help("Changes all privacy settings for your system"), + command!(system_privacy, ("privacy", SystemPrivacyTarget), ("level", PrivacyLevel) => "system_change_privacy") + .help("Changes a specific privacy setting for your system"), ].into_iter(); system_new_cmd @@ -222,6 +235,7 @@ pub fn edit() -> impl Iterator { .chain(system_server_avatar_self_cmd) .chain(system_banner_self_cmd) .chain(system_delete) + .chain(system_privacy_cmd) .chain(system_proxy_cmd) .chain(system_name_cmd) .chain(system_server_name_cmd) diff --git a/crates/command_parser/src/parameter.rs b/crates/command_parser/src/parameter.rs index dde6b535..282cfdc8 100644 --- a/crates/command_parser/src/parameter.rs +++ b/crates/command_parser/src/parameter.rs @@ -14,6 +14,7 @@ pub enum ParameterValue { SystemRef(String), GuildRef(String), MemberPrivacyTarget(String), + SystemPrivacyTarget(String), PrivacyLevel(String), Toggle(bool), Avatar(String), @@ -45,6 +46,7 @@ impl Display for Parameter { ParameterKind::SystemRef => write!(f, ""), ParameterKind::GuildRef => write!(f, ""), ParameterKind::MemberPrivacyTarget => write!(f, ""), + ParameterKind::SystemPrivacyTarget => write!(f, ""), ParameterKind::PrivacyLevel => write!(f, "[privacy level]"), ParameterKind::Toggle => write!(f, "on/off"), ParameterKind::Avatar => write!(f, ""), @@ -78,6 +80,7 @@ pub enum ParameterKind { SystemRef, GuildRef, MemberPrivacyTarget, + SystemPrivacyTarget, PrivacyLevel, Toggle, Avatar, @@ -92,6 +95,7 @@ impl ParameterKind { ParameterKind::SystemRef => "target", ParameterKind::GuildRef => "target", ParameterKind::MemberPrivacyTarget => "member_privacy_target", + ParameterKind::SystemPrivacyTarget => "system_privacy_target", ParameterKind::PrivacyLevel => "privacy_level", ParameterKind::Toggle => "toggle", ParameterKind::Avatar => "avatar", @@ -112,6 +116,9 @@ impl ParameterKind { ParameterKind::SystemRef => Ok(ParameterValue::SystemRef(input.into())), ParameterKind::MemberPrivacyTarget => MemberPrivacyTargetKind::from_str(input) .map(|target| ParameterValue::MemberPrivacyTarget(target.as_ref().into())), + ParameterKind::SystemPrivacyTarget => SystemPrivacyTargetKind::from_str(input).map( + |target| ParameterValue::SystemPrivacyTarget(target.as_ref().into()), + ), ParameterKind::PrivacyLevel => PrivacyLevelKind::from_str(input) .map(|level| ParameterValue::PrivacyLevel(level.as_ref().into())), ParameterKind::Toggle => { @@ -176,6 +183,53 @@ impl FromStr for MemberPrivacyTargetKind { } } +pub enum SystemPrivacyTargetKind { + Name, + Avatar, + Description, + Banner, + Pronouns, + MemberList, + GroupList, + Front, + FrontHistory, +} + +impl AsRef for SystemPrivacyTargetKind { + fn as_ref(&self) -> &str { + match self { + Self::Name => "name", + Self::Avatar => "avatar", + Self::Description => "description", + Self::Banner => "banner", + Self::Pronouns => "pronouns", + Self::MemberList => "members", + Self::GroupList => "groups", + Self::Front => "front", + Self::FrontHistory => "fronthistory", + } + } +} + +impl FromStr for SystemPrivacyTargetKind { + type Err = SmolStr; + + fn from_str(s: &str) -> Result { + match s.to_lowercase().as_str() { + "name" => Ok(Self::Name), + "avatar" | "pfp" | "pic" | "icon" => Ok(Self::Avatar), + "description" | "desc" | "bio" | "info" => Ok(Self::Description), + "banner" | "splash" | "cover" => Ok(Self::Banner), + "pronouns" | "prns" | "pn" => Ok(Self::Pronouns), + "members" | "memberlist" | "list" => Ok(Self::MemberList), + "groups" | "gs" => Ok(Self::GroupList), + "front" | "fronter" | "fronters" => Ok(Self::Front), + "fronthistory" | "fh" | "switches" => Ok(Self::FrontHistory), + _ => Err("invalid system privacy target".into()), + } + } +} + pub enum PrivacyLevelKind { Public, Private, diff --git a/crates/commands/src/bin/write_cs_glue.rs b/crates/commands/src/bin/write_cs_glue.rs index dda4e1b3..dbee8d80 100644 --- a/crates/commands/src/bin/write_cs_glue.rs +++ b/crates/commands/src/bin/write_cs_glue.rs @@ -169,7 +169,8 @@ fn get_param_ty(kind: ParameterKind) -> &'static str { ParameterKind::MemberRef => "PKMember", ParameterKind::SystemRef => "PKSystem", ParameterKind::MemberPrivacyTarget => "MemberPrivacySubject", - ParameterKind::PrivacyLevel => "string", + ParameterKind::SystemPrivacyTarget => "SystemPrivacySubject", + ParameterKind::PrivacyLevel => "PrivacyLevel", ParameterKind::Toggle => "bool", ParameterKind::Avatar => "ParsedImage", ParameterKind::GuildRef => "Guild", @@ -182,6 +183,7 @@ fn get_param_param_ty(kind: ParameterKind) -> &'static str { ParameterKind::MemberRef => "Member", ParameterKind::SystemRef => "System", ParameterKind::MemberPrivacyTarget => "MemberPrivacyTarget", + ParameterKind::SystemPrivacyTarget => "SystemPrivacyTarget", ParameterKind::PrivacyLevel => "PrivacyLevel", ParameterKind::Toggle => "Toggle", ParameterKind::Avatar => "Avatar", diff --git a/crates/commands/src/commands.udl b/crates/commands/src/commands.udl index 1b9f9ff1..5a368266 100644 --- a/crates/commands/src/commands.udl +++ b/crates/commands/src/commands.udl @@ -12,6 +12,7 @@ interface Parameter { SystemRef(string system); GuildRef(string guild); MemberPrivacyTarget(string target); + SystemPrivacyTarget(string target); PrivacyLevel(string level); OpaqueString(string raw); Toggle(boolean toggle); diff --git a/crates/commands/src/lib.rs b/crates/commands/src/lib.rs index 0cbd975a..92ca7e4f 100644 --- a/crates/commands/src/lib.rs +++ b/crates/commands/src/lib.rs @@ -26,6 +26,7 @@ pub enum Parameter { SystemRef { system: String }, GuildRef { guild: String }, MemberPrivacyTarget { target: String }, + SystemPrivacyTarget { target: String }, PrivacyLevel { level: String }, OpaqueString { raw: String }, Toggle { toggle: bool }, @@ -38,6 +39,7 @@ impl From for Parameter { ParameterValue::MemberRef(member) => Self::MemberRef { member }, ParameterValue::SystemRef(system) => Self::SystemRef { system }, ParameterValue::MemberPrivacyTarget(target) => Self::MemberPrivacyTarget { target }, + ParameterValue::SystemPrivacyTarget(target) => Self::SystemPrivacyTarget { target }, ParameterValue::PrivacyLevel(level) => Self::PrivacyLevel { level }, ParameterValue::OpaqueString(raw) => Self::OpaqueString { raw }, ParameterValue::Toggle(toggle) => Self::Toggle { toggle },