Add banner (large) image

This commit is contained in:
spiral 2021-08-02 13:46:12 -04:00
parent 7681978435
commit e144571904
No known key found for this signature in database
GPG Key ID: A6059F0CA0E1BD31
17 changed files with 231 additions and 11 deletions

View File

@ -17,6 +17,7 @@ namespace PluralKit.API
o.Add("description", system.DescriptionFor(ctx));
o.Add("tag", system.Tag);
o.Add("avatar_url", system.AvatarUrl.TryGetCleanCdnUrl());
o.Add("banner", system.DescriptionPrivacy.Get(ctx, system.BannerImage).TryGetCleanCdnUrl());
o.Add("created", system.Created.FormatExport());
o.Add("tz", system.UiTz);
o.Add("description_privacy", ctx == LookupContext.ByOwner ? system.DescriptionPrivacy.ToJsonString() : null);
@ -33,6 +34,7 @@ namespace PluralKit.API
if (o.ContainsKey("description")) patch.Description = o.Value<string>("description").NullIfEmpty().BoundsCheckField(Limits.MaxDescriptionLength, "System description");
if (o.ContainsKey("tag")) patch.Tag = o.Value<string>("tag").NullIfEmpty().BoundsCheckField(Limits.MaxSystemTagLength, "System tag");
if (o.ContainsKey("avatar_url")) patch.AvatarUrl = o.Value<string>("avatar_url").NullIfEmpty().BoundsCheckField(Limits.MaxUriLength, "System avatar URL");
if (o.ContainsKey("banner")) patch.BannerImage = o.Value<string>("banner").NullIfEmpty().BoundsCheckField(Limits.MaxUriLength, "System banner URL");
if (o.ContainsKey("tz")) patch.UiTz = o.Value<string>("tz") ?? "UTC";
if (o.ContainsKey("description_privacy")) patch.DescriptionPrivacy = o.Value<string>("description_privacy").ParsePrivacy("description");
@ -55,6 +57,7 @@ namespace PluralKit.API
o.Add("birthday", member.BirthdayFor(ctx)?.FormatExport());
o.Add("pronouns", member.PronounsFor(ctx));
o.Add("avatar_url", member.AvatarFor(ctx).TryGetCleanCdnUrl());
o.Add("banner", member.DescriptionPrivacy.Get(ctx, member.BannerImage).TryGetCleanCdnUrl());
o.Add("description", member.DescriptionFor(ctx));
var tagArray = new JArray();
@ -98,6 +101,8 @@ namespace PluralKit.API
if (o.ContainsKey("color")) patch.Color = o.Value<string>("color").NullIfEmpty()?.ToLower();
if (o.ContainsKey("display_name")) patch.DisplayName = o.Value<string>("display_name").NullIfEmpty().BoundsCheckField(Limits.MaxMemberNameLength, "Member display name");
if (o.ContainsKey("avatar_url")) patch.AvatarUrl = o.Value<string>("avatar_url").NullIfEmpty().BoundsCheckField(Limits.MaxUriLength, "Member avatar URL");
if (o.ContainsKey("banner")) patch.BannerImage = o.Value<string>("banner").NullIfEmpty().BoundsCheckField(Limits.MaxUriLength, "Member banner URL");
if (o.ContainsKey("birthday"))
{
var str = o.Value<string>("birthday").NullIfEmpty();

View File

@ -16,6 +16,7 @@ namespace PluralKit.Bot
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 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 SystemTimezone = new Command("system timezone", "system timezone [timezone]", "Changes your system's time zone");
public static Command SystemProxy = new Command("system proxy", "system proxy [server id] [on|off]", "Enables or disables message proxying in a specific server");
@ -38,6 +39,7 @@ namespace PluralKit.Bot
public static Command MemberBirthday = new Command("member birthday", "member <member> birthday [birthday]", "Changes a member's birthday");
public static Command MemberProxy = new Command("member proxy", "member <member> proxy [add|remove] [example proxy]", "Changes, adds, or removes a member's proxy tags");
public static Command MemberDelete = new Command("member delete", "member <member> delete", "Deletes a member");
public static Command MemberBannerImage = new Command("member banner", "member <member> banner [url]", "Set the member's banner image");
public static Command MemberAvatar = new Command("member avatar", "member <member> avatar [url|@mention]", "Changes a member's avatar");
public static Command MemberGroups = new Command("member group", "member <member> group", "Shows the groups a member is in");
public static Command MemberGroupAdd = new Command("member group", "member <member> group add <group> [group 2] [group 3...]", "Adds a member to one or more groups");
@ -60,6 +62,7 @@ namespace PluralKit.Bot
public static Command GroupAdd = new Command("group add", "group <group> add <member> [member 2] [member 3...]", "Adds one or more members to a group");
public static Command GroupRemove = new Command("group remove", "group <group> remove <member> [member 2] [member 3...]", "Removes one or more members from a group");
public static Command GroupPrivacy = new Command("group privacy", "group <group> privacy <description|icon|visibility|all> <public|private>", "Changes a group's privacy settings");
public static Command GroupBannerImage = new Command("group banner", "group <group> banner [url]", "Set the group's banner image");
public static Command GroupIcon = new Command("group icon", "group <group> icon [url|@mention]", "Changes a group's icon");
public static Command GroupDelete = new Command("group delete", "group <group> delete", "Deletes a group");
public static Command GroupFrontPercent = new Command("group frontpercent", "group <group> frontpercent [timespan]", "Shows a group's front breakdown.");
@ -93,20 +96,20 @@ namespace PluralKit.Bot
public static Command Admin = new Command("admin", "admin", "Super secret admin commands (sshhhh)");
public static Command[] SystemCommands = {
SystemInfo, SystemNew, SystemRename, SystemTag, SystemDesc, SystemAvatar, SystemColor, SystemDelete, SystemTimezone,
SystemList, SystemFronter, SystemFrontHistory, SystemFrontPercent, SystemPrivacy, SystemProxy
SystemInfo, SystemNew, SystemRename, SystemTag, SystemDesc, SystemAvatar, SystemBannerImage, SystemColor, SystemDelete,
SystemTimezone, SystemList, SystemFronter, SystemFrontHistory, SystemFrontPercent, SystemPrivacy, SystemProxy
};
public static Command[] MemberCommands = {
MemberInfo, MemberNew, MemberRename, MemberDisplayName, MemberServerName, MemberDesc, MemberPronouns,
MemberColor, MemberBirthday, MemberProxy, MemberAutoproxy, MemberKeepProxy, MemberGroups, MemberGroupAdd, MemberGroupRemove,
MemberDelete, MemberAvatar, MemberServerAvatar, MemberPrivacy, MemberRandom
MemberDelete, MemberAvatar, MemberServerAvatar, MemberBannerImage, MemberPrivacy, MemberRandom
};
public static Command[] GroupCommands =
{
GroupInfo, GroupList, GroupNew, GroupAdd, GroupRemove, GroupMemberList, GroupRename, GroupDesc,
GroupIcon, GroupColor, GroupPrivacy, GroupDelete
GroupIcon, GroupBannerImage, GroupColor, GroupPrivacy, GroupDelete
};
public static Command[] GroupCommandsTargeted =
@ -244,6 +247,8 @@ namespace PluralKit.Bot
await ctx.Execute<SystemEdit>(SystemDesc, m => m.Description(ctx));
else if (ctx.Match("color", "colour"))
await ctx.Execute<SystemEdit>(SystemColor, m => m.Color(ctx));
else if (ctx.Match("banner", "splash", "cover"))
await ctx.Execute<SystemEdit>(SystemBannerImage, m => m.BannerImage(ctx));
else if (ctx.Match("avatar", "picture", "icon", "image", "pic", "pfp"))
await ctx.Execute<SystemEdit>(SystemAvatar, m => m.Avatar(ctx));
else if (ctx.Match("delete", "remove", "destroy", "erase", "yeet"))
@ -355,6 +360,8 @@ namespace PluralKit.Bot
await ctx.Execute<MemberEdit>(MemberDelete, m => m.Delete(ctx, target));
else if (ctx.Match("avatar", "profile", "picture", "icon", "image", "pfp", "pic"))
await ctx.Execute<MemberAvatar>(MemberAvatar, m => m.Avatar(ctx, target));
else if (ctx.Match("banner", "splash", "cover"))
await ctx.Execute<MemberEdit>(MemberBannerImage, m => m.BannerImage(ctx, target));
else if (ctx.Match("group", "groups"))
if (ctx.Match("add", "a"))
await ctx.Execute<MemberGroup>(MemberGroupAdd, m => m.AddRemove(ctx, target, Groups.AddRemoveOperation.Add));
@ -422,6 +429,8 @@ namespace PluralKit.Bot
await ctx.Execute<Groups>(GroupDelete, g => g.DeleteGroup(ctx, target));
else if (ctx.Match("avatar", "picture", "icon", "image", "pic", "pfp"))
await ctx.Execute<Groups>(GroupIcon, g => g.GroupIcon(ctx, target));
else if (ctx.Match("banner", "splash", "cover"))
await ctx.Execute<Groups>(GroupBannerImage, g => g.GroupBannerImage(ctx, target));
else if (ctx.Match("fp", "frontpercent", "front%", "frontbreakdown"))
await ctx.Execute<Groups>(GroupFrontPercent, g => g.GroupFrontPercent(ctx, target));
else if (ctx.Match("color", "colour"))

View File

@ -247,6 +247,69 @@ namespace PluralKit.Bot
else
await ShowIcon();
}
public async Task GroupBannerImage(Context ctx, PKGroup target)
{
async Task ClearBannerImage()
{
ctx.CheckOwnGroup(target);
await _db.Execute(c => _repo.UpdateGroup(c, target.Id, new GroupPatch {BannerImage = null}));
await ctx.Reply($"{Emojis.Success} Group banner image cleared.");
}
async Task SetBannerImage(ParsedImage img)
{
ctx.CheckOwnGroup(target);
await AvatarUtils.VerifyAvatarOrThrow(img.Url, isFullSizeImage: true);
await _db.Execute(c => _repo.UpdateGroup(c, target.Id, new GroupPatch {BannerImage = img.Url}));
var msg = img.Source switch
{
AvatarSource.Url => $"{Emojis.Success} Group banner image changed to the image at the given URL.",
AvatarSource.Attachment => $"{Emojis.Success} Group banner image changed to attached image.\n{Emojis.Warn} If you delete the message containing the attachment, the banner image will stop working.",
AvatarSource.User => throw new PKError("Cannot set a banner image to an user's avatar."),
_ => throw new ArgumentOutOfRangeException(),
};
// The attachment's already right there, no need to preview it.
var hasEmbed = img.Source != AvatarSource.Attachment;
await (hasEmbed
? ctx.Reply(msg, embed: new EmbedBuilder().Image(new(img.Url)).Build())
: ctx.Reply(msg));
}
async Task ShowBannerImage()
{
if (!target.DescriptionPrivacy.CanAccess(ctx.LookupContextFor(target.System)))
throw Errors.LookupNotAllowed;
if ((target.BannerImage?.Trim() ?? "").Length > 0)
{
var eb = new EmbedBuilder()
.Title("Group banner image")
.Image(new(target.BannerImage));
if (target.System == ctx.System?.Id)
{
eb.Description($"To clear, use `pk;group {target.Reference()} banner clear`.");
}
await ctx.Reply(embed: eb.Build());
}
else
throw new PKSyntaxError("This group 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("this group's banner image"))
await ClearBannerImage();
else if (await ctx.MatchImage() is {} img)
await SetBannerImage(img);
else
await ShowBannerImage();
}
public async Task GroupColor(Context ctx, PKGroup target)
{
var color = ctx.RemainderOrNull();

View File

@ -136,6 +136,59 @@ namespace PluralKit.Bot
}
}
public async Task BannerImage(Context ctx, PKMember target)
{
ctx.CheckOwnMember(target);
async Task ClearBannerImage()
{
await _db.Execute(c => _repo.UpdateMember(c, target.Id, new MemberPatch {BannerImage = null}));
await ctx.Reply($"{Emojis.Success} Member banner image cleared.");
}
async Task SetBannerImage(ParsedImage img)
{
await AvatarUtils.VerifyAvatarOrThrow(img.Url, isFullSizeImage: true);
await _db.Execute(c => _repo.UpdateMember(c, target.Id, new MemberPatch {BannerImage = img.Url}));
var msg = img.Source switch
{
AvatarSource.Url => $"{Emojis.Success} Member banner image changed to the image at the given URL.",
AvatarSource.Attachment => $"{Emojis.Success} Member banner image changed to attached image.\n{Emojis.Warn} If you delete the message containing the attachment, the banner image will stop working.",
AvatarSource.User => throw new PKError("Cannot set a banner image to an user's avatar."),
_ => throw new ArgumentOutOfRangeException()
};
// The attachment's already right there, no need to preview it.
var hasEmbed = img.Source != AvatarSource.Attachment;
await (hasEmbed
? ctx.Reply(msg, embed: new EmbedBuilder().Image(new(img.Url)).Build())
: ctx.Reply(msg));
}
async Task ShowBannerImage()
{
if ((target.BannerImage?.Trim() ?? "").Length > 0)
{
var eb = new EmbedBuilder()
.Title($"{target.NameFor(ctx)}'s banner image")
.Image(new(target.BannerImage))
.Description($"To clear, use `pk;member {target.Hid} banner clear`.");
await ctx.Reply(embed: eb.Build());
}
else
throw new PKSyntaxError("This member 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("this member's banner image"))
await ClearBannerImage();
else if (await ctx.MatchImage() is {} img)
await SetBannerImage(img);
else
await ShowBannerImage();
}
public async Task Color(Context ctx, PKMember target)
{
var color = ctx.RemainderOrNull();

View File

@ -219,6 +219,59 @@ namespace PluralKit.Bot
await ShowIcon();
}
public async Task BannerImage(Context ctx)
{
ctx.CheckSystem();
async Task ClearImage()
{
await _db.Execute(c => _repo.UpdateSystem(c, ctx.System.Id, new SystemPatch {BannerImage = null}));
await ctx.Reply($"{Emojis.Success} System banner image cleared.");
}
async Task SetImage(ParsedImage img)
{
await AvatarUtils.VerifyAvatarOrThrow(img.Url, isFullSizeImage: true);
await _db.Execute(c => _repo.UpdateSystem(c, ctx.System.Id, new SystemPatch {BannerImage = img.Url}));
var msg = img.Source switch
{
AvatarSource.Url => $"{Emojis.Success} System banner image changed to the image at the given URL.",
AvatarSource.Attachment => $"{Emojis.Success} System banner image changed to attached image.\n{Emojis.Warn} If you delete the message containing the attachment, the banner image will stop working.",
AvatarSource.User => throw new PKError("Cannot set a banner image to an user's avatar."),
_ => throw new ArgumentOutOfRangeException()
};
// The attachment's already right there, no need to preview it.
var hasEmbed = img.Source != AvatarSource.Attachment;
await (hasEmbed
? ctx.Reply(msg, embed: new EmbedBuilder().Image(new(img.Url)).Build())
: ctx.Reply(msg));
}
async Task ShowImage()
{
if ((ctx.System.BannerImage?.Trim() ?? "").Length > 0)
{
var eb = new EmbedBuilder()
.Title("System banner image")
.Image(new(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) {
ctx.CheckSystem();

View File

@ -70,6 +70,9 @@ namespace PluralKit.Bot {
.Footer(new($"System ID: {system.Hid} | Created on {system.Created.FormatZoned(system)}"))
.Color(color);
if (system.DescriptionPrivacy.CanAccess(ctx))
eb.Image(new(system.BannerImage));
var latestSwitch = await _repo.GetLatestSwitch(conn, system.Id);
if (latestSwitch != null && system.FrontPrivacy.CanAccess(ctx))
{
@ -165,6 +168,9 @@ namespace PluralKit.Bot {
.Footer(new(
$"System ID: {system.Hid} | Member ID: {member.Hid} {(member.MetadataPrivacy.CanAccess(ctx) ? $"| Created on {member.Created.FormatZoned(system)}" : "")}"));
if (member.DescriptionPrivacy.CanAccess(ctx))
eb.Image(new(member.BannerImage));
var description = "";
if (member.MemberVisibility == PrivacyLevel.Private) description += "*(this member is hidden)*\n";
if (guildSettings?.AvatarUrl != null)
@ -230,6 +236,9 @@ namespace PluralKit.Bot {
.Color(color)
.Footer(new($"System ID: {system.Hid} | Group ID: {target.Hid} | Created on {target.Created.FormatZoned(system)}"));
if (target.DescriptionPrivacy.CanAccess(ctx.LookupContextFor(target.System)))
eb.Image(new(target.BannerImage));
if (target.DisplayName != null)
eb.Field(new("Display Name", target.DisplayName, true));

View File

@ -10,7 +10,7 @@ using SixLabors.ImageSharp;
namespace PluralKit.Bot {
public static class AvatarUtils {
public static async Task VerifyAvatarOrThrow(string url)
public static async Task VerifyAvatarOrThrow(string url, bool isFullSizeImage = false)
{
if (url.Length > Limits.MaxUriLength)
throw Errors.UrlTooLong(url);
@ -45,7 +45,7 @@ namespace PluralKit.Bot {
var stream = await response.Content.ReadAsStreamAsync();
var image = await Task.Run(() => Image.Identify(stream));
if (image == null) throw Errors.AvatarInvalid;
if (image.Width > Limits.AvatarDimensionLimit || image.Height > Limits.AvatarDimensionLimit) // Check image size
if (!isFullSizeImage && (image.Width > Limits.AvatarDimensionLimit || image.Height > Limits.AvatarDimensionLimit)) // Check image size
throw Errors.AvatarDimensionsTooLarge(image.Width, image.Height);
}
}

View File

@ -0,0 +1,8 @@
-- SCHEMA VERSION 15: 2021-08-01
-- add banner (large) images to entities with "cards"
alter table systems add column banner_image text;
alter table members add column banner_image text;
alter table groups add column banner_image text;
update info set schema_version = 15;

View File

@ -12,7 +12,7 @@ namespace PluralKit.Core
internal class DatabaseMigrator
{
private const string RootPath = "PluralKit.Core.Database"; // "resource path" root for SQL files
private const int TargetSchemaVersion = 14;
private const int TargetSchemaVersion = 15;
private readonly ILogger _logger;
public DatabaseMigrator(ILogger logger)

View File

@ -13,6 +13,7 @@ namespace PluralKit.Core
public string? DisplayName { get; private set; }
public string? Description { get; private set; }
public string? Icon { get; private set; }
public string? BannerImage { get; private set; }
public string? Color { get; private set; }
public PrivacyLevel DescriptionPrivacy { get; private set; }

View File

@ -15,6 +15,7 @@ namespace PluralKit.Core {
public SystemId System { get; private set; }
public string Color { get; private set; }
public string AvatarUrl { get; private set; }
public string BannerImage { get; private set; }
public string Name { get; private set; }
public string DisplayName { get; private set; }
public LocalDate? Birthday { get; private set; }

View File

@ -14,6 +14,7 @@ namespace PluralKit.Core {
public string Description { get; }
public string Tag { get; }
public string AvatarUrl { get; }
public string BannerImage { get; }
public string Color { get; }
public string Token { get; }
public Instant Created { get; }

View File

@ -10,6 +10,7 @@ namespace PluralKit.Core
public Partial<string?> DisplayName { get; set; }
public Partial<string?> Description { get; set; }
public Partial<string?> Icon { get; set; }
public Partial<string?> BannerImage { get; set; }
public Partial<string?> Color { get; set; }
public Partial<PrivacyLevel> DescriptionPrivacy { get; set; }
@ -23,6 +24,7 @@ namespace PluralKit.Core
.With("display_name", DisplayName)
.With("description", Description)
.With("icon", Icon)
.With("banner_image", BannerImage)
.With("color", Color)
.With("description_privacy", DescriptionPrivacy)
.With("icon_privacy", IconPrivacy)
@ -32,7 +34,9 @@ namespace PluralKit.Core
public new void CheckIsValid()
{
if (Icon.Value != null && !MiscUtils.TryMatchUri(Icon.Value, out var avatarUri))
throw new InvalidPatchException("avatar_url");
throw new InvalidPatchException("icon");
if (BannerImage.Value != null && !MiscUtils.TryMatchUri(BannerImage.Value, out var bannerImage))
throw new InvalidPatchException("banner");
if (Color.Value != null && (!Regex.IsMatch(Color.Value, "^[0-9a-fA-F]{6}$")))
throw new InvalidPatchException("color");
}

View File

@ -11,6 +11,7 @@ namespace PluralKit.Core
public Partial<string> Hid { get; set; }
public Partial<string?> DisplayName { get; set; }
public Partial<string?> AvatarUrl { get; set; }
public Partial<string?> BannerImage { get; set; }
public Partial<string?> Color { get; set; }
public Partial<LocalDate?> Birthday { get; set; }
public Partial<string?> Pronouns { get; set; }
@ -32,6 +33,7 @@ namespace PluralKit.Core
.With("hid", Hid)
.With("display_name", DisplayName)
.With("avatar_url", AvatarUrl)
.With("banner_image", BannerImage)
.With("color", Color)
.With("birthday", Birthday)
.With("pronouns", Pronouns)
@ -52,6 +54,8 @@ namespace PluralKit.Core
{
if (AvatarUrl.Value != null && !MiscUtils.TryMatchUri(AvatarUrl.Value, out var avatarUri))
throw new InvalidPatchException("avatar_url");
if (BannerImage.Value != null && !MiscUtils.TryMatchUri(BannerImage.Value, out var bannerImage))
throw new InvalidPatchException("banner");
if (Color.Value != null && (!Regex.IsMatch(Color.Value, "^[0-9a-fA-F]{6}$")))
throw new InvalidPatchException("color");
}

View File

@ -10,6 +10,7 @@ namespace PluralKit.Core
public Partial<string?> Description { get; set; }
public Partial<string?> Tag { get; set; }
public Partial<string?> AvatarUrl { get; set; }
public Partial<string?> BannerImage { get; set; }
public Partial<string?> Color { get; set; }
public Partial<string?> Token { get; set; }
public Partial<string> UiTz { get; set; }
@ -29,6 +30,7 @@ namespace PluralKit.Core
.With("description", Description)
.With("tag", Tag)
.With("avatar_url", AvatarUrl)
.With("banner_image", BannerImage)
.With("color", Color)
.With("token", Token)
.With("ui_tz", UiTz)
@ -46,6 +48,8 @@ namespace PluralKit.Core
{
if (AvatarUrl.Value != null && !MiscUtils.TryMatchUri(AvatarUrl.Value, out var avatarUri))
throw new InvalidPatchException("avatar_url");
if (BannerImage.Value != null && !MiscUtils.TryMatchUri(BannerImage.Value, out var bannerImage))
throw new InvalidPatchException("banner");
if (Color.Value != null && (!Regex.IsMatch(Color.Value, "^[0-9a-fA-F]{6}$")))
throw new InvalidPatchException("color");
}

View File

@ -55,6 +55,7 @@ The following three models (usually represented in JSON format) represent the va
| description | string? | Yes | 1000-character limit. |
| tag | string? | Yes | |
| avatar_url | url? | Yes | Not validated server-side. |
| banner | url? | Yes | Not validated server-side. |
| tz | string? | Yes | Tzdb identifier. Patching with `null` will store `"UTC"`. |
| created | datetime | No | |
| description_privacy | string? | Yes | Patching with `private` will set it to private; `public` or `null` will set it to public. |
@ -70,15 +71,16 @@ The following three models (usually represented in JSON format) represent the va
| name | string? | Yes | 50-character limit. |
| display_name | string? | Yes | 50-character limit. |
| description | string? | Yes | 1000-character limit. |
| pronouns | string? | Yes | 100-character limit. |
| pronouns | string? | Yes | 100-character limit. |
| color | color? | Yes | 6-char hex (eg. `ff7000`), sans `#`. |
| avatar_url | url? | Yes | Not validated server-side. |
| banner | url? | Yes | Not validated server-side. |
| birthday | date? | Yes | ISO-8601 (`YYYY-MM-DD`) format, year of `0001` or `0004` means hidden year. Birthdays set after 2020-02-10 use `0004` as a sentinel year, but both options are recognized as valid. |
| prefix | string? | Yes | **Deprecated.** Use `proxy_tags` instead. |
| suffix | string? | Yes | **Deprecated.** Use `proxy_tags` instead. |
| proxy_tags | ProxyTag[] | Yes (entire array) | An array of ProxyTag (see below) objects, each representing a single prefix/suffix pair. |
| keep_proxy | bool | Yes | Whether to display a member's proxy tags in the proxied message. |
| created | datetime | No |
| created | datetime | No | |
| privacy | string? | Yes | **Deprecated.** Use `<subject>_privacy` and `visibility` fields. |
| visibility | string? | Yes | Patching with `private` will set it to private; `public` or `null` will set it to public. |
| name_privacy | string? | Yes | Patching with `private` will set it to private; `public` or `null` will set it to public. |

View File

@ -38,6 +38,7 @@ Some arguments indicate the use of specific Discord features. These include:
- `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 <subject> <public|private>` - Changes your systems privacy settings.
- `pk;system tag [tag]` - Changes the system tag of your system.
@ -65,6 +66,7 @@ Some arguments indicate the use of specific Discord features. These include:
- `pk;member <member> description [description]` - Changes the description of a member.
- `pk;member <member> avatar [avatar url|@mention|upload]` - Changes the avatar of a member.
- `pk;member <member> serveravatar [avatar url|@mention|upload]` - Changes the avatar of a member in a specific server.
- `pk;member <name> banner [image url|upload]` - Changes the banner image of a member.
- `pk;member <member> privacy` - Displays a members current privacy settings.
- `pk;member <member> privacy <subject> <public|private>` - Changes a members privacy setting.
- `pk;member <member> proxy [tags]` - Changes the proxy tags of a member. use below add/remove commands for members with multiple tag pairs.
@ -91,6 +93,7 @@ Some arguments indicate the use of specific Discord features. These include:
- `pk;group <group> remove <member> [member 2] [member 3...]` - Removes one or more members from a group.
- `pk;group <group> privacy <description|icon|visibility|all> <public|private>` - Changes a group's privacy settings.
- `pk;group <group> icon [icon url|@mention|upload]` - Shows or changes a group's icon.
- `pk;group <group> banner [image url|upload]` - Shows or changes a group's banner image.
- `pk;group <group> delete` - Deletes a group.
## Switching commands