diff --git a/PluralKit.Bot/CommandMeta/CommandTree.cs b/PluralKit.Bot/CommandMeta/CommandTree.cs index 71b6fd71..f62d24b4 100644 --- a/PluralKit.Bot/CommandMeta/CommandTree.cs +++ b/PluralKit.Bot/CommandMeta/CommandTree.cs @@ -24,6 +24,16 @@ public partial class CommandTree Commands.CfgApTimeoutUpdate(CfgApTimeoutUpdateParams param, _) => ctx.Execute(null, m => m.EditAutoproxyTimeout(ctx, param.timeout)), Commands.FunThunder => ctx.Execute(null, m => m.Thunder(ctx)), Commands.FunMeow => ctx.Execute(null, m => m.Meow(ctx)), + Commands.SystemInfo(SystemInfoParams param, SystemInfoFlags flags) => ctx.Execute(SystemInfo, m => m.Query(ctx, param.target, flags.all, flags.@public, flags.@private)), + Commands.SystemInfoSelf(_, SystemInfoSelfFlags flags) => ctx.Execute(SystemInfo, m => m.Query(ctx, ctx.System, flags.all, flags.@public, flags.@private)), + Commands.SystemNew(SystemNewParams param, _) => ctx.Execute(SystemNew, m => m.New(ctx, null)), + Commands.SystemNewName(SystemNewNameParams param, _) => ctx.Execute(SystemNew, m => m.New(ctx, param.name)), + Commands.SystemShowName(SystemShowNameParams param, SystemShowNameFlags flags) => ctx.Execute(SystemRename, m => m.ShowName(ctx, param.target, flags.GetReplyFormat())), + Commands.SystemRename(SystemRenameParams param, _) => ctx.Execute(SystemRename, m => m.Rename(ctx, param.target, param.name)), + Commands.SystemClearName(SystemClearNameParams param, _) => ctx.Execute(SystemRename, m => m.ClearName(ctx, param.target)), + Commands.SystemShowServerName(SystemShowServerNameParams param, SystemShowServerNameFlags flags) => ctx.Execute(SystemServerName, m => m.ShowServerName(ctx, param.target, flags.GetReplyFormat())), + Commands.SystemClearServerName(SystemClearServerNameParams param, _) => ctx.Execute(SystemServerName, m => m.ClearServerName(ctx, param.target)), + Commands.SystemRenameServerName(SystemRenameServerNameParams param, _) => ctx.Execute(SystemServerName, m => m.RenameServerName(ctx, param.target, param.name)), _ => // this should only ever occur when deving if commands are not implemented... ctx.Reply( @@ -217,10 +227,7 @@ public partial class CommandTree private async Task HandleSystemCommand(Context ctx) { - // these commands never take a system target - if (ctx.Match("new", "create", "make", "add", "register", "init", "n")) - await ctx.Execute(SystemNew, m => m.New(ctx)); - else if (ctx.Match("commands", "help")) + if (ctx.Match("commands", "help")) await PrintCommandList(ctx, "systems", SystemCommands); // todo: these aren't deprecated but also shouldn't be here @@ -275,12 +282,7 @@ public partial class CommandTree private async Task HandleSystemCommandTargeted(Context ctx, PKSystem target) { - if (ctx.Match("name", "rename", "changename", "rn")) - await ctx.CheckSystem(target).Execute(SystemRename, m => m.Name(ctx, target)); - else if (ctx.Match("servername", "sn", "sname", "snick", "snickname", "servernick", "servernickname", - "serverdisplayname", "guildname", "guildnick", "guildnickname", "serverdn")) - await ctx.Execute(SystemServerName, m => m.ServerName(ctx, target)); - else if (ctx.Match("tag", "t")) + if (ctx.Match("tag", "t")) await ctx.CheckSystem(target).Execute(SystemTag, m => m.Tag(ctx, target)); else if (ctx.Match("servertag", "st", "stag", "deer")) await ctx.CheckSystem(target).Execute(SystemServerTag, m => m.ServerTag(ctx, target)); @@ -314,8 +316,6 @@ public partial class CommandTree await ctx.CheckSystem(target).Execute(SystemFrontHistory, m => m.SystemFrontHistory(ctx, target)); else if (ctx.Match("fp", "frontpercent", "front%", "frontbreakdown")) await ctx.CheckSystem(target).Execute(SystemFrontPercent, m => m.FrontPercent(ctx, system: target)); - else if (ctx.Match("info", "view", "show")) - await ctx.CheckSystem(target).Execute(SystemInfo, m => m.Query(ctx, target)); else if (ctx.Match("groups", "gs")) await ctx.CheckSystem(target).Execute(GroupList, g => g.ListSystemGroups(ctx, target)); else if (ctx.Match("privacy")) diff --git a/PluralKit.Bot/CommandSystem/Context/Context.cs b/PluralKit.Bot/CommandSystem/Context/Context.cs index ad075cde..69c695da 100644 --- a/PluralKit.Bot/CommandSystem/Context/Context.cs +++ b/PluralKit.Bot/CommandSystem/Context/Context.cs @@ -149,10 +149,11 @@ public class Context public LookupContext DirectLookupContextFor(SystemId systemId) => System?.Id == systemId ? LookupContext.ByOwner : LookupContext.ByNonOwner; - public LookupContext LookupContextFor(SystemId systemId) + public LookupContext LookupContextFor(SystemId systemId, bool? _hasPrivateOverride = null, bool? _hasPublicOverride = null) { - var hasPrivateOverride = Parameters.HasFlag("private", "priv"); - var hasPublicOverride = Parameters.HasFlag("public", "pub"); + // TODO(yusdacra): these should be passed as a parameter to this method all the way from command tree + bool hasPrivateOverride = _hasPrivateOverride ?? Parameters.HasFlag("private", "priv"); + bool hasPublicOverride = _hasPublicOverride ?? Parameters.HasFlag("public", "pub"); if (hasPrivateOverride && hasPublicOverride) throw new PKError("Cannot match both public and private flags at the same time."); diff --git a/PluralKit.Bot/CommandSystem/Parameters.cs b/PluralKit.Bot/CommandSystem/Parameters.cs index 263367cd..d4aa2e79 100644 --- a/PluralKit.Bot/CommandSystem/Parameters.cs +++ b/PluralKit.Bot/CommandSystem/Parameters.cs @@ -111,8 +111,7 @@ public class Parameters // todo: i think this should return null for everything...? if (param == null) return default; return extract_func(param) - // this should never really happen (hopefully!), but in case the parameter names dont match up (typos...) between rust <-> c#... - // (it would be very cool to have this statically checked somehow..?) + // this should never happen unless codegen somehow uses a wrong name ?? throw new PKError($"Flag {flag_name.AsCode()} was not found or did not have a value defined for command {Callback().AsCode()} -- this is a bug!!"); } @@ -122,8 +121,7 @@ public class Parameters // todo: i think this should return null for everything...? if (param == null) return default; return extract_func(param) - // this should never really happen (hopefully!), but in case the parameter names dont match up (typos...) between rust <-> c#... - // (it would be very cool to have this statically checked somehow..?) + // this should never happen unless codegen somehow uses a wrong name ?? throw new PKError($"Parameter {param_name.AsCode()} was not found for command {Callback().AsCode()} -- this is a bug!!"); } } \ No newline at end of file diff --git a/PluralKit.Bot/Commands/System.cs b/PluralKit.Bot/Commands/System.cs index 8d4026d2..3c1cadde 100644 --- a/PluralKit.Bot/Commands/System.cs +++ b/PluralKit.Bot/Commands/System.cs @@ -14,18 +14,17 @@ public class System _embeds = embeds; } - public async Task Query(Context ctx, PKSystem system) + public async Task Query(Context ctx, PKSystem system, bool all, bool @public, bool @private) { if (system == null) throw Errors.NoSystemError(ctx.DefaultPrefix); - await ctx.Reply(embed: await _embeds.CreateSystemEmbed(ctx, system, ctx.LookupContextFor(system.Id))); + await ctx.Reply(embed: await _embeds.CreateSystemEmbed(ctx, system, ctx.LookupContextFor(system.Id, @private, @public), all)); } - public async Task New(Context ctx) + public async Task New(Context ctx, string? systemName) { ctx.CheckNoSystem(); - var systemName = ctx.RemainderOrNull(); if (systemName != null && systemName.Length > Limits.MaxSystemNameLength) throw Errors.StringTooLongError("System name", systemName.Length, Limits.MaxSystemNameLength); diff --git a/PluralKit.Bot/Commands/SystemEdit.cs b/PluralKit.Bot/Commands/SystemEdit.cs index 7df7efff..b9e2c6f1 100644 --- a/PluralKit.Bot/Commands/SystemEdit.cs +++ b/PluralKit.Bot/Commands/SystemEdit.cs @@ -29,7 +29,7 @@ public class SystemEdit _avatarHosting = avatarHosting; } - public async Task Name(Context ctx, PKSystem target) + public async Task ShowName(Context ctx, PKSystem target, ReplyFormat format) { ctx.CheckSystemPrivacy(target.Id, target.NamePrivacy); var isOwnSystem = target.Id == ctx.System?.Id; @@ -38,15 +38,11 @@ public class SystemEdit if (isOwnSystem) noNameSetMessage += $" Type `{ctx.DefaultPrefix}system name ` to set one."; - var format = ctx.MatchFormat(); - - // if there's nothing next or what's next is "raw"/"plaintext" we're doing a query, so check for null - if (!ctx.HasNext(false) || format != ReplyFormat.Standard) - if (target.Name == null) - { - await ctx.Reply(noNameSetMessage); - return; - } + if (target.Name == null) + { + await ctx.Reply(noNameSetMessage); + return; + } if (format == ReplyFormat.Raw) { @@ -61,37 +57,40 @@ public class SystemEdit return; } - if (!ctx.HasNext(false)) - { - await ctx.Reply( - $"{(isOwnSystem ? "Your" : "This")} system's name is currently **{target.Name}**." - + (isOwnSystem ? $" Type `{ctx.DefaultPrefix}system name -clear` to clear it." - + $" Using {target.Name.Length}/{Limits.MaxSystemNameLength} characters." : "")); - return; - } + await ctx.Reply( + $"{(isOwnSystem ? "Your" : "This")} system's name is currently **{target.Name}**." + + (isOwnSystem ? $" Type `{ctx.DefaultPrefix}system name -clear` to clear it." + + $" Using {target.Name.Length}/{Limits.MaxSystemNameLength} characters." : "")); + return; + } + public async Task ClearName(Context ctx, PKSystem target) + { + ctx.CheckSystemPrivacy(target.Id, target.NamePrivacy); ctx.CheckSystem().CheckOwnSystem(target); - if (ctx.MatchClear() && await ctx.ConfirmClear("your system's name")) + if (await ctx.ConfirmClear("your system's name")) { await ctx.Repository.UpdateSystem(target.Id, new SystemPatch { Name = null }); await ctx.Reply($"{Emojis.Success} System name cleared."); } - else - { - var newSystemName = ctx.RemainderOrNull(false).NormalizeLineEndSpacing(); - - if (newSystemName.Length > Limits.MaxSystemNameLength) - throw Errors.StringTooLongError("System name", newSystemName.Length, Limits.MaxSystemNameLength); - - await ctx.Repository.UpdateSystem(target.Id, new SystemPatch { Name = newSystemName }); - - await ctx.Reply($"{Emojis.Success} System name changed (using {newSystemName.Length}/{Limits.MaxSystemNameLength} characters)."); - } } - public async Task ServerName(Context ctx, PKSystem target) + public async Task Rename(Context ctx, PKSystem target, string newSystemName) + { + ctx.CheckSystemPrivacy(target.Id, target.NamePrivacy); + ctx.CheckSystem().CheckOwnSystem(target); + + if (newSystemName.Length > Limits.MaxSystemNameLength) + throw Errors.StringTooLongError("System name", newSystemName.Length, Limits.MaxSystemNameLength); + + await ctx.Repository.UpdateSystem(target.Id, new SystemPatch { Name = newSystemName }); + + await ctx.Reply($"{Emojis.Success} System name changed (using {newSystemName.Length}/{Limits.MaxSystemNameLength} characters)."); + } + + public async Task ShowServerName(Context ctx, PKSystem target, ReplyFormat format) { ctx.CheckGuildContext(); @@ -103,15 +102,11 @@ public class SystemEdit var settings = await ctx.Repository.GetSystemGuild(ctx.Guild.Id, target.Id); - var format = ctx.MatchFormat(); - - // if there's nothing next or what's next is "raw"/"plaintext" we're doing a query, so check for null - if (!ctx.HasNext(false) || format != ReplyFormat.Standard) - if (settings.DisplayName == null) - { - await ctx.Reply(noNameSetMessage); - return; - } + if (settings.DisplayName == null) + { + await ctx.Reply(noNameSetMessage); + return; + } if (format == ReplyFormat.Raw) { @@ -126,34 +121,37 @@ public class SystemEdit return; } - if (!ctx.HasNext(false)) - { - await ctx.Reply( - $"{(isOwnSystem ? "Your" : "This")} system's name for this server is currently **{settings.DisplayName}**." - + (isOwnSystem ? $" Type `{ctx.DefaultPrefix}system servername -clear` to clear it." - + $" Using {settings.DisplayName.Length}/{Limits.MaxSystemNameLength} characters." : "")); - return; - } + await ctx.Reply( + $"{(isOwnSystem ? "Your" : "This")} system's name for this server is currently **{settings.DisplayName}**." + + (isOwnSystem ? $" Type `{ctx.DefaultPrefix}system servername -clear` to clear it." + + $" Using {settings.DisplayName.Length}/{Limits.MaxSystemNameLength} characters." : "")); + return; + } + public async Task ClearServerName(Context ctx, PKSystem target) + { + ctx.CheckGuildContext(); ctx.CheckSystem().CheckOwnSystem(target); - if (ctx.MatchClear() && await ctx.ConfirmClear("your system's name for this server")) + if (await ctx.ConfirmClear("your system's name for this server")) { await ctx.Repository.UpdateSystemGuild(target.Id, ctx.Guild.Id, new SystemGuildPatch { DisplayName = null }); await ctx.Reply($"{Emojis.Success} System name for this server cleared."); } - else - { - var newSystemGuildName = ctx.RemainderOrNull(false).NormalizeLineEndSpacing(); + } - if (newSystemGuildName.Length > Limits.MaxSystemNameLength) - throw Errors.StringTooLongError("System name for this server", newSystemGuildName.Length, Limits.MaxSystemNameLength); + public async Task RenameServerName(Context ctx, PKSystem target, string newSystemGuildName) + { + ctx.CheckGuildContext(); + ctx.CheckSystem().CheckOwnSystem(target); - await ctx.Repository.UpdateSystemGuild(target.Id, ctx.Guild.Id, new SystemGuildPatch { DisplayName = newSystemGuildName }); + if (newSystemGuildName.Length > Limits.MaxSystemNameLength) + throw Errors.StringTooLongError("System name for this server", newSystemGuildName.Length, Limits.MaxSystemNameLength); - await ctx.Reply($"{Emojis.Success} System name for this server changed (using {newSystemGuildName.Length}/{Limits.MaxSystemNameLength} characters)."); - } + await ctx.Repository.UpdateSystemGuild(target.Id, ctx.Guild.Id, new SystemGuildPatch { DisplayName = newSystemGuildName }); + + await ctx.Reply($"{Emojis.Success} System name for this server changed (using {newSystemGuildName.Length}/{Limits.MaxSystemNameLength} characters)."); } public async Task Description(Context ctx, PKSystem target) diff --git a/PluralKit.Bot/Services/EmbedService.cs b/PluralKit.Bot/Services/EmbedService.cs index 178d1339..53551199 100644 --- a/PluralKit.Bot/Services/EmbedService.cs +++ b/PluralKit.Bot/Services/EmbedService.cs @@ -39,14 +39,14 @@ public class EmbedService return Task.WhenAll(ids.Select(Inner)); } - public async Task CreateSystemEmbed(Context cctx, PKSystem system, LookupContext ctx) + public async Task CreateSystemEmbed(Context cctx, PKSystem system, LookupContext ctx, bool countctxByOwner) { // Fetch/render info for all accounts simultaneously var accounts = await _repo.GetSystemAccounts(system.Id); var users = (await GetUsers(accounts)).Select(x => x.User?.NameAndMention() ?? $"(deleted account {x.Id})"); var countctx = LookupContext.ByNonOwner; - if (cctx.MatchFlag("a", "all")) + if (countctxByOwner) { if (system.Id == cctx.System.Id) countctx = LookupContext.ByOwner; diff --git a/crates/command_definitions/src/lib.rs b/crates/command_definitions/src/lib.rs index d25f1daf..b7336a3e 100644 --- a/crates/command_definitions/src/lib.rs +++ b/crates/command_definitions/src/lib.rs @@ -26,6 +26,8 @@ pub fn all() -> impl Iterator { .chain(member::cmds()) .chain(config::cmds()) .chain(fun::cmds()) + .map(|cmd| cmd.flag(("plaintext", ["pt"]))) + .map(|cmd| cmd.flag(("raw", ["r"]))) } pub const RESET: (&str, [&str; 2]) = ("reset", ["clear", "default"]); diff --git a/crates/command_definitions/src/system.rs b/crates/command_definitions/src/system.rs index da1d85f1..e08e61c4 100644 --- a/crates/command_definitions/src/system.rs +++ b/crates/command_definitions/src/system.rs @@ -2,15 +2,51 @@ use super::*; pub fn cmds() -> impl Iterator { let system = ("system", ["s"]); - let new = ("new", ["n"]); + let system_target = tokens!(system, SystemRef); - let system_new = tokens!(system, new); + let system_info_cmd = [ + command!(system => "system_info_self").help("Shows information about your system"), + command!(system_target, ("info", ["show", "view"]) => "system_info") + .help("Shows information about your system"), + ] + .into_iter() + .map(|cmd| { + cmd.flag(("public", ["pub"])) + .flag(("private", ["priv"])) + .flag(("all", ["a"])) + }); - [ - command!(system => "system_show").help("Shows information about your system"), + let system_name = tokens!(system_target, "name"); + let system_name_cmd = [ + command!(system_name => "system_show_name").help("Shows the systems name"), + command!(system_name, ("clear", ["c"]) => "system_clear_name") + .help("Clears the system's name"), + command!(system_name, ("name", OpaqueString) => "system_rename") + .help("Renames a given system"), + ] + .into_iter(); + + let system_server_name = tokens!(system_target, ("servername", ["sn", "guildname"])); + let system_server_name_cmd = [ + command!(system_server_name => "system_show_server_name") + .help("Shows the system's server name"), + command!(system_server_name, ("clear", ["c"]) => "system_clear_server_name") + .help("Clears the system's server name"), + command!(system_server_name, ("name", OpaqueString) => "system_rename_server_name") + .help("Renames the system's server name"), + ] + .into_iter(); + + let system_new = tokens!(system, ("new", ["n"])); + let system_new_cmd = [ command!(system_new => "system_new").help("Creates a new system"), command!(system_new, ("name", OpaqueString) => "system_new_name") .help("Creates a new system (using the provided name)"), ] - .into_iter() + .into_iter(); + + system_info_cmd + .chain(system_name_cmd) + .chain(system_server_name_cmd) + .chain(system_new_cmd) } diff --git a/crates/command_parser/src/flag.rs b/crates/command_parser/src/flag.rs index fbeb0b1b..ca61f64d 100644 --- a/crates/command_parser/src/flag.rs +++ b/crates/command_parser/src/flag.rs @@ -106,18 +106,18 @@ impl From<(&str, ParameterKind)> for Flag { } } -impl From<[&str; L]> for Flag { - fn from(value: [&str; L]) -> Self { - let mut flag = Flag::new(value[0]); - for alias in &value[1..] { - flag = flag.alias(*alias); +impl From<(&str, [&str; L])> for Flag { + fn from((name, aliases): (&str, [&str; L])) -> Self { + let mut flag = Flag::new(name); + for alias in aliases { + flag = flag.alias(alias); } flag } } -impl From<([&str; L], ParameterKind)> for Flag { - fn from((names, value): ([&str; L], ParameterKind)) -> Self { - Flag::from(names).value(value) +impl From<((&str, [&str; L]), ParameterKind)> for Flag { + fn from(((name, aliases), value): ((&str, [&str; L]), ParameterKind)) -> Self { + Flag::from((name, aliases)).value(value) } } diff --git a/crates/commands/src/bin/write_cs_glue.rs b/crates/commands/src/bin/write_cs_glue.rs index baa825c0..1d64a91a 100644 --- a/crates/commands/src/bin/write_cs_glue.rs +++ b/crates/commands/src/bin/write_cs_glue.rs @@ -1,8 +1,7 @@ use std::{env, fmt::Write, fs, path::PathBuf, str::FromStr}; use command_parser::{ - parameter::{Parameter, ParameterKind}, - token::Token, + command, parameter::{Parameter, ParameterKind}, token::Token }; fn main() -> Result<(), Box> { @@ -34,7 +33,7 @@ fn main() -> Result<(), Box> { for param in &command_params { writeln!( &mut command_params_init, - r#"{name} = await ctx.ParamResolve{extract_fn_name}("{name}") ?? throw new PKError("this is a bug"),"#, + r#"@{name} = await ctx.ParamResolve{extract_fn_name}("{name}") ?? throw new PKError("this is a bug"),"#, name = param.name(), extract_fn_name = get_param_param_ty(param.kind()), )?; @@ -44,14 +43,14 @@ fn main() -> Result<(), Box> { if let Some(kind) = flag.get_value() { writeln!( &mut command_flags_init, - r#"{name} = await ctx.FlagResolve{extract_fn_name}("{name}"),"#, + r#"@{name} = await ctx.FlagResolve{extract_fn_name}("{name}"),"#, name = flag.get_name(), extract_fn_name = get_param_param_ty(kind), )?; } else { writeln!( &mut command_flags_init, - r#"{name} = ctx.Parameters.HasFlag("{name}"),"#, + r#"@{name} = ctx.Parameters.HasFlag("{name}"),"#, name = flag.get_name(), )?; } @@ -92,7 +91,7 @@ fn main() -> Result<(), Box> { for param in &command_params { writeln!( &mut command_params_fields, - r#"public required {ty} {name};"#, + r#"public required {ty} @{name};"#, name = param.name(), ty = get_param_ty(param.kind()), )?; @@ -102,18 +101,32 @@ fn main() -> Result<(), Box> { if let Some(kind) = flag.get_value() { writeln!( &mut command_flags_fields, - r#"public {ty}? {name};"#, + r#"public {ty}? @{name};"#, name = flag.get_name(), ty = get_param_ty(kind), )?; } else { writeln!( &mut command_flags_fields, - r#"public required bool {name};"#, + r#"public required bool @{name};"#, name = flag.get_name(), )?; } } + let mut command_reply_format = String::new(); + if command.flags.iter().any(|flag| flag.get_name() == "plaintext") { + writeln!( + &mut command_reply_format, + r#"if (plaintext) return ReplyFormat.Plaintext;"#, + )?; + } + if command.flags.iter().any(|flag| flag.get_name() == "raw") { + writeln!( + &mut command_reply_format, + r#"if (raw) return ReplyFormat.Raw;"#, + )?; + } + command_reply_format.push_str("return ReplyFormat.Standard;\n"); write!( &mut glue, r#" @@ -124,6 +137,11 @@ fn main() -> Result<(), Box> { public class {command_name}Flags {{ {command_flags_fields} + + public ReplyFormat GetReplyFormat() + {{ + {command_reply_format} + }} }} "#, command_name = command_callback_to_name(&command.cb),