diff --git a/PluralKit.Bot/CommandMeta/CommandHelp.cs b/PluralKit.Bot/CommandMeta/CommandHelp.cs index ae5eb3fd..9feb3bdb 100644 --- a/PluralKit.Bot/CommandMeta/CommandHelp.cs +++ b/PluralKit.Bot/CommandMeta/CommandHelp.cs @@ -4,21 +4,21 @@ public partial class CommandTree { public static Command SystemInfo = new Command("system", "system [system]", "Looks up information about a system"); public static Command SystemNew = new Command("system new", "system new [name]", "Creates a new system"); - public static Command SystemRename = new Command("system name", "system rename [name]", "Renames your system"); - public static Command SystemDesc = new Command("system description", "system description [description]", "Changes your system's description"); - public static Command SystemColor = new Command("system color", "system color [color]", "Changes your system's color"); - public static Command SystemTag = new Command("system tag", "system tag [tag]", "Changes your system's tag"); - public static Command SystemServerTag = new Command("system servertag", "system servertag [tag|enable|disable]", "Changes your system's tag in the current server"); - public static Command SystemAvatar = new Command("system icon", "system icon [url|@mention]", "Changes your system's icon"); - public static Command SystemBannerImage = new Command("system banner", "system banner [url]", "Set the system's banner image"); - public static Command SystemDelete = new Command("system delete", "system delete", "Deletes your system"); + public static Command SystemRename = new Command("system name", "system [system] rename [name]", "Renames your system"); + public static Command SystemDesc = new Command("system description", "system [system] description [description]", "Changes your system's description"); + public static Command SystemColor = new Command("system color", "system [system] color [color]", "Changes your system's color"); + public static Command SystemTag = new Command("system tag", "system [system] tag [tag]", "Changes your system's tag"); + public static Command SystemServerTag = new Command("system servertag", "system [system] servertag [tag|enable|disable]", "Changes your system's tag in the current server"); + public static Command SystemAvatar = new Command("system icon", "system [system] icon [url|@mention]", "Changes your system's icon"); + public static Command SystemBannerImage = new Command("system banner", "system [system] banner [url]", "Set the system's banner image"); + public static Command SystemDelete = new Command("system delete", "system [system] delete", "Deletes your system"); public static Command SystemProxy = new Command("system proxy", "system proxy [server id] [on|off]", "Enables or disables message proxying in a specific server"); public static Command SystemList = new Command("system list", "system [system] list [full]", "Lists a system's members"); public static Command SystemFind = new Command("system find", "system [system] find [full] ", "Searches a system's members given a search term"); public static Command SystemFronter = new Command("system fronter", "system [system] fronter", "Shows a system's fronter(s)"); public static Command SystemFrontHistory = new Command("system fronthistory", "system [system] fronthistory", "Shows a system's front history"); public static Command SystemFrontPercent = new Command("system frontpercent", "system [system] frontpercent [timespan]", "Shows a system's front breakdown"); - public static Command SystemPrivacy = new Command("system privacy", "system privacy ", "Changes your system's privacy settings"); + public static Command SystemPrivacy = new Command("system privacy", "system [system] privacy ", "Changes your system's privacy settings"); public static Command ConfigTimezone = new Command("config timezone", "config timezone [timezone]", "Changes your system's time zone"); public static Command ConfigPing = new Command("config ping", "config ping ", "Changes your system's ping preferences"); public static Command ConfigAutoproxyAccount = new Command("config autoproxy account", "autoproxy account [on|off]", "Toggles autoproxy globally for the current account"); diff --git a/PluralKit.Bot/CommandMeta/CommandTree.cs b/PluralKit.Bot/CommandMeta/CommandTree.cs index 82baae82..c77e3db6 100644 --- a/PluralKit.Bot/CommandMeta/CommandTree.cs +++ b/PluralKit.Bot/CommandMeta/CommandTree.cs @@ -143,75 +143,73 @@ public partial class CommandTree private async Task HandleSystemCommand(Context ctx) { - // If we have no parameters, default to self-target + // these commands never take a system target if (!ctx.HasNext()) await ctx.Execute(SystemInfo, m => m.Query(ctx, ctx.System)); - - // First, we match own-system-only commands (ie. no target system parameter) else if (ctx.Match("new", "create", "make", "add", "register", "init", "n")) await ctx.Execute(SystemNew, m => m.New(ctx)); - else if (ctx.Match("name", "rename", "changename")) - await ctx.Execute(SystemRename, m => m.Name(ctx)); - else if (ctx.Match("tag")) - await ctx.Execute(SystemTag, m => m.Tag(ctx)); - else if (ctx.Match("servertag")) - await ctx.Execute(SystemServerTag, m => m.ServerTag(ctx)); - else if (ctx.Match("description", "desc", "bio")) - await ctx.Execute(SystemDesc, m => m.Description(ctx)); - else if (ctx.Match("color", "colour")) - await ctx.Execute(SystemColor, m => m.Color(ctx)); - else if (ctx.Match("banner", "splash", "cover")) - await ctx.Execute(SystemBannerImage, m => m.BannerImage(ctx)); - else if (ctx.Match("avatar", "picture", "icon", "image", "pic", "pfp")) - await ctx.Execute(SystemAvatar, m => m.Avatar(ctx)); - else if (ctx.Match("delete", "remove", "destroy", "erase", "yeet")) - await ctx.Execute(SystemDelete, m => m.Delete(ctx)); - else if (ctx.Match("webhook", "hook")) - await ctx.Execute(null, m => m.SystemWebhook(ctx)); - else if (ctx.Match("timezone", "tz")) - await ctx.Execute(ConfigTimezone, m => m.SystemTimezone(ctx), true); - else if (ctx.Match("proxy")) - await ctx.Execute(SystemProxy, m => m.SystemProxy(ctx)); - else if (ctx.Match("list", "l", "members")) - await ctx.Execute(SystemList, m => m.MemberList(ctx, ctx.System)); - else if (ctx.Match("find", "search", "query", "fd", "s")) - await ctx.Execute(SystemFind, m => m.MemberList(ctx, ctx.System)); - else if (ctx.Match("f", "front", "fronter", "fronters")) - { - if (ctx.Match("h", "history")) - await ctx.Execute(SystemFrontHistory, m => m.SystemFrontHistory(ctx, ctx.System)); - else if (ctx.Match("p", "percent", "%")) - await ctx.Execute(SystemFrontPercent, m => m.SystemFrontPercent(ctx, ctx.System)); - else - await ctx.Execute(SystemFronter, m => m.SystemFronter(ctx, ctx.System)); - } - else if (ctx.Match("fh", "fronthistory", "history", "switches")) - await ctx.Execute(SystemFrontHistory, m => m.SystemFrontHistory(ctx, ctx.System)); - else if (ctx.Match("fp", "frontpercent", "front%", "frontbreakdown")) - await ctx.Execute(SystemFrontPercent, m => m.SystemFrontPercent(ctx, ctx.System)); - else if (ctx.Match("privacy")) - await ctx.Execute(SystemPrivacy, m => m.SystemPrivacy(ctx)); - else if (ctx.Match("ping")) - await ctx.Execute(ConfigPing, m => m.SystemPing(ctx), true); else if (ctx.Match("commands", "help")) await PrintCommandList(ctx, "systems", SystemCommands); - else if (ctx.Match("groups", "gs", "g")) - await ctx.Execute(GroupList, g => g.ListSystemGroups(ctx, null)); + + // these are deprecated (and not accessible by other users anyway), let's leave them out of new parsing + else if (ctx.Match("timezone", "tz")) + await ctx.Execute(ConfigTimezone, m => m.SystemTimezone(ctx), true); + else if (ctx.Match("ping")) + await ctx.Execute(ConfigPing, m => m.SystemPing(ctx), true); + + // 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 *do* take a system target else - await HandleSystemCommandTargeted(ctx); + { + // try matching a system ID + var target = await ctx.MatchSystem(); + var previousPtr = ctx.Parameters._ptr; + + // if we have a parsed target and no more commands, don't bother with the command flow + // we skip the `target != null` check here since the argument isn't be popped if it's not a system + if (!ctx.HasNext()) + { + await ctx.Execute(SystemInfo, m => m.Query(ctx, target)); + return; + } + + await HandleSystemCommandTargeted(ctx, target ?? ctx.System); + + // if we *still* haven't matched anything, the user entered an invalid command name or system reference + if (ctx.Parameters._ptr == previousPtr) + { + if (ctx.Parameters.Peek().Length != 5 && !ulong.TryParse(ctx.Parameters.Peek(), out _)) + { + await PrintCommandNotFoundError(ctx, SystemCommands); + return; + } + + var list = CreatePotentialCommandList(SystemCommands); + await ctx.Reply($"{Emojis.Error} {await CreateSystemNotFoundError(ctx)}\n\n" + + $"Perhaps you meant to use one of the following commands?\n{list}"); + } + } } - private async Task HandleSystemCommandTargeted(Context ctx) + private async Task HandleSystemCommandTargeted(Context ctx, PKSystem target) { - // Commands that have a system target (eg. pk;system fronthistory) - var target = await ctx.MatchSystem(); - if (target == null) - { - var list = CreatePotentialCommandList(SystemInfo, SystemNew, SystemRename, SystemTag, SystemDesc, - SystemAvatar, SystemDelete, SystemList, SystemFronter, SystemFrontHistory, SystemFrontPercent); - await ctx.Reply( - $"{Emojis.Error} {await CreateSystemNotFoundError(ctx)}\n\nPerhaps you meant to use one of the following commands?\n{list}"); - } + if (ctx.Match("name", "rename", "changename")) + await ctx.Execute(SystemRename, m => m.Name(ctx, target)); + else if (ctx.Match("tag")) + await ctx.Execute(SystemTag, m => m.Tag(ctx, target)); + else if (ctx.Match("servertag")) + await ctx.Execute(SystemServerTag, m => m.ServerTag(ctx, target)); + else if (ctx.Match("description", "desc", "bio")) + await ctx.Execute(SystemDesc, m => m.Description(ctx, target)); + else if (ctx.Match("color", "colour")) + await ctx.Execute(SystemColor, m => m.Color(ctx, target)); + else if (ctx.Match("banner", "splash", "cover")) + await ctx.Execute(SystemBannerImage, m => m.BannerImage(ctx, target)); else if (ctx.Match("avatar", "picture", "icon", "image", "pic", "pfp")) await ctx.Execute(SystemAvatar, m => m.Avatar(ctx, target)); else if (ctx.Match("list", "l", "members")) @@ -235,11 +233,10 @@ public partial class CommandTree await ctx.Execute(SystemInfo, m => m.Query(ctx, target)); else if (ctx.Match("groups", "gs")) await ctx.Execute(GroupList, g => g.ListSystemGroups(ctx, target)); - else if (!ctx.HasNext()) - await ctx.Execute(SystemInfo, m => m.Query(ctx, target)); - else - await PrintCommandNotFoundError(ctx, SystemList, SystemFronter, SystemFrontHistory, SystemFrontPercent, - SystemInfo); + else if (ctx.Match("privacy")) + await ctx.Execute(SystemPrivacy, m => m.SystemPrivacy(ctx, target)); + else if (ctx.Match("delete", "remove", "destroy", "erase", "yeet")) + await ctx.Execute(SystemDelete, m => m.Delete(ctx, target)); } private async Task HandleMemberCommand(Context ctx) diff --git a/PluralKit.Bot/CommandSystem/Context/ContextChecksExt.cs b/PluralKit.Bot/CommandSystem/Context/ContextChecksExt.cs index fb9d1ef1..e280d340 100644 --- a/PluralKit.Bot/CommandSystem/Context/ContextChecksExt.cs +++ b/PluralKit.Bot/CommandSystem/Context/ContextChecksExt.cs @@ -27,6 +27,13 @@ public static class ContextChecksExt throw new PKError("You do not have permission to access this information."); } + public static Context CheckOwnSystem(this Context ctx, PKSystem system) + { + if (system.Id != ctx.System?.Id) + throw Errors.NotOwnSystemError; + return ctx; + } + public static Context CheckOwnMember(this Context ctx, PKMember member) { if (member.System != ctx.System?.Id) diff --git a/PluralKit.Bot/Commands/SystemEdit.cs b/PluralKit.Bot/Commands/SystemEdit.cs index 02670cf0..0ea5c408 100644 --- a/PluralKit.Bot/Commands/SystemEdit.cs +++ b/PluralKit.Bot/Commands/SystemEdit.cs @@ -22,16 +22,18 @@ public class SystemEdit _client = client; } - public async Task Name(Context ctx) + public async Task Name(Context ctx, PKSystem target) { - var noNameSetMessage = "Your system does not have a name set. Type `pk;system name ` to set one."; + var isOwnSystem = target.Id == ctx.System?.Id; - ctx.CheckSystem(); + var noNameSetMessage = $"{(isOwnSystem ? "Your" : "This")} system does not have a name set."; + if (isOwnSystem) + noNameSetMessage += " Type `pk;system name ` to set one."; if (ctx.MatchRaw()) { - if (ctx.System.Name != null) - await ctx.Reply($"```\n{ctx.System.Name}\n```"); + if (target.Name != null) + await ctx.Reply($"```\n{target.Name}\n```"); else await ctx.Reply(noNameSetMessage); return; @@ -39,17 +41,20 @@ public class SystemEdit if (!ctx.HasNext(false)) { - if (ctx.System.Name != null) + if (target.Name != null) await ctx.Reply( - $"Your system's name is currently **{ctx.System.Name}**. Type `pk;system name -clear` to clear it."); + $"{(isOwnSystem ? "Your" : "This")} system's name is currently **{target.Name}**." + + (isOwnSystem ? " Type `pk;system name -clear` to clear it." : "")); else await ctx.Reply(noNameSetMessage); return; } + ctx.CheckSystem().CheckOwnSystem(target); + if (await ctx.MatchClear("your system's name")) { - await _repo.UpdateSystem(ctx.System.Id, new SystemPatch { Name = null }); + await _repo.UpdateSystem(target.Id, new SystemPatch { Name = null }); await ctx.Reply($"{Emojis.Success} System name cleared."); } @@ -60,22 +65,25 @@ public class SystemEdit if (newSystemName.Length > Limits.MaxSystemNameLength) throw Errors.StringTooLongError("System name", newSystemName.Length, Limits.MaxSystemNameLength); - await _repo.UpdateSystem(ctx.System.Id, new SystemPatch { Name = newSystemName }); + await _repo.UpdateSystem(target.Id, new SystemPatch { Name = newSystemName }); await ctx.Reply($"{Emojis.Success} System name changed."); } } - public async Task Description(Context ctx) + public async Task Description(Context ctx, PKSystem target) { - var noDescriptionSetMessage = - "Your system does not have a description set. To set one, type `pk;s description `."; + var isOwnSystem = target.Id == ctx.System?.Id; + + var noDescriptionSetMessage = "This system does not have a description set."; + if (isOwnSystem) + noDescriptionSetMessage += " To set one, type `pk;s description `."; ctx.CheckSystem(); if (ctx.MatchRaw()) { - if (ctx.System.Description == null) + if (target.Description == null) await ctx.Reply(noDescriptionSetMessage); else await ctx.Reply($"```\n{ctx.System.Description}\n```"); @@ -84,21 +92,24 @@ public class SystemEdit if (!ctx.HasNext(false)) { - if (ctx.System.Description == null) + if (target.Description == null) await ctx.Reply(noDescriptionSetMessage); else await ctx.Reply(embed: new EmbedBuilder() .Title("System description") - .Description(ctx.System.Description) + .Description(target.Description) .Footer(new Embed.EmbedFooter( - "To print the description with formatting, type `pk;s description -raw`. To clear it, type `pk;s description -clear`. To change it, type `pk;s description `.")) + "To print the description with formatting, type `pk;s description -raw`." + + (isOwnSystem ? "To clear it, type `pk;s description -clear`. To change it, type `pk;s description `." : ""))) .Build()); return; } + ctx.CheckSystem().CheckOwnSystem(target); + if (await ctx.MatchClear("your system's description")) { - await _repo.UpdateSystem(ctx.System.Id, new SystemPatch { Description = null }); + await _repo.UpdateSystem(target.Id, new SystemPatch { Description = null }); await ctx.Reply($"{Emojis.Success} System description cleared."); } @@ -108,36 +119,39 @@ public class SystemEdit if (newDescription.Length > Limits.MaxDescriptionLength) throw Errors.StringTooLongError("Description", newDescription.Length, Limits.MaxDescriptionLength); - await _repo.UpdateSystem(ctx.System.Id, new SystemPatch { Description = newDescription }); + await _repo.UpdateSystem(target.Id, new SystemPatch { Description = newDescription }); await ctx.Reply($"{Emojis.Success} System description changed."); } } - public async Task Color(Context ctx) + public async Task Color(Context ctx, PKSystem target) { - ctx.CheckSystem(); + var isOwnSystem = ctx.System?.Id == target.Id; - if (await ctx.MatchClear()) + if (!ctx.HasNext()) { - await _repo.UpdateSystem(ctx.System.Id, new SystemPatch { Color = Partial.Null() }); - - await ctx.Reply($"{Emojis.Success} System color cleared."); - } - else if (!ctx.HasNext()) - { - if (ctx.System.Color == null) + if (target.Color == null) await ctx.Reply( - "Your system does not have a color set. To set one, type `pk;system color `."); + "This system does not have a color set." + (isOwnSystem ? " To set one, type `pk;system color `." : "")); else await ctx.Reply(embed: new EmbedBuilder() .Title("System color") - .Color(ctx.System.Color.ToDiscordColor()) + .Color(target.Color.ToDiscordColor()) .Thumbnail(new Embed.EmbedThumbnail($"https://fakeimg.pl/256x256/{ctx.System.Color}/?text=%20")) .Description( - $"Your system's color is **#{ctx.System.Color}**. To clear it, type `pk;s color -clear`.") + $"This system's color is **#{ctx.System.Color}**." + (isOwnSystem ? " To clear it, type `pk;s color -clear`." : "")) .Build()); } + + ctx.CheckSystem().CheckOwnSystem(target); + + if (await ctx.MatchClear()) + { + await _repo.UpdateSystem(target.Id, new SystemPatch { Color = Partial.Null() }); + + await ctx.Reply($"{Emojis.Success} System color cleared."); + } else { var color = ctx.RemainderOrNull(); @@ -145,7 +159,7 @@ public class SystemEdit if (color.StartsWith("#")) color = color.Substring(1); if (!Regex.IsMatch(color, "^[0-9a-fA-F]{6}$")) throw Errors.InvalidColorError(color); - await _repo.UpdateSystem(ctx.System.Id, + await _repo.UpdateSystem(target.Id, new SystemPatch { Color = Partial.Present(color.ToLowerInvariant()) }); await ctx.Reply(embed: new EmbedBuilder() @@ -156,34 +170,38 @@ public class SystemEdit } } - public async Task Tag(Context ctx) + public async Task Tag(Context ctx, PKSystem target) { - var noTagSetMessage = "You currently have no system tag. To set one, type `pk;s tag `."; + var isOwnSystem = ctx.System?.Id == target.Id; - ctx.CheckSystem(); + var noTagSetMessage = isOwnSystem + ? "You currently have no system tag set. To set one, type `pk;s tag `." + : "This system currently has no system tag set."; if (ctx.MatchRaw()) { - if (ctx.System.Tag == null) + if (target.Tag == null) await ctx.Reply(noTagSetMessage); else - await ctx.Reply($"```\n{ctx.System.Tag}\n```"); + await ctx.Reply($"```\n{target.Tag}\n```"); return; } if (!ctx.HasNext(false)) { - if (ctx.System.Tag == null) + if (target.Tag == null) await ctx.Reply(noTagSetMessage); else - await ctx.Reply( - $"Your current system tag is {ctx.System.Tag.AsCode()}. To change it, type `pk;s tag `. To clear it, type `pk;s tag -clear`."); + await ctx.Reply($"{(isOwnSystem ? "Your" : "This system's")} current system tag is {ctx.System.Tag.AsCode()}." + + (isOwnSystem ? "To change it, type `pk;s tag `. To clear it, type `pk;s tag -clear`." : "")); return; } + ctx.CheckSystem().CheckOwnSystem(target); + if (await ctx.MatchClear("your system's tag")) { - await _repo.UpdateSystem(ctx.System.Id, new SystemPatch { Tag = null }); + await _repo.UpdateSystem(target.Id, new SystemPatch { Tag = null }); await ctx.Reply($"{Emojis.Success} System tag cleared."); } @@ -194,16 +212,16 @@ public class SystemEdit if (newTag.Length > Limits.MaxSystemTagLength) throw Errors.StringTooLongError("System tag", newTag.Length, Limits.MaxSystemTagLength); - await _repo.UpdateSystem(ctx.System.Id, new SystemPatch { Tag = newTag }); + await _repo.UpdateSystem(target.Id, new SystemPatch { Tag = newTag }); await ctx.Reply( $"{Emojis.Success} System tag changed. Member names will now end with {newTag.AsCode()} when proxied."); } } - public async Task ServerTag(Context ctx) + public async Task ServerTag(Context ctx, PKSystem target) { - ctx.CheckSystem().CheckGuildContext(); + ctx.CheckSystem().CheckOwnSystem(target).CheckGuildContext(); var setDisabledWarning = $"{Emojis.Warn} Your system tag is currently **disabled** in this server. No tag will be applied when proxying.\nTo re-enable the system tag in the current server, type `pk;s servertag -enable`."; @@ -389,17 +407,40 @@ public class SystemEdit await ShowIcon(); } - public async Task BannerImage(Context ctx) + public async Task BannerImage(Context ctx, PKSystem target) { - ctx.CheckSystem(); + var isOwnSystem = target.Id == ctx.System?.Id; - async Task ClearImage() + if (!ctx.HasNext()) + { + if ((target.BannerImage?.Trim() ?? "").Length > 0) + { + var eb = new EmbedBuilder() + .Title("System banner image") + .Image(new Embed.EmbedImage(target.BannerImage)); + + if (isOwnSystem) + eb.Description("To clear, use `pk;system banner clear`."); + + await ctx.Reply(embed: eb.Build()); + } + else + { + throw new PKSyntaxError( + "This system does not have a banner image set." + (isOwnSystem ? "Set one by attaching an image to this command, or by passing an image URL or @mention." : "")); + } + return; + } + + ctx.CheckSystem().CheckOwnSystem(target); + + if (await ctx.MatchClear("your system's banner image")) { await _repo.UpdateSystem(ctx.System.Id, new SystemPatch { BannerImage = null }); await ctx.Reply($"{Emojis.Success} System banner image cleared."); } - async Task SetImage(ParsedImage img) + else if (await ctx.MatchImage() is { } img) { await AvatarUtils.VerifyAvatarOrThrow(_client, img.Url, true); @@ -421,34 +462,11 @@ public class SystemEdit : ctx.Reply(msg)); } - async Task ShowImage() - { - if ((ctx.System.BannerImage?.Trim() ?? "").Length > 0) - { - var eb = new EmbedBuilder() - .Title("System banner image") - .Image(new Embed.EmbedImage(ctx.System.BannerImage)) - .Description("To clear, use `pk;system banner clear`."); - await ctx.Reply(embed: eb.Build()); - } - else - { - throw new PKSyntaxError( - "This system does not have a banner image set. Set one by attaching an image to this command, or by passing an image URL or @mention."); - } - } - - if (await ctx.MatchClear("your system's banner image")) - await ClearImage(); - else if (await ctx.MatchImage() is { } img) - await SetImage(img); - else - await ShowImage(); } - public async Task Delete(Context ctx) + public async Task Delete(Context ctx, PKSystem target) { - ctx.CheckSystem(); + ctx.CheckSystem().CheckOwnSystem(target); await ctx.Reply( $"{Emojis.Warn} Are you sure you want to delete your system? If so, reply to this message with your system's ID (`{ctx.System.Hid}`).\n**Note: this action is permanent.**"); @@ -509,9 +527,9 @@ public class SystemEdit await ctx.Reply($"Message proxying in {serverText} is now **disabled** for your system."); } - public async Task SystemPrivacy(Context ctx) + public async Task SystemPrivacy(Context ctx, PKSystem target) { - ctx.CheckSystem(); + ctx.CheckSystem().CheckOwnSystem(target); Task PrintEmbed() { diff --git a/docs/content/command-list.md b/docs/content/command-list.md index 7246c910..59cc6efd 100644 --- a/docs/content/command-list.md +++ b/docs/content/command-list.md @@ -34,17 +34,17 @@ Some arguments indicate the use of specific Discord features. These include: ## System commands *Optionally replace `[system]` with a @mention, Discord account ID, or 5-character ID. For most commands, adding `-clear` will clear/delete the field.* - `pk;system [system]` - Shows information about a system. -- `pk;system new [name]` - Creates a new system registered to your account. -- `pk;system rename [new name]` - Changes the name of your system. -- `pk;system description [description]` - Changes the description of your system. -- `pk;system avatar [avatar url|@mention|upload]` - Changes the avatar of your system. -- `pk;system banner [image url|upload]` - Changes your system's banner image. -- `pk;system privacy` - Displays your system's current privacy settings. -- `pk;system privacy ` - Changes your systems privacy settings. -- `pk;system tag [tag]` - Changes the system tag of your system. -- `pk;system servertag [tag|-enable|-disable]` - Changes your system's tag in the current server, or disables it for the current server. +- `pk;system [system] new [name]` - Creates a new system registered to your account. +- `pk;system [system] rename [new name]` - Changes the name of your system. +- `pk;system [system] description [description]` - Changes the description of your system. +- `pk;system [system] avatar [avatar url|@mention|upload]` - Changes the avatar of your system. +- `pk;system [system] banner [image url|upload]` - Changes your system's banner image. +- `pk;system [system] privacy` - Displays your system's current privacy settings. +- `pk;system [system] privacy ` - Changes your systems privacy settings. +- `pk;system [system] tag [tag]` - Changes the system tag of your system. +- `pk;system [system] servertag [tag|-enable|-disable]` - Changes your system's tag in the current server, or disables it for the current server. - `pk;system proxy [server id] [on|off]` - Toggles message proxying for a specific server. -- `pk;system delete` - Deletes your system. +- `pk;system [system] delete` - Deletes your system. - `pk;system [system] fronter` - Shows the current fronter of a system. - `pk;system [system] fronthistory` - Shows the last 10 fronters of a system. - `pk;system [system] frontpercent [timeframe]` - Shows the aggregated front history of a system within a given time frame. @@ -53,8 +53,6 @@ Some arguments indicate the use of specific Discord features. These include: - `pk;find ` - Searches members by name. - `pk;system [system] find ` - (same as above, but for a specific system) - `pk;autoproxy [off|front|latch|member]` - Updates the system's autoproxy settings for a given server. -- `pk;link ` - Links this system to a different account. -- `pk;unlink [account]` - Unlinks an account from this system. ## Member commands *Replace `` with a member's name, 5-character ID or display name. For most commands, adding `-clear` will clear/delete the field.* @@ -133,6 +131,8 @@ Some arguments indicate the use of specific Discord features. These include: - `pk;debug permissions [server id]` - [Checks the given server's permission setup](./staff/permissions.md#permission-checker-command) to check if it's compatible with PluralKit. - `pk;debug proxying ` - Checks why your message has not been proxied. - `pk;edit [message link|reply] ` - Edits a proxied message. Without an explicit message target, will target the last message proxied by your system in the current channel. **Does not support message IDs!** +- `pk;link ` - Links your system to a different account. +- `pk;unlink [account]` - Unlinks an account from your system. ## API *(for using the [PluralKit API](./api-documentation.md), useful for developers)*