diff --git a/PluralKit.API/Controllers/v1/JsonModelExt.cs b/PluralKit.API/Controllers/v1/JsonModelExt.cs index e65401a2..28a2dc2f 100644 --- a/PluralKit.API/Controllers/v1/JsonModelExt.cs +++ b/PluralKit.API/Controllers/v1/JsonModelExt.cs @@ -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("description").NullIfEmpty().BoundsCheckField(Limits.MaxDescriptionLength, "System description"); if (o.ContainsKey("tag")) patch.Tag = o.Value("tag").NullIfEmpty().BoundsCheckField(Limits.MaxSystemTagLength, "System tag"); if (o.ContainsKey("avatar_url")) patch.AvatarUrl = o.Value("avatar_url").NullIfEmpty().BoundsCheckField(Limits.MaxUriLength, "System avatar URL"); + if (o.ContainsKey("banner")) patch.BannerImage = o.Value("banner").NullIfEmpty().BoundsCheckField(Limits.MaxUriLength, "System banner URL"); if (o.ContainsKey("tz")) patch.UiTz = o.Value("tz") ?? "UTC"; if (o.ContainsKey("description_privacy")) patch.DescriptionPrivacy = o.Value("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("color").NullIfEmpty()?.ToLower(); if (o.ContainsKey("display_name")) patch.DisplayName = o.Value("display_name").NullIfEmpty().BoundsCheckField(Limits.MaxMemberNameLength, "Member display name"); if (o.ContainsKey("avatar_url")) patch.AvatarUrl = o.Value("avatar_url").NullIfEmpty().BoundsCheckField(Limits.MaxUriLength, "Member avatar URL"); + if (o.ContainsKey("banner")) patch.BannerImage = o.Value("banner").NullIfEmpty().BoundsCheckField(Limits.MaxUriLength, "Member banner URL"); + if (o.ContainsKey("birthday")) { var str = o.Value("birthday").NullIfEmpty(); diff --git a/PluralKit.Bot/Commands/CommandTree.cs b/PluralKit.Bot/Commands/CommandTree.cs index 254f602a..6bea8129 100644 --- a/PluralKit.Bot/Commands/CommandTree.cs +++ b/PluralKit.Bot/Commands/CommandTree.cs @@ -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 birthday [birthday]", "Changes a member's birthday"); public static Command MemberProxy = new Command("member proxy", "member proxy [add|remove] [example proxy]", "Changes, adds, or removes a member's proxy tags"); public static Command MemberDelete = new Command("member delete", "member delete", "Deletes a member"); + public static Command MemberBannerImage = new Command("member banner", "member banner [url]", "Set the member's banner image"); public static Command MemberAvatar = new Command("member avatar", "member avatar [url|@mention]", "Changes a member's avatar"); public static Command MemberGroups = new Command("member group", "member group", "Shows the groups a member is in"); public static Command MemberGroupAdd = new Command("member group", "member group add [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 add [member 2] [member 3...]", "Adds one or more members to a group"); public static Command GroupRemove = new Command("group remove", "group remove [member 2] [member 3...]", "Removes one or more members from a group"); public static Command GroupPrivacy = new Command("group privacy", "group privacy ", "Changes a group's privacy settings"); + public static Command GroupBannerImage = new Command("group banner", "group banner [url]", "Set the group's banner image"); public static Command GroupIcon = new Command("group icon", "group icon [url|@mention]", "Changes a group's icon"); public static Command GroupDelete = new Command("group delete", "group delete", "Deletes a group"); public static Command GroupFrontPercent = new Command("group frontpercent", "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(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")) @@ -355,6 +360,8 @@ namespace PluralKit.Bot await ctx.Execute(MemberDelete, m => m.Delete(ctx, target)); else if (ctx.Match("avatar", "profile", "picture", "icon", "image", "pfp", "pic")) await ctx.Execute(MemberAvatar, m => m.Avatar(ctx, target)); + else if (ctx.Match("banner", "splash", "cover")) + await ctx.Execute(MemberBannerImage, m => m.BannerImage(ctx, target)); else if (ctx.Match("group", "groups")) if (ctx.Match("add", "a")) await ctx.Execute(MemberGroupAdd, m => m.AddRemove(ctx, target, Groups.AddRemoveOperation.Add)); @@ -422,6 +429,8 @@ namespace PluralKit.Bot await ctx.Execute(GroupDelete, g => g.DeleteGroup(ctx, target)); else if (ctx.Match("avatar", "picture", "icon", "image", "pic", "pfp")) await ctx.Execute(GroupIcon, g => g.GroupIcon(ctx, target)); + else if (ctx.Match("banner", "splash", "cover")) + await ctx.Execute(GroupBannerImage, g => g.GroupBannerImage(ctx, target)); else if (ctx.Match("fp", "frontpercent", "front%", "frontbreakdown")) await ctx.Execute(GroupFrontPercent, g => g.GroupFrontPercent(ctx, target)); else if (ctx.Match("color", "colour")) diff --git a/PluralKit.Bot/Commands/Groups.cs b/PluralKit.Bot/Commands/Groups.cs index 66d9090b..b727d567 100644 --- a/PluralKit.Bot/Commands/Groups.cs +++ b/PluralKit.Bot/Commands/Groups.cs @@ -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(); diff --git a/PluralKit.Bot/Commands/MemberEdit.cs b/PluralKit.Bot/Commands/MemberEdit.cs index 76ade3ba..ef31ad8e 100644 --- a/PluralKit.Bot/Commands/MemberEdit.cs +++ b/PluralKit.Bot/Commands/MemberEdit.cs @@ -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(); diff --git a/PluralKit.Bot/Commands/SystemEdit.cs b/PluralKit.Bot/Commands/SystemEdit.cs index 071f3b3f..da042646 100644 --- a/PluralKit.Bot/Commands/SystemEdit.cs +++ b/PluralKit.Bot/Commands/SystemEdit.cs @@ -218,7 +218,60 @@ namespace PluralKit.Bot else 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(); diff --git a/PluralKit.Bot/Services/EmbedService.cs b/PluralKit.Bot/Services/EmbedService.cs index 55592930..95306542 100644 --- a/PluralKit.Bot/Services/EmbedService.cs +++ b/PluralKit.Bot/Services/EmbedService.cs @@ -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)); diff --git a/PluralKit.Bot/Utils/AvatarUtils.cs b/PluralKit.Bot/Utils/AvatarUtils.cs index 6fc06c2b..ebde651f 100644 --- a/PluralKit.Bot/Utils/AvatarUtils.cs +++ b/PluralKit.Bot/Utils/AvatarUtils.cs @@ -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); } } diff --git a/PluralKit.Core/Database/Migrations/15.sql b/PluralKit.Core/Database/Migrations/15.sql new file mode 100644 index 00000000..5fca600f --- /dev/null +++ b/PluralKit.Core/Database/Migrations/15.sql @@ -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; diff --git a/PluralKit.Core/Database/Utils/DatabaseMigrator.cs b/PluralKit.Core/Database/Utils/DatabaseMigrator.cs index ea93938b..c299eda6 100644 --- a/PluralKit.Core/Database/Utils/DatabaseMigrator.cs +++ b/PluralKit.Core/Database/Utils/DatabaseMigrator.cs @@ -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) diff --git a/PluralKit.Core/Models/PKGroup.cs b/PluralKit.Core/Models/PKGroup.cs index a76290c3..018f945e 100644 --- a/PluralKit.Core/Models/PKGroup.cs +++ b/PluralKit.Core/Models/PKGroup.cs @@ -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; } diff --git a/PluralKit.Core/Models/PKMember.cs b/PluralKit.Core/Models/PKMember.cs index 2d464ced..8f4894cd 100644 --- a/PluralKit.Core/Models/PKMember.cs +++ b/PluralKit.Core/Models/PKMember.cs @@ -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; } diff --git a/PluralKit.Core/Models/PKSystem.cs b/PluralKit.Core/Models/PKSystem.cs index 88d42266..de059db6 100644 --- a/PluralKit.Core/Models/PKSystem.cs +++ b/PluralKit.Core/Models/PKSystem.cs @@ -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; } diff --git a/PluralKit.Core/Models/Patch/GroupPatch.cs b/PluralKit.Core/Models/Patch/GroupPatch.cs index b6d7dc35..68b6477f 100644 --- a/PluralKit.Core/Models/Patch/GroupPatch.cs +++ b/PluralKit.Core/Models/Patch/GroupPatch.cs @@ -10,6 +10,7 @@ namespace PluralKit.Core public Partial DisplayName { get; set; } public Partial Description { get; set; } public Partial Icon { get; set; } + public Partial BannerImage { get; set; } public Partial Color { get; set; } public Partial 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"); } diff --git a/PluralKit.Core/Models/Patch/MemberPatch.cs b/PluralKit.Core/Models/Patch/MemberPatch.cs index cd7054ed..8a528c38 100644 --- a/PluralKit.Core/Models/Patch/MemberPatch.cs +++ b/PluralKit.Core/Models/Patch/MemberPatch.cs @@ -11,6 +11,7 @@ namespace PluralKit.Core public Partial Hid { get; set; } public Partial DisplayName { get; set; } public Partial AvatarUrl { get; set; } + public Partial BannerImage { get; set; } public Partial Color { get; set; } public Partial Birthday { get; set; } public Partial 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"); } diff --git a/PluralKit.Core/Models/Patch/SystemPatch.cs b/PluralKit.Core/Models/Patch/SystemPatch.cs index 38e122d0..9c9fdaf6 100644 --- a/PluralKit.Core/Models/Patch/SystemPatch.cs +++ b/PluralKit.Core/Models/Patch/SystemPatch.cs @@ -10,6 +10,7 @@ namespace PluralKit.Core public Partial Description { get; set; } public Partial Tag { get; set; } public Partial AvatarUrl { get; set; } + public Partial BannerImage { get; set; } public Partial Color { get; set; } public Partial Token { get; set; } public Partial 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"); } diff --git a/docs/content/api-documentation.md b/docs/content/api-documentation.md index 58b519f3..1c59802b 100644 --- a/docs/content/api-documentation.md +++ b/docs/content/api-documentation.md @@ -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 `_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. | diff --git a/docs/content/command-list.md b/docs/content/command-list.md index 59e06042..601749f4 100644 --- a/docs/content/command-list.md +++ b/docs/content/command-list.md @@ -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 ` - 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 description [description]` - Changes the description of a member. - `pk;member avatar [avatar url|@mention|upload]` - Changes the avatar of a member. - `pk;member serveravatar [avatar url|@mention|upload]` - Changes the avatar of a member in a specific server. +- `pk;member banner [image url|upload]` - Changes the banner image of a member. - `pk;member privacy` - Displays a members current privacy settings. - `pk;member privacy ` - Changes a members privacy setting. - `pk;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 remove [member 2] [member 3...]` - Removes one or more members from a group. - `pk;group privacy ` - Changes a group's privacy settings. - `pk;group icon [icon url|@mention|upload]` - Shows or changes a group's icon. +- `pk;group banner [image url|upload]` - Shows or changes a group's banner image. - `pk;group delete` - Deletes a group. ## Switching commands