From 860fd824655bbeee69eca35894a9495fdfbf3860 Mon Sep 17 00:00:00 2001 From: drobbins329 Date: Fri, 6 Aug 2021 11:58:37 -0400 Subject: [PATCH 1/3] Application webhooks (#86) * Added webhook components for hooks having an application ID. --- src/Discord.Net.Core/Discord.Net.Core.xml | 5 +++++ src/Discord.Net.Core/Entities/Webhooks/IWebhook.cs | 7 ++++++- src/Discord.Net.Rest/API/Common/Webhook.cs | 2 ++ .../API/Rest/ModifyWebhookMessageParams.cs | 2 ++ src/Discord.Net.Rest/Discord.Net.Rest.xml | 3 +++ src/Discord.Net.Rest/Entities/Webhooks/RestWebhook.cs | 4 ++++ src/Discord.Net.Rest/Extensions/EntityExtensions.cs | 1 + src/Discord.Net.Webhook/Discord.Net.Webhook.xml | 7 ++++++- src/Discord.Net.Webhook/DiscordWebhookClient.cs | 4 ++-- .../Entities/Messages/WebhookMessageProperties.cs | 4 ++++ .../Entities/Webhooks/RestInternalWebhook.cs | 3 +++ src/Discord.Net.Webhook/WebhookClientHelper.cs | 7 +++++-- 12 files changed, 43 insertions(+), 6 deletions(-) diff --git a/src/Discord.Net.Core/Discord.Net.Core.xml b/src/Discord.Net.Core/Discord.Net.Core.xml index ca9e9696f..13bbc8eb5 100644 --- a/src/Discord.Net.Core/Discord.Net.Core.xml +++ b/src/Discord.Net.Core/Discord.Net.Core.xml @@ -10089,6 +10089,11 @@ Gets the user that created this webhook. + + + Gets the ID of the application owning this webhook. + + Modifies this webhook. diff --git a/src/Discord.Net.Core/Entities/Webhooks/IWebhook.cs b/src/Discord.Net.Core/Entities/Webhooks/IWebhook.cs index b2d017316..d5bc70d71 100644 --- a/src/Discord.Net.Core/Entities/Webhooks/IWebhook.cs +++ b/src/Discord.Net.Core/Entities/Webhooks/IWebhook.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Threading.Tasks; namespace Discord @@ -49,6 +49,11 @@ namespace Discord /// IUser Creator { get; } + /// + /// Gets the ID of the application owning this webhook. + /// + ulong? ApplicationId { get; } + /// /// Modifies this webhook. /// diff --git a/src/Discord.Net.Rest/API/Common/Webhook.cs b/src/Discord.Net.Rest/API/Common/Webhook.cs index cbd5fdad5..ff1dca9bd 100644 --- a/src/Discord.Net.Rest/API/Common/Webhook.cs +++ b/src/Discord.Net.Rest/API/Common/Webhook.cs @@ -21,5 +21,7 @@ namespace Discord.API [JsonProperty("user")] public Optional Creator { get; set; } + [JsonProperty("application_id")] + public Optional ApplicationId { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Rest/ModifyWebhookMessageParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyWebhookMessageParams.cs index ba8fcbb4e..8298ff19c 100644 --- a/src/Discord.Net.Rest/API/Rest/ModifyWebhookMessageParams.cs +++ b/src/Discord.Net.Rest/API/Rest/ModifyWebhookMessageParams.cs @@ -12,5 +12,7 @@ namespace Discord.API.Rest public Optional Embeds { get; set; } [JsonProperty("allowed_mentions")] public Optional AllowedMentions { get; set; } + [JsonProperty("components")] + public Optional Components { get; set; } } } diff --git a/src/Discord.Net.Rest/Discord.Net.Rest.xml b/src/Discord.Net.Rest/Discord.Net.Rest.xml index c8865f786..e92b92725 100644 --- a/src/Discord.Net.Rest/Discord.Net.Rest.xml +++ b/src/Discord.Net.Rest/Discord.Net.Rest.xml @@ -4766,6 +4766,9 @@ + + + diff --git a/src/Discord.Net.Rest/Entities/Webhooks/RestWebhook.cs b/src/Discord.Net.Rest/Entities/Webhooks/RestWebhook.cs index 9baddf003..0d24f08df 100644 --- a/src/Discord.Net.Rest/Entities/Webhooks/RestWebhook.cs +++ b/src/Discord.Net.Rest/Entities/Webhooks/RestWebhook.cs @@ -24,6 +24,8 @@ namespace Discord.Rest public ulong? GuildId { get; private set; } /// public IUser Creator { get; private set; } + /// + public ulong? ApplicationId { get; private set; } /// public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); @@ -66,6 +68,8 @@ namespace Discord.Rest GuildId = model.GuildId.Value; if (model.Name.IsSpecified) Name = model.Name.Value; + if (model.ApplicationId.IsSpecified) + ApplicationId = model.ApplicationId.Value; } /// diff --git a/src/Discord.Net.Rest/Extensions/EntityExtensions.cs b/src/Discord.Net.Rest/Extensions/EntityExtensions.cs index f8676c783..0c2e0f8c2 100644 --- a/src/Discord.Net.Rest/Extensions/EntityExtensions.cs +++ b/src/Discord.Net.Rest/Extensions/EntityExtensions.cs @@ -68,6 +68,7 @@ namespace Discord.Rest model.Video = entity.Video.Value.ToModel(); return model; } + public static API.AllowedMentions ToModel(this AllowedMentions entity) { return new API.AllowedMentions() diff --git a/src/Discord.Net.Webhook/Discord.Net.Webhook.xml b/src/Discord.Net.Webhook/Discord.Net.Webhook.xml index f629c2c29..d1bafb9a3 100644 --- a/src/Discord.Net.Webhook/Discord.Net.Webhook.xml +++ b/src/Discord.Net.Webhook/Discord.Net.Webhook.xml @@ -31,7 +31,7 @@ Thrown if the is an invalid format. Thrown if the is null or whitespace. - + Sends a message to the channel for this webhook. Returns the ID of the created message. @@ -99,6 +99,11 @@ Gets or sets the allowed mentions of the message. + + + Gets or sets the components that the message should display. + + Could not find a webhook with the supplied credentials. diff --git a/src/Discord.Net.Webhook/DiscordWebhookClient.cs b/src/Discord.Net.Webhook/DiscordWebhookClient.cs index 91d077411..d4affb08b 100644 --- a/src/Discord.Net.Webhook/DiscordWebhookClient.cs +++ b/src/Discord.Net.Webhook/DiscordWebhookClient.cs @@ -88,8 +88,8 @@ namespace Discord.Webhook /// Sends a message to the channel for this webhook. /// Returns the ID of the created message. public Task SendMessageAsync(string text = null, bool isTTS = false, IEnumerable embeds = null, - string username = null, string avatarUrl = null, RequestOptions options = null, AllowedMentions allowedMentions = null) - => WebhookClientHelper.SendMessageAsync(this, text, isTTS, embeds, username, avatarUrl, allowedMentions, options); + string username = null, string avatarUrl = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageComponent component = null) + => WebhookClientHelper.SendMessageAsync(this, text, isTTS, embeds, username, avatarUrl, allowedMentions, options, component); /// /// Modifies a message posted using this webhook. diff --git a/src/Discord.Net.Webhook/Entities/Messages/WebhookMessageProperties.cs b/src/Discord.Net.Webhook/Entities/Messages/WebhookMessageProperties.cs index dec7b6e3b..ca2ff10a0 100644 --- a/src/Discord.Net.Webhook/Entities/Messages/WebhookMessageProperties.cs +++ b/src/Discord.Net.Webhook/Entities/Messages/WebhookMessageProperties.cs @@ -22,5 +22,9 @@ namespace Discord.Webhook /// Gets or sets the allowed mentions of the message. /// public Optional AllowedMentions { get; set; } + /// + /// Gets or sets the components that the message should display. + /// + public Optional Components { get; set; } } } diff --git a/src/Discord.Net.Webhook/Entities/Webhooks/RestInternalWebhook.cs b/src/Discord.Net.Webhook/Entities/Webhooks/RestInternalWebhook.cs index bbb160fcd..210d8eda0 100644 --- a/src/Discord.Net.Webhook/Entities/Webhooks/RestInternalWebhook.cs +++ b/src/Discord.Net.Webhook/Entities/Webhooks/RestInternalWebhook.cs @@ -17,6 +17,7 @@ namespace Discord.Webhook public string Name { get; private set; } public string AvatarId { get; private set; } public ulong? GuildId { get; private set; } + public ulong? ApplicationId { get; private set; } public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); @@ -44,6 +45,8 @@ namespace Discord.Webhook GuildId = model.GuildId.Value; if (model.Name.IsSpecified) Name = model.Name.Value; + if (model.ApplicationId.IsSpecified) + ApplicationId = model.ApplicationId.Value; } public string GetAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128) diff --git a/src/Discord.Net.Webhook/WebhookClientHelper.cs b/src/Discord.Net.Webhook/WebhookClientHelper.cs index 528848f7f..6e3651323 100644 --- a/src/Discord.Net.Webhook/WebhookClientHelper.cs +++ b/src/Discord.Net.Webhook/WebhookClientHelper.cs @@ -21,7 +21,7 @@ namespace Discord.Webhook return RestInternalWebhook.Create(client, model); } public static async Task SendMessageAsync(DiscordWebhookClient client, - string text, bool isTTS, IEnumerable embeds, string username, string avatarUrl, AllowedMentions allowedMentions, RequestOptions options) + string text, bool isTTS, IEnumerable embeds, string username, string avatarUrl, AllowedMentions allowedMentions, RequestOptions options, MessageComponent component) { var args = new CreateWebhookMessageParams { @@ -37,6 +37,8 @@ namespace Discord.Webhook args.AvatarUrl = avatarUrl; if (allowedMentions != null) args.AllowedMentions = allowedMentions.ToModel(); + if (component != null) + args.Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray(); var model = await client.ApiClient.CreateWebhookMessageAsync(client.Webhook.Id, args, options: options).ConfigureAwait(false); return model.Id; @@ -83,7 +85,8 @@ namespace Discord.Webhook : Optional.Create(), AllowedMentions = args.AllowedMentions.IsSpecified ? args.AllowedMentions.Value.ToModel() - : Optional.Create() + : Optional.Create(), + Components = args.Components.IsSpecified ? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() : Optional.Unspecified, }; await client.ApiClient.ModifyWebhookMessageAsync(client.Webhook.Id, messageId, apiArgs, options) From e6efead6a1c60e9cbc31f4ac5bd3dfb2c9b98ddd Mon Sep 17 00:00:00 2001 From: d4n3436 Date: Fri, 6 Aug 2021 19:46:58 -0500 Subject: [PATCH 2/3] Fix tests (#90) --- test/Discord.Net.Tests.Unit/GuildPermissionsTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/Discord.Net.Tests.Unit/GuildPermissionsTests.cs b/test/Discord.Net.Tests.Unit/GuildPermissionsTests.cs index 137dc5575..bb841fd1b 100644 --- a/test/Discord.Net.Tests.Unit/GuildPermissionsTests.cs +++ b/test/Discord.Net.Tests.Unit/GuildPermissionsTests.cs @@ -91,7 +91,7 @@ namespace Discord AssertFlag(() => new GuildPermissions(manageNicknames: true), GuildPermission.ManageNicknames); AssertFlag(() => new GuildPermissions(manageRoles: true), GuildPermission.ManageRoles); AssertFlag(() => new GuildPermissions(manageWebhooks: true), GuildPermission.ManageWebhooks); - AssertFlag(() => new GuildPermissions(manageEmojis: true), GuildPermission.ManageEmojis); + AssertFlag(() => new GuildPermissions(manageEmojis: true), GuildPermission.ManageEmojisAndStickers); } /// @@ -161,7 +161,7 @@ namespace Discord AssertUtil(GuildPermission.ManageNicknames, x => x.ManageNicknames, (p, enable) => p.Modify(manageNicknames: enable)); AssertUtil(GuildPermission.ManageRoles, x => x.ManageRoles, (p, enable) => p.Modify(manageRoles: enable)); AssertUtil(GuildPermission.ManageWebhooks, x => x.ManageWebhooks, (p, enable) => p.Modify(manageWebhooks: enable)); - AssertUtil(GuildPermission.ManageEmojis, x => x.ManageEmojisAndStickers, (p, enable) => p.Modify(manageEmojis: enable)); + AssertUtil(GuildPermission.ManageEmojisAndStickers, x => x.ManageEmojisAndStickers, (p, enable) => p.Modify(manageEmojis: enable)); } } } From af9519b0f57e511d15aae0dd26d922d8b3f1cecd Mon Sep 17 00:00:00 2001 From: Nikon <47792796+INikonI@users.noreply.github.com> Date: Sat, 7 Aug 2021 23:57:53 +0500 Subject: [PATCH 3/3] Add banner and accent color to user and some fixes/improvements (#81) * Add banner and accent color to user and some fixes * Fix * Fix! * increase size of user banners to 256 * Some changes and mini refactor of color class * add constant maxDecimalValue to color and checks with exceptions * add `NotSupportedException` for `BannerId` and `AccentColor` in `SocketWebhookUser` * Update ComponentBuilder.cs - `MaxLabelLength` from `ComponentBuilder` moved to `ButtonBuilder` - Added `MaxLabelLength` for `SelectMenuOptionBuilder` - Changed `MaxDescriptionLength` to 100 --- src/Discord.Net.Core/CDN.cs | 18 ++++ .../Message Components/ComponentBuilder.cs | 33 +++--- .../Entities/Permissions/GuildPermissions.cs | 8 +- src/Discord.Net.Core/Entities/Roles/Color.cs | 101 ++++++++++-------- src/Discord.Net.Core/Entities/Users/IUser.cs | 32 +++++- src/Discord.Net.Rest/API/Common/User.cs | 4 + .../Entities/Users/RestThreadUser.cs | 4 +- .../Entities/Users/RestUser.cs | 12 +++ .../Entities/Users/SocketGlobalUser.cs | 4 +- .../Entities/Users/SocketGroupUser.cs | 4 + .../Entities/Users/SocketGuildUser.cs | 7 +- .../Entities/Users/SocketSelfUser.cs | 4 + .../Entities/Users/SocketThreadUser.cs | 16 ++- .../Entities/Users/SocketUnknownUser.cs | 9 +- .../Entities/Users/SocketUser.cs | 18 ++++ .../Entities/Users/SocketWebhookUser.cs | 17 +++ .../GuildPermissionsTests.cs | 4 +- 17 files changed, 222 insertions(+), 73 deletions(-) diff --git a/src/Discord.Net.Core/CDN.cs b/src/Discord.Net.Core/CDN.cs index e1e8e5e1a..b1879eebc 100644 --- a/src/Discord.Net.Core/CDN.cs +++ b/src/Discord.Net.Core/CDN.cs @@ -46,6 +46,24 @@ namespace Discord string extension = FormatToExtension(format, avatarId); return $"{DiscordConfig.CDNUrl}avatars/{userId}/{avatarId}.{extension}?size={size}"; } + + /// + /// Returns a user banner URL. + /// + /// The user snowflake identifier. + /// The banner identifier. + /// The size of the image to return in horizontal pixels. This can be any power of two between 16 and 2048. + /// The format to return. + /// + /// A URL pointing to the user's banner in the specified size. + /// + public static string GetUserBannerUrl(ulong userId, string bannerId, ushort size, ImageFormat format) + { + if (bannerId == null) + return null; + string extension = FormatToExtension(format, bannerId); + return $"{DiscordConfig.CDNUrl}banners/{userId}/{bannerId}.{extension}?size={size}"; + } /// /// Returns the default user avatar URL. /// diff --git a/src/Discord.Net.Core/Entities/Interactions/Message Components/ComponentBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/Message Components/ComponentBuilder.cs index bb2f80a81..4027db408 100644 --- a/src/Discord.Net.Core/Entities/Interactions/Message Components/ComponentBuilder.cs +++ b/src/Discord.Net.Core/Entities/Interactions/Message Components/ComponentBuilder.cs @@ -10,11 +10,6 @@ namespace Discord /// public class ComponentBuilder { - /// - /// The max length of a . - /// - public const int MaxLabelLength = 80; - /// /// The max length of a . /// @@ -307,17 +302,22 @@ namespace Discord /// public class ButtonBuilder { + /// + /// The max length of a . + /// + public const int MaxLabelLength = 80; + /// /// Gets or sets the label of the current button. /// - /// length exceeds . + /// length exceeds . public string Label { get => _label; set { - if (value != null && value.Length > ComponentBuilder.MaxLabelLength) - throw new ArgumentException(message: $"Button label must be {ComponentBuilder.MaxLabelLength} characters or less!", paramName: nameof(Label)); + if (value != null && value.Length > MaxLabelLength) + throw new ArgumentException(message: $"Button label must be {MaxLabelLength} characters or less!", paramName: nameof(Label)); _label = value; } @@ -539,8 +539,8 @@ namespace Discord if (string.IsNullOrEmpty(this.Url)) throw new InvalidOperationException("Link buttons must have a link associated with them"); else - UrlValidation.Validate(this.Url); - } + UrlValidation.Validate(this.Url); + } else if (string.IsNullOrEmpty(this.CustomId)) throw new InvalidOperationException("Non-link buttons must have a custom id associated with them"); @@ -831,23 +831,28 @@ namespace Discord /// public class SelectMenuOptionBuilder { + /// + /// The maximum length of a . + /// + public const int MaxLabelLength = 100; + /// /// The maximum length of a . /// - public const int MaxDescriptionLength = 50; + public const int MaxDescriptionLength = 100; /// /// Gets or sets the label of the current select menu. /// - /// length exceeds + /// length exceeds public string Label { get => _label; set { if (value != null) - if (value.Length > ComponentBuilder.MaxLabelLength) - throw new ArgumentException(message: $"Button label must be {ComponentBuilder.MaxLabelLength} characters or less!", paramName: nameof(Label)); + if (value.Length > MaxLabelLength) + throw new ArgumentException(message: $"Button label must be {MaxLabelLength} characters or less!", paramName: nameof(Label)); _label = value; } diff --git a/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs b/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs index 1914a6f86..d15297627 100644 --- a/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs +++ b/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs @@ -192,7 +192,7 @@ namespace Discord bool manageNicknames = false, bool manageRoles = false, bool manageWebhooks = false, - bool manageEmojis = false) + bool manageEmojisAndStickers = false) : this(0, createInstantInvite: createInstantInvite, manageRoles: manageRoles, @@ -224,7 +224,7 @@ namespace Discord changeNickname: changeNickname, manageNicknames: manageNicknames, manageWebhooks: manageWebhooks, - manageEmojisAndStickers: manageEmojis) + manageEmojisAndStickers: manageEmojisAndStickers) { } /// Creates a new from this one, changing the provided non-null permissions. @@ -259,11 +259,11 @@ namespace Discord bool? manageNicknames = null, bool? manageRoles = null, bool? manageWebhooks = null, - bool? manageEmojis = null) + bool? manageEmojisAndStickers = null) => new GuildPermissions(RawValue, createInstantInvite, kickMembers, banMembers, administrator, manageChannels, manageGuild, addReactions, viewAuditLog, viewGuildInsights, viewChannel, sendMessages, sendTTSMessages, manageMessages, embedLinks, attachFiles, readMessageHistory, mentionEveryone, useExternalEmojis, connect, speak, muteMembers, deafenMembers, moveMembers, - useVoiceActivation, prioritySpeaker, stream, changeNickname, manageNicknames, manageRoles, manageWebhooks, manageEmojis); + useVoiceActivation, prioritySpeaker, stream, changeNickname, manageNicknames, manageRoles, manageWebhooks, manageEmojisAndStickers); /// /// Returns a value that indicates if a specific is enabled diff --git a/src/Discord.Net.Core/Entities/Roles/Color.cs b/src/Discord.Net.Core/Entities/Roles/Color.cs index 7c2d152a4..ee50710e8 100644 --- a/src/Discord.Net.Core/Entities/Roles/Color.cs +++ b/src/Discord.Net.Core/Entities/Roles/Color.cs @@ -10,68 +10,70 @@ namespace Discord [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public struct Color { + /// Gets the max decimal value of color. + public const uint MaxDecimalValue = 0xFFFFFF; /// Gets the default user color value. - public static readonly Color Default = new Color(0); + public static readonly Color Default = new(0); /// Gets the teal color value. /// A color struct with the hex value of 1ABC9C. - public static readonly Color Teal = new Color(0x1ABC9C); + public static readonly Color Teal = new(0x1ABC9C); /// Gets the dark teal color value. /// A color struct with the hex value of 11806A. - public static readonly Color DarkTeal = new Color(0x11806A); + public static readonly Color DarkTeal = new(0x11806A); /// Gets the green color value. /// A color struct with the hex value of 2ECC71. - public static readonly Color Green = new Color(0x2ECC71); + public static readonly Color Green = new(0x2ECC71); /// Gets the dark green color value. /// A color struct with the hex value of 1F8B4C. - public static readonly Color DarkGreen = new Color(0x1F8B4C); + public static readonly Color DarkGreen = new(0x1F8B4C); /// Gets the blue color value. /// A color struct with the hex value of 3498DB. - public static readonly Color Blue = new Color(0x3498DB); + public static readonly Color Blue = new(0x3498DB); /// Gets the dark blue color value. /// A color struct with the hex value of 206694. - public static readonly Color DarkBlue = new Color(0x206694); + public static readonly Color DarkBlue = new(0x206694); /// Gets the purple color value. /// A color struct with the hex value of 9B59B6. - public static readonly Color Purple = new Color(0x9B59B6); + public static readonly Color Purple = new(0x9B59B6); /// Gets the dark purple color value. /// A color struct with the hex value of 71368A. - public static readonly Color DarkPurple = new Color(0x71368A); + public static readonly Color DarkPurple = new(0x71368A); /// Gets the magenta color value. /// A color struct with the hex value of E91E63. - public static readonly Color Magenta = new Color(0xE91E63); + public static readonly Color Magenta = new(0xE91E63); /// Gets the dark magenta color value. /// A color struct with the hex value of AD1457. - public static readonly Color DarkMagenta = new Color(0xAD1457); + public static readonly Color DarkMagenta = new(0xAD1457); /// Gets the gold color value. /// A color struct with the hex value of F1C40F. - public static readonly Color Gold = new Color(0xF1C40F); + public static readonly Color Gold = new(0xF1C40F); /// Gets the light orange color value. /// A color struct with the hex value of C27C0E. - public static readonly Color LightOrange = new Color(0xC27C0E); + public static readonly Color LightOrange = new(0xC27C0E); /// Gets the orange color value. /// A color struct with the hex value of E67E22. - public static readonly Color Orange = new Color(0xE67E22); + public static readonly Color Orange = new(0xE67E22); /// Gets the dark orange color value. /// A color struct with the hex value of A84300. - public static readonly Color DarkOrange = new Color(0xA84300); + public static readonly Color DarkOrange = new(0xA84300); /// Gets the red color value. /// A color struct with the hex value of E74C3C. - public static readonly Color Red = new Color(0xE74C3C); + public static readonly Color Red = new(0xE74C3C); /// Gets the dark red color value. /// A color struct with the hex value of 992D22. - public static readonly Color DarkRed = new Color(0x992D22); + public static readonly Color DarkRed = new(0x992D22); /// Gets the light grey color value. /// A color struct with the hex value of 979C9F. - public static readonly Color LightGrey = new Color(0x979C9F); + public static readonly Color LightGrey = new(0x979C9F); /// Gets the lighter grey color value. /// A color struct with the hex value of 95A5A6. - public static readonly Color LighterGrey = new Color(0x95A5A6); + public static readonly Color LighterGrey = new(0x95A5A6); /// Gets the dark grey color value. /// A color struct with the hex value of 607D8B. - public static readonly Color DarkGrey = new Color(0x607D8B); + public static readonly Color DarkGrey = new(0x607D8B); /// Gets the darker grey color value. /// A color struct with the hex value of 546E7A. - public static readonly Color DarkerGrey = new Color(0x546E7A); + public static readonly Color DarkerGrey = new(0x546E7A); /// Gets the encoded value for this color. /// @@ -91,22 +93,27 @@ namespace Discord /// Initializes a struct with the given raw value. /// /// - /// The following will create a color that has a hex value of + /// The following will create a color that has a hex value of /// #607D8B. /// /// Color darkGrey = new Color(0x607D8B); /// /// /// The raw value of the color (e.g. 0x607D8B). + /// Value exceeds . public Color(uint rawValue) { + if (rawValue > MaxDecimalValue) + throw new ArgumentException($"{nameof(RawValue)} of color cannot be greater than {MaxDecimalValue}!", nameof(rawValue)); + RawValue = rawValue; } + /// /// Initializes a struct with the given RGB bytes. /// /// - /// The following will create a color that has a value of + /// The following will create a color that has a value of /// #607D8B. /// /// Color darkGrey = new Color((byte)0b_01100000, (byte)0b_01111101, (byte)0b_10001011); @@ -115,19 +122,24 @@ namespace Discord /// The byte that represents the red color. /// The byte that represents the green color. /// The byte that represents the blue color. + /// Value exceeds . public Color(byte r, byte g, byte b) { - RawValue = - ((uint)r << 16) | - ((uint)g << 8) | - (uint)b; + uint value = ((uint)r << 16) + | ((uint)g << 8) + | (uint)b; + + if (value > MaxDecimalValue) + throw new ArgumentException($"{nameof(RawValue)} of color cannot be greater than {MaxDecimalValue}!"); + + RawValue = value; } /// /// Initializes a struct with the given RGB value. /// /// - /// The following will create a color that has a value of + /// The following will create a color that has a value of /// #607D8B. /// /// Color darkGrey = new Color(96, 125, 139); @@ -145,16 +157,15 @@ namespace Discord throw new ArgumentOutOfRangeException(nameof(g), "Value must be within [0,255]."); if (b < 0 || b > 255) throw new ArgumentOutOfRangeException(nameof(b), "Value must be within [0,255]."); - RawValue = - ((uint)r << 16) | - ((uint)g << 8) | - (uint)b; + RawValue = ((uint)r << 16) + | ((uint)g << 8) + | (uint)b; } /// /// Initializes a struct with the given RGB float value. /// /// - /// The following will create a color that has a value of + /// The following will create a color that has a value of /// #607c8c. /// /// Color darkGrey = new Color(0.38f, 0.49f, 0.55f); @@ -172,10 +183,9 @@ namespace Discord throw new ArgumentOutOfRangeException(nameof(g), "Value must be within [0,1]."); if (b < 0.0f || b > 1.0f) throw new ArgumentOutOfRangeException(nameof(b), "Value must be within [0,1]."); - RawValue = - ((uint)(r * 255.0f) << 16) | - ((uint)(g * 255.0f) << 8) | - (uint)(b * 255.0f); + RawValue = ((uint)(r * 255.0f) << 16) + | ((uint)(g * 255.0f) << 8) + | (uint)(b * 255.0f); } public static bool operator ==(Color lhs, Color rhs) @@ -184,15 +194,22 @@ namespace Discord public static bool operator !=(Color lhs, Color rhs) => lhs.RawValue != rhs.RawValue; + public static implicit operator Color(uint rawValue) + => new(rawValue); + + public static implicit operator uint(Color color) + => color.RawValue; + public override bool Equals(object obj) - => (obj is Color c && RawValue == c.RawValue); + => obj is Color c && RawValue == c.RawValue; public override int GetHashCode() => RawValue.GetHashCode(); - public static implicit operator StandardColor(Color color) => - StandardColor.FromArgb((int)color.RawValue); - public static explicit operator Color(StandardColor color) => - new Color((uint)color.ToArgb() << 8 >> 8); + public static implicit operator StandardColor(Color color) + => StandardColor.FromArgb((int)color.RawValue); + + public static explicit operator Color(StandardColor color) + => new((uint)color.ToArgb() << 8 >> 8); /// /// Gets the hexadecimal representation of the color (e.g. #000ccc). diff --git a/src/Discord.Net.Core/Entities/Users/IUser.cs b/src/Discord.Net.Core/Entities/Users/IUser.cs index 9596a8338..f265bb938 100644 --- a/src/Discord.Net.Core/Entities/Users/IUser.cs +++ b/src/Discord.Net.Core/Entities/Users/IUser.cs @@ -12,17 +12,29 @@ namespace Discord /// string AvatarId { get; } /// + /// Gets the identifier of this user's banner. + /// + string BannerId { get; } + /// + /// Gets the user's banner color. + /// + /// + /// A struct representing the accent color of this user's banner. + /// + Color? AccentColor { get; } + /// /// Gets the avatar URL for this user. /// /// /// This property retrieves a URL for this user's avatar. In event that the user does not have a valid avatar - /// (i.e. their avatar identifier is not set), this property will return null. If you wish to + /// (i.e. their avatar identifier is not set), this method will return null. If you wish to /// retrieve the default avatar for this user, consider using (see /// example). /// /// - /// The following example attempts to retrieve the user's current avatar and send it to a channel; if one is - /// not set, a default avatar for this user will be returned instead. + /// The following example attempts to retrieve the user's current avatar and send it to a channel; if one is + /// not set, a default avatar for this user will be returned instead. /// /// @@ -34,6 +46,16 @@ namespace Discord /// string GetAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128); /// + /// Gets the banner URL for this user. + /// + /// The format to return. + /// The size of the image to return in. This can be any power of two between 16 and 2048. + /// + /// + /// A string representing the user's avatar URL; null if the user does not have an banner in place. + /// + string GetBannerUrl(ImageFormat format = ImageFormat.Auto, ushort size = 256); + /// /// Gets the default avatar URL for this user. /// /// @@ -93,8 +115,8 @@ namespace Discord /// This method is used to obtain or create a channel used to send a direct message. /// /// In event that the current user cannot send a message to the target user, a channel can and will - /// still be created by Discord. However, attempting to send a message will yield a - /// with a 403 as its + /// still be created by Discord. However, attempting to send a message will yield a + /// with a 403 as its /// . There are currently no official workarounds by /// Discord. /// diff --git a/src/Discord.Net.Rest/API/Common/User.cs b/src/Discord.Net.Rest/API/Common/User.cs index d1f436afb..4d1b5b2b7 100644 --- a/src/Discord.Net.Rest/API/Common/User.cs +++ b/src/Discord.Net.Rest/API/Common/User.cs @@ -15,6 +15,10 @@ namespace Discord.API public Optional Bot { get; set; } [JsonProperty("avatar")] public Optional Avatar { get; set; } + [JsonProperty("banner")] + public Optional Banner { get; set; } + [JsonProperty("accent_color")] + public Optional AccentColor { get; set; } //CurrentUser [JsonProperty("verified")] diff --git a/src/Discord.Net.Rest/Entities/Users/RestThreadUser.cs b/src/Discord.Net.Rest/Entities/Users/RestThreadUser.cs index d74591e75..bb981bfdb 100644 --- a/src/Discord.Net.Rest/Entities/Users/RestThreadUser.cs +++ b/src/Discord.Net.Rest/Entities/Users/RestThreadUser.cs @@ -9,7 +9,7 @@ using Model = Discord.API.ThreadMember; namespace Discord.Rest { /// - /// Represents a thread user recieved over the REST api. + /// Represents a thread user received over the REST api. /// public class RestThreadUser : RestEntity { @@ -51,7 +51,7 @@ namespace Discord.Rest /// Gets the guild user for this thread user. /// /// - /// A task representing the asyncronous get operation. The task returns a + /// A task representing the asynchronous get operation. The task returns a /// that represents the current thread user. /// public Task GetGuildUser() diff --git a/src/Discord.Net.Rest/Entities/Users/RestUser.cs b/src/Discord.Net.Rest/Entities/Users/RestUser.cs index 7bc1447fe..618804fef 100644 --- a/src/Discord.Net.Rest/Entities/Users/RestUser.cs +++ b/src/Discord.Net.Rest/Entities/Users/RestUser.cs @@ -22,6 +22,10 @@ namespace Discord.Rest /// public string AvatarId { get; private set; } /// + public string BannerId { get; private set; } + /// + public Color? AccentColor { get; private set; } + /// public UserProperties? PublicFlags { get; private set; } /// @@ -61,6 +65,10 @@ namespace Discord.Rest { if (model.Avatar.IsSpecified) AvatarId = model.Avatar.Value; + if (model.Banner.IsSpecified) + BannerId = model.Banner.Value; + if (model.AccentColor.IsSpecified) + AccentColor = model.AccentColor.Value; if (model.Discriminator.IsSpecified) DiscriminatorValue = ushort.Parse(model.Discriminator.Value, NumberStyles.None, CultureInfo.InvariantCulture); if (model.Bot.IsSpecified) @@ -92,6 +100,10 @@ namespace Discord.Rest public string GetAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128) => CDN.GetUserAvatarUrl(Id, AvatarId, size, format); + /// + public string GetBannerUrl(ImageFormat format = ImageFormat.Auto, ushort size = 256) + => CDN.GetUserBannerUrl(Id, BannerId, size, format); + /// public string GetDefaultAvatarUrl() => CDN.GetDefaultUserAvatarUrl(DiscriminatorValue); diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketGlobalUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketGlobalUser.cs index 15c5182fc..b1bce5934 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketGlobalUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketGlobalUser.cs @@ -12,6 +12,8 @@ namespace Discord.WebSocket public override string Username { get; internal set; } public override ushort DiscriminatorValue { get; internal set; } public override string AvatarId { get; internal set; } + public override string BannerId { get; internal set; } + public override Color? AccentColor { get; internal set; } internal override SocketPresence Presence { get; set; } public override bool IsWebhook => false; @@ -47,7 +49,7 @@ namespace Discord.WebSocket discord.RemoveUser(Id); } } - + internal void Update(ClientState state, PresenceModel model) { Presence = SocketPresence.Create(model); diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketGroupUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketGroupUser.cs index 805a88110..d99310540 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketGroupUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketGroupUser.cs @@ -29,6 +29,10 @@ namespace Discord.WebSocket /// public override string AvatarId { get { return GlobalUser.AvatarId; } internal set { GlobalUser.AvatarId = value; } } /// + public override string BannerId { get { return GlobalUser.BannerId; } internal set { GlobalUser.BannerId = value; } } + /// + public override Color? AccentColor { get { return GlobalUser.AccentColor; } internal set { GlobalUser.AccentColor = value; } } + /// internal override SocketPresence Presence { get { return GlobalUser.Presence; } set { GlobalUser.Presence = value; } } /// diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs index f79fc7afe..ac8409a32 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs @@ -38,6 +38,11 @@ namespace Discord.WebSocket public override ushort DiscriminatorValue { get { return GlobalUser.DiscriminatorValue; } internal set { GlobalUser.DiscriminatorValue = value; } } /// public override string AvatarId { get { return GlobalUser.AvatarId; } internal set { GlobalUser.AvatarId = value; } } + /// + public override string BannerId { get { return GlobalUser.BannerId; } internal set { GlobalUser.BannerId = value; } } + /// + public override Color? AccentColor { get { return GlobalUser.AccentColor; } internal set { GlobalUser.AccentColor = value; } } + /// public GuildPermissions GuildPermissions => new GuildPermissions(Permissions.ResolveGuild(Guild, this)); internal override SocketPresence Presence { get; set; } @@ -91,7 +96,7 @@ namespace Discord.WebSocket /// Returns the position of the user within the role hierarchy. /// /// - /// The returned value equal to the position of the highest role the user has, or + /// The returned value equal to the position of the highest role the user has, or /// if user is the server owner. /// public int Hierarchy diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketSelfUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketSelfUser.cs index 7b11257a3..e821238ee 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketSelfUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketSelfUser.cs @@ -29,6 +29,10 @@ namespace Discord.WebSocket /// public override string AvatarId { get { return GlobalUser.AvatarId; } internal set { GlobalUser.AvatarId = value; } } /// + public override string BannerId { get { return GlobalUser.BannerId; } internal set { GlobalUser.BannerId = value; } } + /// + public override Color? AccentColor { get { return GlobalUser.AccentColor; } internal set { GlobalUser.AccentColor = value; } } + /// internal override SocketPresence Presence { get { return GlobalUser.Presence; } set { GlobalUser.Presence = value; } } /// public UserProperties Flags { get; internal set; } diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketThreadUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketThreadUser.cs index d1237d598..5fb1f56e5 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketThreadUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketThreadUser.cs @@ -36,7 +36,7 @@ namespace Discord.WebSocket /// public string Nickname - => GuildUser.Nickname; + => GuildUser.Nickname; /// public DateTimeOffset? PremiumSince @@ -53,6 +53,20 @@ namespace Discord.WebSocket internal set => GuildUser.AvatarId = value; } + /// + public override string BannerId + { + get => GuildUser.BannerId; + internal set => GuildUser.BannerId = value; + } + + /// + public override Color? AccentColor + { + get => GuildUser.AccentColor; + internal set => GuildUser.AccentColor = value; + } + /// public override ushort DiscriminatorValue { diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketUnknownUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketUnknownUser.cs index 840a1c30b..180e60a3b 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketUnknownUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketUnknownUser.cs @@ -19,9 +19,16 @@ namespace Discord.WebSocket public override ushort DiscriminatorValue { get; internal set; } /// public override string AvatarId { get; internal set; } + + /// + public override string BannerId { get; internal set; } + + /// + public override Color? AccentColor { get; internal set; } + /// public override bool IsBot { get; internal set; } - + /// public override bool IsWebhook => false; /// diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs index 025daf29a..c50fbee4f 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs @@ -25,6 +25,10 @@ namespace Discord.WebSocket /// public abstract string AvatarId { get; internal set; } /// + public abstract string BannerId { get; internal set; } + /// + public abstract Color? AccentColor { get; internal set; } + /// public abstract bool IsWebhook { get; } /// public UserProperties? PublicFlags { get; private set; } @@ -64,6 +68,16 @@ namespace Discord.WebSocket AvatarId = model.Avatar.Value; hasChanges = true; } + if (model.Banner.IsSpecified && model.Banner.Value != BannerId) + { + BannerId = model.Banner.Value; + hasChanges = true; + } + if (model.AccentColor.IsSpecified && model.AccentColor.Value != AccentColor?.RawValue) + { + AccentColor = model.AccentColor.Value; + hasChanges = true; + } if (model.Discriminator.IsSpecified) { var newVal = ushort.Parse(model.Discriminator.Value, NumberStyles.None, CultureInfo.InvariantCulture); @@ -99,6 +113,10 @@ namespace Discord.WebSocket public string GetAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128) => CDN.GetUserAvatarUrl(Id, AvatarId, size, format); + /// + public string GetBannerUrl(ImageFormat format = ImageFormat.Auto, ushort size = 256) + => CDN.GetUserBannerUrl(Id, BannerId, size, format); + /// public string GetDefaultAvatarUrl() => CDN.GetDefaultUserAvatarUrl(DiscriminatorValue); diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketWebhookUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketWebhookUser.cs index 2b0ecbb19..f1269e649 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketWebhookUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketWebhookUser.cs @@ -24,6 +24,23 @@ namespace Discord.WebSocket public override ushort DiscriminatorValue { get; internal set; } /// public override string AvatarId { get; internal set; } + + /// + /// Webhook users does not support banners. + public override string BannerId + { + get => throw new NotSupportedException("Webhook users does not support banners."); + internal set => throw new NotSupportedException("Webhook users does not support banners."); + } + + /// + /// Webhook users does not support accent colors. + public override Color? AccentColor + { + get => throw new NotSupportedException("Webhook users does not support accent colors."); + internal set => throw new NotSupportedException("Webhook users does not support accent colors."); + } + /// public override bool IsBot { get; internal set; } diff --git a/test/Discord.Net.Tests.Unit/GuildPermissionsTests.cs b/test/Discord.Net.Tests.Unit/GuildPermissionsTests.cs index bb841fd1b..7ff17ac04 100644 --- a/test/Discord.Net.Tests.Unit/GuildPermissionsTests.cs +++ b/test/Discord.Net.Tests.Unit/GuildPermissionsTests.cs @@ -91,7 +91,7 @@ namespace Discord AssertFlag(() => new GuildPermissions(manageNicknames: true), GuildPermission.ManageNicknames); AssertFlag(() => new GuildPermissions(manageRoles: true), GuildPermission.ManageRoles); AssertFlag(() => new GuildPermissions(manageWebhooks: true), GuildPermission.ManageWebhooks); - AssertFlag(() => new GuildPermissions(manageEmojis: true), GuildPermission.ManageEmojisAndStickers); + AssertFlag(() => new GuildPermissions(manageEmojisAndStickers: true), GuildPermission.ManageEmojisAndStickers); } /// @@ -161,7 +161,7 @@ namespace Discord AssertUtil(GuildPermission.ManageNicknames, x => x.ManageNicknames, (p, enable) => p.Modify(manageNicknames: enable)); AssertUtil(GuildPermission.ManageRoles, x => x.ManageRoles, (p, enable) => p.Modify(manageRoles: enable)); AssertUtil(GuildPermission.ManageWebhooks, x => x.ManageWebhooks, (p, enable) => p.Modify(manageWebhooks: enable)); - AssertUtil(GuildPermission.ManageEmojisAndStickers, x => x.ManageEmojisAndStickers, (p, enable) => p.Modify(manageEmojis: enable)); + AssertUtil(GuildPermission.ManageEmojisAndStickers, x => x.ManageEmojisAndStickers, (p, enable) => p.Modify(manageEmojisAndStickers: enable)); } } }