feat: implement system name etc. commands

This commit is contained in:
dusk 2025-03-31 22:22:38 +09:00
parent 87f6fe9d75
commit ac52b5c257
No known key found for this signature in database
10 changed files with 155 additions and 103 deletions

View file

@ -24,6 +24,16 @@ public partial class CommandTree
Commands.CfgApTimeoutUpdate(CfgApTimeoutUpdateParams param, _) => ctx.Execute<Config>(null, m => m.EditAutoproxyTimeout(ctx, param.timeout)),
Commands.FunThunder => ctx.Execute<Fun>(null, m => m.Thunder(ctx)),
Commands.FunMeow => ctx.Execute<Fun>(null, m => m.Meow(ctx)),
Commands.SystemInfo(SystemInfoParams param, SystemInfoFlags flags) => ctx.Execute<System>(SystemInfo, m => m.Query(ctx, param.target, flags.all, flags.@public, flags.@private)),
Commands.SystemInfoSelf(_, SystemInfoSelfFlags flags) => ctx.Execute<System>(SystemInfo, m => m.Query(ctx, ctx.System, flags.all, flags.@public, flags.@private)),
Commands.SystemNew(SystemNewParams param, _) => ctx.Execute<System>(SystemNew, m => m.New(ctx, null)),
Commands.SystemNewName(SystemNewNameParams param, _) => ctx.Execute<System>(SystemNew, m => m.New(ctx, param.name)),
Commands.SystemShowName(SystemShowNameParams param, SystemShowNameFlags flags) => ctx.Execute<SystemEdit>(SystemRename, m => m.ShowName(ctx, param.target, flags.GetReplyFormat())),
Commands.SystemRename(SystemRenameParams param, _) => ctx.Execute<SystemEdit>(SystemRename, m => m.Rename(ctx, param.target, param.name)),
Commands.SystemClearName(SystemClearNameParams param, _) => ctx.Execute<SystemEdit>(SystemRename, m => m.ClearName(ctx, param.target)),
Commands.SystemShowServerName(SystemShowServerNameParams param, SystemShowServerNameFlags flags) => ctx.Execute<SystemEdit>(SystemServerName, m => m.ShowServerName(ctx, param.target, flags.GetReplyFormat())),
Commands.SystemClearServerName(SystemClearServerNameParams param, _) => ctx.Execute<SystemEdit>(SystemServerName, m => m.ClearServerName(ctx, param.target)),
Commands.SystemRenameServerName(SystemRenameServerNameParams param, _) => ctx.Execute<SystemEdit>(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<System>(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<SystemEdit>(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<SystemEdit>(SystemServerName, m => m.ServerName(ctx, target));
else if (ctx.Match("tag", "t"))
if (ctx.Match("tag", "t"))
await ctx.CheckSystem(target).Execute<SystemEdit>(SystemTag, m => m.Tag(ctx, target));
else if (ctx.Match("servertag", "st", "stag", "deer"))
await ctx.CheckSystem(target).Execute<SystemEdit>(SystemServerTag, m => m.ServerTag(ctx, target));
@ -314,8 +316,6 @@ public partial class CommandTree
await ctx.CheckSystem(target).Execute<SystemFront>(SystemFrontHistory, m => m.SystemFrontHistory(ctx, target));
else if (ctx.Match("fp", "frontpercent", "front%", "frontbreakdown"))
await ctx.CheckSystem(target).Execute<SystemFront>(SystemFrontPercent, m => m.FrontPercent(ctx, system: target));
else if (ctx.Match("info", "view", "show"))
await ctx.CheckSystem(target).Execute<System>(SystemInfo, m => m.Query(ctx, target));
else if (ctx.Match("groups", "gs"))
await ctx.CheckSystem(target).Execute<Groups>(GroupList, g => g.ListSystemGroups(ctx, target));
else if (ctx.Match("privacy"))

View file

@ -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.");

View file

@ -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!!");
}
}

View file

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

View file

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

View file

@ -39,14 +39,14 @@ public class EmbedService
return Task.WhenAll(ids.Select(Inner));
}
public async Task<Embed> CreateSystemEmbed(Context cctx, PKSystem system, LookupContext ctx)
public async Task<Embed> 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;

View file

@ -26,6 +26,8 @@ pub fn all() -> impl Iterator<Item = Command> {
.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"]);

View file

@ -2,15 +2,51 @@ use super::*;
pub fn cmds() -> impl Iterator<Item = Command> {
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)
}

View file

@ -106,18 +106,18 @@ impl From<(&str, ParameterKind)> for Flag {
}
}
impl<const L: usize> 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<const L: usize> 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<const L: usize> From<([&str; L], ParameterKind)> for Flag {
fn from((names, value): ([&str; L], ParameterKind)) -> Self {
Flag::from(names).value(value)
impl<const L: usize> From<((&str, [&str; L]), ParameterKind)> for Flag {
fn from(((name, aliases), value): ((&str, [&str; L]), ParameterKind)) -> Self {
Flag::from((name, aliases)).value(value)
}
}

View file

@ -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<dyn std::error::Error>> {
@ -34,7 +33,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
public class {command_name}Flags
{{
{command_flags_fields}
public ReplyFormat GetReplyFormat()
{{
{command_reply_format}
}}
}}
"#,
command_name = command_callback_to_name(&command.cb),