From 8993911d68a241a96d756f427a3a096f2528bf82 Mon Sep 17 00:00:00 2001 From: drobbins329 Date: Fri, 20 Aug 2021 23:03:27 -0400 Subject: [PATCH] Implemented Context Menus (#106) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update README.md * Update README.md * Fix SocketSlashCommandDataOption to use long for Number instead of int (#89) * Application webhooks (#86) * Added webhook components for hooks having an application ID. * resolved #88 * resolved #85 * Update device for gateway * Fix MessageProperties.Embed being ignored in some methods that modifies a message (#92) * Update label/description lengths for selects (ref: https://github.com/discord/discord-api-docs/pull/3598/files) (#91) https://github.com/discord/discord-api-docs/pull/3598/files * Fix tests (#90) * Fix gateway serialization to include nulls (#96) * Add missing guild permissions (#93) * Update GuildPermissions.cs * Update GuildPermissionsTests.cs * 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 * Interface Method Declarations for Interaction Methods (#99) * added interface method declarations * inline docs * Fix serialization error * meta: bump versions * Fix debug pragma * meta: bump version * Remove rich presence button * Assign CurrentUserId in Sharded Client (#100) * added interface method declarations * inline docs * current user id assignment in sharded client * Allow EmbedBuilder.ImageUrl to use attachment scheme syntax (#104) * Make Webhook ApplicationId nullable instead of optional + fix IDiscordInteraction DeferAsync method (#110) * Make Webhook ApplicationId nullable instead of optional * Fix IDiscordInteraction DeferAsync to account for ephemeral defer * Fix application command and thread starter messages being created as SocketSystemMessage * Added description of ApplicationCommandType Enums * Requested Fixes renamed SocketApplicationUserCommand to SocketUserCommand renamed SocketApplicationMessageCommand to SocketMessageCommand using ContextMenuCreationProperties for both User and Message commands * Added Summary to public members removed whitespace from DiscordRestApiClient.cs * Fixing guide to use switch statement * implemented TrySendApplicationCommandAsync * implemented ephemeral in SocketCommandBase Defer, and RespondAsync. assigning int 64 was error. changed to "MessageFlags.Ephemeral", built and tested to work. * removed ApplicationCommandType from SocketUser and SocketMessageCommandData Co-authored-by: Quin Lynch <49576606+quinchs@users.noreply.github.com> Co-authored-by: František Boháček Co-authored-by: quin lynch Co-authored-by: d4n3436 Co-authored-by: MrCakeSlayer <13650699+MrCakeSlayer@users.noreply.github.com> Co-authored-by: Nikon <47792796+INikonI@users.noreply.github.com> Co-authored-by: Cenk Ergen <57065323+Cenngo@users.noreply.github.com> --- Discord.Net.sln | 2 - .../creating-context-menu-commands.md | 15 +- .../receiving-context-menu-command-events.md | 31 +- src/Discord.Net.Core/CDN.cs | 18 ++ src/Discord.Net.Core/Discord.Net.Core.csproj | 8 +- src/Discord.Net.Core/Discord.Net.Core.xml | 286 ++++++++++++------ .../Entities/Guilds/IGuild.cs | 10 + .../Interactions/ApplicationCommandTypes.cs | 12 + ...> ContextMenuCommandCreationProperties.cs} | 8 +- .../Interactions/IApplicationCommand.cs | 9 +- .../Interactions/IDiscordInteraction.cs | 54 ++++ .../Message Components/ComponentBuilder.cs | 35 ++- .../Interactions/MessageCommandBuilder.cs | 48 +-- .../Interactions/UserCommandBuilder.cs | 48 +-- .../Entities/Messages/EmbedBuilder.cs | 2 +- .../Entities/Messages/MessageProperties.cs | 2 +- .../Entities/Permissions/GuildPermissions.cs | 55 +++- src/Discord.Net.Core/Entities/Roles/Color.cs | 101 ++++--- src/Discord.Net.Core/Entities/Users/IUser.cs | 32 +- .../Entities/Webhooks/IWebhook.cs | 7 +- src/Discord.Net.Rest/API/Common/Game.cs | 4 +- .../API/Common/InteractionCallbackData.cs | 2 +- .../API/Common/RichPresenceButton.cs | 18 -- src/Discord.Net.Rest/API/Common/User.cs | 4 + src/Discord.Net.Rest/API/Common/Webhook.cs | 2 + .../API/Rest/CreateWebhookMessageParams.cs | 2 +- .../API/Rest/ModifyWebhookMessageParams.cs | 2 + src/Discord.Net.Rest/Discord.Net.Rest.csproj | 6 +- src/Discord.Net.Rest/Discord.Net.Rest.xml | 29 +- src/Discord.Net.Rest/DiscordRestApiClient.cs | 62 +--- src/Discord.Net.Rest/DiscordRestClient.cs | 16 +- .../Entities/Guilds/RestGuild.cs | 16 +- .../Interactions/InteractionHelper.cs | 97 +++--- .../Entities/Messages/MessageHelper.cs | 45 ++- .../Entities/Users/RestThreadUser.cs | 4 +- .../Entities/Users/RestUser.cs | 12 + .../Entities/Webhooks/RestWebhook.cs | 4 + .../Extensions/EntityExtensions.cs | 1 + .../Discord.Net.WebSocket.csproj | 4 +- .../Discord.Net.WebSocket.xml | 121 +++++++- .../DiscordShardedClient.cs | 11 +- .../DiscordSocketApiClient.cs | 17 +- .../Entities/Guilds/SocketGuild.cs | 3 + ...sageCommand.cs => SocketMessageCommand.cs} | 10 +- ...andData.cs => SocketMessageCommandData.cs} | 17 +- ...ionUserCommand.cs => SocketUserCommand.cs} | 10 +- ...ommandData.cs => SocketUserCommandData.cs} | 18 +- .../SocketMessageComponent.cs | 61 +++- .../Slash Commands/SocketSlashCommand.cs | 2 +- .../SocketSlashCommandDataOption.cs | 6 +- .../SocketBaseCommand/SocketCommandBase.cs | 9 +- .../SocketCommandBaseData.cs | 10 +- .../SocketCommandBaseDataOption.cs | 4 + .../Entities/Interaction/SocketInteraction.cs | 25 +- .../Entities/Messages/SocketMessage.cs | 5 +- .../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 ++ .../Discord.Net.Webhook.csproj | 2 +- .../Discord.Net.Webhook.xml | 7 +- .../DiscordWebhookClient.cs | 4 +- .../Messages/WebhookMessageProperties.cs | 4 + .../Entities/Webhooks/RestInternalWebhook.cs | 3 + .../WebhookClientHelper.cs | 7 +- src/Discord.Net/Discord.Net.nuspec | 20 +- .../GuildPermissionsTests.cs | 16 +- 71 files changed, 1067 insertions(+), 517 deletions(-) rename src/Discord.Net.Core/Entities/Interactions/{MessageCommandCreationProperties.cs => ContextMenuCommandCreationProperties.cs} (73%) delete mode 100644 src/Discord.Net.Rest/API/Common/RichPresenceButton.cs rename src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/Message Commands/{SocketApplicationMessageCommand.cs => SocketMessageCommand.cs} (69%) rename src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/Message Commands/{SocketApplicationMessageCommandData.cs => SocketMessageCommandData.cs} (89%) rename src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/User Commands/{SocketApplicationUserCommand.cs => SocketUserCommand.cs} (70%) rename src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/User Commands/{SocketApplicationUserCommandData.cs => SocketUserCommandData.cs} (86%) diff --git a/Discord.Net.sln b/Discord.Net.sln index c11739aef..9f888cd12 100644 --- a/Discord.Net.sln +++ b/Discord.Net.sln @@ -42,8 +42,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discord.Net.Examples", "src EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "idn", "samples\idn\idn.csproj", "{4A03840B-9EBE-47E3-89AB-E0914DF21AFB}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FeatureTesting", "..\FeatureTesting\FeatureTesting\FeatureTesting.csproj", "{0CC57A32-3AC7-489D-8DF5-C431925E4675}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU diff --git a/docs/guides/application-commands/context-menu-commands/creating-context-menu-commands.md b/docs/guides/application-commands/context-menu-commands/creating-context-menu-commands.md index 4e1a42987..9100c3b42 100644 --- a/docs/guides/application-commands/context-menu-commands/creating-context-menu-commands.md +++ b/docs/guides/application-commands/context-menu-commands/creating-context-menu-commands.md @@ -15,14 +15,23 @@ Guild commands are specific to the guild you specify when making them. Guild com If you don't have the code for a bot ready yet please follow [this guide](https://docs.stillu.cc/guides/getting_started/first-bot.html). -## SlashCommandBuilder +## UserCommandBuilder -The slash command builder will help you create slash commands. The builder has these available fields and methods: +The context menu user command builder will help you create user commands. The builder has these available fields and methods: + +| Name | Type | Description | +| --------------------- | -------------------------------- | -------------------------------------------------------------------------------------------- | +| Name | string | The name of this context menu command. | +| WithName | Function | Sets the field name. | +| Build | Function | Builds the builder into the appropriate `CommandCreationProperties` class used to make Menu commands | + +## MessageCommandBuilder + +The context menu message command builder will help you create message commands. The builder has these available fields and methods: | Name | Type | Description | | --------------------- | -------------------------------- | -------------------------------------------------------------------------------------------- | | Name | string | The name of this context menu command. | -| Description | string | A 0 length string. Left in place for possible future use. | | WithName | Function | Sets the field name. | | Build | Function | Builds the builder into the appropriate `CommandCreationProperties` class used to make Menu commands | diff --git a/docs/guides/application-commands/context-menu-commands/receiving-context-menu-command-events.md b/docs/guides/application-commands/context-menu-commands/receiving-context-menu-command-events.md index a1492fc50..52bc303e7 100644 --- a/docs/guides/application-commands/context-menu-commands/receiving-context-menu-command-events.md +++ b/docs/guides/application-commands/context-menu-commands/receiving-context-menu-command-events.md @@ -15,24 +15,21 @@ public async Task InteractionCreatedHandler(SocketInteraction arg) public async Task ApplicationCommandHandler(SocketInteraction arg) { - var slashCommand = arg as SocketSlashCommand; - if(slashCommand != null) - Console.Writeline("Slash command received!") - - var userCommand = arg as SocketApplicationUserCommand; - if(userCommand != null) + switch (arg) { - Console.Writeline("User command received!") - // userCommand.User = User who ran command. - // userCommand.Data.Member = User who was clicked. - } - - var messageCommand = arg as SocketApplicationMessageCommand; - if(messageCommand != null) - { - Console.Writeline("Message command received!") - // messageCommand.User = User who ran command. - // messageCommand.Data.Message = Message that was clicked. + case SocketSlashCommand slashCommand: + Console.Writeline("Slash command received!"); + break; + case SocketUserCommand userCommand: + Console.Writeline("User command received!") + // userCommand.User = User who ran command. + // userCommand.Data.Member = User who was clicked. + break; + case SocketMessageCommand messageCommand: + Console.Writeline("Message command received!") + // messageCommand.User = User who ran command. + // messageCommand.Data.Message = Message that was clicked. + break; } } ``` 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/Discord.Net.Core.csproj b/src/Discord.Net.Core/Discord.Net.Core.csproj index f0be10059..ac54b259c 100644 --- a/src/Discord.Net.Core/Discord.Net.Core.csproj +++ b/src/Discord.Net.Core/Discord.Net.Core.csproj @@ -1,4 +1,4 @@ - + @@ -8,12 +8,12 @@ net461;netstandard2.0;netstandard2.1 netstandard2.0;netstandard2.1 Discord.Net.Labs.Core - 3.0.0-pre + 3.0.1-pre Discord.Net.Labs.Core https://github.com/Discord-Net-Labs/Discord.Net-Labs Temporary.png - 2.3.8 - 2.3.8 + 3.3.1 + 3.0.1 false diff --git a/src/Discord.Net.Core/Discord.Net.Core.xml b/src/Discord.Net.Core/Discord.Net.Core.xml index d1e4770e3..8be13e6bd 100644 --- a/src/Discord.Net.Core/Discord.Net.Core.xml +++ b/src/Discord.Net.Core/Discord.Net.Core.xml @@ -100,6 +100,18 @@ A URL pointing to the user's avatar in the specified 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. + + Returns the default user avatar URL. @@ -3902,6 +3914,16 @@ A task that represents the asynchronous removal operation. + + + Gets this guilds slash commands commands + + The options to be used when sending the request. + + A task that represents the asynchronous get operation. The task result contains a read-only collection + of application commands found within the guild. + + Holds information for a guild integration feature. @@ -4467,6 +4489,41 @@ Whether the command is enabled by default when the app is added to a guild. Default is + + + ApplicationCommandType is enum of current valid Application Command Types: Slash, User, Message + + + + + ApplicationCommandType.Slash is Slash command type + + + + + ApplicationCommandType.User is Context Menu User command type + + + + + ApplicationCommandType.Message is Context Menu Message command type + + + + + A class used to create Message commands. + + + + + The name of this command. + + + + + Gets or sets the type for this command. + + The base command model that belongs to an application. see @@ -4502,13 +4559,6 @@ If the option is a subcommand or subcommand group type, this nested options will be the parameters. - - - Deletes this command - - The options to be used when sending the request. - A task that represents the asynchronous delete operation. - Represents data of an Interaction Command, see . @@ -4642,6 +4692,58 @@ read-only property, always 1. + + + Responds to an Interaction with type . + + The text of the message to be sent. + A array of embeds to send with this response. Max 10 + if the message should be read out by a text-to-speech reader, otherwise . + if the response should be hidden to everyone besides the invoker of the command, otherwise . + The allowed mentions for this response. + The request options for this response. + A to be sent with this response + A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored. + + + + Sends a followup message for this interaction. + + The text of the message to be sent + A array of embeds to send with this response. Max 10 + if the message should be read out by a text-to-speech reader, otherwise . + if the response should be hidden to everyone besides the invoker of the command, otherwise . + The allowed mentions for this response. + The request options for this response. + A to be sent with this response + A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored. + + The sent message. + + + + + Gets the original response for this interaction. + + The request options for this async request. + A that represents the initial response. + + + + Edits original response for this interaction. + + A delegate containing the properties to modify the message with. + The request options for this async request. + A that represents the initial response. + + + + Acknowledges this interaction. + + + A task that represents the asynchronous operation of acknowledging the interaction. + + Represents an interface used to specify classes that they are a vaild dataype of a class. @@ -4792,7 +4894,7 @@ Represents a builder for creating a . - + The max length of a . @@ -4915,11 +5017,16 @@ Represents a class used to build 's. + + + The max length of a . + + Gets or sets the label of the current button. - length exceeds . + length exceeds . @@ -5239,16 +5346,26 @@ Represents a class used to build 's. + + + The maximum length of a . + + The maximum length of a . + + + The maximum length of a . + + Gets or sets the label of the current select menu. - length exceeds + length exceeds @@ -5456,7 +5573,7 @@ - A class used to build slash commands. + A class used to build Message commands. @@ -5464,26 +5581,16 @@ Returns the maximun length a commands name allowed by Discord - - - Returns the maximum length of a commands description allowed by Discord. - - - The name of this slash command. - - - - - A 1-100 length description of this slash command + The name of this Message command. - Build the current builder into a class. + Build the current builder into a class. - A that can be used to create user commands over rest. + A that can be used to create message commands over rest. @@ -5494,33 +5601,6 @@ The current builder. - - - Sets the description of the current command. - - The description of this command. - The current builder. - - - - A class used to create Message commands. - - - - - The name of this command. - - - - - The discription of this command. - - - - - Gets or sets the type for this command. - - A class used to build slash commands. @@ -5785,7 +5865,7 @@ - A class used to build slash commands. + A class used to build user commands. @@ -5793,26 +5873,16 @@ Returns the maximun length a commands name allowed by Discord - - - Returns the maximum length of a commands description allowed by Discord. - - - The name of this slash command. - - - - - A 1-100 length description of this slash command + The name of this User command. - Build the current builder into a class. + Build the current builder into a class. - A that can be used to create user commands over rest. + A that can be used to create user commands over rest. @@ -5823,13 +5893,6 @@ The current builder. - - - Sets the description of the current command. - - The description of this command. - The current builder. - A class used to create User commands. @@ -7751,7 +7814,7 @@ Gets or sets a single embed for this message. - This property will be added to the array, in the future please use the array rather then this property. + This property will be added to the array, in the future please use the array rather than this property. @@ -8794,7 +8857,25 @@ If true, a user may edit the webhooks for this guild. - If true, a user may edit the emojis for this guild. + If true, a user may edit the emojis and stickers for this guild. + + + If true, a user may use slash commands in this guild. + + + If true, a user may request to speak in stage channels. + + + If true, a user may manage threads in this guild. + + + If true, a user may create public threads in this guild. + + + If true, a user may create private threads in this guild. + + + If true, a user may use external stickers in this guild. Creates a new with the provided packed value. @@ -8802,10 +8883,10 @@ Creates a new with the provided packed value after converting to ulong. - + Creates a new structure with the provided permissions. - + Creates a new from this one, changing the provided non-null permissions. @@ -8992,6 +9073,9 @@ Represents a color used in Discord. + + Gets the max decimal value of color. + Gets the default user color value. @@ -9096,20 +9180,21 @@ 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 . 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); @@ -9118,13 +9203,14 @@ The byte that represents the red color. The byte that represents the green color. The byte that represents the blue color. + Value exceeds . 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); @@ -9140,7 +9226,7 @@ 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); @@ -9908,19 +9994,33 @@ Gets the identifier of this user's avatar. + + + Gets the identifier of this user's banner. + + + + + Gets the user's banner color. + + + A struct representing the accent color of this user's banner. + + 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. @@ -9931,6 +10031,17 @@ A string representing the user's avatar URL; null if the user does not have an avatar in place. + + + 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. + + Gets the default avatar URL for this user. @@ -9998,8 +10109,8 @@ 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. @@ -10293,6 +10404,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/Guilds/IGuild.cs b/src/Discord.Net.Core/Entities/Guilds/IGuild.cs index ad2e0317d..414b6fe73 100644 --- a/src/Discord.Net.Core/Entities/Guilds/IGuild.cs +++ b/src/Discord.Net.Core/Entities/Guilds/IGuild.cs @@ -941,5 +941,15 @@ namespace Discord /// A task that represents the asynchronous removal operation. /// Task DeleteEmoteAsync(GuildEmote emote, RequestOptions options = null); + + /// + /// Gets this guilds slash commands commands + /// + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains a read-only collection + /// of application commands found within the guild. + /// + Task> GetApplicationCommandsAsync (RequestOptions options = null); } } diff --git a/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandTypes.cs b/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandTypes.cs index 23b00f2a2..de9c1a263 100644 --- a/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandTypes.cs +++ b/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandTypes.cs @@ -6,10 +6,22 @@ using System.Threading.Tasks; namespace Discord { + /// + /// ApplicationCommandType is enum of current valid Application Command Types: Slash, User, Message + /// public enum ApplicationCommandType : byte { + /// + /// ApplicationCommandType.Slash is Slash command type + /// Slash = 1, + /// + /// ApplicationCommandType.User is Context Menu User command type + /// User = 2, + /// + /// ApplicationCommandType.Message is Context Menu Message command type + /// Message = 3 } } diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageCommandCreationProperties.cs b/src/Discord.Net.Core/Entities/Interactions/ContextMenuCommandCreationProperties.cs similarity index 73% rename from src/Discord.Net.Core/Entities/Interactions/MessageCommandCreationProperties.cs rename to src/Discord.Net.Core/Entities/Interactions/ContextMenuCommandCreationProperties.cs index 7c7dac593..4ddc0a7b1 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageCommandCreationProperties.cs +++ b/src/Discord.Net.Core/Entities/Interactions/ContextMenuCommandCreationProperties.cs @@ -9,19 +9,13 @@ namespace Discord /// /// A class used to create Message commands. /// - public class MessageCommandCreationProperties + public class ContextMenuCommandCreationProperties { /// /// The name of this command. /// public string Name { get; set; } - /// - /// The discription of this command. - /// - public string Description { get; set; } - - /// /// Gets or sets the type for this command. /// diff --git a/src/Discord.Net.Core/Entities/Interactions/IApplicationCommand.cs b/src/Discord.Net.Core/Entities/Interactions/IApplicationCommand.cs index ab4b3eac0..e15843d98 100644 --- a/src/Discord.Net.Core/Entities/Interactions/IApplicationCommand.cs +++ b/src/Discord.Net.Core/Entities/Interactions/IApplicationCommand.cs @@ -9,7 +9,7 @@ namespace Discord /// /// The base command model that belongs to an application. see /// - public interface IApplicationCommand : ISnowflakeEntity + public interface IApplicationCommand : ISnowflakeEntity, IDeletable { /// /// Gets the unique id of the parent application. @@ -40,12 +40,5 @@ namespace Discord /// If the option is a subcommand or subcommand group type, this nested options will be the parameters. /// IReadOnlyCollection Options { get; } - - /// - /// Deletes this command - /// - /// The options to be used when sending the request. - /// A task that represents the asynchronous delete operation. - Task DeleteAsync(RequestOptions options = null); } } diff --git a/src/Discord.Net.Core/Entities/Interactions/IDiscordInteraction.cs b/src/Discord.Net.Core/Entities/Interactions/IDiscordInteraction.cs index 466bf3e91..ae015c2a6 100644 --- a/src/Discord.Net.Core/Entities/Interactions/IDiscordInteraction.cs +++ b/src/Discord.Net.Core/Entities/Interactions/IDiscordInteraction.cs @@ -39,5 +39,59 @@ namespace Discord /// read-only property, always 1. /// int Version { get; } + + /// + /// Responds to an Interaction with type . + /// + /// The text of the message to be sent. + /// A array of embeds to send with this response. Max 10 + /// if the message should be read out by a text-to-speech reader, otherwise . + /// if the response should be hidden to everyone besides the invoker of the command, otherwise . + /// The allowed mentions for this response. + /// The request options for this response. + /// A to be sent with this response + /// A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored. + Task RespondAsync (string text = null, Embed[] embeds = null, bool isTTS = false, + bool ephemeral = false, AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent component = null, Embed embed = null); + + /// + /// Sends a followup message for this interaction. + /// + /// The text of the message to be sent + /// A array of embeds to send with this response. Max 10 + /// if the message should be read out by a text-to-speech reader, otherwise . + /// if the response should be hidden to everyone besides the invoker of the command, otherwise . + /// The allowed mentions for this response. + /// The request options for this response. + /// A to be sent with this response + /// A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored. + /// + /// The sent message. + /// + Task FollowupAsync (string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, + AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent component = null, Embed embed = null); + + /// + /// Gets the original response for this interaction. + /// + /// The request options for this async request. + /// A that represents the initial response. + Task GetOriginalResponseAsync (RequestOptions options = null); + + /// + /// Edits original response for this interaction. + /// + /// A delegate containing the properties to modify the message with. + /// The request options for this async request. + /// A that represents the initial response. + Task ModifyOriginalResponseAsync (Action func, RequestOptions options = null); + + /// + /// Acknowledges this interaction. + /// + /// + /// A task that represents the asynchronous operation of acknowledging the interaction. + /// + Task DeferAsync (bool ephemeral = false, RequestOptions options = null); } } 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..085c62cef 100644 --- a/src/Discord.Net.Core/Entities/Interactions/Message Components/ComponentBuilder.cs +++ b/src/Discord.Net.Core/Entities/Interactions/Message Components/ComponentBuilder.cs @@ -13,7 +13,7 @@ namespace Discord /// /// The max length of a . /// - public const int MaxLabelLength = 80; + public const int MaxButtonLabelLength = 80; /// /// The max length of a . @@ -307,17 +307,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 > ComponentBuilder.MaxButtonLabelLength) + throw new ArgumentException(message: $"Button label must be {ComponentBuilder.MaxButtonLabelLength} characters or less!", paramName: nameof(Label)); _label = value; } @@ -539,8 +544,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 +836,33 @@ 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; + + /// + /// The maximum length of a . + /// + public const int MaxSelectLabelLength = 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 > MaxSelectLabelLength) + throw new ArgumentException(message: $"Button label must be {MaxSelectLabelLength} characters or less!", paramName: nameof(Label)); _label = value; } diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageCommandBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/MessageCommandBuilder.cs index 792d7d19f..b658a181b 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageCommandBuilder.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageCommandBuilder.cs @@ -8,7 +8,7 @@ using System.Threading.Tasks; namespace Discord { /// - /// A class used to build slash commands. + /// A class used to build Message commands. /// public class MessageCommandBuilder { @@ -16,13 +16,9 @@ namespace Discord /// Returns the maximun length a commands name allowed by Discord /// public const int MaxNameLength = 32; - /// - /// Returns the maximum length of a commands description allowed by Discord. - /// - public const int MaxDescriptionLength = 0; /// - /// The name of this slash command. + /// The name of this Message command. /// public string Name { @@ -45,36 +41,17 @@ namespace Discord } } - /// - /// A 1-100 length description of this slash command - /// - public string Description - { - get - { - return _description; - } - set - { - Preconditions.Equals(value, ""); - - _description = value; - } - } - private string _name { get; set; } - private string _description { get; set; } /// - /// Build the current builder into a class. + /// Build the current builder into a class. /// - /// A that can be used to create user commands over rest. - public MessageCommandCreationProperties Build() + /// A that can be used to create message commands over rest. + public ContextMenuCommandCreationProperties Build() { - MessageCommandCreationProperties props = new MessageCommandCreationProperties() + ContextMenuCommandCreationProperties props = new ContextMenuCommandCreationProperties() { Name = this.Name, - Description = this.Description, Type=ApplicationCommandType.Message }; @@ -93,17 +70,6 @@ namespace Discord { this.Name = name; return this; - } - - /// - /// Sets the description of the current command. - /// - /// The description of this command. - /// The current builder. - public MessageCommandBuilder WithDescription(string description) - { - this.Description = description; - return this; - } + } } } diff --git a/src/Discord.Net.Core/Entities/Interactions/UserCommandBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/UserCommandBuilder.cs index 0dc6526ba..f10b6b123 100644 --- a/src/Discord.Net.Core/Entities/Interactions/UserCommandBuilder.cs +++ b/src/Discord.Net.Core/Entities/Interactions/UserCommandBuilder.cs @@ -8,7 +8,7 @@ using System.Threading.Tasks; namespace Discord { /// - /// A class used to build slash commands. + /// A class used to build user commands. /// public class UserCommandBuilder { @@ -16,13 +16,9 @@ namespace Discord /// Returns the maximun length a commands name allowed by Discord /// public const int MaxNameLength = 32; - /// - /// Returns the maximum length of a commands description allowed by Discord. - /// - public const int MaxDescriptionLength = 0; /// - /// The name of this slash command. + /// The name of this User command. /// public string Name { @@ -45,36 +41,17 @@ namespace Discord } } - /// - /// A 1-100 length description of this slash command - /// - public string Description - { - get - { - return _description; - } - set - { - Preconditions.Equals(value, ""); - - _description = value; - } - } - private string _name { get; set; } - private string _description { get; set; } /// - /// Build the current builder into a class. + /// Build the current builder into a class. /// - /// A that can be used to create user commands over rest. - public UserCommandCreationProperties Build() + /// A that can be used to create user commands over rest. + public ContextMenuCommandCreationProperties Build() { - UserCommandCreationProperties props = new UserCommandCreationProperties() + ContextMenuCommandCreationProperties props = new ContextMenuCommandCreationProperties() { Name = this.Name, - Description = this.Description, Type=ApplicationCommandType.User }; @@ -93,17 +70,6 @@ namespace Discord { this.Name = name; return this; - } - - /// - /// Sets the description of the current command. - /// - /// The description of this command. - /// The current builder. - public UserCommandBuilder WithDescription(string description) - { - this.Description = description; - return this; - } + } } } diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs b/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs index 2ab2699a6..5b92e02a5 100644 --- a/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs +++ b/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs @@ -411,7 +411,7 @@ namespace Discord UrlValidation.Validate(Url); if (!string.IsNullOrEmpty(ThumbnailUrl)) UrlValidation.Validate(ThumbnailUrl); - if (!string.IsNullOrEmpty(ImageUrl)) + if (!string.IsNullOrEmpty(ImageUrl) && !ImageUrl.StartsWith("attachment://", StringComparison.Ordinal)) UrlValidation.Validate(ImageUrl); if (Author != null) { diff --git a/src/Discord.Net.Core/Entities/Messages/MessageProperties.cs b/src/Discord.Net.Core/Entities/Messages/MessageProperties.cs index 19cfacebe..abd09d856 100644 --- a/src/Discord.Net.Core/Entities/Messages/MessageProperties.cs +++ b/src/Discord.Net.Core/Entities/Messages/MessageProperties.cs @@ -22,7 +22,7 @@ namespace Discord /// Gets or sets a single embed for this message. /// /// - /// This property will be added to the array, in the future please use the array rather then this property. + /// This property will be added to the array, in the future please use the array rather than this property. /// public Optional Embed { get; set; } diff --git a/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs b/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs index 1914a6f86..9503e5b3b 100644 --- a/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs +++ b/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs @@ -81,8 +81,20 @@ namespace Discord public bool ManageRoles => Permissions.GetValue(RawValue, GuildPermission.ManageRoles); /// If true, a user may edit the webhooks for this guild. public bool ManageWebhooks => Permissions.GetValue(RawValue, GuildPermission.ManageWebhooks); - /// If true, a user may edit the emojis for this guild. + /// If true, a user may edit the emojis and stickers for this guild. public bool ManageEmojisAndStickers => Permissions.GetValue(RawValue, GuildPermission.ManageEmojisAndStickers); + /// If true, a user may use slash commands in this guild. + public bool UseSlashCommands => Permissions.GetValue(RawValue, GuildPermission.UseSlashCommands); + /// If true, a user may request to speak in stage channels. + public bool RequestToSpeak => Permissions.GetValue(RawValue, GuildPermission.RequestToSpeak); + /// If true, a user may manage threads in this guild. + public bool ManageThreads => Permissions.GetValue(RawValue, GuildPermission.ManageThreads); + /// If true, a user may create public threads in this guild. + public bool UsePublicThreads => Permissions.GetValue(RawValue, GuildPermission.UsePublicThreads); + /// If true, a user may create private threads in this guild. + public bool UsePrivateThreads => Permissions.GetValue(RawValue, GuildPermission.UsePrivateThreads); + /// If true, a user may use external stickers in this guild. + public bool UseExternalStickers => Permissions.GetValue(RawValue, GuildPermission.UseExternalStickers); /// Creates a new with the provided packed value. public GuildPermissions(ulong rawValue) { RawValue = rawValue; } @@ -121,7 +133,13 @@ namespace Discord bool? manageNicknames = null, bool? manageRoles = null, bool? manageWebhooks = null, - bool? manageEmojisAndStickers = null) + bool? manageEmojisAndStickers = null, + bool? useSlashCommands = null, + bool? requestToSpeak = null, + bool? manageThreads = null, + bool? usePublicThreads = null, + bool? usePrivateThreads = null, + bool? useExternalStickers = null) { ulong value = initialValue; @@ -156,6 +174,12 @@ namespace Discord Permissions.SetValue(ref value, manageRoles, GuildPermission.ManageRoles); Permissions.SetValue(ref value, manageWebhooks, GuildPermission.ManageWebhooks); Permissions.SetValue(ref value, manageEmojisAndStickers, GuildPermission.ManageEmojisAndStickers); + Permissions.SetValue(ref value, useSlashCommands, GuildPermission.UseSlashCommands); + Permissions.SetValue(ref value, requestToSpeak, GuildPermission.RequestToSpeak); + Permissions.SetValue(ref value, manageThreads, GuildPermission.ManageThreads); + Permissions.SetValue(ref value, usePublicThreads, GuildPermission.UsePublicThreads); + Permissions.SetValue(ref value, usePrivateThreads, GuildPermission.UseExternalStickers); + Permissions.SetValue(ref value, useExternalStickers, GuildPermission.UseExternalStickers); RawValue = value; } @@ -192,7 +216,13 @@ namespace Discord bool manageNicknames = false, bool manageRoles = false, bool manageWebhooks = false, - bool manageEmojis = false) + bool manageEmojisAndStickers = false, + bool useSlashCommands = false, + bool requestToSpeak = false, + bool manageThreads = false, + bool usePublicThreads = false, + bool usePrivateThreads = false, + bool useExternalStickers = false) : this(0, createInstantInvite: createInstantInvite, manageRoles: manageRoles, @@ -224,7 +254,13 @@ namespace Discord changeNickname: changeNickname, manageNicknames: manageNicknames, manageWebhooks: manageWebhooks, - manageEmojisAndStickers: manageEmojis) + manageEmojisAndStickers: manageEmojisAndStickers, + useSlashCommands: useSlashCommands, + requestToSpeak: requestToSpeak, + manageThreads: manageThreads, + usePublicThreads: usePublicThreads, + usePrivateThreads: usePrivateThreads, + useExternalStickers: useExternalStickers) { } /// Creates a new from this one, changing the provided non-null permissions. @@ -259,11 +295,18 @@ namespace Discord bool? manageNicknames = null, bool? manageRoles = null, bool? manageWebhooks = null, - bool? manageEmojis = null) + bool? manageEmojisAndStickers = null, + bool? useSlashCommands = null, + bool? requestToSpeak = null, + bool? manageThreads = null, + bool? usePublicThreads = null, + bool? usePrivateThreads = null, + bool? useExternalStickers = 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, + useSlashCommands, requestToSpeak, manageThreads, usePublicThreads, usePrivateThreads, useExternalStickers); /// /// 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.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/Game.cs b/src/Discord.Net.Rest/API/Common/Game.cs index 294b0c7fd..775b6aabc 100644 --- a/src/Discord.Net.Rest/API/Common/Game.cs +++ b/src/Discord.Net.Rest/API/Common/Game.cs @@ -41,8 +41,8 @@ namespace Discord.API public Optional Emoji { get; set; } [JsonProperty("created_at")] public Optional CreatedAt { get; set; } - [JsonProperty("buttons")] - public Optional Buttons { get; set; } + //[JsonProperty("buttons")] + //public Optional Buttons { get; set; } [OnError] internal void OnError(StreamingContext context, ErrorContext errorContext) diff --git a/src/Discord.Net.Rest/API/Common/InteractionCallbackData.cs b/src/Discord.Net.Rest/API/Common/InteractionCallbackData.cs index f03cb8870..ba233cc6b 100644 --- a/src/Discord.Net.Rest/API/Common/InteractionCallbackData.cs +++ b/src/Discord.Net.Rest/API/Common/InteractionCallbackData.cs @@ -18,7 +18,7 @@ namespace Discord.API // New flags prop. this make the response "ephemeral". see https://discord.com/developers/docs/interactions/slash-commands#interaction-response-interactionapplicationcommandcallbackdata [JsonProperty("flags")] - public Optional Flags { get; set; } + public Optional Flags { get; set; } [JsonProperty("components")] public Optional Components { get; set; } diff --git a/src/Discord.Net.Rest/API/Common/RichPresenceButton.cs b/src/Discord.Net.Rest/API/Common/RichPresenceButton.cs deleted file mode 100644 index 66f34c82e..000000000 --- a/src/Discord.Net.Rest/API/Common/RichPresenceButton.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Discord.API -{ - internal class RichPresenceButton - { - [JsonProperty("label")] - public string Label { get; set; } - - [JsonProperty("url")] - public string Url { get; set; } - } -} 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/API/Common/Webhook.cs b/src/Discord.Net.Rest/API/Common/Webhook.cs index cbd5fdad5..eb504a27c 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 ulong? ApplicationId { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Rest/CreateWebhookMessageParams.cs b/src/Discord.Net.Rest/API/Rest/CreateWebhookMessageParams.cs index bd4cc1a6c..1806d487c 100644 --- a/src/Discord.Net.Rest/API/Rest/CreateWebhookMessageParams.cs +++ b/src/Discord.Net.Rest/API/Rest/CreateWebhookMessageParams.cs @@ -28,7 +28,7 @@ namespace Discord.API.Rest public Optional AllowedMentions { get; set; } [JsonProperty("flags")] - public Optional Flags { get; set; } + public Optional Flags { get; set; } [JsonProperty("components")] public Optional Components { 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.csproj b/src/Discord.Net.Rest/Discord.Net.Rest.csproj index b1efafae2..16b165575 100644 --- a/src/Discord.Net.Rest/Discord.Net.Rest.csproj +++ b/src/Discord.Net.Rest/Discord.Net.Rest.csproj @@ -9,11 +9,11 @@ netstandard2.0;netstandard2.1 Temporary.png https://github.com/Discord-Net-Labs/Discord.Net-Labs - 3.0.0-pre + 3.0.1-pre Discord.Net.Labs.Rest https://github.com/Discord-Net-Labs/Discord.Net-Labs - 2.3.4 - 2.3.4 + 3.0.1 + 3.0.1 ..\Discord.Net.Rest\Discord.Net.Rest.xml diff --git a/src/Discord.Net.Rest/Discord.Net.Rest.xml b/src/Discord.Net.Rest/Discord.Net.Rest.xml index c6c81ee75..0fc188b2e 100644 --- a/src/Discord.Net.Rest/Discord.Net.Rest.xml +++ b/src/Discord.Net.Rest/Discord.Net.Rest.xml @@ -3493,6 +3493,16 @@ of webhooks found within the guild. + + + Gets this guilds slash commands commands + + The options to be used when sending the request. + + A task that represents the asynchronous get operation. The task result contains a read-only collection + of application commands found within the guild. + + Returns the name of the guild. @@ -3650,6 +3660,9 @@ + + + @@ -4667,7 +4680,7 @@ - Represents a thread user recieved over the REST api. + Represents a thread user received over the REST api. @@ -4690,7 +4703,7 @@ 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. @@ -4711,6 +4724,12 @@ + + + + + + @@ -4753,6 +4772,9 @@ + + + @@ -4875,6 +4897,9 @@ + + + diff --git a/src/Discord.Net.Rest/DiscordRestApiClient.cs b/src/Discord.Net.Rest/DiscordRestApiClient.cs index 42cde4cfc..8bf7f6e43 100644 --- a/src/Discord.Net.Rest/DiscordRestApiClient.cs +++ b/src/Discord.Net.Rest/DiscordRestApiClient.cs @@ -55,7 +55,7 @@ namespace Discord.API _restClientProvider = restClientProvider; UserAgent = userAgent; DefaultRetryMode = defaultRetryMode; - _serializer = serializer ?? new JsonSerializer { ContractResolver = new DiscordContractResolver(), NullValueHandling = NullValueHandling.Ignore }; + _serializer = serializer ?? new JsonSerializer { ContractResolver = new DiscordContractResolver(), NullValueHandling = NullValueHandling.Include }; UseSystemClock = useSystemClock; RequestQueue = new RequestQueue(); @@ -1113,11 +1113,8 @@ namespace Discord.API Preconditions.NotNull(command, nameof(command)); Preconditions.AtMost(command.Name.Length, 32, nameof(command.Name)); Preconditions.AtLeast(command.Name.Length, 3, nameof(command.Name)); - Preconditions.Equals(command.Description, ""); - options = RequestOptions.CreateOrClone(options); - - + options = RequestOptions.CreateOrClone(options); return await TrySendApplicationCommand(SendJsonAsync("POST", () => $"applications/{this.CurrentUserId}/commands", command, new BucketIds(), options: options)).ConfigureAwait(false); } @@ -1126,12 +1123,9 @@ namespace Discord.API Preconditions.NotNull(command, nameof(command)); Preconditions.AtMost(command.Name.Length, 32, nameof(command.Name)); Preconditions.AtLeast(command.Name.Length, 3, nameof(command.Name)); - Preconditions.Equals(command.Description, ""); options = RequestOptions.CreateOrClone(options); - - return await TrySendApplicationCommand(SendJsonAsync("POST", () => $"applications/{this.CurrentUserId}/commands", command, new BucketIds(), options: options)).ConfigureAwait(false); } @@ -1174,7 +1168,6 @@ namespace Discord.API var bucket = new BucketIds(guildId: guildId); return await TrySendApplicationCommand(SendJsonAsync("POST", () => $"applications/{this.CurrentUserId}/guilds/{guildId}/commands", command, bucket, options: options)).ConfigureAwait(false); - } public async Task ModifyGuildApplicationCommandAsync(ModifyApplicationCommandParams command, ulong guildId, ulong commandId, RequestOptions options = null) { @@ -1182,21 +1175,7 @@ namespace Discord.API var bucket = new BucketIds(guildId: guildId); - try - { - return await SendJsonAsync("PATCH", () => $"applications/{this.CurrentUserId}/guilds/{guildId}/commands/{commandId}", command, bucket, options: options).ConfigureAwait(false); - } - catch (HttpException x) - { - if (x.HttpCode == HttpStatusCode.BadRequest) - { - var json = (x.Request as JsonRestRequest).Json; - throw new ApplicationCommandException(json, x); - } - - // Re-throw the http exception - throw; - } + return await TrySendApplicationCommand(SendJsonAsync("PATCH", () => $"applications/{this.CurrentUserId}/guilds/{guildId}/commands/{commandId}", command, bucket, options: options)).ConfigureAwait(false); } public async Task DeleteGuildApplicationCommandAsync(ulong guildId, ulong commandId, RequestOptions options = null) { @@ -1223,29 +1202,15 @@ namespace Discord.API var bucket = new BucketIds(guildId: guildId); return await TrySendApplicationCommand(SendJsonAsync("POST", () => $"applications/{this.CurrentUserId}/guilds/{guildId}/commands", command, bucket, options: options)).ConfigureAwait(false); - } + public async Task ModifyGuildApplicationUserCommandAsync(ModifyApplicationCommandParams command, ulong guildId, ulong commandId, RequestOptions options = null) { options = RequestOptions.CreateOrClone(options); var bucket = new BucketIds(guildId: guildId); - try - { - return await SendJsonAsync("PATCH", () => $"applications/{this.CurrentUserId}/guilds/{guildId}/commands/{commandId}", command, bucket, options: options).ConfigureAwait(false); - } - catch (HttpException x) - { - if (x.HttpCode == HttpStatusCode.BadRequest) - { - var json = (x.Request as JsonRestRequest).Json; - throw new ApplicationCommandException(json, x); - } - - // Re-throw the http exception - throw; - } + return await TrySendApplicationCommand(SendJsonAsync("PATCH", () => $"applications/{this.CurrentUserId}/guilds/{guildId}/commands/{commandId}", command, bucket, options: options)).ConfigureAwait(false); } public async Task BulkOverwriteGuildApplicationUserCommands(ulong guildId, CreateApplicationCommandParams[] commands, RequestOptions options = null) { @@ -1263,7 +1228,6 @@ namespace Discord.API var bucket = new BucketIds(guildId: guildId); return await TrySendApplicationCommand(SendJsonAsync("POST", () => $"applications/{this.CurrentUserId}/guilds/{guildId}/commands", command, bucket, options: options)).ConfigureAwait(false); - } public async Task ModifyGuildApplicationMessageCommandAsync(ModifyApplicationCommandParams command, ulong guildId, ulong commandId, RequestOptions options = null) { @@ -1271,21 +1235,7 @@ namespace Discord.API var bucket = new BucketIds(guildId: guildId); - try - { - return await SendJsonAsync("PATCH", () => $"applications/{this.CurrentUserId}/guilds/{guildId}/commands/{commandId}", command, bucket, options: options).ConfigureAwait(false); - } - catch (HttpException x) - { - if (x.HttpCode == HttpStatusCode.BadRequest) - { - var json = (x.Request as JsonRestRequest).Json; - throw new ApplicationCommandException(json, x); - } - - // Re-throw the http exception - throw; - } + return await TrySendApplicationCommand(SendJsonAsync("PATCH", () => $"applications/{this.CurrentUserId}/guilds/{guildId}/commands/{commandId}", command, bucket, options: options)).ConfigureAwait(false); } public async Task BulkOverwriteGuildApplicationMessageCommands(ulong guildId, CreateApplicationCommandParams[] commands, RequestOptions options = null) diff --git a/src/Discord.Net.Rest/DiscordRestClient.cs b/src/Discord.Net.Rest/DiscordRestClient.cs index cb28d19b6..c204a2d1c 100644 --- a/src/Discord.Net.Rest/DiscordRestClient.cs +++ b/src/Discord.Net.Rest/DiscordRestClient.cs @@ -112,25 +112,25 @@ namespace Discord.Rest => InteractionHelper.CreateGlobalCommand(this, properties, options); public Task CreateGlobalCommand(Action func, RequestOptions options = null) => InteractionHelper.CreateGlobalCommand(this, func, options); - public Task CreateGlobalUserCommand(UserCommandCreationProperties properties, RequestOptions options = null) + public Task CreateGlobalUserCommand(ContextMenuCommandCreationProperties properties, RequestOptions options = null) => InteractionHelper.CreateGlobalUserCommand(this, properties, options); - public Task CreateGlobalUserCommand(Action func, RequestOptions options = null) + public Task CreateGlobalUserCommand(Action func, RequestOptions options = null) => InteractionHelper.CreateGlobalUserCommand(this, func, options); - public Task CreateGlobalMessageCommand(MessageCommandCreationProperties properties, RequestOptions options = null) + public Task CreateGlobalMessageCommand(ContextMenuCommandCreationProperties properties, RequestOptions options = null) => InteractionHelper.CreateGlobalMessageCommand(this, properties, options); - public Task CreateGlobalMessageCommand(Action func, RequestOptions options = null) + public Task CreateGlobalMessageCommand(Action func, RequestOptions options = null) => InteractionHelper.CreateGlobalMessageCommand(this, func, options); public Task CreateGuildCommand(SlashCommandCreationProperties properties, ulong guildId, RequestOptions options = null) => InteractionHelper.CreateGuildCommand(this, guildId, properties, options); public Task CreateGuildCommand(Action func, ulong guildId, RequestOptions options = null) => InteractionHelper.CreateGuildCommand(this, guildId, func, options); - public Task CreateGuildUserCommand(UserCommandCreationProperties properties, ulong guildId, RequestOptions options = null) + public Task CreateGuildUserCommand(ContextMenuCommandCreationProperties properties, ulong guildId, RequestOptions options = null) => InteractionHelper.CreateGuildUserCommand(this, guildId, properties, options); - public Task CreateGuildUserCommand(Action func, ulong guildId, RequestOptions options = null) + public Task CreateGuildUserCommand(Action func, ulong guildId, RequestOptions options = null) => InteractionHelper.CreateGuildUserCommand(this, guildId, func, options); - public Task CreateGuildMessageCommand(MessageCommandCreationProperties properties, ulong guildId, RequestOptions options = null) + public Task CreateGuildMessageCommand(ContextMenuCommandCreationProperties properties, ulong guildId, RequestOptions options = null) => InteractionHelper.CreateGuildMessageCommand(this, guildId, properties, options); - public Task CreateGuildMessageCommand(Action func, ulong guildId, RequestOptions options = null) + public Task CreateGuildMessageCommand(Action func, ulong guildId, RequestOptions options = null) => InteractionHelper.CreateGuildMessageCommand(this, guildId, func, options); public Task> GetGlobalApplicationCommands(RequestOptions options = null) => ClientHelper.GetGlobalApplicationCommands(this, options); diff --git a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs index 2fab63347..126a211c8 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs @@ -869,6 +869,18 @@ namespace Discord.Rest public Task> GetWebhooksAsync(RequestOptions options = null) => GuildHelper.GetWebhooksAsync(this, Discord, options); + //Interactions + /// + /// Gets this guilds slash commands commands + /// + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains a read-only collection + /// of application commands found within the guild. + /// + public async Task> GetApplicationCommandsAsync (RequestOptions options = null) + => await ClientHelper.GetGuildApplicationCommands(Discord, Id, options).ConfigureAwait(false); + /// /// Returns the name of the guild. /// @@ -1154,6 +1166,8 @@ namespace Discord.Rest /// async Task> IGuild.GetWebhooksAsync(RequestOptions options) => await GetWebhooksAsync(options).ConfigureAwait(false); - + /// + async Task> IGuild.GetApplicationCommandsAsync (RequestOptions options) + => await GetApplicationCommandsAsync(options).ConfigureAwait(false); } } diff --git a/src/Discord.Net.Rest/Entities/Interactions/InteractionHelper.cs b/src/Discord.Net.Rest/Entities/Interactions/InteractionHelper.cs index d5006a6a9..b5c354047 100644 --- a/src/Discord.Net.Rest/Entities/Interactions/InteractionHelper.cs +++ b/src/Discord.Net.Rest/Entities/Interactions/InteractionHelper.cs @@ -200,22 +200,20 @@ namespace Discord.Rest await client.ApiClient.DeleteGlobalApplicationCommandAsync(command.Id, options).ConfigureAwait(false); } - public static async Task CreateGlobalUserCommand(BaseDiscordClient client, Action func, RequestOptions options = null) + public static async Task CreateGlobalUserCommand(BaseDiscordClient client, Action func, RequestOptions options = null) { - var args = new UserCommandCreationProperties(); + var args = new ContextMenuCommandCreationProperties(); func(args); return await CreateGlobalUserCommand(client, args, options).ConfigureAwait(false); } - public static async Task CreateGlobalUserCommand(BaseDiscordClient client, UserCommandCreationProperties arg, RequestOptions options = null) + public static async Task CreateGlobalUserCommand(BaseDiscordClient client, ContextMenuCommandCreationProperties arg, RequestOptions options = null) { Preconditions.NotNullOrEmpty(arg.Name, nameof(arg.Name)); - Preconditions.Equals(arg.Description, ""); var model = new CreateApplicationCommandParams() { Name = arg.Name, - Description = arg.Description, Type = arg.Type }; @@ -223,22 +221,20 @@ namespace Discord.Rest return RestGlobalUserCommand.Create(client, cmd); } - public static async Task CreateGlobalMessageCommand(BaseDiscordClient client, Action func, RequestOptions options = null) + public static async Task CreateGlobalMessageCommand(BaseDiscordClient client, Action func, RequestOptions options = null) { - var args = new MessageCommandCreationProperties(); + var args = new ContextMenuCommandCreationProperties(); func(args); return await CreateGlobalMessageCommand(client, args, options).ConfigureAwait(false); } - public static async Task CreateGlobalMessageCommand(BaseDiscordClient client, MessageCommandCreationProperties arg, RequestOptions options = null) + public static async Task CreateGlobalMessageCommand(BaseDiscordClient client, ContextMenuCommandCreationProperties arg, RequestOptions options = null) { Preconditions.NotNullOrEmpty(arg.Name, nameof(arg.Name)); - Preconditions.Equals(arg.Description, ""); var model = new CreateApplicationCommandParams() { Name = arg.Name, - Description = arg.Description, Type = arg.Type }; @@ -246,7 +242,7 @@ namespace Discord.Rest return RestGlobalMessageCommand.Create(client, cmd); } - public static async Task> BulkOverwriteGlobalUserCommands(BaseDiscordClient client, UserCommandCreationProperties[] args, RequestOptions options = null) + public static async Task> BulkOverwriteGlobalUserCommands(BaseDiscordClient client, ContextMenuCommandCreationProperties[] args, RequestOptions options = null) { Preconditions.NotNull(args, nameof(args)); @@ -255,13 +251,11 @@ namespace Discord.Rest foreach (var arg in args) { Preconditions.NotNullOrEmpty(arg.Name, nameof(arg.Name)); - Preconditions.NotNullOrEmpty(arg.Description, nameof(arg.Description)); Preconditions.Equals(arg.Type, ApplicationCommandType.User); var model = new CreateApplicationCommandParams() { Name = arg.Name, - Description = arg.Description, Type = arg.Type }; @@ -307,7 +301,7 @@ namespace Discord.Rest await client.ApiClient.DeleteGlobalApplicationCommandAsync(command.Id, options).ConfigureAwait(false); } - public static async Task> BulkOverwriteGlobalMessageCommands(BaseDiscordClient client, MessageCommandCreationProperties[] args, RequestOptions options = null) + public static async Task> BulkOverwriteGlobalMessageCommands(BaseDiscordClient client, ContextMenuCommandCreationProperties[] args, RequestOptions options = null) { Preconditions.NotNull(args, nameof(args)); @@ -316,13 +310,11 @@ namespace Discord.Rest foreach (var arg in args) { Preconditions.NotNullOrEmpty(arg.Name, nameof(arg.Name)); - Preconditions.NotNullOrEmpty(arg.Description, nameof(arg.Description)); Preconditions.Equals(arg.Type, ApplicationCommandType.Message); var model = new CreateApplicationCommandParams() { Name = arg.Name, - Description = arg.Description, Type = arg.Type }; @@ -465,22 +457,20 @@ namespace Discord.Rest await client.ApiClient.DeleteGuildApplicationCommandAsync(guildId, command.Id, options).ConfigureAwait(false); } - public static async Task CreateGuildUserCommand(BaseDiscordClient client, ulong guildId, Action func, RequestOptions options = null) + public static async Task CreateGuildUserCommand(BaseDiscordClient client, ulong guildId, Action func, RequestOptions options = null) { - var args = new UserCommandCreationProperties(); + var args = new ContextMenuCommandCreationProperties(); func(args); return await CreateGuildUserCommand(client, guildId, args, options).ConfigureAwait(false); } - public static async Task CreateGuildUserCommand(BaseDiscordClient client, ulong guildId, UserCommandCreationProperties arg, RequestOptions options = null) + public static async Task CreateGuildUserCommand(BaseDiscordClient client, ulong guildId, ContextMenuCommandCreationProperties arg, RequestOptions options = null) { Preconditions.NotNullOrEmpty(arg.Name, nameof(arg.Name)); - Preconditions.Equals(arg.Description, ""); var model = new CreateApplicationCommandParams() { Name = arg.Name, - Description = arg.Description, Type = arg.Type }; @@ -488,22 +478,20 @@ namespace Discord.Rest return RestGuildUserCommand.Create(client, cmd, guildId); } - public static async Task CreateGuildMessageCommand(BaseDiscordClient client, ulong guildId, Action func, RequestOptions options = null) + public static async Task CreateGuildMessageCommand(BaseDiscordClient client, ulong guildId, Action func, RequestOptions options = null) { - var args = new MessageCommandCreationProperties(); + var args = new ContextMenuCommandCreationProperties(); func(args); return await CreateGuildMessageCommand(client, guildId, args, options).ConfigureAwait(false); } - public static async Task CreateGuildMessageCommand(BaseDiscordClient client, ulong guildId, MessageCommandCreationProperties arg, RequestOptions options = null) + public static async Task CreateGuildMessageCommand(BaseDiscordClient client, ulong guildId, ContextMenuCommandCreationProperties arg, RequestOptions options = null) { Preconditions.NotNullOrEmpty(arg.Name, nameof(arg.Name)); - Preconditions.Equals(arg.Description, ""); var model = new CreateApplicationCommandParams() { Name = arg.Name, - Description = arg.Description, Type = arg.Type }; @@ -511,7 +499,7 @@ namespace Discord.Rest return RestGuildMessageCommand.Create(client, cmd, guildId); } - public static async Task> BulkOverwriteGuildUserCommands(BaseDiscordClient client, ulong guildId, UserCommandCreationProperties[] args, RequestOptions options = null) + public static async Task> BulkOverwriteGuildUserCommands(BaseDiscordClient client, ulong guildId, ContextMenuCommandCreationProperties[] args, RequestOptions options = null) { Preconditions.NotNull(args, nameof(args)); @@ -520,13 +508,11 @@ namespace Discord.Rest foreach (var arg in args) { Preconditions.NotNullOrEmpty(arg.Name, nameof(arg.Name)); - Preconditions.NotNullOrEmpty(arg.Description, nameof(arg.Description)); Preconditions.Equals(arg.Type, ApplicationCommandType.User); var model = new CreateApplicationCommandParams() { Name = arg.Name, - Description = arg.Description, Type = arg.Type }; @@ -573,7 +559,7 @@ namespace Discord.Rest await client.ApiClient.DeleteGuildApplicationCommandAsync(guildId, command.Id, options).ConfigureAwait(false); } - public static async Task> BulkOverwriteGuildMessageCommands(BaseDiscordClient client, ulong guildId, MessageCommandCreationProperties[] args, RequestOptions options = null) + public static async Task> BulkOverwriteGuildMessageCommands(BaseDiscordClient client, ulong guildId, ContextMenuCommandCreationProperties[] args, RequestOptions options = null) { Preconditions.NotNull(args, nameof(args)); @@ -582,13 +568,11 @@ namespace Discord.Rest foreach (var arg in args) { Preconditions.NotNullOrEmpty(arg.Name, nameof(arg.Name)); - Preconditions.NotNullOrEmpty(arg.Description, nameof(arg.Description)); Preconditions.Equals(arg.Type, ApplicationCommandType.Message); var model = new CreateApplicationCommandParams() { Name = arg.Name, - Description = arg.Description, Type = arg.Type }; @@ -642,15 +626,33 @@ namespace Discord.Rest var args = new MessageProperties(); func(args); + var embed = args.Embed; + var embeds = args.Embeds; + bool hasText = args.Content.IsSpecified ? !string.IsNullOrEmpty(args.Content.Value) : !string.IsNullOrEmpty(message.Content); - bool hasEmbed = args.Embeds.IsSpecified ? args.Embeds.Value != null : message.Embeds.Any(); - if (!hasText && !hasEmbed) + bool hasEmbeds = (embed.IsSpecified && embed.Value != null) || (embeds.IsSpecified && embeds.Value?.Length > 0) || message.Embeds.Any(); + + if (!hasText && !hasEmbeds) Preconditions.NotNullOrEmpty(args.Content.IsSpecified ? args.Content.Value : string.Empty, nameof(args.Content)); + var apiEmbeds = embed.IsSpecified || embeds.IsSpecified ? new List() : null; + + if (embed.IsSpecified && embed.Value != null) + { + apiEmbeds.Add(embed.Value.ToModel()); + } + + if (embeds.IsSpecified && embeds.Value != null) + { + apiEmbeds.AddRange(embeds.Value.Select(x => x.ToModel())); + } + + Preconditions.AtMost(apiEmbeds?.Count ?? 0, 10, nameof(args.Embeds), "A max of 10 embeds are allowed."); + var apiArgs = new API.Rest.ModifyInteractionResponseParams { Content = args.Content, - Embeds = args.Embeds.IsSpecified ? args.Embeds.Value.Select(x => x.ToModel()).ToArray() : Optional.Create(), + Embeds = apiEmbeds?.ToArray() ?? Optional.Unspecified, AllowedMentions = args.AllowedMentions.IsSpecified ? args.AllowedMentions.Value.ToModel() : Optional.Unspecified, Components = args.Components.IsSpecified ? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() : Optional.Unspecified, }; @@ -667,10 +669,33 @@ namespace Discord.Rest var args = new MessageProperties(); func(args); + var embed = args.Embed; + var embeds = args.Embeds; + + bool hasText = !string.IsNullOrEmpty(args.Content.GetValueOrDefault()); + bool hasEmbeds = (embed.IsSpecified && embed.Value != null) || (embeds.IsSpecified && embeds.Value?.Length > 0); + + if (!hasText && !hasEmbeds) + Preconditions.NotNullOrEmpty(args.Content.IsSpecified ? args.Content.Value : string.Empty, nameof(args.Content)); + + var apiEmbeds = embed.IsSpecified || embeds.IsSpecified ? new List() : null; + + if (embed.IsSpecified && embed.Value != null) + { + apiEmbeds.Add(embed.Value.ToModel()); + } + + if (embeds.IsSpecified && embeds.Value != null) + { + apiEmbeds.AddRange(embeds.Value.Select(x => x.ToModel())); + } + + Preconditions.AtMost(apiEmbeds?.Count ?? 0, 10, nameof(args.Embeds), "A max of 10 embeds are allowed."); + var apiArgs = new ModifyInteractionResponseParams { Content = args.Content, - Embeds = args.Embeds.IsSpecified ? args.Embeds.Value?.Select(x => x.ToModel()).ToArray() : Optional.Unspecified, + Embeds = apiEmbeds?.ToArray() ?? Optional.Unspecified, AllowedMentions = args.AllowedMentions.IsSpecified ? args.AllowedMentions.Value?.ToModel() : Optional.Unspecified, Components = args.Components.IsSpecified ? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() : Optional.Unspecified, Flags = args.Flags diff --git a/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs b/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs index 83ad2777e..9546025b0 100644 --- a/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs +++ b/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs @@ -1,3 +1,4 @@ +using Discord.API; using Discord.API.Rest; using System; using System.Collections.Generic; @@ -33,9 +34,13 @@ namespace Discord.Rest if (msg.Author.Id != client.CurrentUser.Id && (args.Content.IsSpecified || args.Embeds.IsSpecified || args.AllowedMentions.IsSpecified)) throw new InvalidOperationException("Only the author of a message may modify the message content, embed, or allowed mentions."); + var embed = args.Embed; + var embeds = args.Embeds; + bool hasText = args.Content.IsSpecified ? !string.IsNullOrEmpty(args.Content.Value) : !string.IsNullOrEmpty(msg.Content); - bool hasEmbed = args.Embeds.IsSpecified ? args.Embeds.Value != null : msg.Embeds.Any(); - if (!hasText && !hasEmbed) + bool hasEmbeds = (embed.IsSpecified && embed.Value != null) || (embeds.IsSpecified && embeds.Value?.Length > 0) || msg.Embeds.Any(); + + if (!hasText && !hasEmbeds) Preconditions.NotNullOrEmpty(args.Content.IsSpecified ? args.Content.Value : string.Empty, nameof(args.Content)); if (args.AllowedMentions.IsSpecified) @@ -61,22 +66,24 @@ namespace Discord.Rest } } - var embeds = new List(); + var apiEmbeds = embed.IsSpecified || embeds.IsSpecified ? new List() : null; - if (args.Embed.IsSpecified) + if (embed.IsSpecified && embed.Value != null) { - embeds.Add(args.Embed.Value.ToModel()); + apiEmbeds.Add(embed.Value.ToModel()); } - if (args.Embeds.IsSpecified) + if (embeds.IsSpecified && embeds.Value != null) { - embeds.AddRange(args.Embeds.Value.Select(x => x.ToModel())); + apiEmbeds.AddRange(embeds.Value.Select(x => x.ToModel())); } + Preconditions.AtMost(apiEmbeds?.Count ?? 0, 10, nameof(args.Embeds), "A max of 10 embeds are allowed."); + var apiArgs = new ModifyMessageParams { Content = args.Content, - Embeds = embeds.ToArray(), + Embeds = apiEmbeds?.ToArray() ?? Optional.Unspecified, Components = args.Components.IsSpecified ? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() : Optional.Unspecified, Flags = args.Flags, AllowedMentions = args.AllowedMentions.IsSpecified ? args.AllowedMentions.Value.ToModel() : Optional.Unspecified, @@ -90,7 +97,13 @@ namespace Discord.Rest var args = new MessageProperties(); func(args); - if (args.Content.IsSpecified && string.IsNullOrEmpty(args.Content.Value) && args.Embeds.IsSpecified && args.Embeds.Value == null) + var embed = args.Embed; + var embeds = args.Embeds; + + bool hasText = args.Content.IsSpecified && string.IsNullOrEmpty(args.Content.Value); + bool hasEmbeds = (embed.IsSpecified && embed.Value != null) || (embeds.IsSpecified && embeds.Value?.Length > 0); + + if (!hasText && !hasEmbeds) Preconditions.NotNullOrEmpty(args.Content.IsSpecified ? args.Content.Value : string.Empty, nameof(args.Content)); if (args.AllowedMentions.IsSpecified) @@ -117,22 +130,24 @@ namespace Discord.Rest } } - var embeds = new List(); + var apiEmbeds = embed.IsSpecified || embeds.IsSpecified ? new List() : null; - if (args.Embed.IsSpecified) + if (embed.IsSpecified && embed.Value != null) { - embeds.Add(args.Embed.Value.ToModel()); + apiEmbeds.Add(embed.Value.ToModel()); } - if (args.Embeds.IsSpecified) + if (embeds.IsSpecified && embeds.Value != null) { - embeds.AddRange(args.Embeds.Value.Select(x => x.ToModel())); + apiEmbeds.AddRange(embeds.Value.Select(x => x.ToModel())); } + Preconditions.AtMost(apiEmbeds?.Count ?? 0, 10, nameof(args.Embeds), "A max of 10 embeds are allowed."); + var apiArgs = new API.Rest.ModifyMessageParams { Content = args.Content, - Embeds = embeds.ToArray(), + Embeds = apiEmbeds?.ToArray() ?? Optional.Unspecified, Flags = args.Flags.IsSpecified ? args.Flags.Value : Optional.Create(), AllowedMentions = args.AllowedMentions.IsSpecified ? args.AllowedMentions.Value.ToModel() : Optional.Create(), Components = args.Components.IsSpecified ? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() : Optional.Unspecified, 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.Rest/Entities/Webhooks/RestWebhook.cs b/src/Discord.Net.Rest/Entities/Webhooks/RestWebhook.cs index 9baddf003..5ae09fde0 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; + + ApplicationId = model.ApplicationId; } /// 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.WebSocket/Discord.Net.WebSocket.csproj b/src/Discord.Net.WebSocket/Discord.Net.WebSocket.csproj index af17578fb..167ccebb0 100644 --- a/src/Discord.Net.WebSocket/Discord.Net.WebSocket.csproj +++ b/src/Discord.Net.WebSocket/Discord.Net.WebSocket.csproj @@ -8,7 +8,7 @@ net461;netstandard2.0;netstandard2.1 netstandard2.0;netstandard2.1 true - 3.0.0-pre + 3.0.1-pre https://github.com/Discord-Net-Labs/Discord.Net-Labs https://github.com/Discord-Net-Labs/Discord.Net-Labs Temporary.png @@ -16,6 +16,8 @@ ..\Discord.Net.WebSocket\Discord.Net.WebSocket.xml + 3.0.1 + 3.0.1 TRACE; diff --git a/src/Discord.Net.WebSocket/Discord.Net.WebSocket.xml b/src/Discord.Net.WebSocket/Discord.Net.WebSocket.xml index 742bebac0..485a83a61 100644 --- a/src/Discord.Net.WebSocket/Discord.Net.WebSocket.xml +++ b/src/Discord.Net.WebSocket/Discord.Net.WebSocket.xml @@ -3599,40 +3599,43 @@ - + + + + Represents a Websocket-based slash command received over the gateway. - + The data associated with this interaction. - + - Represents the data tied with the interaction. + Represents the data tied with the interaction. - + - + Represents a Websocket-based slash command received over the gateway. - + The data associated with this interaction. - + - Represents the data tied with the interaction. + Represents the data tied with the interaction. - + @@ -3664,15 +3667,19 @@ - + - Acknowledges this interaction with the . + Defers an interaction and responds with type 5 () + to send this message ephemerally, otherwise . The request options for this async request. A task that represents the asynchronous operation of acknowledging the interaction. + + + Represents the data sent with a . @@ -3812,6 +3819,11 @@ If the option is a subcommand or subcommand group type, this nested options will be the parameters. + + + Base class for User, Message, and Slash command interactions + + The data associated with this interaction. @@ -3823,7 +3835,7 @@ - + Acknowledges this interaction with the . @@ -3831,6 +3843,27 @@ A task that represents the asynchronous operation of acknowledging the interaction. + + + Represents the base data tied with the interaction. + + + + + + + + The 's received with this interaction. + + + + + Represents the base Websocket-based recieved by the gateway + + + + + @@ -3943,14 +3976,25 @@ A task that represents the asynchronous operation of acknowledging the interaction. - + Acknowledges this interaction. + to send this message ephemerally, otherwise . + The request options for this async request. A task that represents the asynchronous operation of acknowledging the interaction. + + + + + + + + + Represents a WebSocket-based invite to a guild. @@ -4487,6 +4531,12 @@ + + + + + + @@ -4545,6 +4595,12 @@ + + + + + + @@ -4608,7 +4664,7 @@ 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. @@ -4730,6 +4786,12 @@ + + + + + + @@ -4783,6 +4845,12 @@ + + + + + + @@ -4890,6 +4958,12 @@ + + + + + + @@ -4920,6 +4994,12 @@ + + + + + + @@ -4958,6 +5038,9 @@ + + + @@ -5039,6 +5122,14 @@ + + + Webhook users does not support banners. + + + + Webhook users does not support accent colors. + diff --git a/src/Discord.Net.WebSocket/DiscordShardedClient.cs b/src/Discord.Net.WebSocket/DiscordShardedClient.cs index 6847d8580..450145f1c 100644 --- a/src/Discord.Net.WebSocket/DiscordShardedClient.cs +++ b/src/Discord.Net.WebSocket/DiscordShardedClient.cs @@ -30,7 +30,16 @@ namespace Discord.WebSocket /// public override IActivity Activity { get => _shards[0].Activity; protected set { } } - internal new DiscordSocketApiClient ApiClient => base.ApiClient as DiscordSocketApiClient; + internal new DiscordSocketApiClient ApiClient + { + get + { + if (base.ApiClient.CurrentUserId == null) + base.ApiClient.CurrentUserId = CurrentUser?.Id; + + return base.ApiClient; + } + } /// public override IReadOnlyCollection Guilds => GetGuilds().ToReadOnlyCollection(GetGuildCount); /// diff --git a/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs b/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs index 9221a3faa..e19cedb33 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs @@ -79,7 +79,7 @@ namespace Discord.API if (msg != null) { #if DEBUG_PACKETS - Console.WriteLine($"<- {msg.Operation} [{msg.Type ?? "none"}] : {(msg.Payload as Newtonsoft.Json.Linq.JToken)?.ToString().Length}"); + Console.WriteLine($"<- {(GatewayOpCode)msg.Operation} [{msg.Type ?? "none"}] : {(msg.Payload as Newtonsoft.Json.Linq.JToken)?.ToString().Length}"); #endif await _receivedGatewayEvent.InvokeAsync((GatewayOpCode)msg.Operation, msg.Sequence, msg.Type, msg.Payload).ConfigureAwait(false); @@ -96,7 +96,7 @@ namespace Discord.API if (msg != null) { #if DEBUG_PACKETS - Console.WriteLine($"<- {msg.Operation} [{msg.Type ?? "none"}] : {(msg.Payload as Newtonsoft.Json.Linq.JToken)?.ToString().Length}"); + Console.WriteLine($"<- {(GatewayOpCode)msg.Operation} [{msg.Type ?? "none"}] : {(msg.Payload as Newtonsoft.Json.Linq.JToken)?.ToString().Length}"); #endif await _receivedGatewayEvent.InvokeAsync((GatewayOpCode)msg.Operation, msg.Sequence, msg.Type, msg.Payload).ConfigureAwait(false); @@ -105,6 +105,10 @@ namespace Discord.API }; WebSocketClient.Closed += async ex => { +#if DEBUG_PACKETS + Console.WriteLine(ex); +#endif + await DisconnectAsync().ConfigureAwait(false); await _disconnectedEvent.InvokeAsync(ex).ConfigureAwait(false); }; @@ -166,6 +170,11 @@ namespace Discord.API var gatewayResponse = await GetGatewayAsync().ConfigureAwait(false); _gatewayUrl = $"{gatewayResponse.Url}?v={DiscordConfig.APIVersion}&encoding={DiscordSocketConfig.GatewayEncoding}&compress=zlib-stream"; } + +#if DEBUG_PACKETS + Console.WriteLine("Connecting to gateway: " + _gatewayUrl); +#endif + await WebSocketClient.ConnectAsync(_gatewayUrl).ConfigureAwait(false); ConnectionState = ConnectionState.Connected; @@ -237,7 +246,9 @@ namespace Discord.API options = RequestOptions.CreateOrClone(options); var props = new Dictionary { - ["$device"] = "Discord.Net" + ["$device"] = "Discord.Net Labs", + ["$os"] = Environment.OSVersion.Platform.ToString(), + [$"browser"] = "Discord.Net Labs" }; var msg = new IdentifyParams() { diff --git a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs index c32bb3f49..5c385fe01 100644 --- a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs +++ b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs @@ -1478,6 +1478,9 @@ namespace Discord.WebSocket /// async Task> IGuild.GetWebhooksAsync(RequestOptions options) => await GetWebhooksAsync(options).ConfigureAwait(false); + /// + async Task> IGuild.GetApplicationCommandsAsync (RequestOptions options) + => await GetApplicationCommandsAsync(options).ConfigureAwait(false); void IDisposable.Dispose() { diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/Message Commands/SocketApplicationMessageCommand.cs b/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/Message Commands/SocketMessageCommand.cs similarity index 69% rename from src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/Message Commands/SocketApplicationMessageCommand.cs rename to src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/Message Commands/SocketMessageCommand.cs index 828ed14bb..5d8b0af43 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/Message Commands/SocketApplicationMessageCommand.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/Message Commands/SocketMessageCommand.cs @@ -10,14 +10,14 @@ namespace Discord.WebSocket /// /// Represents a Websocket-based slash command received over the gateway. /// - public class SocketApplicationMessageCommand : SocketCommandBase + public class SocketMessageCommand : SocketCommandBase { /// /// The data associated with this interaction. /// - new public SocketApplicationMessageCommandData Data { get; } + new public SocketMessageCommandData Data { get; } - internal SocketApplicationMessageCommand(DiscordSocketClient client, Model model, ISocketMessageChannel channel) + internal SocketMessageCommand(DiscordSocketClient client, Model model, ISocketMessageChannel channel) : base(client, model, channel) { var dataModel = model.Data.IsSpecified ? @@ -28,12 +28,12 @@ namespace Discord.WebSocket if (this.Channel is SocketGuildChannel guildChannel) guildId = guildChannel.Guild.Id; - Data = SocketApplicationMessageCommandData.Create(client, dataModel, model.Id, guildId); + Data = SocketMessageCommandData.Create(client, dataModel, model.Id, guildId); } new internal static SocketInteraction Create(DiscordSocketClient client, Model model, ISocketMessageChannel channel) { - var entity = new SocketApplicationMessageCommand(client, model, channel); + var entity = new SocketMessageCommand(client, model, channel); entity.Update(model); return entity; } diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/Message Commands/SocketApplicationMessageCommandData.cs b/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/Message Commands/SocketMessageCommandData.cs similarity index 89% rename from src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/Message Commands/SocketApplicationMessageCommandData.cs rename to src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/Message Commands/SocketMessageCommandData.cs index ead330ef6..9c925c4ce 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/Message Commands/SocketApplicationMessageCommandData.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/Message Commands/SocketMessageCommandData.cs @@ -6,12 +6,15 @@ using Model = Discord.API.ApplicationCommandInteractionData; namespace Discord.WebSocket { /// - /// Represents the data tied with the interaction. + /// Represents the data tied with the interaction. /// - public class SocketApplicationMessageCommandData : SocketEntity, IApplicationCommandInteractionData + public class SocketMessageCommandData : SocketEntity, IApplicationCommandInteractionData { /// public string Name { get; private set; } + /// + /// The message selected to run the command + /// public SocketMessage Message { get; private set; } internal Dictionary guildMembers { get; private set; } @@ -27,15 +30,11 @@ namespace Discord.WebSocket private ulong? guildId; - private ApplicationCommandType Type; - - internal SocketApplicationMessageCommandData(DiscordSocketClient client, Model model, ulong? guildId) + internal SocketMessageCommandData(DiscordSocketClient client, Model model, ulong? guildId) : base(client, model.Id) { this.guildId = guildId; - this.Type = (ApplicationCommandType)model.Type; - if (model.Resolved.IsSpecified) { var guild = this.guildId.HasValue ? Discord.GetGuild(this.guildId.Value) : null; @@ -126,9 +125,9 @@ namespace Discord.WebSocket } } - internal static SocketApplicationMessageCommandData Create(DiscordSocketClient client, Model model, ulong id, ulong? guildId) + internal static SocketMessageCommandData Create(DiscordSocketClient client, Model model, ulong id, ulong? guildId) { - var entity = new SocketApplicationMessageCommandData(client, model, guildId); + var entity = new SocketMessageCommandData(client, model, guildId); entity.Update(model); return entity; } diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/User Commands/SocketApplicationUserCommand.cs b/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/User Commands/SocketUserCommand.cs similarity index 70% rename from src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/User Commands/SocketApplicationUserCommand.cs rename to src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/User Commands/SocketUserCommand.cs index 603a11397..f4c3a8b7b 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/User Commands/SocketApplicationUserCommand.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/User Commands/SocketUserCommand.cs @@ -10,14 +10,14 @@ namespace Discord.WebSocket /// /// Represents a Websocket-based slash command received over the gateway. /// - public class SocketApplicationUserCommand : SocketCommandBase + public class SocketUserCommand : SocketCommandBase { /// /// The data associated with this interaction. /// - new public SocketApplicationUserCommandData Data { get; } + new public SocketUserCommandData Data { get; } - internal SocketApplicationUserCommand(DiscordSocketClient client, Model model, ISocketMessageChannel channel) + internal SocketUserCommand(DiscordSocketClient client, Model model, ISocketMessageChannel channel) : base(client, model, channel) { var dataModel = model.Data.IsSpecified ? @@ -28,12 +28,12 @@ namespace Discord.WebSocket if (this.Channel is SocketGuildChannel guildChannel) guildId = guildChannel.Guild.Id; - Data = SocketApplicationUserCommandData.Create(client, dataModel, model.Id, guildId); + Data = SocketUserCommandData.Create(client, dataModel, model.Id, guildId); } new internal static SocketInteraction Create(DiscordSocketClient client, Model model, ISocketMessageChannel channel) { - var entity = new SocketApplicationUserCommand(client, model, channel); + var entity = new SocketUserCommand(client, model, channel); entity.Update(model); return entity; } diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/User Commands/SocketApplicationUserCommandData.cs b/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/User Commands/SocketUserCommandData.cs similarity index 86% rename from src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/User Commands/SocketApplicationUserCommandData.cs rename to src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/User Commands/SocketUserCommandData.cs index a6eb24ca5..1cdfed097 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/User Commands/SocketApplicationUserCommandData.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/User Commands/SocketUserCommandData.cs @@ -6,13 +6,15 @@ using Model = Discord.API.ApplicationCommandInteractionData; namespace Discord.WebSocket { /// - /// Represents the data tied with the interaction. + /// Represents the data tied with the interaction. /// - public class SocketApplicationUserCommandData : SocketEntity, IApplicationCommandInteractionData + public class SocketUserCommandData : SocketEntity, IApplicationCommandInteractionData { /// public string Name { get; private set; } - + /// + /// The user used to run the command + /// public SocketUser Member { get; private set; } internal Dictionary guildMembers { get; private set; } @@ -28,15 +30,11 @@ namespace Discord.WebSocket private ulong? guildId; - private ApplicationCommandType Type; - - internal SocketApplicationUserCommandData(DiscordSocketClient client, Model model, ulong? guildId) + internal SocketUserCommandData(DiscordSocketClient client, Model model, ulong? guildId) : base(client, model.Id) { this.guildId = guildId; - this.Type = (ApplicationCommandType)model.Type; - if (model.Resolved.IsSpecified) { var guild = this.guildId.HasValue ? Discord.GetGuild(this.guildId.Value) : null; @@ -99,9 +97,9 @@ namespace Discord.WebSocket } } - internal static SocketApplicationUserCommandData Create(DiscordSocketClient client, Model model, ulong id, ulong? guildId) + internal static SocketUserCommandData Create(DiscordSocketClient client, Model model, ulong id, ulong? guildId) { - var entity = new SocketApplicationUserCommandData(client, model, guildId); + var entity = new SocketUserCommandData(client, model, guildId); entity.Update(model); return entity; } diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/Message Components/SocketMessageComponent.cs b/src/Discord.Net.WebSocket/Entities/Interaction/Message Components/SocketMessageComponent.cs index 921f81de4..cc91b098f 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/Message Components/SocketMessageComponent.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/Message Components/SocketMessageComponent.cs @@ -4,12 +4,13 @@ using System.Threading.Tasks; using Model = Discord.API.Interaction; using DataModel = Discord.API.MessageComponentInteractionData; using Discord.Rest; +using System.Collections.Generic; namespace Discord.WebSocket { - /// - /// Represents a Websocket-based interaction type for Message Components. - /// +/// +/// Represents a Websocket-based interaction type for Message Components. +/// public class SocketMessageComponent : SocketInteraction { /// @@ -123,7 +124,7 @@ namespace Discord.WebSocket }; if (ephemeral) - response.Data.Value.Flags = 64; + response.Data.Value.Flags = MessageFlags.Ephemeral; await InteractionHelper.SendInteractionResponse(this.Discord, response, this.Id, Token, options); } @@ -149,8 +150,28 @@ namespace Discord.WebSocket Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions), "A max of 100 user Ids are allowed."); } - if (args.Embeds.IsSpecified) - Preconditions.AtMost(args.Embeds.Value?.Length ?? 0, 10, nameof(args.Embeds), "A max of 10 embeds are allowed."); + var embed = args.Embed; + var embeds = args.Embeds; + + bool hasText = args.Content.IsSpecified ? !string.IsNullOrEmpty(args.Content.Value) : !string.IsNullOrEmpty(Message.Content); + bool hasEmbeds = (embed.IsSpecified && embed.Value != null) || (embeds.IsSpecified && embeds.Value?.Length > 0) || Message.Embeds.Any(); + + if (!hasText && !hasEmbeds) + Preconditions.NotNullOrEmpty(args.Content.IsSpecified ? args.Content.Value : string.Empty, nameof(args.Content)); + + var apiEmbeds = embed.IsSpecified || embeds.IsSpecified ? new List() : null; + + if (embed.IsSpecified && embed.Value != null) + { + apiEmbeds.Add(embed.Value.ToModel()); + } + + if (embeds.IsSpecified && embeds.Value != null) + { + apiEmbeds.AddRange(embeds.Value.Select(x => x.ToModel())); + } + + Preconditions.AtMost(apiEmbeds?.Count ?? 0, 10, nameof(args.Embeds), "A max of 10 embeds are allowed."); // check that user flag and user Id list are exclusive, same with role flag and role Id list if (args.AllowedMentions.IsSpecified && args.AllowedMentions.Value != null && args.AllowedMentions.Value.AllowedTypes.HasValue) @@ -176,11 +197,11 @@ namespace Discord.WebSocket { Content = args.Content, AllowedMentions = args.AllowedMentions.IsSpecified ? args.AllowedMentions.Value?.ToModel() : Optional.Unspecified, - Embeds = args.Embeds.IsSpecified ? args.Embeds.Value?.Select(x => x.ToModel()).ToArray() : Optional.Unspecified, + Embeds = apiEmbeds?.ToArray() ?? Optional.Unspecified, Components = args.Components.IsSpecified ? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() : Optional.Unspecified, - Flags = args.Flags.IsSpecified ? (int?)args.Flags.Value ?? Optional.Unspecified : Optional.Unspecified + Flags = args.Flags.IsSpecified ? args.Flags.Value ?? Optional.Unspecified : Optional.Unspecified } }; @@ -213,27 +234,43 @@ namespace Discord.WebSocket AllowedMentions = allowedMentions?.ToModel() ?? Optional.Unspecified, IsTTS = isTTS, Embeds = embeds?.Select(x => x.ToModel()).ToArray() ?? Optional.Unspecified, - Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional.Unspecified + Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional.Unspecified, }; if (ephemeral) - args.Flags = 64; + args.Flags = MessageFlags.Ephemeral; return await InteractionHelper.SendFollowupAsync(Discord.Rest, args, Token, Channel, options); } /// - /// Acknowledges this interaction with the . + /// Defers an interaction and responds with type 5 () /// + /// to send this message ephemerally, otherwise . /// The request options for this async request. /// /// A task that represents the asynchronous operation of acknowledging the interaction. /// - public override Task DeferAsync(RequestOptions options = null) + public Task DeferLoadingAsync(bool ephemeral = false, RequestOptions options = null) + { + var response = new API.InteractionResponse() + { + Type = InteractionResponseType.DeferredChannelMessageWithSource, + Data = ephemeral ? new API.InteractionCallbackData() { Flags = MessageFlags.Ephemeral } : Optional.Unspecified + + }; + + return Discord.Rest.ApiClient.CreateInteractionResponse(response, this.Id, this.Token, options); + } + + /// + public override Task DeferAsync(bool ephemeral = false, RequestOptions options = null) { var response = new API.InteractionResponse() { Type = InteractionResponseType.DeferredUpdateMessage, + Data = ephemeral ? new API.InteractionCallbackData() { Flags = MessageFlags.Ephemeral } : Optional.Unspecified + }; return Discord.Rest.ApiClient.CreateInteractionResponse(response, this.Id, this.Token, options); diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketSlashCommand.cs b/src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketSlashCommand.cs index 3013099c7..36542f15a 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketSlashCommand.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketSlashCommand.cs @@ -36,6 +36,6 @@ namespace Discord.WebSocket var entity = new SocketSlashCommand(client, model, channel); entity.Update(model); return entity; - } + } } } diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketSlashCommandDataOption.cs b/src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketSlashCommandDataOption.cs index f33008cf3..f9c12257e 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketSlashCommandDataOption.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketSlashCommandDataOption.cs @@ -86,9 +86,9 @@ namespace Discord.WebSocket break; case ApplicationCommandOptionType.Integer: { - if (model.Value.Value is int val) + if (model.Value.Value is long val) this.Value = val; - else if (int.TryParse(model.Value.Value.ToString(), out int res)) + else if (long.TryParse(model.Value.Value.ToString(), out long res)) this.Value = res; } break; @@ -109,7 +109,7 @@ namespace Discord.WebSocket } break; } - + } this.Options = model.Options.IsSpecified diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBase.cs b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBase.cs index 4dbadc552..6ba9ba05a 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBase.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBase.cs @@ -9,6 +9,9 @@ using Model = Discord.API.Interaction; namespace Discord.WebSocket { + /// + /// Base class for User, Message, and Slash command interactions + /// public class SocketCommandBase : SocketInteraction { /// @@ -105,7 +108,7 @@ namespace Discord.WebSocket }; if (ephemeral) - response.Data.Value.Flags = 64; + response.Data.Value.Flags = MessageFlags.Ephemeral; await InteractionHelper.SendInteractionResponse(this.Discord, response, this.Id, Token, options); } @@ -140,7 +143,7 @@ namespace Discord.WebSocket }; if (ephemeral) - args.Flags = 64; + args.Flags = MessageFlags.Ephemeral; return await InteractionHelper.SendFollowupAsync(Discord.Rest, args, Token, Channel, options); } @@ -151,7 +154,7 @@ namespace Discord.WebSocket /// /// A task that represents the asynchronous operation of acknowledging the interaction. /// - public override Task DeferAsync(RequestOptions options = null) + public override Task DeferAsync(bool ephemeral = false, RequestOptions options = null) { var response = new API.InteractionResponse { diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBaseData.cs b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBaseData.cs index 7d7dbbce7..dde981eb9 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBaseData.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBaseData.cs @@ -5,13 +5,17 @@ using Model = Discord.API.ApplicationCommandInteractionData; namespace Discord.WebSocket { + /// + /// Represents the base data tied with the interaction. + /// public class SocketCommandBaseData : SocketEntity, IApplicationCommandInteractionData { + /// public string Name { get; private set; } - + /// + /// The 's received with this interaction. + /// public IReadOnlyCollection Options { get; private set; } - // id - // type internal Dictionary guildMembers { get; private set; } = new Dictionary(); internal Dictionary users { get; private set; } diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBaseDataOption.cs b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBaseDataOption.cs index 369f1f868..6aa15978e 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBaseDataOption.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBaseDataOption.cs @@ -9,8 +9,12 @@ using Model = Discord.API.ApplicationCommandInteractionDataOption; namespace Discord.WebSocket { + /// + /// Represents the base Websocket-based recieved by the gateway + /// public class SocketCommandBaseDataOption : IApplicationCommandInteractionDataOption { + /// public string Name { get; private set; } /// diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs b/src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs index 59bae6f08..9b42ed0e2 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs @@ -71,9 +71,9 @@ namespace Discord.WebSocket if (dataModel != null) { if (dataModel.Type.Equals(ApplicationCommandType.User)) - return SocketApplicationUserCommand.Create(client, model, channel); + return SocketUserCommand.Create(client, model, channel); if (dataModel.Type.Equals(ApplicationCommandType.Message)) - return SocketApplicationMessageCommand.Create(client, model, channel); + return SocketMessageCommand.Create(client, model, channel); } } return SocketSlashCommand.Create(client, model, channel); @@ -172,20 +172,37 @@ namespace Discord.WebSocket /// A task that represents the asynchronous operation of acknowledging the interaction. /// [Obsolete("This method deprecated, please use DeferAsync instead")] - public Task AcknowledgeAsync(RequestOptions options = null) => DeferAsync(options); + public Task AcknowledgeAsync(RequestOptions options = null) => DeferAsync(options: options); /// /// Acknowledges this interaction. /// + /// to send this message ephemerally, otherwise . + /// The request options for this async request. /// /// A task that represents the asynchronous operation of acknowledging the interaction. /// - public abstract Task DeferAsync(RequestOptions options = null); + public abstract Task DeferAsync(bool ephemeral = false, RequestOptions options = null); private bool CheckToken() { // Tokens last for 15 minutes according to https://discord.com/developers/docs/interactions/slash-commands#responding-to-an-interaction return (DateTime.UtcNow - this.CreatedAt.UtcDateTime).TotalMinutes <= 15d; } + + // IDiscordInteraction + + /// + async Task IDiscordInteraction.FollowupAsync (string text, Embed[] embeds, bool isTTS, bool ephemeral, AllowedMentions allowedMentions, + RequestOptions options, MessageComponent component, Embed embed) + => await FollowupAsync(text, embeds, isTTS, ephemeral, allowedMentions, options, component, embed).ConfigureAwait(false); + + /// + async Task IDiscordInteraction.GetOriginalResponseAsync (RequestOptions options) + => await GetOriginalResponseAsync(options).ConfigureAwait(false); + + /// + async Task IDiscordInteraction.ModifyOriginalResponseAsync (Action func, RequestOptions options) + => await ModifyOriginalResponseAsync(func, options).ConfigureAwait(false); } } diff --git a/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs index 82c656486..1b62d14dd 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs @@ -122,7 +122,10 @@ namespace Discord.WebSocket } internal static SocketMessage Create(DiscordSocketClient discord, ClientState state, SocketUser author, ISocketMessageChannel channel, Model model) { - if (model.Type == MessageType.Default || model.Type == MessageType.Reply) + if (model.Type == MessageType.Default || + model.Type == MessageType.Reply || + model.Type == MessageType.ApplicationCommand || + model.Type == MessageType.ThreadStarterMessage) return SocketUserMessage.Create(discord, state, author, channel, model); else return SocketSystemMessage.Create(discord, state, author, channel, model); 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/src/Discord.Net.Webhook/Discord.Net.Webhook.csproj b/src/Discord.Net.Webhook/Discord.Net.Webhook.csproj index 13e5cf111..f04dedf43 100644 --- a/src/Discord.Net.Webhook/Discord.Net.Webhook.csproj +++ b/src/Discord.Net.Webhook/Discord.Net.Webhook.csproj @@ -6,7 +6,7 @@ Discord.Webhook A core Discord.Net Labs library containing the Webhook client and models. netstandard2.0;netstandard2.1 - 2.3.4 + 3.0.0-pre Discord.Net.Labs.Webhook https://github.com/Discord-Net-Labs/Discord.Net-Labs https://github.com/Discord-Net-Labs/Discord.Net-Labs 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..2a5c4786e 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; + + ApplicationId = model.ApplicationId; } 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) diff --git a/src/Discord.Net/Discord.Net.nuspec b/src/Discord.Net/Discord.Net.nuspec index 595203eba..eb3bd9b6d 100644 --- a/src/Discord.Net/Discord.Net.nuspec +++ b/src/Discord.Net/Discord.Net.nuspec @@ -2,7 +2,7 @@ Discord.Net.Labs - 3.0.1-pre$suffix$ + 3.0.2-pre$suffix$ Discord.Net Labs Discord.Net Contributors quinchs @@ -14,23 +14,23 @@ https://avatars.githubusercontent.com/u/84047264 - - - + + + - - - + + + - - - + + + diff --git a/test/Discord.Net.Tests.Unit/GuildPermissionsTests.cs b/test/Discord.Net.Tests.Unit/GuildPermissionsTests.cs index 137dc5575..9be109c6e 100644 --- a/test/Discord.Net.Tests.Unit/GuildPermissionsTests.cs +++ b/test/Discord.Net.Tests.Unit/GuildPermissionsTests.cs @@ -91,7 +91,13 @@ 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(manageEmojisAndStickers: true), GuildPermission.ManageEmojisAndStickers); + AssertFlag(() => new GuildPermissions(useSlashCommands: true), GuildPermission.UseSlashCommands); + AssertFlag(() => new GuildPermissions(requestToSpeak: true), GuildPermission.RequestToSpeak); + AssertFlag(() => new GuildPermissions(manageThreads: true), GuildPermission.ManageThreads); + AssertFlag(() => new GuildPermissions(usePublicThreads: true), GuildPermission.UsePublicThreads); + AssertFlag(() => new GuildPermissions(usePrivateThreads: true), GuildPermission.UsePrivateThreads); + AssertFlag(() => new GuildPermissions(useExternalStickers: true), GuildPermission.UseExternalStickers); } /// @@ -161,7 +167,13 @@ 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(manageEmojisAndStickers: enable)); + AssertUtil(GuildPermission.UseSlashCommands, x => x.UseSlashCommands, (p, enable) => p.Modify(useSlashCommands: enable)); + AssertUtil(GuildPermission.RequestToSpeak, x => x.RequestToSpeak, (p, enable) => p.Modify(requestToSpeak: enable)); + AssertUtil(GuildPermission.ManageThreads, x => x.ManageThreads, (p, enable) => p.Modify(manageThreads: enable)); + AssertUtil(GuildPermission.UsePublicThreads, x => x.UsePublicThreads, (p, enable) => p.Modify(usePublicThreads: enable)); + AssertUtil(GuildPermission.UsePrivateThreads, x => x.UsePrivateThreads, (p, enable) => p.Modify(usePrivateThreads: enable)); + AssertUtil(GuildPermission.UseExternalStickers, x => x.UseExternalStickers, (p, enable) => p.Modify(useExternalStickers: enable)); } } }