From 097d284e5312fdd9fc6a708169b6539cd838e812 Mon Sep 17 00:00:00 2001 From: quin lynch Date: Sat, 16 Oct 2021 22:27:52 -0300 Subject: [PATCH] Merge branch 'release/3.x' of https://github.com/Discord-Net-Labs/Discord.Net-Labs into merger-labs Update requested changes of obsolete and references to labs. Added `Interaction` to `IMessage` Fixed grammar Fixed bugs relating to interactions. --- .../02-creating-slash-commands.md | 13 +- .../03-responding-to-slash-commands.md | 9 +- .../slash-commands/04-parameters.md | 5 + .../05-responding-ephemerally.md | 21 +- .../slash-commands/06-subcommands.md | 5 + .../slash-commands/07-choice-slash-command.md | 5 + ...bulk-overwrite-of-global-slash-commands.md | 12 +- .../slash-commands/README.md | 15 +- docs/guides/interactions/intro.md | 10 + docs/guides/toc.yml | 20 +- .../02_commands_framework.csproj | 2 +- samples/02_commands_framework/Program.cs | 2 +- .../03_sharded_client.csproj | 2 +- .../Services/CommandHandlingService.cs | 2 +- samples/idn/idn.csproj | 2 +- .../Discord.Net.Analyzers.csproj | 3 +- .../Attributes/AliasAttribute.cs | 2 +- .../Builders/ModuleClassBuilder.cs | 2 +- src/Discord.Net.Commands/CommandService.cs | 2 +- src/Discord.Net.Commands/ModuleBase.cs | 2 +- src/Discord.Net.Commands/RunMode.cs | 2 +- src/Discord.Net.Core/CDN.cs | 10 + .../Entities/Activities/ActivityType.cs | 4 + .../Entities/AuditLogs/ActionType.cs | 12 ++ .../Entities/Channels/IStageChannel.cs | 3 - .../Entities/Channels/IThreadChannel.cs | 13 +- .../Channels/StageInstanceProperties.cs | 6 - .../Entities/Channels/StagePrivacyLevel.cs | 6 - .../Channels/ThreadArchiveDuration.cs | 12 +- .../Entities/Channels/ThreadType.cs | 8 +- .../Entities/Guilds/IGuild.cs | 10 +- .../Interactions/ApplicationCommandOption.cs | 26 ++- .../ApplicationCommandOptionChoice.cs | 20 +- .../ApplicationCommandOptionType.cs | 10 +- .../ApplicationCommandProperties.cs | 6 - .../Interactions/ApplicationCommandTypes.cs | 6 - .../Interactions/AutocompleteOption.cs | 6 - .../Interactions/AutocompleteResult.cs | 42 ++-- .../Context Menus/MessageCommandBuilder.cs | 21 +- .../Context Menus/MessageCommandProperties.cs | 6 - .../Context Menus/UserCommandBuilder.cs | 19 +- .../Context Menus/UserCommandProperties.cs | 6 - .../Interactions/IApplicationCommand.cs | 2 - .../IApplicationCommandInteractionData.cs | 4 - ...ApplicationCommandInteractionDataOption.cs | 5 - .../Interactions/IApplicationCommandOption.cs | 13 +- .../IApplicationCommandOptionChoice.cs | 7 - .../Interactions/IDiscordInteraction.cs | 17 +- .../Interactions/IDiscordInteractionData.cs | 8 +- .../Interactions/InteractionResponseType.cs | 18 +- .../Entities/Interactions/InteractionType.cs | 8 +- .../Message Components/ActionRowComponent.cs | 12 +- .../Message Components/ButtonComponent.cs | 9 +- .../Message Components/ButtonStyle.cs | 6 - .../Message Components/ComponentType.cs | 16 +- .../Message Components/IMessageComponent.cs | 6 - .../Message Components/MessageComponent.cs | 4 - .../Message Components/SelectMenuComponent.cs | 3 - .../Message Components/SelectMenuOption.cs | 6 - .../Slash Commands/SlashCommandBuilder.cs | 183 +++++++++-------- .../Slash Commands/SlashCommandProperties.cs | 4 - .../Entities/Messages/EmbedBuilder.cs | 14 +- .../Entities/Messages/IAttachment.cs | 7 + .../Entities/Messages/IMessage.cs | 17 +- .../Entities/Messages/IMessageInteraction.cs | 34 ++++ .../Entities/Messages/MessageInteraction.cs | 45 +++++ .../Entities/Messages/MessageType.cs | 16 +- .../Entities/Messages/StickerFormatType.cs | 22 +- .../Entities/Messages/TimestampTag.cs | 17 +- .../Entities/Messages/TimestampTagStyle.cs | 6 - .../ApplicationCommandPermissionTarget.cs | 8 +- .../Entities/Permissions/ChannelPermission.cs | 17 -- .../Permissions/ChannelPermissions.cs | 4 +- .../GuildApplicationCommandPermissions.cs | 4 - .../Entities/Permissions/GuildPermission.cs | 15 -- .../Entities/Permissions/GuildPermissions.cs | 2 +- .../Permissions/OverwritePermissions.cs | 2 +- src/Discord.Net.Core/Entities/Roles/IRole.cs | 15 ++ .../Entities/Stickers/ICustomSticker.cs | 5 +- .../Entities/Stickers/ISticker.cs | 6 +- .../Entities/Stickers/IStickerItem.cs | 6 - .../Entities/Stickers/StickerPack.cs | 6 +- .../Entities/Stickers/StickerProperties.cs | 4 - .../Entities/Stickers/StickerType.cs | 10 +- .../Extensions/MessageExtensions.cs | 4 +- src/Discord.Net.Core/Format.cs | 15 +- .../Net/ApplicationCommandException.cs | 4 - src/Discord.Net.Core/RequestOptions.cs | 4 +- src/Discord.Net.Core/Utils/UrlValidation.cs | 30 ++- .../Channels/IMessageChannel.Examples.cs | 1 - .../Discord.Net.Examples.csproj | 2 +- .../Discord.Net.Providers.WS4Net.csproj | 3 +- .../API/Common/ActionRowComponent.cs | 7 +- .../API/Common/ApplicationCommand.cs | 5 - .../ApplicationCommandInteractionData.cs | 2 - ...ApplicationCommandInteractionDataOption.cs | 1 - .../API/Common/ApplicationCommandOption.cs | 53 ++--- .../Common/ApplicationCommandOptionChoice.cs | 5 - .../Common/ApplicationCommandPermissions.cs | 5 - src/Discord.Net.Rest/API/Common/Attachment.cs | 2 + src/Discord.Net.Rest/API/Common/AuditLog.cs | 8 +- .../API/Common/AuditLogEntry.cs | 2 +- .../API/Common/AutocompleteInteractionData.cs | 5 - .../AutocompleteInteractionDataOption.cs | 12 +- .../API/Common/ButtonComponent.cs | 11 +- .../API/Common/ChannelThreads.cs | 5 - .../GuildApplicationCommandPermissions.cs | 7 +- .../API/Common/Interaction.cs | 5 - .../API/Common/InteractionCallbackData.cs | 6 +- .../API/Common/InteractionResponse.cs | 5 - src/Discord.Net.Rest/API/Common/Message.cs | 3 +- .../Common/MessageComponentInteractionData.cs | 5 - .../API/Common/MessageInteraction.cs | 21 ++ .../API/Common/NitroStickerPacks.cs | 4 - src/Discord.Net.Rest/API/Common/Role.cs | 2 + .../API/Common/SelectMenuComponent.cs | 4 - .../API/Common/SelectMenuOption.cs | 13 +- .../API/Common/StageInstance.cs | 5 - src/Discord.Net.Rest/API/Common/Sticker.cs | 2 +- .../API/Common/StickerItem.cs | 5 - .../API/Common/StickerPack.cs | 5 - .../API/Common/ThreadMember.cs | 6 +- .../API/Common/ThreadMetadata.cs | 4 - .../Rest/CreateApplicationCommandParams.cs | 8 +- .../API/Rest/CreateStageInstanceParams.cs | 5 - .../API/Rest/CreateStickerParams.cs | 8 +- .../Rest/ModifyApplicationCommandParams.cs | 5 - ...odifyGuildApplicationCommandPermissions.cs | 5 - ...uildApplicationCommandPermissionsParams.cs | 5 - .../Rest/ModifyInteractionResponseParams.cs | 7 +- .../API/Rest/ModifyStageInstanceParams.cs | 6 - .../API/Rest/ModifyStickerParams.cs | 5 - .../API/Rest/ModifyThreadParams.cs | 6 - .../API/Rest/StartThreadParams.cs | 5 - src/Discord.Net.Rest/ClientHelper.cs | 30 +-- src/Discord.Net.Rest/Discord.Net.Rest.csproj | 4 +- src/Discord.Net.Rest/DiscordRestApiClient.cs | 44 ++-- src/Discord.Net.Rest/DiscordRestClient.cs | 14 +- .../Entities/AuditLogs/AuditLogHelper.cs | 3 +- .../Entities/AuditLogs/DataTypes/StageInfo.cs | 30 +++ .../StageInstanceCreateAuditLogData.cs | 50 +++++ .../StageInstanceDeleteAuditLogData.cs | 50 +++++ .../StageInstanceUpdatedAuditLogData.cs | 51 +++++ .../Entities/Channels/ChannelHelper.cs | 1 + .../Entities/Channels/RestStageChannel.cs | 27 +-- .../Entities/Channels/RestThreadChannel.cs | 45 ++--- .../Entities/Channels/ThreadHelper.cs | 18 +- .../Entities/Guilds/RestGuild.cs | 16 +- .../Interactions/InteractionHelper.cs | 158 ++++++++------- .../Interactions/RestApplicationCommand.cs | 23 +-- .../RestApplicationCommandChoice.cs | 5 - .../RestApplicationCommandOption.cs | 18 +- .../Interactions/RestGlobalCommand.cs | 12 +- .../Entities/Interactions/RestGuildCommand.cs | 7 +- .../Entities/Messages/Attachment.cs | 8 +- .../Entities/Messages/CustomSticker.cs | 5 +- .../Entities/Messages/MessageHelper.cs | 10 +- .../Entities/Messages/RestFollowupMessage.cs | 16 +- .../Messages/RestInteractionMessage.cs | 10 +- .../Entities/Messages/RestMessage.cs | 45 ++++- .../Entities/Messages/RestUserMessage.cs | 21 +- .../Entities/Messages/Sticker.cs | 5 +- .../Entities/Messages/StickerItem.cs | 12 +- .../Entities/Roles/RestRole.cs | 7 + .../Entities/Users/RestThreadUser.cs | 4 - .../Entities/Webhooks/WebhookHelper.cs | 1 - .../Net/Converters/InteractionConverter.cs | 4 - .../Converters/MessageComponentConverter.cs | 4 - .../Net/Queue/RequestQueueBucket.cs | 6 +- .../ApplicationCommandCreatedUpdatedEvent.cs | 7 +- .../API/Gateway/GuildStickerUpdateEvent.cs | 5 - .../API/Gateway/ThreadListSyncEvent.cs | 5 - .../API/Gateway/ThreadMembersUpdate.cs | 5 - .../API/Voice/ReadyEvent.cs | 2 +- .../Audio/Streams/BufferedWriteStream.cs | 2 +- .../BaseSocketClient.Events.cs | 11 +- .../DiscordShardedClient.cs | 1 + .../DiscordSocketClient.cs | 13 +- .../DiscordSocketConfig.cs | 2 +- .../Entities/Channels/SocketStageChannel.cs | 31 +-- .../Entities/Channels/SocketThreadChannel.cs | 49 ++--- .../Entities/Guilds/SocketGuild.cs | 6 +- .../Message Commands/SocketMessageCommand.cs | 10 +- .../SocketMessageCommandData.cs | 2 +- .../User Commands/SocketUserCommand.cs | 12 +- .../SocketMessageComponent.cs | 191 +++++++++++------- .../SocketMessageComponentData.cs | 4 - .../SocketAutocompleteInteraction.cs | 49 +++-- .../SocketAutocompleteInteractionData.cs | 27 ++- .../Slash Commands/SocketSlashCommand.cs | 12 +- .../Slash Commands/SocketSlashCommandData.cs | 3 +- .../SocketSlashCommandDataOption.cs | 11 +- .../SocketApplicationCommand.cs | 25 +-- .../SocketApplicationCommandChoice.cs | 5 - .../SocketApplicationCommandOption.cs | 20 +- .../SocketBaseCommand/SocketCommandBase.cs | 129 ++++++------ .../SocketCommandBaseData.cs | 2 - .../SocketBaseCommand/SocketResolvableData.cs | 4 - .../Entities/Interaction/SocketInteraction.cs | 59 +++--- .../Entities/Messages/SocketMessage.cs | 60 +++++- .../Entities/Messages/SocketUserMessage.cs | 27 +-- .../Entities/Roles/SocketRole.cs | 7 + .../Entities/Stickers/SocketCustomSticker.cs | 10 +- .../Entities/Stickers/SocketSticker.cs | 34 +--- .../Entities/Stickers/SocketUnknownSticker.cs | 3 - .../Entities/Users/SocketThreadUser.cs | 4 - .../Discord.Net.Webhook.csproj | 4 +- .../Discord.Net.Analyzers.Tests.csproj | 6 +- .../Discord.Net.Tests.Integration.csproj | 4 +- .../Discord.Net.Tests.Unit.csproj | 4 +- .../EmbedBuilderTests.cs | 2 +- test/Discord.Net.Tests.Unit/FormatTests.cs | 15 ++ 212 files changed, 1533 insertions(+), 1471 deletions(-) create mode 100644 docs/guides/interactions/intro.md create mode 100644 src/Discord.Net.Core/Entities/Messages/IMessageInteraction.cs create mode 100644 src/Discord.Net.Core/Entities/Messages/MessageInteraction.cs create mode 100644 src/Discord.Net.Rest/API/Common/MessageInteraction.cs create mode 100644 src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/StageInfo.cs create mode 100644 src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/StageInstanceCreateAuditLogData.cs create mode 100644 src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/StageInstanceDeleteAuditLogData.cs create mode 100644 src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/StageInstanceUpdatedAuditLogData.cs diff --git a/docs/guides/interactions/application-commands/slash-commands/02-creating-slash-commands.md b/docs/guides/interactions/application-commands/slash-commands/02-creating-slash-commands.md index 830c52aa5..1abab1a25 100644 --- a/docs/guides/interactions/application-commands/slash-commands/02-creating-slash-commands.md +++ b/docs/guides/interactions/application-commands/slash-commands/02-creating-slash-commands.md @@ -1,3 +1,8 @@ +--- +uid: Guides.SlashCommands.Creating +title: Creating Slash Commands +--- + # Creating your first slash commands. There are two kinds of Slash Commands: global commands and guild commands. @@ -35,7 +40,10 @@ The slash command builder will help you create slash commands. The builder has t | AddOption | Function | Adds an option to the current slash command. | | Build | Function | Builds the builder into a `SlashCommandCreationProperties` class used to make slash commands | -**Note**: Slash command names must be all lowercase! +> [!NOTE] +> Slash command names must be all lowercase! + +## Creating a Slash Command Let's use the slash command builder to make a global and guild command. @@ -84,4 +92,5 @@ public async Task Client_Ready() ``` -**Note**: Slash commands only need to be created once. They do _not_ have to be 'created' on every startup or connection. The example simple shows creating them in the ready event as it's simpler than creating normal bot commands to register slash commands. +> [!NOTE] +> Slash commands only need to be created once. They do _not_ have to be 'created' on every startup or connection. The example simple shows creating them in the ready event as it's simpler than creating normal bot commands to register slash commands. diff --git a/docs/guides/interactions/application-commands/slash-commands/03-responding-to-slash-commands.md b/docs/guides/interactions/application-commands/slash-commands/03-responding-to-slash-commands.md index ad3c7145b..cd6ad5217 100644 --- a/docs/guides/interactions/application-commands/slash-commands/03-responding-to-slash-commands.md +++ b/docs/guides/interactions/application-commands/slash-commands/03-responding-to-slash-commands.md @@ -1,3 +1,8 @@ +--- +uid: Guides.SlashCommands.Receiving +title: Receiving and Responding to Slash Commands +--- + # Responding to interactions. Interactions are the base thing sent over by Discord. Slash commands are one of the interaction types. In order to receive a slash command we have to listen to the `InteractionCreated` event. Let's add this to our code. @@ -45,6 +50,8 @@ Let's try this out! Let's go over the response types quickly, as you would only change them for style points :P -> After receiving an interaction, you must respond to acknowledge it. You can choose to respond with a message immediately using `RespondAsync()` or you can choose to send a deferred response with `DeferAsync()`. If choosing a deferred response, the user will see a loading state for the interaction, and you'll have up to 15 minutes to edit the original deferred response using `ModifyOriginalResponseAsync()`. You can read more about response types [here](https://discord.com/developers/docs/interactions/slash-commands#interaction-response) +> [!NOTE] +> After receiving an interaction, you must respond to acknowledge it. You can choose to respond with a message immediately using `RespondAsync()` or you can choose to send a deferred response with `DeferAsync()`. +> If choosing a deferred response, the user will see a loading state for the interaction, and you'll have up to 15 minutes to edit the original deferred response using `ModifyOriginalResponseAsync()`. You can read more about response types [here](https://discord.com/developers/docs/interactions/slash-commands#interaction-response) This seems to be working! Next, we will look at parameters for slash commands. diff --git a/docs/guides/interactions/application-commands/slash-commands/04-parameters.md b/docs/guides/interactions/application-commands/slash-commands/04-parameters.md index ddac72203..cc61796c8 100644 --- a/docs/guides/interactions/application-commands/slash-commands/04-parameters.md +++ b/docs/guides/interactions/application-commands/slash-commands/04-parameters.md @@ -1,3 +1,8 @@ +--- +uid: Guides.SlashCommands.Parameters +title: Slash Command Parameters +--- + # Slash command parameters Slash commands can have a bunch of parameters, each their own type. Let's first go over the types of parameters we can have. diff --git a/docs/guides/interactions/application-commands/slash-commands/05-responding-ephemerally.md b/docs/guides/interactions/application-commands/slash-commands/05-responding-ephemerally.md index f92504924..fa5ac449b 100644 --- a/docs/guides/interactions/application-commands/slash-commands/05-responding-ephemerally.md +++ b/docs/guides/interactions/application-commands/slash-commands/05-responding-ephemerally.md @@ -1,20 +1,13 @@ -# Responding ephemerally - -What is an ephemeral response? Basically, only the user who executed the command can see the result of it. In labs this is pretty simple to do. - -First, we need to talk about `AlwaysAcknowledgeInteractions` in the discord config. `AlwaysAcknowledgeInteractions` will always acknowledge the message non-ephemerally, meaning any follow-up messages or responses will also be non-ephemeral. If you set `AlwaysAcknowledgeInteractions` to false, you can acknowledge interactions yourself with the ephemeral field set to your discretion. +--- +uid: Guides.SlashCommands.Ephemeral +title: Ephemeral Responses +--- -**Note**: You don't have to run arg.AcknowledgeAsync() to capture the interaction, you can use arg.RespondAsync with a message to capture it, this also follows the ephemeral rule. +# Responding ephemerally -Let's start by changing our client config. +What is an ephemeral response? Basically, only the user who executed the command can see the result of it, this is pretty simple to implement. -```cs -client = new DiscordSocketClient(new DiscordSocketConfig() -{ - // Add this! - AlwaysAcknowledgeInteractions = false, -}); -``` +**Note**: You don't have to run arg.DeferAsync() to capture the interaction, you can use arg.RespondAsync() with a message to capture it, this also follows the ephemeral rule. When responding with either `FollowupAsync` or `RespondAsync` you can pass in an `ephemeral` property. When setting it to true it will respond ephemerally, false and it will respond non-ephemerally. diff --git a/docs/guides/interactions/application-commands/slash-commands/06-subcommands.md b/docs/guides/interactions/application-commands/slash-commands/06-subcommands.md index 54ff03cdc..ac8c7313d 100644 --- a/docs/guides/interactions/application-commands/slash-commands/06-subcommands.md +++ b/docs/guides/interactions/application-commands/slash-commands/06-subcommands.md @@ -1,3 +1,8 @@ +--- +uid: Guides.SlashCommands.SubCommand +title: Sub Commands +--- + # Subcommands Subcommands allow you to have multiple commands available in a single command. They can be useful for representing sub options for a command. For example: A settings command. Let's first look at some limitations with subcommands set by discord. diff --git a/docs/guides/interactions/application-commands/slash-commands/07-choice-slash-command.md b/docs/guides/interactions/application-commands/slash-commands/07-choice-slash-command.md index 53cccb39e..79de5ffbc 100644 --- a/docs/guides/interactions/application-commands/slash-commands/07-choice-slash-command.md +++ b/docs/guides/interactions/application-commands/slash-commands/07-choice-slash-command.md @@ -1,3 +1,8 @@ +--- +uid: Guides.SlashCommands.Choices +title: Slash Command Choices +--- + # Slash Command Choices. With slash command options you can add choices, making the user select between some set values. Lets create a command that asks how much they like our bot! diff --git a/docs/guides/interactions/application-commands/slash-commands/08-bulk-overwrite-of-global-slash-commands.md b/docs/guides/interactions/application-commands/slash-commands/08-bulk-overwrite-of-global-slash-commands.md index 0a1aab461..e2511ab98 100644 --- a/docs/guides/interactions/application-commands/slash-commands/08-bulk-overwrite-of-global-slash-commands.md +++ b/docs/guides/interactions/application-commands/slash-commands/08-bulk-overwrite-of-global-slash-commands.md @@ -1,4 +1,10 @@ +--- +uid: Guides.SlashCommands.BulkOverwrite +title: Slash Command Bulk Overwrites +--- + If you have too many global commands then you might want to consider using the bulk overwrite function. + ```cs public async Task Client_Ready() { List applicationCommandProperties = new(); @@ -8,7 +14,7 @@ public async Task Client_Ready() { globalCommandHelp.WithName("help"); globalCommandHelp.WithDescription("Shows information about the bot."); applicationCommandProperties.Add(globalCommandHelp.Build()); - + // Slash command with name as its parameter. SlashCommandOptionBuilder slashCommandOptionBuilder = new(); slashCommandOptionBuilder.WithName("name"); @@ -16,11 +22,11 @@ public async Task Client_Ready() { slashCommandOptionBuilder.WithDescription("Add a family"); slashCommandOptionBuilder.WithRequired(true); // Only add this if you want it to be required - SlashCommandBuilder globalCommandAddFamily = new SlashCommandBuilder(); + SlashCommandBuilder globalCommandAddFamily = new SlashCommandBuilder(); globalCommandAddFamily.WithName("add-family"); globalCommandAddFamily.WithDescription("Add a family"); applicationCommandProperties.Add(globalCommandAddFamily.Build()); - + await _client.BulkOverwriteGlobalApplicationCommandsAsync(applicationCommandProperties.ToArray()); } catch (ApplicationCommandException exception) { var json = JsonConvert.SerializeObject(exception.Error, Formatting.Indented); diff --git a/docs/guides/interactions/application-commands/slash-commands/README.md b/docs/guides/interactions/application-commands/slash-commands/README.md index 70e31a8b4..2abbddf0f 100644 --- a/docs/guides/interactions/application-commands/slash-commands/README.md +++ b/docs/guides/interactions/application-commands/slash-commands/README.md @@ -2,11 +2,10 @@ Here you can find some guides on how to use slash commands. -1. [Getting started](https://github.com/Discord-Net-Labs/Discord.Net-Labs/blob/Interactions/docs/guides/slash-commands/01-getting-started.md) -2. [Creating a slash command](https://github.com/Discord-Net-Labs/Discord.Net-Labs/blob/Interactions/docs/guides/slash-commands/02-creating-slash-commands.md) -3. [Responding to slash commands](https://github.com/Discord-Net-Labs/Discord.Net-Labs/blob/Interactions/docs/guides/slash-commands/03-responding-to-slash-commands.md) -4. [Parameters in slash commands](https://github.com/Discord-Net-Labs/Discord.Net-Labs/blob/Interactions/docs/guides/slash-commands/04-parameters.md) -5. [Responding ephemerally](https://github.com/Discord-Net-Labs/Discord.Net-Labs/blob/Interactions/docs/guides/slash-commands/05-responding-ephemerally.md) -6. [Subcommands](https://github.com/Discord-Net-Labs/Discord.Net-Labs/blob/Interactions/docs/guides/slash-commands/06-subcommands.md) -7. [Choices](https://github.com/Discord-Net-Labs/Discord.Net-Labs/blob/Interactions/docs/guides/slash-commands/07-choice-slash-command.md) -7. [Bulk overwrite of global slash commands](https://github.com/Discord-Net-Labs/Discord.Net-Labs/blob/release/3.x/docs/guides/interactions/application-commands/slash-commands/08-bulk-overwrite-of-global-slash-commands.md) +1. [Creating a slash command](https://github.com/discord-net/Discord.Net/blob/dev/docs/guides/interactions/application-commands/slash-commands/02-creating-slash-commands.md) +2. [Responding to slash commands](https://github.com/discord-net/Discord.Net/blob/dev/docs/guides/interactions/application-commands/slash-commands/03-responding-to-slash-commands.md) +3. [Parameters in slash commands](https://github.com/discord-net/Discord.Net/blob/dev/docs/guides/interactions/application-commands/slash-commands/04-parameters.md) +4. [Responding ephemerally](https://github.com/discord-net/Discord.Net/blob/dev/docs/guides/interactions/application-commands/slash-commands/05-responding-ephemerally.md) +5. [Subcommands](https://github.com/discord-net/Discord.Net/blob/dev/docs/guides/interactions/application-commands/slash-commands/06-subcommands.md) +6. [Choices](https://github.com/discord-net/Discord.Net/blob/dev/docs/guides/interactions/application-commands/slash-commands/07-choice-slash-command.md) +7. [Bulk overwrite of global slash commands](https://github.com/discord-net/Discord.Net/blob/dev/docs/guides/interactions/application-commands/slash-commands/08-bulk-overwrite-of-global-slash-commands.md) diff --git a/docs/guides/interactions/intro.md b/docs/guides/interactions/intro.md new file mode 100644 index 000000000..62b2dfdb5 --- /dev/null +++ b/docs/guides/interactions/intro.md @@ -0,0 +1,10 @@ +--- +uid: Guides.Interactions.Intro +title: Introduction to Interactions +--- + +# Interactions + +Placeholder text does the brrr. + +Links to different sections of guides: msg comp / slash commands. diff --git a/docs/guides/toc.yml b/docs/guides/toc.yml index a6c38768f..64ed1c8cc 100644 --- a/docs/guides/toc.yml +++ b/docs/guides/toc.yml @@ -35,9 +35,27 @@ topicUid: Guides.Commands.DI - name: Post-execution Handling topicUid: Guides.Commands.PostExecution +- name: Working with Interactions + items: + - name: Introduction + topicUid: Guides.Interactions.Intro + - name: Creating slash commands + topicUid: Guides.SlashCommands.Creating + - name: Receiving and responding to slash commands + topicUid: Guides.SlashCommands.Receiving + - name: Slash command parameters + topicUid: Guides.SlashCommands.Parameters + - name: Ephemeral responses + topicUid: Guides.SlashCommands.Ephemeral + - name: Sub commands + topicUid: Guides.SlashCommands.SubCommand + - name: Slash command choices + topicUid: Guides.SlashCommands.Choices + - name: Slash ommands Bulk Overwrites + topicUid: Guides.SlashCommands.BulkOverwrite - name: Emoji topicUid: Guides.Emoji - name: Voice topicUid: Guides.Voice.SendingVoice - name: Deployment - topicUid: Guides.Deployment \ No newline at end of file + topicUid: Guides.Deployment diff --git a/samples/02_commands_framework/02_commands_framework.csproj b/samples/02_commands_framework/02_commands_framework.csproj index 151e546a2..83a62f8d7 100644 --- a/samples/02_commands_framework/02_commands_framework.csproj +++ b/samples/02_commands_framework/02_commands_framework.csproj @@ -6,7 +6,7 @@ - + diff --git a/samples/02_commands_framework/Program.cs b/samples/02_commands_framework/Program.cs index 67cb87764..8a2f37dce 100644 --- a/samples/02_commands_framework/Program.cs +++ b/samples/02_commands_framework/Program.cs @@ -39,7 +39,7 @@ namespace _02_commands_framework services.GetRequiredService().Log += LogAsync; // Tokens should be considered secret data and never hard-coded. - // We can read from the environment variable to avoid hardcoding. + // We can read from the environment variable to avoid hard coding. await client.LoginAsync(TokenType.Bot, Environment.GetEnvironmentVariable("token")); await client.StartAsync(); diff --git a/samples/03_sharded_client/03_sharded_client.csproj b/samples/03_sharded_client/03_sharded_client.csproj index 24f9942f9..91cacef64 100644 --- a/samples/03_sharded_client/03_sharded_client.csproj +++ b/samples/03_sharded_client/03_sharded_client.csproj @@ -7,7 +7,7 @@ - + diff --git a/samples/03_sharded_client/Services/CommandHandlingService.cs b/samples/03_sharded_client/Services/CommandHandlingService.cs index 1230cbcff..adc91b12c 100644 --- a/samples/03_sharded_client/Services/CommandHandlingService.cs +++ b/samples/03_sharded_client/Services/CommandHandlingService.cs @@ -54,7 +54,7 @@ namespace _03_sharded_client.Services if (!command.IsSpecified) return; - // the command was succesful, we don't care about this result, unless we want to log that a command succeeded. + // the command was successful, we don't care about this result, unless we want to log that a command succeeded. if (result.IsSuccess) return; diff --git a/samples/idn/idn.csproj b/samples/idn/idn.csproj index 984c86383..f982ff86d 100644 --- a/samples/idn/idn.csproj +++ b/samples/idn/idn.csproj @@ -6,7 +6,7 @@ - + diff --git a/src/Discord.Net.Analyzers/Discord.Net.Analyzers.csproj b/src/Discord.Net.Analyzers/Discord.Net.Analyzers.csproj index cfaf67d34..b28d9c2c1 100644 --- a/src/Discord.Net.Analyzers/Discord.Net.Analyzers.csproj +++ b/src/Discord.Net.Analyzers/Discord.Net.Analyzers.csproj @@ -5,12 +5,13 @@ Discord.Analyzers A Discord.Net extension adding support for design-time analysis of the API usage. netstandard2.0;netstandard2.1 + Discord.Net.Labs.Analyzers ..\Discord.Net.Analyzers\Discord.Net.Analyzers.xml - + diff --git a/src/Discord.Net.Commands/Attributes/AliasAttribute.cs b/src/Discord.Net.Commands/Attributes/AliasAttribute.cs index 16eb3ba73..c4b78f534 100644 --- a/src/Discord.Net.Commands/Attributes/AliasAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/AliasAttribute.cs @@ -16,7 +16,7 @@ namespace Discord.Commands /// /// [Command("stats")] /// [Alias("stat", "info")] - /// public async Task GetStatsAsync(IUser user) + /// public Task GetStatsAsync(IUser user) /// { /// // ...pull stats /// } diff --git a/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs b/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs index 59500439a..8c10ae806 100644 --- a/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs +++ b/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs @@ -291,7 +291,7 @@ namespace Discord.Commands return reader; } - //We dont have a cached type reader, create one + //We don't have a cached type reader, create one reader = ReflectionUtils.CreateObject(typeReaderType.GetTypeInfo(), service, services); service.AddTypeReader(paramType, reader, false); diff --git a/src/Discord.Net.Commands/CommandService.cs b/src/Discord.Net.Commands/CommandService.cs index 468f7739f..db08d0d79 100644 --- a/src/Discord.Net.Commands/CommandService.cs +++ b/src/Discord.Net.Commands/CommandService.cs @@ -602,7 +602,7 @@ namespace Discord.Commands //If we get this far, at least one parse was successful. Execute the most likely overload. var chosenOverload = successfulParses[0]; var result = await chosenOverload.Key.ExecuteAsync(context, chosenOverload.Value, services).ConfigureAwait(false); - if (!result.IsSuccess && !(result is RuntimeResult || result is ExecuteResult)) // succesful results raise the event in CommandInfo#ExecuteInternalAsync (have to raise it there b/c deffered execution) + if (!result.IsSuccess && !(result is RuntimeResult || result is ExecuteResult)) // successful results raise the event in CommandInfo#ExecuteInternalAsync (have to raise it there b/c deferred execution) await _commandExecutedEvent.InvokeAsync(chosenOverload.Key.Command, context, result); return result; } diff --git a/src/Discord.Net.Commands/ModuleBase.cs b/src/Discord.Net.Commands/ModuleBase.cs index eb517e65d..3eddc11d2 100644 --- a/src/Discord.Net.Commands/ModuleBase.cs +++ b/src/Discord.Net.Commands/ModuleBase.cs @@ -36,7 +36,7 @@ namespace Discord.Commands /// Specifies if notifications are sent for mentioned users and roles in the . /// If null, all mentioned roles and users will be notified. /// - /// The request options for this async request. + /// The request options for this request. /// The message references to be included. Used to reply to specific messages. /// The message components to be included with this message. Used for interactions. /// A collection of stickers to send with the file. diff --git a/src/Discord.Net.Commands/RunMode.cs b/src/Discord.Net.Commands/RunMode.cs index 8e230b500..d6b49065b 100644 --- a/src/Discord.Net.Commands/RunMode.cs +++ b/src/Discord.Net.Commands/RunMode.cs @@ -8,7 +8,7 @@ namespace Discord.Commands public enum RunMode { /// - /// The default behaviour set in . + /// The default behavior set in . /// Default, /// diff --git a/src/Discord.Net.Core/CDN.cs b/src/Discord.Net.Core/CDN.cs index 78fba574f..b170336dc 100644 --- a/src/Discord.Net.Core/CDN.cs +++ b/src/Discord.Net.Core/CDN.cs @@ -86,6 +86,16 @@ namespace Discord public static string GetGuildIconUrl(ulong guildId, string iconId) => iconId != null ? $"{DiscordConfig.CDNUrl}icons/{guildId}/{iconId}.jpg" : null; /// + /// Returns a guild role's icon URL. + /// + /// The role identifier. + /// The icon hash. + /// + /// A URL pointing to the guild role's icon. + /// + public static string GetGuildRoleIconUrl(ulong roleId, string roleHash) + => roleHash != null ? $"{DiscordConfig.CDNUrl}role-icons/{roleId}/{roleHash}.png" : null; + /// /// Returns a guild splash URL. /// /// The guild snowflake identifier. diff --git a/src/Discord.Net.Core/Entities/Activities/ActivityType.cs b/src/Discord.Net.Core/Entities/Activities/ActivityType.cs index 8c44f49e3..1f67886eb 100644 --- a/src/Discord.Net.Core/Entities/Activities/ActivityType.cs +++ b/src/Discord.Net.Core/Entities/Activities/ActivityType.cs @@ -25,5 +25,9 @@ namespace Discord /// The user has set a custom status. /// CustomStatus = 4, + /// + /// The user is competing in a game. + /// + Competing = 5, } } diff --git a/src/Discord.Net.Core/Entities/AuditLogs/ActionType.cs b/src/Discord.Net.Core/Entities/AuditLogs/ActionType.cs index b016a4c50..5092b4e7f 100644 --- a/src/Discord.Net.Core/Entities/AuditLogs/ActionType.cs +++ b/src/Discord.Net.Core/Entities/AuditLogs/ActionType.cs @@ -180,5 +180,17 @@ namespace Discord /// A sticker was deleted. /// StickerDeleted = 92, + /// + /// A thread was created. + /// + ThreadCreate = 110, + /// + /// A thread was updated. + /// + ThreadUpdate = 111, + /// + /// A thread was deleted. + /// + ThreadDelete = 112 } } diff --git a/src/Discord.Net.Core/Entities/Channels/IStageChannel.cs b/src/Discord.Net.Core/Entities/Channels/IStageChannel.cs index 0efc9d818..3b1bc08da 100644 --- a/src/Discord.Net.Core/Entities/Channels/IStageChannel.cs +++ b/src/Discord.Net.Core/Entities/Channels/IStageChannel.cs @@ -1,7 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Threading.Tasks; namespace Discord diff --git a/src/Discord.Net.Core/Entities/Channels/IThreadChannel.cs b/src/Discord.Net.Core/Entities/Channels/IThreadChannel.cs index e20d87d57..50e46efa6 100644 --- a/src/Discord.Net.Core/Entities/Channels/IThreadChannel.cs +++ b/src/Discord.Net.Core/Entities/Channels/IThreadChannel.cs @@ -1,7 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Threading.Tasks; namespace Discord @@ -9,7 +6,7 @@ namespace Discord /// /// Represents a thread channel inside of a guild. /// - public interface IThreadChannel : ITextChannel, IGuildChannel + public interface IThreadChannel : ITextChannel { /// /// Gets the type of the current thread channel. @@ -56,7 +53,7 @@ namespace Discord /// /// The options to be used when sending the request. /// - /// A task that represents the asynchronous join operation. + /// A task that represents the asynchronous join operation. /// Task JoinAsync(RequestOptions options = null); @@ -65,7 +62,7 @@ namespace Discord /// /// The options to be used when sending the request. /// - /// A task that represents the asynchronous leave operation. + /// A task that represents the asynchronous leave operation. /// Task LeaveAsync(RequestOptions options = null); @@ -75,7 +72,7 @@ namespace Discord /// The to add. /// The options to be used when sending the request. /// - /// A task that represents the asynchronous operation of adding a member to a thread. + /// A task that represents the asynchronous operation of adding a member to a thread. /// Task AddUserAsync(IGuildUser user, RequestOptions options = null); @@ -85,7 +82,7 @@ namespace Discord /// The to remove from this thread. /// The options to be used when sending the request. /// - /// A task that represents the asynchronous operation of removing a user from this thread. + /// A task that represents the asynchronous operation of removing a user from this thread. /// Task RemoveUserAsync(IGuildUser user, RequestOptions options = null); } diff --git a/src/Discord.Net.Core/Entities/Channels/StageInstanceProperties.cs b/src/Discord.Net.Core/Entities/Channels/StageInstanceProperties.cs index ad539adc3..35201fe0f 100644 --- a/src/Discord.Net.Core/Entities/Channels/StageInstanceProperties.cs +++ b/src/Discord.Net.Core/Entities/Channels/StageInstanceProperties.cs @@ -1,9 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - namespace Discord { /// diff --git a/src/Discord.Net.Core/Entities/Channels/StagePrivacyLevel.cs b/src/Discord.Net.Core/Entities/Channels/StagePrivacyLevel.cs index 3a91b61aa..c6eb3803f 100644 --- a/src/Discord.Net.Core/Entities/Channels/StagePrivacyLevel.cs +++ b/src/Discord.Net.Core/Entities/Channels/StagePrivacyLevel.cs @@ -1,9 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - namespace Discord { /// diff --git a/src/Discord.Net.Core/Entities/Channels/ThreadArchiveDuration.cs b/src/Discord.Net.Core/Entities/Channels/ThreadArchiveDuration.cs index 01d1574bf..2c8a0652c 100644 --- a/src/Discord.Net.Core/Entities/Channels/ThreadArchiveDuration.cs +++ b/src/Discord.Net.Core/Entities/Channels/ThreadArchiveDuration.cs @@ -1,9 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - namespace Discord { /// @@ -24,7 +18,7 @@ namespace Discord /// /// Three days (4320 minutes). /// - /// This option is explicity avaliable to nitro users. + /// This option is explicitly available to nitro users. /// /// ThreeDays = 4320, @@ -32,9 +26,9 @@ namespace Discord /// /// One week (10080 minutes). /// - /// This option is explicity avaliable to nitro users. + /// This option is explicitly available to nitro users. /// /// - OneWeek = 10080, + OneWeek = 10080 } } diff --git a/src/Discord.Net.Core/Entities/Channels/ThreadType.cs b/src/Discord.Net.Core/Entities/Channels/ThreadType.cs index 2db09bcb9..379128d21 100644 --- a/src/Discord.Net.Core/Entities/Channels/ThreadType.cs +++ b/src/Discord.Net.Core/Entities/Channels/ThreadType.cs @@ -1,9 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - namespace Discord { /// @@ -24,6 +18,6 @@ namespace Discord /// /// Represents a temporary sub-channel within a GUILD_TEXT channel that is only viewable by those invited and those with the MANAGE_THREADS permission /// - PrivateThread = 12, + PrivateThread = 12 } } diff --git a/src/Discord.Net.Core/Entities/Guilds/IGuild.cs b/src/Discord.Net.Core/Entities/Guilds/IGuild.cs index 62e34cabb..4a2333645 100644 --- a/src/Discord.Net.Core/Entities/Guilds/IGuild.cs +++ b/src/Discord.Net.Core/Entities/Guilds/IGuild.cs @@ -609,12 +609,12 @@ namespace Discord /// Task GetRulesChannelAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); /// - /// Gets the text channel channel where admins and moderators of Community guilds receive notices from Discord. + /// Gets the text channel where admins and moderators of Community guilds receive notices from Discord. /// /// The that determines whether the object should be fetched from cache. /// The options to be used when sending the request. /// - /// A task that represents the asynchronous get operation. The task result contains the text channel channel where + /// A task that represents the asynchronous get operation. The task result contains the text channel where /// admins and moderators of Community guilds receive notices from Discord; if none is set. /// Task GetPublicUpdatesChannelAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); @@ -771,7 +771,7 @@ namespace Discord /// A guild user associated with the specified ; if the user is already in the guild. Task AddGuildUserAsync(ulong userId, string accessToken, Action func = null, RequestOptions options = null); /// - /// Disconnects the user from its current voice channel. + /// Disconnects the user from its current voice channel. /// /// The user to disconnect. /// A task that represents the asynchronous operation for disconnecting a user. @@ -834,7 +834,7 @@ namespace Discord /// Downloads all users for this guild if the current list is incomplete. /// /// - /// This method downloads all users found within this guild throught the Gateway and caches them. + /// This method downloads all users found within this guild through the Gateway and caches them. /// /// /// A task that represents the asynchronous download operation. @@ -1057,7 +1057,7 @@ namespace Discord /// 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); + Task> GetApplicationCommandsAsync(RequestOptions options = null); /// /// Gets an application command within this guild with the specified id. diff --git a/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandOption.cs b/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandOption.cs index 63ec3b900..10aa6ab85 100644 --- a/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandOption.cs +++ b/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandOption.cs @@ -1,9 +1,6 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Text.RegularExpressions; -using System.Threading.Tasks; namespace Discord { @@ -24,13 +21,13 @@ namespace Discord set { if (value == null) - throw new ArgumentNullException($"{nameof(Name)} cannot be null!"); + throw new ArgumentNullException(nameof(value), $"{nameof(Name)} cannot be null."); if (value.Length > 32) - throw new ArgumentException($"{nameof(Name)} length must be less than or equal to 32"); + throw new ArgumentOutOfRangeException(nameof(value), "Name length must be less than or equal to 32."); if (!Regex.IsMatch(value, @"^[\w-]{1,32}$")) - throw new ArgumentException($"{nameof(Name)} must match the regex ^[\\w-]{{1,32}}$"); + throw new FormatException($"{nameof(value)} must match the regex ^[\\w-]{{1,32}}$"); _name = value; } @@ -42,14 +39,12 @@ namespace Discord public string Description { get => _description; - set + set => _description = value?.Length switch { - if (value?.Length > 100) - throw new ArgumentException("Description length must be less than or equal to 100"); - if (value?.Length < 1) - throw new ArgumentException("Description length must at least 1 character in length"); - _description = value; - } + > 100 => throw new ArgumentOutOfRangeException(nameof(value), "Description length must be less than or equal to 100."), + 0 => throw new ArgumentOutOfRangeException(nameof(value), "Description length must be at least 1."), + _ => value + }; } /// @@ -81,6 +76,9 @@ namespace Discord /// public List Options { get; set; } - + /// + /// Gets or sets the allowed channel types for this option. + /// + public List ChannelTypes { get; set; } } } diff --git a/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandOptionChoice.cs b/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandOptionChoice.cs index f7979f20d..a9910a0af 100644 --- a/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandOptionChoice.cs +++ b/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandOptionChoice.cs @@ -1,8 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Discord { @@ -19,20 +15,18 @@ namespace Discord public string Name { get => _name; - set + set => _name = value?.Length switch { - if(value?.Length > 100) - throw new ArgumentException("Name length must be less than or equal to 100"); - if (value?.Length < 1) - throw new ArgumentException("Name length must at least 1 character in length"); - _name = value; - } + > 100 => throw new ArgumentOutOfRangeException(nameof(value), "Name length must be less than or equal to 100."), + 0 => throw new ArgumentOutOfRangeException(nameof(value), "Name length must at least 1."), + _ => value + }; } /// /// Gets or sets the value of this choice. /// - /// Discord only accepts int and string as the input. + /// Discord only accepts int, string, and doubles as the input. /// /// public object Value @@ -40,6 +34,8 @@ namespace Discord get => _value; set { + if (value != null && value is not int && value is not string && value is not double) + throw new ArgumentException("The value of a choice must be a string, int, or double!"); _value = value; } } diff --git a/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandOptionType.cs b/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandOptionType.cs index a1b366e18..0f919f1f6 100644 --- a/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandOptionType.cs +++ b/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandOptionType.cs @@ -1,9 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - namespace Discord { /// @@ -37,7 +31,7 @@ namespace Discord Boolean = 5, /// - /// A . + /// A . /// User = 6, @@ -55,7 +49,7 @@ namespace Discord /// A or . /// Mentionable = 9, - + /// /// A . /// diff --git a/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandProperties.cs b/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandProperties.cs index bde3fcc91..501a0e905 100644 --- a/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandProperties.cs +++ b/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandProperties.cs @@ -1,9 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - namespace Discord { /// diff --git a/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandTypes.cs b/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandTypes.cs index 57ba37e18..8cd31a420 100644 --- a/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandTypes.cs +++ b/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandTypes.cs @@ -1,9 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - namespace Discord { /// diff --git a/src/Discord.Net.Core/Entities/Interactions/AutocompleteOption.cs b/src/Discord.Net.Core/Entities/Interactions/AutocompleteOption.cs index 2a97a7295..4f7d3cb5f 100644 --- a/src/Discord.Net.Core/Entities/Interactions/AutocompleteOption.cs +++ b/src/Discord.Net.Core/Entities/Interactions/AutocompleteOption.cs @@ -1,9 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - namespace Discord { /// diff --git a/src/Discord.Net.Core/Entities/Interactions/AutocompleteResult.cs b/src/Discord.Net.Core/Entities/Interactions/AutocompleteResult.cs index a0992fd00..a4333ceb2 100644 --- a/src/Discord.Net.Core/Entities/Interactions/AutocompleteResult.cs +++ b/src/Discord.Net.Core/Entities/Interactions/AutocompleteResult.cs @@ -1,8 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Discord { @@ -11,8 +7,8 @@ namespace Discord /// public class AutocompleteResult { - private object _value { get; set; } - private string _name { get; set; } + private object _value; + private string _name; /// /// Gets or sets the name of the result. @@ -28,12 +24,13 @@ namespace Discord set { if (value == null) - throw new ArgumentException("Name cannot be null!"); - if (value.Length > 100) - throw new ArgumentException("Name length must be less than or equal to 100 characters in length!"); - if (value.Length < 1) - throw new ArgumentException("Name length must at least 1 character in length!"); - _name = value; + throw new ArgumentNullException(nameof(value), $"{nameof(Name)} cannot be null."); + _name = value.Length switch + { + > 100 => throw new ArgumentOutOfRangeException(nameof(value), "Name length must be less than or equal to 100."), + 0 => throw new ArgumentOutOfRangeException(nameof(value), "Name length must be at least 1."), + _ => value + }; } } @@ -48,20 +45,15 @@ namespace Discord public object Value { get => _value; - set + set => _value = value switch { - if (value == null) - throw new ArgumentNullException("Value cannot be null"); - - _value = value switch - { - string str => str, - int integer => integer, - long lng => lng, - double number => number, - _ => throw new ArgumentException($"Type {value.GetType().Name} cannot be set as a value! Only string, int, and double allowed!"), - }; - } + string str => str, + int integer => integer, + long lng => lng, + double number => number, + null => throw new ArgumentNullException(nameof(value), $"{nameof(Value)} cannot be null."), + _ => throw new ArgumentException($"Type {value.GetType().Name} cannot be set as a value! Only string, int, and double allowed!") + }; } /// diff --git a/src/Discord.Net.Core/Entities/Interactions/Context Menus/MessageCommandBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/Context Menus/MessageCommandBuilder.cs index e600beba7..5e7bb660b 100644 --- a/src/Discord.Net.Core/Entities/Interactions/Context Menus/MessageCommandBuilder.cs +++ b/src/Discord.Net.Core/Entities/Interactions/Context Menus/MessageCommandBuilder.cs @@ -1,10 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading.Tasks; - namespace Discord { /// @@ -12,8 +5,8 @@ namespace Discord /// public class MessageCommandBuilder { - /// - /// Returns the maximun length a commands name allowed by Discord + /// + /// Returns the maximum length a commands name allowed by Discord /// public const int MaxNameLength = 32; @@ -22,10 +15,7 @@ namespace Discord /// public string Name { - get - { - return _name; - } + get => _name; set { Preconditions.NotNullOrEmpty(value, nameof(Name)); @@ -41,7 +31,7 @@ namespace Discord /// public bool IsDefaultPermission { get; set; } = true; - private string _name { get; set; } + private string _name; /// /// Build the current builder into a class. @@ -51,14 +41,13 @@ namespace Discord /// public MessageCommandProperties Build() { - MessageCommandProperties props = new MessageCommandProperties() + var props = new MessageCommandProperties { Name = Name, IsDefaultPermission = IsDefaultPermission }; return props; - } /// diff --git a/src/Discord.Net.Core/Entities/Interactions/Context Menus/MessageCommandProperties.cs b/src/Discord.Net.Core/Entities/Interactions/Context Menus/MessageCommandProperties.cs index 3af9b47f3..356ed23d6 100644 --- a/src/Discord.Net.Core/Entities/Interactions/Context Menus/MessageCommandProperties.cs +++ b/src/Discord.Net.Core/Entities/Interactions/Context Menus/MessageCommandProperties.cs @@ -1,9 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - namespace Discord { /// diff --git a/src/Discord.Net.Core/Entities/Interactions/Context Menus/UserCommandBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/Context Menus/UserCommandBuilder.cs index 34a9706ad..f9c1c6f67 100644 --- a/src/Discord.Net.Core/Entities/Interactions/Context Menus/UserCommandBuilder.cs +++ b/src/Discord.Net.Core/Entities/Interactions/Context Menus/UserCommandBuilder.cs @@ -1,10 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading.Tasks; - namespace Discord { /// @@ -13,7 +6,7 @@ namespace Discord public class UserCommandBuilder { /// - /// Returns the maximun length a commands name allowed by Discord + /// Returns the maximum length a commands name allowed by Discord. /// public const int MaxNameLength = 32; @@ -22,10 +15,7 @@ namespace Discord /// public string Name { - get - { - return _name; - } + get => _name; set { Preconditions.NotNullOrEmpty(value, nameof(Name)); @@ -41,7 +31,7 @@ namespace Discord /// public bool IsDefaultPermission { get; set; } = true; - private string _name { get; set; } + private string _name; /// /// Build the current builder into a class. @@ -49,14 +39,13 @@ namespace Discord /// A that can be used to create user commands. public UserCommandProperties Build() { - UserCommandProperties props = new UserCommandProperties() + var props = new UserCommandProperties { Name = Name, IsDefaultPermission = IsDefaultPermission }; return props; - } /// diff --git a/src/Discord.Net.Core/Entities/Interactions/Context Menus/UserCommandProperties.cs b/src/Discord.Net.Core/Entities/Interactions/Context Menus/UserCommandProperties.cs index 091166a17..c42e916d9 100644 --- a/src/Discord.Net.Core/Entities/Interactions/Context Menus/UserCommandProperties.cs +++ b/src/Discord.Net.Core/Entities/Interactions/Context Menus/UserCommandProperties.cs @@ -1,9 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - namespace Discord { /// diff --git a/src/Discord.Net.Core/Entities/Interactions/IApplicationCommand.cs b/src/Discord.Net.Core/Entities/Interactions/IApplicationCommand.cs index dd192958d..7f33e7747 100644 --- a/src/Discord.Net.Core/Entities/Interactions/IApplicationCommand.cs +++ b/src/Discord.Net.Core/Entities/Interactions/IApplicationCommand.cs @@ -1,7 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Threading.Tasks; namespace Discord diff --git a/src/Discord.Net.Core/Entities/Interactions/IApplicationCommandInteractionData.cs b/src/Discord.Net.Core/Entities/Interactions/IApplicationCommandInteractionData.cs index 3d1a0349a..6cfd62e72 100644 --- a/src/Discord.Net.Core/Entities/Interactions/IApplicationCommandInteractionData.cs +++ b/src/Discord.Net.Core/Entities/Interactions/IApplicationCommandInteractionData.cs @@ -1,8 +1,4 @@ -using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Discord { diff --git a/src/Discord.Net.Core/Entities/Interactions/IApplicationCommandInteractionDataOption.cs b/src/Discord.Net.Core/Entities/Interactions/IApplicationCommandInteractionDataOption.cs index f801ce76c..2a7e9b01e 100644 --- a/src/Discord.Net.Core/Entities/Interactions/IApplicationCommandInteractionDataOption.cs +++ b/src/Discord.Net.Core/Entities/Interactions/IApplicationCommandInteractionDataOption.cs @@ -1,8 +1,4 @@ -using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Discord { @@ -33,6 +29,5 @@ namespace Discord /// Gets the nested options of this option. /// IReadOnlyCollection Options { get; } - } } diff --git a/src/Discord.Net.Core/Entities/Interactions/IApplicationCommandOption.cs b/src/Discord.Net.Core/Entities/Interactions/IApplicationCommandOption.cs index 457c04613..1ca5fc56a 100644 --- a/src/Discord.Net.Core/Entities/Interactions/IApplicationCommandOption.cs +++ b/src/Discord.Net.Core/Entities/Interactions/IApplicationCommandOption.cs @@ -1,8 +1,4 @@ -using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Discord { @@ -39,11 +35,16 @@ namespace Discord /// /// Gets a collection of choices for the user to pick from. /// - IReadOnlyCollection? Choices { get; } + IReadOnlyCollection Choices { get; } /// /// Gets the nested options of this option. /// - IReadOnlyCollection? Options { get; } + IReadOnlyCollection Options { get; } + + /// + /// The allowed channel types for this option. + /// + IReadOnlyCollection ChannelTypes { get; } } } diff --git a/src/Discord.Net.Core/Entities/Interactions/IApplicationCommandOptionChoice.cs b/src/Discord.Net.Core/Entities/Interactions/IApplicationCommandOptionChoice.cs index 24fae04c2..9b7aa3858 100644 --- a/src/Discord.Net.Core/Entities/Interactions/IApplicationCommandOptionChoice.cs +++ b/src/Discord.Net.Core/Entities/Interactions/IApplicationCommandOptionChoice.cs @@ -1,9 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - namespace Discord { /// @@ -20,6 +14,5 @@ namespace Discord /// Gets the value of the choice. /// object Value { get; } - } } diff --git a/src/Discord.Net.Core/Entities/Interactions/IDiscordInteraction.cs b/src/Discord.Net.Core/Entities/Interactions/IDiscordInteraction.cs index 0dea203ef..925ae08fc 100644 --- a/src/Discord.Net.Core/Entities/Interactions/IDiscordInteraction.cs +++ b/src/Discord.Net.Core/Entities/Interactions/IDiscordInteraction.cs @@ -1,7 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Threading.Tasks; namespace Discord @@ -51,7 +48,7 @@ namespace Discord /// 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, + 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); /// @@ -68,23 +65,23 @@ namespace Discord /// /// The sent message. /// - Task FollowupAsync (string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, + 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. + /// The request options for this request. /// A that represents the initial response. - Task GetOriginalResponseAsync (RequestOptions options = null); + 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. + /// The request options for this request. /// A that represents the initial response. - Task ModifyOriginalResponseAsync (Action func, RequestOptions options = null); + Task ModifyOriginalResponseAsync(Action func, RequestOptions options = null); /// /// Acknowledges this interaction. @@ -92,6 +89,6 @@ namespace Discord /// /// A task that represents the asynchronous operation of acknowledging the interaction. /// - Task DeferAsync (bool ephemeral = false, RequestOptions options = null); + Task DeferAsync(bool ephemeral = false, RequestOptions options = null); } } diff --git a/src/Discord.Net.Core/Entities/Interactions/IDiscordInteractionData.cs b/src/Discord.Net.Core/Entities/Interactions/IDiscordInteractionData.cs index 7083810b7..42b95738e 100644 --- a/src/Discord.Net.Core/Entities/Interactions/IDiscordInteractionData.cs +++ b/src/Discord.Net.Core/Entities/Interactions/IDiscordInteractionData.cs @@ -1,13 +1,7 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - namespace Discord { /// - /// Represents an interface used to specify classes that they are a vaild dataype of a class. + /// Represents an interface used to specify classes that they are a valid data type of a class. /// public interface IDiscordInteractionData { } } diff --git a/src/Discord.Net.Core/Entities/Interactions/InteractionResponseType.cs b/src/Discord.Net.Core/Entities/Interactions/InteractionResponseType.cs index 43e614b57..ebdf29781 100644 --- a/src/Discord.Net.Core/Entities/Interactions/InteractionResponseType.cs +++ b/src/Discord.Net.Core/Entities/Interactions/InteractionResponseType.cs @@ -1,8 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Discord { @@ -13,7 +9,7 @@ namespace Discord /// After receiving an interaction, you must respond to acknowledge it. You can choose to respond with a message immediately using /// or you can choose to send a deferred response with . If choosing a deferred response, the user will see a loading state for the interaction, /// and you'll have up to 15 minutes to edit the original deferred response using Edit Original Interaction Response. - /// You can read more about Response types Here + /// You can read more about Response types Here. /// public enum InteractionResponseType : byte { @@ -22,18 +18,6 @@ namespace Discord /// Pong = 1, - /// - /// ACK a command without sending a message, eating the user's input. - /// - [Obsolete("This response type has been depricated by discord. Either use ChannelMessageWithSource or DeferredChannelMessageWithSource", true)] - Acknowledge = 2, - - /// - /// Respond with a message, showing the user's input. - /// - [Obsolete("This response type has been depricated by discord. Either use ChannelMessageWithSource or DeferredChannelMessageWithSource", true)] - ChannelMessage = 3, - /// /// Respond to an interaction with a message. /// diff --git a/src/Discord.Net.Core/Entities/Interactions/InteractionType.cs b/src/Discord.Net.Core/Entities/Interactions/InteractionType.cs index 989114505..e09c906b5 100644 --- a/src/Discord.Net.Core/Entities/Interactions/InteractionType.cs +++ b/src/Discord.Net.Core/Entities/Interactions/InteractionType.cs @@ -1,9 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - namespace Discord { /// @@ -29,6 +23,6 @@ namespace Discord /// /// An autocomplete request sent from discord. /// - ApplicationCommandAutocomplete = 4, + ApplicationCommandAutocomplete = 4 } } diff --git a/src/Discord.Net.Core/Entities/Interactions/Message Components/ActionRowComponent.cs b/src/Discord.Net.Core/Entities/Interactions/Message Components/ActionRowComponent.cs index df08e5ee3..202a5687f 100644 --- a/src/Discord.Net.Core/Entities/Interactions/Message Components/ActionRowComponent.cs +++ b/src/Discord.Net.Core/Entities/Interactions/Message Components/ActionRowComponent.cs @@ -1,28 +1,22 @@ -using Newtonsoft.Json; -using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Discord { /// - /// Represents a Row for child components to live in. + /// Represents a Row for child components to live in. /// public class ActionRowComponent : IMessageComponent { /// - public ComponentType Type { get; } = ComponentType.ActionRow; + public ComponentType Type => ComponentType.ActionRow; /// /// Gets the child components in this row. /// public IReadOnlyCollection Components { get; internal set; } - - internal ActionRowComponent() { } + internal ActionRowComponent(List components) { Components = components; diff --git a/src/Discord.Net.Core/Entities/Interactions/Message Components/ButtonComponent.cs b/src/Discord.Net.Core/Entities/Interactions/Message Components/ButtonComponent.cs index 64a2ab0e9..a3badf32d 100644 --- a/src/Discord.Net.Core/Entities/Interactions/Message Components/ButtonComponent.cs +++ b/src/Discord.Net.Core/Entities/Interactions/Message Components/ButtonComponent.cs @@ -1,10 +1,3 @@ -using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - namespace Discord { /// @@ -13,7 +6,7 @@ namespace Discord public class ButtonComponent : IMessageComponent { /// - public ComponentType Type { get; } = ComponentType.Button; + public ComponentType Type => ComponentType.Button; /// /// Gets the of this button, example buttons with each style can be found Here. diff --git a/src/Discord.Net.Core/Entities/Interactions/Message Components/ButtonStyle.cs b/src/Discord.Net.Core/Entities/Interactions/Message Components/ButtonStyle.cs index c72767cbd..92d48ab4f 100644 --- a/src/Discord.Net.Core/Entities/Interactions/Message Components/ButtonStyle.cs +++ b/src/Discord.Net.Core/Entities/Interactions/Message Components/ButtonStyle.cs @@ -1,9 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - namespace Discord { /// diff --git a/src/Discord.Net.Core/Entities/Interactions/Message Components/ComponentType.cs b/src/Discord.Net.Core/Entities/Interactions/Message Components/ComponentType.cs index 5fb4fc092..70bc1f301 100644 --- a/src/Discord.Net.Core/Entities/Interactions/Message Components/ComponentType.cs +++ b/src/Discord.Net.Core/Entities/Interactions/Message Components/ComponentType.cs @@ -1,29 +1,23 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - namespace Discord { /// - /// Represents a type of a component + /// Represents a type of a component. /// public enum ComponentType { /// - /// A container for other components + /// A container for other components. /// ActionRow = 1, /// - /// A clickable button + /// A clickable button. /// Button = 2, /// - /// A select menu for picking from choices + /// A select menu for picking from choices. /// - SelectMenu = 3, + SelectMenu = 3 } } diff --git a/src/Discord.Net.Core/Entities/Interactions/Message Components/IMessageComponent.cs b/src/Discord.Net.Core/Entities/Interactions/Message Components/IMessageComponent.cs index 7614fbfd5..9366a44d6 100644 --- a/src/Discord.Net.Core/Entities/Interactions/Message Components/IMessageComponent.cs +++ b/src/Discord.Net.Core/Entities/Interactions/Message Components/IMessageComponent.cs @@ -1,9 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - namespace Discord { /// diff --git a/src/Discord.Net.Core/Entities/Interactions/Message Components/MessageComponent.cs b/src/Discord.Net.Core/Entities/Interactions/Message Components/MessageComponent.cs index eb37a57b8..720588681 100644 --- a/src/Discord.Net.Core/Entities/Interactions/Message Components/MessageComponent.cs +++ b/src/Discord.Net.Core/Entities/Interactions/Message Components/MessageComponent.cs @@ -1,8 +1,4 @@ -using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Discord { diff --git a/src/Discord.Net.Core/Entities/Interactions/Message Components/SelectMenuComponent.cs b/src/Discord.Net.Core/Entities/Interactions/Message Components/SelectMenuComponent.cs index db7936365..a1ec7acd8 100644 --- a/src/Discord.Net.Core/Entities/Interactions/Message Components/SelectMenuComponent.cs +++ b/src/Discord.Net.Core/Entities/Interactions/Message Components/SelectMenuComponent.cs @@ -1,8 +1,5 @@ -using System; using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Discord { diff --git a/src/Discord.Net.Core/Entities/Interactions/Message Components/SelectMenuOption.cs b/src/Discord.Net.Core/Entities/Interactions/Message Components/SelectMenuOption.cs index 3c7fce181..6856e1ee3 100644 --- a/src/Discord.Net.Core/Entities/Interactions/Message Components/SelectMenuOption.cs +++ b/src/Discord.Net.Core/Entities/Interactions/Message Components/SelectMenuOption.cs @@ -1,9 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - namespace Discord { /// diff --git a/src/Discord.Net.Core/Entities/Interactions/Slash Commands/SlashCommandBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/Slash Commands/SlashCommandBuilder.cs index 7c337892f..737092cb7 100644 --- a/src/Discord.Net.Core/Entities/Interactions/Slash Commands/SlashCommandBuilder.cs +++ b/src/Discord.Net.Core/Entities/Interactions/Slash Commands/SlashCommandBuilder.cs @@ -1,9 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; using System.Text.RegularExpressions; -using System.Threading.Tasks; namespace Discord { @@ -12,15 +10,15 @@ namespace Discord /// public class SlashCommandBuilder { - /// - /// Returns the maximun length a commands name allowed by Discord + /// + /// Returns the maximum length a commands name allowed by Discord /// public const int MaxNameLength = 32; - /// - /// Returns the maximum length of a commands description allowed by Discord. + /// + /// Returns the maximum length of a commands description allowed by Discord. /// public const int MaxDescriptionLength = 100; - /// + /// /// Returns the maximum count of command options allowed by Discord /// public const int MaxOptionsCount = 25; @@ -30,20 +28,17 @@ namespace Discord /// public string Name { - get - { - return _name; - } + get => _name; set { - Preconditions.NotNullOrEmpty(value, nameof(Name)); - Preconditions.AtLeast(value.Length, 1, nameof(Name)); - Preconditions.AtMost(value.Length, MaxNameLength, nameof(Name)); + Preconditions.NotNullOrEmpty(value, nameof(value)); + Preconditions.AtLeast(value.Length, 1, nameof(value)); + Preconditions.AtMost(value.Length, MaxNameLength, nameof(value)); // Discord updated the docs, this regex prevents special characters like @!$%(... etc, // https://discord.com/developers/docs/interactions/slash-commands#applicationcommand if (!Regex.IsMatch(value, @"^[\w-]{1,32}$")) - throw new ArgumentException("Command name cannot contain any special characters or whitespaces!"); + throw new ArgumentException("Command name cannot contain any special characters or whitespaces!", nameof(value)); _name = value; } @@ -54,12 +49,10 @@ namespace Discord /// public string Description { - get - { - return _description; - } + get => _description; set { + Preconditions.NotNullOrEmpty(value, nameof(Description)); Preconditions.AtLeast(value.Length, 1, nameof(Description)); Preconditions.AtMost(value.Length, MaxDescriptionLength, nameof(Description)); @@ -75,10 +68,7 @@ namespace Discord get => _options; set { - if (value != null) - if (value.Count > MaxOptionsCount) - throw new ArgumentException(message: $"Option count must be less than or equal to {MaxOptionsCount}.", paramName: nameof(Options)); - + Preconditions.AtMost(value?.Count ?? 0, MaxOptionsCount, nameof(value)); _options = value; } } @@ -88,9 +78,9 @@ namespace Discord /// public bool IsDefaultPermission { get; set; } = true; - private string _name { get; set; } - private string _description { get; set; } - private List _options { get; set; } + private string _name; + private string _description; + private List _options; /// /// Build the current builder into a class. @@ -98,7 +88,7 @@ namespace Discord /// A that can be used to create slash commands. public SlashCommandProperties Build() { - SlashCommandProperties props = new SlashCommandProperties() + var props = new SlashCommandProperties { Name = Name, Description = Description, @@ -160,12 +150,14 @@ namespace Discord /// The description of this option. /// If this option is required for this command. /// If this option is the default option. - /// If this option is set to autocompleate. + /// If this option is set to autocomplete. /// The options of the option to add. + /// The allowed channel types for this option. /// The choices of this option. /// The current builder. public SlashCommandBuilder AddOption(string name, ApplicationCommandOptionType type, - string description, bool? isRequired = null, bool? isDefault = null, bool isAutocomplete = false, List options = null, params ApplicationCommandOptionChoiceProperties[] choices) + string description, bool? isRequired = null, bool? isDefault = null, bool isAutocomplete = false, List options = null, + List channelTypes = null, params ApplicationCommandOptionChoiceProperties[] choices) { // Make sure the name matches the requirements from discord Preconditions.NotNullOrEmpty(name, nameof(name)); @@ -175,7 +167,7 @@ namespace Discord // Discord updated the docs, this regex prevents special characters like @!$%( and s p a c e s.. etc, // https://discord.com/developers/docs/interactions/slash-commands#applicationcommand if (!Regex.IsMatch(name, @"^[\w-]{1,32}$")) - throw new ArgumentException("Command name cannot contian any special characters or whitespaces!", nameof(name)); + throw new ArgumentException("Command name cannot contain any special characters or whitespaces!", nameof(name)); // same with description Preconditions.NotNullOrEmpty(description, nameof(description)); @@ -183,14 +175,10 @@ namespace Discord Preconditions.AtMost(description.Length, MaxDescriptionLength, nameof(description)); // make sure theres only one option with default set to true - if (isDefault.HasValue && isDefault.Value) - { - if (Options != null) - if (Options.Any(x => x.IsDefault.HasValue && x.IsDefault.Value)) - throw new ArgumentException("There can only be one command option with default set to true!", nameof(isDefault)); - } + if (isDefault == true && Options?.Any(x => x.IsDefault == true) == true) + throw new ArgumentException("There can only be one command option with default set to true!", nameof(isDefault)); - SlashCommandOptionBuilder option = new SlashCommandOptionBuilder + var option = new SlashCommandOptionBuilder { Name = name, Description = description, @@ -199,7 +187,8 @@ namespace Discord Options = options, Type = type, IsAutocomplete = isAutocomplete, - Choices = choices != null ? new List(choices) : null + Choices = (choices ?? Array.Empty()).ToList(), + ChannelTypes = channelTypes }; return AddOption(option); @@ -212,14 +201,12 @@ namespace Discord /// The current builder. public SlashCommandBuilder AddOption(SlashCommandOptionBuilder option) { - if (Options == null) - Options = new List(); + Options ??= new List(); if (Options.Count >= MaxOptionsCount) - throw new ArgumentOutOfRangeException(nameof(Options), $"Cannot have more than {MaxOptionsCount} options!"); + throw new InvalidOperationException($"Cannot have more than {MaxOptionsCount} options!"); - if (option == null) - throw new ArgumentNullException(nameof(option), "Option cannot be null"); + Preconditions.NotNull(option, nameof(option)); Options.Add(option); return this; @@ -235,10 +222,9 @@ namespace Discord throw new ArgumentNullException(nameof(options), "Options cannot be null!"); if (options.Length == 0) - throw new ArgumentException(nameof(options), "Options cannot be empty!"); + throw new ArgumentException("Options cannot be empty!", nameof(options)); - if (Options == null) - Options = new List(); + Options ??= new List(); if (Options.Count + options.Length > MaxOptionsCount) throw new ArgumentOutOfRangeException(nameof(options), $"Cannot have more than {MaxOptionsCount} options!"); @@ -274,14 +260,13 @@ namespace Discord get => _name; set { - if (value?.Length > SlashCommandBuilder.MaxNameLength) - throw new ArgumentException($"Name length must be less than or equal to {SlashCommandBuilder.MaxNameLength}"); - if (value?.Length < 1) - throw new ArgumentException("Name length must at least 1 characters in length"); - if (value != null) + { + Preconditions.AtLeast(value.Length, 1, nameof(value)); + Preconditions.AtMost(value.Length, SlashCommandBuilder.MaxNameLength, nameof(value)); if (!Regex.IsMatch(value, @"^[\w-]{1,32}$")) - throw new ArgumentException("Option name cannot contian any special characters or whitespaces!"); + throw new ArgumentException("Option name cannot contain any special characters or whitespaces!", nameof(value)); + } _name = value; } @@ -295,10 +280,11 @@ namespace Discord get => _description; set { - if (value?.Length > SlashCommandBuilder.MaxDescriptionLength) - throw new ArgumentException($"Description length must be less than or equal to {SlashCommandBuilder.MaxDescriptionLength}"); - if (value?.Length < 1) - throw new ArgumentException("Description length must at least 1 character in length"); + if (value != null) + { + Preconditions.AtLeast(value.Length, 1, nameof(value)); + Preconditions.AtMost(value.Length, SlashCommandBuilder.MaxDescriptionLength, nameof(value)); + } _description = value; } @@ -334,6 +320,11 @@ namespace Discord /// public List Options { get; set; } + /// + /// Gets or sets the allowed channel types for this option. + /// + public List ChannelTypes { get; set; } + /// /// Builds the current option. /// @@ -343,21 +334,22 @@ namespace Discord bool isSubType = Type == ApplicationCommandOptionType.SubCommandGroup; if (isSubType && (Options == null || !Options.Any())) - throw new ArgumentException(nameof(Options), "SubCommands/SubCommandGroups must have at least one option"); + throw new InvalidOperationException("SubCommands/SubCommandGroups must have at least one option"); - if (!isSubType && (Options != null && Options.Any()) && Type != ApplicationCommandOptionType.SubCommand) - throw new ArgumentException(nameof(Options), $"Cannot have options on {Type} type"); + if (!isSubType && Options != null && Options.Any() && Type != ApplicationCommandOptionType.SubCommand) + throw new InvalidOperationException($"Cannot have options on {Type} type"); - return new ApplicationCommandOptionProperties() + return new ApplicationCommandOptionProperties { Name = Name, Description = Description, IsDefault = IsDefault, IsRequired = IsRequired, Type = Type, - Options = Options?.Count > 0 ? new List(Options.Select(x => x.Build())) : null, + Options = Options?.Count > 0 ? Options.Select(x => x.Build()).ToList() : new List(), Choices = Choices, - IsAutocomplete = IsAutocomplete + IsAutocomplete = IsAutocomplete, + ChannelTypes = ChannelTypes }; } @@ -369,11 +361,14 @@ namespace Discord /// The description of this option. /// If this option is required for this command. /// If this option is the default option. + /// If this option supports autocomplete. /// The options of the option to add. + /// The allowed channel types for this option. /// The choices of this option. /// The current builder. public SlashCommandOptionBuilder AddOption(string name, ApplicationCommandOptionType type, - string description, bool? isRequired = null, bool isDefault = false, bool isAutocomplete = false, List options = null, params ApplicationCommandOptionChoiceProperties[] choices) + string description, bool? isRequired = null, bool isDefault = false, bool isAutocomplete = false, List options = null, + List channelTypes = null, params ApplicationCommandOptionChoiceProperties[] choices) { // Make sure the name matches the requirements from discord Preconditions.NotNullOrEmpty(name, nameof(name)); @@ -383,7 +378,7 @@ namespace Discord // Discord updated the docs, this regex prevents special characters like @!$%( and s p a c e s.. etc, // https://discord.com/developers/docs/interactions/slash-commands#applicationcommand if (!Regex.IsMatch(name, @"^[\w-]{1,32}$")) - throw new ArgumentException("Command name cannot contian any special characters or whitespaces!", nameof(name)); + throw new ArgumentException("Command name cannot contain any special characters or whitespaces!", nameof(name)); // same with description Preconditions.NotNullOrEmpty(description, nameof(description)); @@ -391,14 +386,10 @@ namespace Discord Preconditions.AtMost(description.Length, SlashCommandBuilder.MaxDescriptionLength, nameof(description)); // make sure theres only one option with default set to true - if (isDefault) - { - if (Options != null) - if (Options.Any(x => x.IsDefault.HasValue && x.IsDefault.Value)) - throw new ArgumentException("There can only be one command option with default set to true!", nameof(isDefault)); - } + if (isDefault && Options?.Any(x => x.IsDefault == true) == true) + throw new ArgumentException("There can only be one command option with default set to true!", nameof(isDefault)); - SlashCommandOptionBuilder option = new SlashCommandOptionBuilder + var option = new SlashCommandOptionBuilder { Name = name, Description = description, @@ -407,7 +398,8 @@ namespace Discord IsDefault = isDefault, Options = options, Type = type, - Choices = choices != null ? new List(choices) : null + Choices = (choices ?? Array.Empty()).ToList(), + ChannelTypes = channelTypes }; return AddOption(option); @@ -419,14 +411,12 @@ namespace Discord /// The current builder. public SlashCommandOptionBuilder AddOption(SlashCommandOptionBuilder option) { - if (Options == null) - Options = new List(); + Options ??= new List(); if (Options.Count >= SlashCommandBuilder.MaxOptionsCount) - throw new ArgumentOutOfRangeException(nameof(Choices), $"There can only be {SlashCommandBuilder.MaxOptionsCount} options per sub command group!"); + throw new InvalidOperationException($"There can only be {SlashCommandBuilder.MaxOptionsCount} options per sub command group!"); - if (option == null) - throw new ArgumentNullException(nameof(option), "Option cannot be null"); + Preconditions.NotNull(option, nameof(option)); Options.Add(option); return this; @@ -489,17 +479,13 @@ namespace Discord private SlashCommandOptionBuilder AddChoiceInternal(string name, object value) { - if (Choices == null) - Choices = new List(); + Choices ??= new List(); if (Choices.Count >= MaxChoiceCount) - throw new ArgumentOutOfRangeException(nameof(Choices), $"Cannot add more than {MaxChoiceCount} choices!"); - - if (name == null) - throw new ArgumentNullException($"{nameof(name)} cannot be null!"); + throw new InvalidOperationException($"Cannot add more than {MaxChoiceCount} choices!"); - if (value == null) - throw new ArgumentNullException($"{nameof(value)} cannot be null!"); + Preconditions.NotNull(name, nameof(name)); + Preconditions.NotNull(value, nameof(value)); Preconditions.AtLeast(name.Length, 1, nameof(name)); Preconditions.AtMost(name.Length, 100, nameof(name)); @@ -510,7 +496,7 @@ namespace Discord Preconditions.AtMost(str.Length, 100, nameof(value)); } - Choices.Add(new ApplicationCommandOptionChoiceProperties() + Choices.Add(new ApplicationCommandOptionChoiceProperties { Name = name, Value = value @@ -519,6 +505,20 @@ namespace Discord return this; } + /// + /// Adds a channel type to the current option. + /// + /// The to add. + /// The current builder. + public SlashCommandOptionBuilder AddChannelType(ChannelType channelType) + { + ChannelTypes ??= new List(); + + ChannelTypes.Add(channelType); + + return this; + } + /// /// Sets the current builders name. /// @@ -564,6 +564,17 @@ namespace Discord return this; } + /// + /// Sets the current builders autocomplete field. + /// + /// The value to set. + /// The current builder. + public SlashCommandOptionBuilder WithAutocomplete(bool value) + { + IsAutocomplete = value; + return this; + } + /// /// Sets the current type of this builder. /// diff --git a/src/Discord.Net.Core/Entities/Interactions/Slash Commands/SlashCommandProperties.cs b/src/Discord.Net.Core/Entities/Interactions/Slash Commands/SlashCommandProperties.cs index 9405b1881..20ba2868f 100644 --- a/src/Discord.Net.Core/Entities/Interactions/Slash Commands/SlashCommandProperties.cs +++ b/src/Discord.Net.Core/Entities/Interactions/Slash Commands/SlashCommandProperties.cs @@ -1,8 +1,4 @@ -using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Discord { diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs b/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs index 5b92e02a5..0304120f5 100644 --- a/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs +++ b/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs @@ -408,22 +408,22 @@ namespace Discord if (Length > MaxEmbedLength) throw new InvalidOperationException($"Total embed length must be less than or equal to {MaxEmbedLength}."); if (!string.IsNullOrEmpty(Url)) - UrlValidation.Validate(Url); + UrlValidation.Validate(Url, true); if (!string.IsNullOrEmpty(ThumbnailUrl)) - UrlValidation.Validate(ThumbnailUrl); - if (!string.IsNullOrEmpty(ImageUrl) && !ImageUrl.StartsWith("attachment://", StringComparison.Ordinal)) - UrlValidation.Validate(ImageUrl); + UrlValidation.Validate(ThumbnailUrl, true); + if (!string.IsNullOrEmpty(ImageUrl)) + UrlValidation.Validate(ImageUrl, true); if (Author != null) { if (!string.IsNullOrEmpty(Author.Url)) - UrlValidation.Validate(Author.Url); + UrlValidation.Validate(Author.Url, true); if (!string.IsNullOrEmpty(Author.IconUrl)) - UrlValidation.Validate(Author.IconUrl); + UrlValidation.Validate(Author.IconUrl, true); } if(Footer != null) { if (!string.IsNullOrEmpty(Footer.IconUrl)) - UrlValidation.Validate(Footer.IconUrl); + UrlValidation.Validate(Footer.IconUrl, true); } var fields = ImmutableArray.CreateBuilder(Fields.Count); for (int i = 0; i < Fields.Count; i++) diff --git a/src/Discord.Net.Core/Entities/Messages/IAttachment.cs b/src/Discord.Net.Core/Entities/Messages/IAttachment.cs index 655777998..e94e9f97c 100644 --- a/src/Discord.Net.Core/Entities/Messages/IAttachment.cs +++ b/src/Discord.Net.Core/Entities/Messages/IAttachment.cs @@ -55,5 +55,12 @@ namespace Discord /// The width of this attachment if it is a picture; otherwise null. /// int? Width { get; } + /// + /// Gets whether or not this attachment is ephemeral. + /// + /// + /// if the attachment is ephemeral; otherwise . + /// + bool Ephemeral { get; } } } diff --git a/src/Discord.Net.Core/Entities/Messages/IMessage.cs b/src/Discord.Net.Core/Entities/Messages/IMessage.cs index 39419f068..f5f2ca007 100644 --- a/src/Discord.Net.Core/Entities/Messages/IMessage.cs +++ b/src/Discord.Net.Core/Entities/Messages/IMessage.cs @@ -53,6 +53,13 @@ namespace Discord /// string Content { get; } /// + /// Gets the clean content for this message. + /// + /// + /// A string that contains the body of the message stripped of mentions, markdown, emojis and pings; note that this field may be empty if there is an embed. + /// + string CleanContent { get; } + /// /// Gets the time this message was sent. /// /// @@ -187,7 +194,15 @@ namespace Discord /// A message's flags, if any is associated. /// MessageFlags? Flags { get; } - + + /// + /// Gets the interaction this message is a response to. + /// + /// + /// A if the message is a response to an interaction; otherwise . + /// + IMessageInteraction Interaction { get; } + /// /// Adds a reaction to this message. /// diff --git a/src/Discord.Net.Core/Entities/Messages/IMessageInteraction.cs b/src/Discord.Net.Core/Entities/Messages/IMessageInteraction.cs new file mode 100644 index 000000000..ebd03b627 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Messages/IMessageInteraction.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord +{ + /// + /// Represents a partial within a message. + /// + public interface IMessageInteraction + { + /// + /// Gets the snowflake id of the interaction. + /// + ulong Id { get; } + + /// + /// Gets the type of the interaction. + /// + InteractionType Type { get; } + + /// + /// Gets the name of the application command used. + /// + string Name { get; } + + /// + /// Gets the who invoked the interaction. + /// + IUser User { get; } + } +} diff --git a/src/Discord.Net.Core/Entities/Messages/MessageInteraction.cs b/src/Discord.Net.Core/Entities/Messages/MessageInteraction.cs new file mode 100644 index 000000000..cbbebd932 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Messages/MessageInteraction.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord +{ + /// + /// Represents a partial within a message. + /// + /// The type of the user. + public class MessageInteraction : IMessageInteraction where TUser : IUser + { + /// + /// Gets the snowflake id of the interaction. + /// + public ulong Id { get; } + + /// + /// Gets the type of the interaction. + /// + public InteractionType Type { get; } + + /// + /// Gets the name of the application command used. + /// + public string Name { get; } + + /// + /// Gets the who invoked the interaction. + /// + public TUser User { get; } + + internal MessageInteraction(ulong id, InteractionType type, string name, TUser user) + { + Id = id; + Type = type; + Name = name; + User = user; + } + + IUser IMessageInteraction.User => User; + } +} diff --git a/src/Discord.Net.Core/Entities/Messages/MessageType.cs b/src/Discord.Net.Core/Entities/Messages/MessageType.cs index de30d8047..b83f88434 100644 --- a/src/Discord.Net.Core/Entities/Messages/MessageType.cs +++ b/src/Discord.Net.Core/Entities/Messages/MessageType.cs @@ -81,26 +81,30 @@ namespace Discord /// The message is an inline reply. /// /// - /// Only available in API v8 + /// Only available in API v8. /// Reply = 19, /// - /// The message is an Application Command + /// The message is an Application Command. /// /// - /// Only available in API v8 + /// Only available in API v8. /// ApplicationCommand = 20, /// /// The message that starts a thread. /// /// - /// Only available in API v9 + /// Only available in API v9. /// ThreadStarterMessage = 21, /// - /// The message for a invite reminder + /// The message for a invite reminder. /// - GuildInviteReminder = 22 + GuildInviteReminder = 22, + /// + /// The message for a context menu command. + /// + ContextMenuCommand = 23, } } diff --git a/src/Discord.Net.Core/Entities/Messages/StickerFormatType.cs b/src/Discord.Net.Core/Entities/Messages/StickerFormatType.cs index d24a38534..82e6b15a4 100644 --- a/src/Discord.Net.Core/Entities/Messages/StickerFormatType.cs +++ b/src/Discord.Net.Core/Entities/Messages/StickerFormatType.cs @@ -1,15 +1,25 @@ namespace Discord { - /// Defines the types of formats for stickers. + /// + /// Defines the types of formats for stickers. + /// public enum StickerFormatType { - /// Default value for a sticker format type. + /// + /// Default value for a sticker format type. + /// None = 0, - /// The sticker format type is png. + /// + /// The sticker format type is png. + /// Png = 1, - /// The sticker format type is apng. + /// + /// The sticker format type is apng. + /// Apng = 2, - /// The sticker format type is lottie. - Lottie = 3, + /// + /// The sticker format type is lottie. + /// + Lottie = 3 } } diff --git a/src/Discord.Net.Core/Entities/Messages/TimestampTag.cs b/src/Discord.Net.Core/Entities/Messages/TimestampTag.cs index 829b35098..347b0daaa 100644 --- a/src/Discord.Net.Core/Entities/Messages/TimestampTag.cs +++ b/src/Discord.Net.Core/Entities/Messages/TimestampTag.cs @@ -1,8 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Discord { @@ -24,18 +20,13 @@ namespace Discord /// /// Converts the current timestamp tag to the string representation supported by discord. /// - /// If the is null then the default 0 will be used. + /// If the is null then the default 0 will be used. /// /// - /// A string thats compatable in a discord message, ex: <t:1625944201:f> + /// A string that is compatible in a discord message, ex: <t:1625944201:f> public override string ToString() { - if (Time == null) - return $""; - - var offset = (DateTimeOffset)this.Time; - - return $""; + return $""; } /// @@ -46,7 +37,7 @@ namespace Discord /// The newly create timestamp tag. public static TimestampTag FromDateTime(DateTime time, TimestampTagStyles style = TimestampTagStyles.ShortDateTime) { - return new TimestampTag() + return new TimestampTag { Style = style, Time = time diff --git a/src/Discord.Net.Core/Entities/Messages/TimestampTagStyle.cs b/src/Discord.Net.Core/Entities/Messages/TimestampTagStyle.cs index 9c83a070e..89f3c79b5 100644 --- a/src/Discord.Net.Core/Entities/Messages/TimestampTagStyle.cs +++ b/src/Discord.Net.Core/Entities/Messages/TimestampTagStyle.cs @@ -1,9 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - namespace Discord { /// diff --git a/src/Discord.Net.Core/Entities/Permissions/ApplicationCommandPermissionTarget.cs b/src/Discord.Net.Core/Entities/Permissions/ApplicationCommandPermissionTarget.cs index 5410075ba..9a99b34f1 100644 --- a/src/Discord.Net.Core/Entities/Permissions/ApplicationCommandPermissionTarget.cs +++ b/src/Discord.Net.Core/Entities/Permissions/ApplicationCommandPermissionTarget.cs @@ -1,9 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - namespace Discord { /// @@ -18,6 +12,6 @@ namespace Discord /// /// The target of the permission is a user. /// - User = 2, + User = 2 } } diff --git a/src/Discord.Net.Core/Entities/Permissions/ChannelPermission.cs b/src/Discord.Net.Core/Entities/Permissions/ChannelPermission.cs index d76a579d9..45e24b7fa 100644 --- a/src/Discord.Net.Core/Entities/Permissions/ChannelPermission.cs +++ b/src/Discord.Net.Core/Entities/Permissions/ChannelPermission.cs @@ -110,12 +110,6 @@ namespace Discord /// ManageEmojis = 0x00_40_00_00_00, - /// - /// Allows members to use slash commands in text channels. - /// - [Obsolete("UseSlashCommands has been replaced by UseApplicationCommands", true)] - UseSlashCommands = 0x00_80_00_00_00, - /// /// Allows members to use slash commands in text channels. /// @@ -131,17 +125,6 @@ namespace Discord /// ManageThreads = 0x04_00_00_00_00, - /// - /// Allows for creating and participating in threads - /// - [Obsolete("UsePublicThreads has been replaced by CreatePublicThreads and SendMessagesInThreads", true)] - UsePublicThreads = 0x08_00_00_00_00, - - /// - /// Allows for creating and participating in private threads - /// - [Obsolete("UsePrivateThreads has been replaced by CreatePrivateThreads and SendMessagesInThreads", true)] - UsePrivateThreads = 0x10_00_00_00_00, /// /// Allows for creating public threads. /// diff --git a/src/Discord.Net.Core/Entities/Permissions/ChannelPermissions.cs b/src/Discord.Net.Core/Entities/Permissions/ChannelPermissions.cs index 11130731e..ee5c9984a 100644 --- a/src/Discord.Net.Core/Entities/Permissions/ChannelPermissions.cs +++ b/src/Discord.Net.Core/Entities/Permissions/ChannelPermissions.cs @@ -85,7 +85,7 @@ namespace Discord /// If true, a user may stream video in a voice channel. public bool Stream => Permissions.GetValue(RawValue, ChannelPermission.Stream); - /// If true, a user may adjust role permissions. This also implictly grants all other permissions. + /// If true, a user may adjust role permissions. This also implicitly grants all other permissions. public bool ManageRoles => Permissions.GetValue(RawValue, ChannelPermission.ManageRoles); /// If true, a user may edit the webhooks for this channel. public bool ManageWebhooks => Permissions.GetValue(RawValue, ChannelPermission.ManageWebhooks); @@ -103,7 +103,7 @@ namespace Discord public bool UseExternalStickers => Permissions.GetValue(RawValue, ChannelPermission.UseExternalStickers); /// If true, a user may send messages in threads in this guild. public bool SendMessagesInThreads => Permissions.GetValue(RawValue, ChannelPermission.SendMessagesInThreads); - /// If true, a user launch application activites in voice channels in this guild. + /// If true, a user launch application activities in voice channels in this guild. public bool StartEmbeddedActivities => Permissions.GetValue(RawValue, ChannelPermission.StartEmbeddedActivities); /// Creates a new with the provided packed value. diff --git a/src/Discord.Net.Core/Entities/Permissions/GuildApplicationCommandPermissions.cs b/src/Discord.Net.Core/Entities/Permissions/GuildApplicationCommandPermissions.cs index 4be724453..e738fec4c 100644 --- a/src/Discord.Net.Core/Entities/Permissions/GuildApplicationCommandPermissions.cs +++ b/src/Discord.Net.Core/Entities/Permissions/GuildApplicationCommandPermissions.cs @@ -1,8 +1,4 @@ -using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Discord { diff --git a/src/Discord.Net.Core/Entities/Permissions/GuildPermission.cs b/src/Discord.Net.Core/Entities/Permissions/GuildPermission.cs index 746981c6e..ac2f7276b 100644 --- a/src/Discord.Net.Core/Entities/Permissions/GuildPermission.cs +++ b/src/Discord.Net.Core/Entities/Permissions/GuildPermission.cs @@ -176,11 +176,6 @@ namespace Discord /// ManageEmojisAndStickers = 0x40_00_00_00, /// - /// Allows members to use slash commands in text channels. - /// - [Obsolete("UseSlashCommands has been replaced by UseApplicationCommands", true)] - UseSlashCommands = 0x80_00_00_00, - /// /// Allows members to use application commands like slash commands and context menus in text channels. /// UseApplicationCommands = 0x80_00_00_00, @@ -205,16 +200,6 @@ namespace Discord /// CreatePrivateThreads = 0x10_00_00_00_00, /// - /// Allows for creating public threads. - /// - [Obsolete("UsePublicThreads has been replaced by CreatePublicThreads and SendMessagesInThreads", true)] - UsePublicThreads = 0x08_00_00_00_00, - /// - /// Allows for creating private threads. - /// - [Obsolete("UsePrivateThreads has been replaced by CreatePrivateThreads and SendMessagesInThreads", true)] - UsePrivateThreads = 0x10_00_00_00_00, - /// /// Allows the usage of custom stickers from other servers. /// UseExternalStickers = 0x20_00_00_00_00, diff --git a/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs b/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs index 8924936a0..31f74ea22 100644 --- a/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs +++ b/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs @@ -97,7 +97,7 @@ namespace Discord public bool UseExternalStickers => Permissions.GetValue(RawValue, GuildPermission.UseExternalStickers); /// If true, a user may send messages in threads in this guild. public bool SendMessagesInThreads => Permissions.GetValue(RawValue, GuildPermission.SendMessagesInThreads); - /// If true, a user launch application activites in voice channels in this guild. + /// If true, a user launch application activities in voice channels in this guild. public bool StartEmbeddedActivities => Permissions.GetValue(RawValue, GuildPermission.StartEmbeddedActivities); /// Creates a new with the provided packed value. diff --git a/src/Discord.Net.Core/Entities/Permissions/OverwritePermissions.cs b/src/Discord.Net.Core/Entities/Permissions/OverwritePermissions.cs index cff938465..0e634ad1a 100644 --- a/src/Discord.Net.Core/Entities/Permissions/OverwritePermissions.cs +++ b/src/Discord.Net.Core/Entities/Permissions/OverwritePermissions.cs @@ -97,7 +97,7 @@ namespace Discord public PermValue UseExternalStickers => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.UseExternalStickers); /// If true, a user may send messages in threads in this guild. public PermValue SendMessagesInThreads => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.SendMessagesInThreads); - /// If true, a user launch application activites in voice channels in this guild. + /// If true, a user launch application activities in voice channels in this guild. public PermValue StartEmbeddedActivities => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.StartEmbeddedActivities); /// Creates a new OverwritePermissions with the provided allow and deny packed values. diff --git a/src/Discord.Net.Core/Entities/Roles/IRole.cs b/src/Discord.Net.Core/Entities/Roles/IRole.cs index c02322be9..c9e7ffef7 100644 --- a/src/Discord.Net.Core/Entities/Roles/IRole.cs +++ b/src/Discord.Net.Core/Entities/Roles/IRole.cs @@ -52,6 +52,13 @@ namespace Discord /// string Name { get; } /// + /// Gets the icon of this role. + /// + /// + /// A string containing the hash of this role's icon. + /// + string Icon { get; } + /// /// Gets the permissions granted to members of this role. /// /// @@ -86,5 +93,13 @@ namespace Discord /// A task that represents the asynchronous modification operation. /// Task ModifyAsync(Action func, RequestOptions options = null); + + /// + /// Gets the image url of the icon role. + /// + /// + /// An image url of the icon role. + /// + string GetIconUrl(); } } diff --git a/src/Discord.Net.Core/Entities/Stickers/ICustomSticker.cs b/src/Discord.Net.Core/Entities/Stickers/ICustomSticker.cs index 03f4ac2eb..9cba38c80 100644 --- a/src/Discord.Net.Core/Entities/Stickers/ICustomSticker.cs +++ b/src/Discord.Net.Core/Entities/Stickers/ICustomSticker.cs @@ -1,7 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Threading.Tasks; namespace Discord @@ -51,7 +48,7 @@ namespace Discord /// Deletes the current sticker. /// /// - /// The bot neeeds the MANAGE_EMOJIS_AND_STICKERS permission inside the guild in order to delete stickers. + /// The bot needs the MANAGE_EMOJIS_AND_STICKERS permission inside the guild in order to delete stickers. /// /// The options to be used when sending the request. /// diff --git a/src/Discord.Net.Core/Entities/Stickers/ISticker.cs b/src/Discord.Net.Core/Entities/Stickers/ISticker.cs index a3c520b4a..9deea753f 100644 --- a/src/Discord.Net.Core/Entities/Stickers/ISticker.cs +++ b/src/Discord.Net.Core/Entities/Stickers/ISticker.cs @@ -1,6 +1,4 @@ -using System; using System.Collections.Generic; -using System.Threading.Tasks; namespace Discord { @@ -57,12 +55,12 @@ namespace Discord new StickerFormatType Format { get; } /// - /// Gets whether this guild sticker can be used, may be false due to loss of Server Boosts + /// Gets whether this guild sticker can be used, may be false due to loss of Server Boosts. /// bool? IsAvailable { get; } /// - /// Gets the standard sticker's sort order within its pack + /// Gets the standard sticker's sort order within its pack. /// int? SortOrder { get; } /// diff --git a/src/Discord.Net.Core/Entities/Stickers/IStickerItem.cs b/src/Discord.Net.Core/Entities/Stickers/IStickerItem.cs index 3825a2702..07ea63db9 100644 --- a/src/Discord.Net.Core/Entities/Stickers/IStickerItem.cs +++ b/src/Discord.Net.Core/Entities/Stickers/IStickerItem.cs @@ -1,9 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - namespace Discord { /// diff --git a/src/Discord.Net.Core/Entities/Stickers/StickerPack.cs b/src/Discord.Net.Core/Entities/Stickers/StickerPack.cs index 76d424a47..c0c90aa69 100644 --- a/src/Discord.Net.Core/Entities/Stickers/StickerPack.cs +++ b/src/Discord.Net.Core/Entities/Stickers/StickerPack.cs @@ -1,16 +1,12 @@ -using System; using System.Collections.Generic; using System.Collections.Immutable; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Discord { /// /// Represents a discord sticker pack. /// - /// The type of the stickers within the collection + /// The type of the stickers within the collection. public class StickerPack where TSticker : ISticker { /// diff --git a/src/Discord.Net.Core/Entities/Stickers/StickerProperties.cs b/src/Discord.Net.Core/Entities/Stickers/StickerProperties.cs index 21267cdda..5f51e5f3d 100644 --- a/src/Discord.Net.Core/Entities/Stickers/StickerProperties.cs +++ b/src/Discord.Net.Core/Entities/Stickers/StickerProperties.cs @@ -1,8 +1,4 @@ -using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Discord { diff --git a/src/Discord.Net.Core/Entities/Stickers/StickerType.cs b/src/Discord.Net.Core/Entities/Stickers/StickerType.cs index 35946df7a..0db550772 100644 --- a/src/Discord.Net.Core/Entities/Stickers/StickerType.cs +++ b/src/Discord.Net.Core/Entities/Stickers/StickerType.cs @@ -1,13 +1,7 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - namespace Discord { /// - /// Represents a type of sticker + /// Represents a type of sticker.. /// public enum StickerType { @@ -19,6 +13,6 @@ namespace Discord /// /// Represents a sticker that was created within a guild. /// - Guild = 2, + Guild = 2 } } diff --git a/src/Discord.Net.Core/Extensions/MessageExtensions.cs b/src/Discord.Net.Core/Extensions/MessageExtensions.cs index 3b21c128a..c187ecd5b 100644 --- a/src/Discord.Net.Core/Extensions/MessageExtensions.cs +++ b/src/Discord.Net.Core/Extensions/MessageExtensions.cs @@ -24,7 +24,7 @@ namespace Discord /// Add multiple reactions to a message. /// /// - /// This method does not bulk add reactions! It will send a request for each reaction inculded. + /// This method does not bulk add reactions! It will send a request for each reaction included. /// /// /// @@ -76,7 +76,7 @@ namespace Discord /// /// Sends an inline reply that references a message. /// - /// The message that is being replyed on. + /// The message that is being replied on. /// The message to be sent. /// Determines whether the message should be read aloud by Discord or not. /// The to be sent. diff --git a/src/Discord.Net.Core/Format.cs b/src/Discord.Net.Core/Format.cs index 0ab70f89c..63f9d15a6 100644 --- a/src/Discord.Net.Core/Format.cs +++ b/src/Discord.Net.Core/Format.cs @@ -1,4 +1,5 @@ using System.Text; +using System.Text.RegularExpressions; namespace Discord { @@ -14,7 +15,7 @@ namespace Discord public static string Italics(string text) => $"*{text}*"; /// Returns a markdown-formatted string with underline formatting. public static string Underline(string text) => $"__{text}__"; - /// Returns a markdown-formatted string with strikethrough formatting. + /// Returns a markdown-formatted string with strike-through formatting. public static string Strikethrough(string text) => $"~~{text}~~"; /// Returns a string with spoiler formatting. public static string Spoiler(string text) => $"||{text}||"; @@ -91,5 +92,17 @@ namespace Discord return $">>> {text}"; } + + /// + /// Remove discord supported markdown from text. + /// + /// The to remove markdown from. + /// Gets the unformatted text. + public static string StripMarkDown(string text) + { + //Remove discord supported markdown + var newText = Regex.Replace(text, @"(\*|_|`|~|>|\\)", ""); + return newText; + } } } diff --git a/src/Discord.Net.Core/Net/ApplicationCommandException.cs b/src/Discord.Net.Core/Net/ApplicationCommandException.cs index acf19afe4..62c80b388 100644 --- a/src/Discord.Net.Core/Net/ApplicationCommandException.cs +++ b/src/Discord.Net.Core/Net/ApplicationCommandException.cs @@ -1,8 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Discord.Net { diff --git a/src/Discord.Net.Core/RequestOptions.cs b/src/Discord.Net.Core/RequestOptions.cs index 101925ab2..46aa2681f 100644 --- a/src/Discord.Net.Core/RequestOptions.cs +++ b/src/Discord.Net.Core/RequestOptions.cs @@ -16,10 +16,10 @@ namespace Discord public static RequestOptions Default => new RequestOptions(); /// - /// Gets or sets the maximum time to wait for for this request to complete. + /// Gets or sets the maximum time to wait for this request to complete. /// /// - /// Gets or set the max time, in milliseconds, to wait for for this request to complete. If + /// Gets or set the max time, in milliseconds, to wait for this request to complete. If /// null, a request will not time out. If a rate limit has been triggered for this request's bucket /// and will not be unpaused in time, this request will fail immediately. /// diff --git a/src/Discord.Net.Core/Utils/UrlValidation.cs b/src/Discord.Net.Core/Utils/UrlValidation.cs index 23234b899..8e877bd4e 100644 --- a/src/Discord.Net.Core/Utils/UrlValidation.cs +++ b/src/Discord.Net.Core/Utils/UrlValidation.cs @@ -6,16 +6,36 @@ namespace Discord.Utils { /// /// Not full URL validation right now. Just ensures protocol is present and that it's either http or https + /// should be used for url buttons. /// - /// url to validate before sending to Discord. + /// The URL to validate before sending to Discord. + /// to allow the attachment:// protocol; otherwise . /// A URL must include a protocol (http or https). - /// true if url is valid by our standard, false if null, throws an error upon invalid - public static bool Validate(string url) + /// true if URL is valid by our standard, false if null, throws an error upon invalid. + public static bool Validate(string url, bool allowAttachments = false) { if (string.IsNullOrEmpty(url)) return false; - if(!(url.StartsWith("http://", StringComparison.OrdinalIgnoreCase) || (url.StartsWith("https://", StringComparison.OrdinalIgnoreCase)))) - throw new InvalidOperationException($"Url {url} must be include its protocol (either HTTP or HTTPS)"); + if (!(url.StartsWith("http://", StringComparison.OrdinalIgnoreCase) || url.StartsWith("https://", StringComparison.OrdinalIgnoreCase) || (allowAttachments ? url.StartsWith("attachment://", StringComparison.Ordinal) : false))) + throw new InvalidOperationException($"The url {url} must include a protocol (either {(allowAttachments ? "HTTP, HTTPS, or ATTACHMENT" : "HTTP or HTTPS")})"); + return true; + } + + /// + /// Not full URL validation right now. Just Ensures the protocol is either http, https, or discord + /// should be used everything other than url buttons. + /// + /// The URL to validate before sending to discord. + /// A URL must include a protocol (either http, https, or discord). + /// true if the URL is valid by our standard, false if null, throws an error upon invalid. + public static bool ValidateButton(string url) + { + if (string.IsNullOrEmpty(url)) + return false; + if (!(url.StartsWith("http://", StringComparison.OrdinalIgnoreCase) || + url.StartsWith("https://", StringComparison.OrdinalIgnoreCase) || + url.StartsWith("discord://", StringComparison.OrdinalIgnoreCase))) + throw new InvalidOperationException($"The url {url} must include a protocol (either HTTP, HTTPS, or DISCORD)"); return true; } } diff --git a/src/Discord.Net.Examples/Core/Entities/Channels/IMessageChannel.Examples.cs b/src/Discord.Net.Examples/Core/Entities/Channels/IMessageChannel.Examples.cs index 5f9d4b5d6..d920e9710 100644 --- a/src/Discord.Net.Examples/Core/Entities/Channels/IMessageChannel.Examples.cs +++ b/src/Discord.Net.Examples/Core/Entities/Channels/IMessageChannel.Examples.cs @@ -108,7 +108,6 @@ namespace Discord.Net.Examples.Core.Entities.Channels using (channel.EnterTypingState()) await LongRunningAsync(); #endregion - } } } diff --git a/src/Discord.Net.Examples/Discord.Net.Examples.csproj b/src/Discord.Net.Examples/Discord.Net.Examples.csproj index ec0253428..3371432b8 100644 --- a/src/Discord.Net.Examples/Discord.Net.Examples.csproj +++ b/src/Discord.Net.Examples/Discord.Net.Examples.csproj @@ -15,7 +15,7 @@ - + diff --git a/src/Discord.Net.Providers.WS4Net/Discord.Net.Providers.WS4Net.csproj b/src/Discord.Net.Providers.WS4Net/Discord.Net.Providers.WS4Net.csproj index e143340e1..ec7ee9b8f 100644 --- a/src/Discord.Net.Providers.WS4Net/Discord.Net.Providers.WS4Net.csproj +++ b/src/Discord.Net.Providers.WS4Net/Discord.Net.Providers.WS4Net.csproj @@ -3,8 +3,9 @@ Discord.Net.Providers.WS4Net Discord.Providers.WS4Net - An optional WebSocket client provider for Discord.Net using WebSocket4Net + An optional WebSocket client provider for Discord.Net Labs using WebSocket4Net netstandard2.0 + Discord.Net.Labs.Providers.WS4Net diff --git a/src/Discord.Net.Rest/API/Common/ActionRowComponent.cs b/src/Discord.Net.Rest/API/Common/ActionRowComponent.cs index 92778849c..9dede7e03 100644 --- a/src/Discord.Net.Rest/API/Common/ActionRowComponent.cs +++ b/src/Discord.Net.Rest/API/Common/ActionRowComponent.cs @@ -1,10 +1,5 @@ using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using System; -using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Discord.API { @@ -26,7 +21,7 @@ namespace Discord.API { ComponentType.Button => new ButtonComponent(x as Discord.ButtonComponent), ComponentType.SelectMenu => new SelectMenuComponent(x as Discord.SelectMenuComponent), - _ => null, + _ => null }; }).ToArray(); } diff --git a/src/Discord.Net.Rest/API/Common/ApplicationCommand.cs b/src/Discord.Net.Rest/API/Common/ApplicationCommand.cs index 9de272706..81598b96e 100644 --- a/src/Discord.Net.Rest/API/Common/ApplicationCommand.cs +++ b/src/Discord.Net.Rest/API/Common/ApplicationCommand.cs @@ -1,9 +1,4 @@ using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Discord.API { diff --git a/src/Discord.Net.Rest/API/Common/ApplicationCommandInteractionData.cs b/src/Discord.Net.Rest/API/Common/ApplicationCommandInteractionData.cs index bb395df13..a98ed77d6 100644 --- a/src/Discord.Net.Rest/API/Common/ApplicationCommandInteractionData.cs +++ b/src/Discord.Net.Rest/API/Common/ApplicationCommandInteractionData.cs @@ -1,5 +1,4 @@ using Newtonsoft.Json; -using System.Collections.Generic; namespace Discord.API { @@ -19,6 +18,5 @@ namespace Discord.API [JsonProperty("type")] public ApplicationCommandType Type { get; set; } - } } diff --git a/src/Discord.Net.Rest/API/Common/ApplicationCommandInteractionDataOption.cs b/src/Discord.Net.Rest/API/Common/ApplicationCommandInteractionDataOption.cs index 5f75c901f..1e488c4e6 100644 --- a/src/Discord.Net.Rest/API/Common/ApplicationCommandInteractionDataOption.cs +++ b/src/Discord.Net.Rest/API/Common/ApplicationCommandInteractionDataOption.cs @@ -1,5 +1,4 @@ using Newtonsoft.Json; -using System.Collections.Generic; namespace Discord.API { diff --git a/src/Discord.Net.Rest/API/Common/ApplicationCommandOption.cs b/src/Discord.Net.Rest/API/Common/ApplicationCommandOption.cs index c7b304c66..b437d0ebf 100644 --- a/src/Discord.Net.Rest/API/Common/ApplicationCommandOption.cs +++ b/src/Discord.Net.Rest/API/Common/ApplicationCommandOption.cs @@ -1,9 +1,5 @@ using Newtonsoft.Json; -using System; -using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Discord.API { @@ -33,11 +29,14 @@ namespace Discord.API [JsonProperty("autocomplete")] public Optional Autocomplete { get; set; } + [JsonProperty("channel_types")] + public Optional ChannelTypes { get; set; } + public ApplicationCommandOption() { } public ApplicationCommandOption(IApplicationCommandOption cmd) { - Choices = cmd.Choices.Select(x => new ApplicationCommandOptionChoice() + Choices = cmd.Choices.Select(x => new ApplicationCommandOptionChoice { Name = x.Name, Value = x.Value @@ -45,38 +44,30 @@ namespace Discord.API Options = cmd.Options.Select(x => new ApplicationCommandOption(x)).ToArray(); - Required = cmd.IsRequired.HasValue - ? cmd.IsRequired.Value - : Optional.Unspecified; - Default = cmd.IsDefault.HasValue - ? cmd.IsDefault.Value - : Optional.Unspecified; + ChannelTypes = cmd.ChannelTypes.ToArray(); + + Required = cmd.IsRequired ?? Optional.Unspecified; + Default = cmd.IsDefault ?? Optional.Unspecified; Name = cmd.Name; Type = cmd.Type; Description = cmd.Description; } - public ApplicationCommandOption(Discord.ApplicationCommandOptionProperties option) + public ApplicationCommandOption(ApplicationCommandOptionProperties option) { - Choices = option.Choices != null - ? option.Choices.Select(x => new ApplicationCommandOptionChoice() - { - Name = x.Name, - Value = x.Value - }).ToArray() - : Optional.Unspecified; - - Options = option.Options != null - ? option.Options.Select(x => new ApplicationCommandOption(x)).ToArray() - : Optional.Unspecified; - - Required = option.IsRequired.HasValue - ? option.IsRequired.Value - : Optional.Unspecified; - - Default = option.IsDefault.HasValue - ? option.IsDefault.Value - : Optional.Unspecified; + Choices = option.Choices?.Select(x => new ApplicationCommandOptionChoice + { + Name = x.Name, + Value = x.Value + }).ToArray() ?? Optional.Unspecified; + + Options = option.Options?.Select(x => new ApplicationCommandOption(x)).ToArray() ?? Optional.Unspecified; + + Required = option.IsRequired ?? Optional.Unspecified; + + Default = option.IsDefault ?? Optional.Unspecified; + + ChannelTypes = option.ChannelTypes?.ToArray() ?? Optional.Unspecified; Name = option.Name; Type = option.Type; diff --git a/src/Discord.Net.Rest/API/Common/ApplicationCommandOptionChoice.cs b/src/Discord.Net.Rest/API/Common/ApplicationCommandOptionChoice.cs index b847fceba..6f84437f6 100644 --- a/src/Discord.Net.Rest/API/Common/ApplicationCommandOptionChoice.cs +++ b/src/Discord.Net.Rest/API/Common/ApplicationCommandOptionChoice.cs @@ -1,9 +1,4 @@ using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Discord.API { diff --git a/src/Discord.Net.Rest/API/Common/ApplicationCommandPermissions.cs b/src/Discord.Net.Rest/API/Common/ApplicationCommandPermissions.cs index 281ded90f..8bde80f50 100644 --- a/src/Discord.Net.Rest/API/Common/ApplicationCommandPermissions.cs +++ b/src/Discord.Net.Rest/API/Common/ApplicationCommandPermissions.cs @@ -1,9 +1,4 @@ using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Discord.API { diff --git a/src/Discord.Net.Rest/API/Common/Attachment.cs b/src/Discord.Net.Rest/API/Common/Attachment.cs index 3ac445f8c..0d98fc3a4 100644 --- a/src/Discord.Net.Rest/API/Common/Attachment.cs +++ b/src/Discord.Net.Rest/API/Common/Attachment.cs @@ -18,5 +18,7 @@ namespace Discord.API public Optional Height { get; set; } [JsonProperty("width")] public Optional Width { get; set; } + [JsonProperty("ephemeral")] + public Optional Ephemeral { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/AuditLog.cs b/src/Discord.Net.Rest/API/Common/AuditLog.cs index cd8ad147d..c8bd6d3e2 100644 --- a/src/Discord.Net.Rest/API/Common/AuditLog.cs +++ b/src/Discord.Net.Rest/API/Common/AuditLog.cs @@ -1,4 +1,4 @@ -using Newtonsoft.Json; +using Newtonsoft.Json; namespace Discord.API { @@ -7,6 +7,12 @@ namespace Discord.API [JsonProperty("webhooks")] public Webhook[] Webhooks { get; set; } + [JsonProperty("threads")] + public Channel[] Threads { get; set; } + + [JsonProperty("integrations")] + public Integration[] Integrations { get; set; } + [JsonProperty("users")] public User[] Users { get; set; } diff --git a/src/Discord.Net.Rest/API/Common/AuditLogEntry.cs b/src/Discord.Net.Rest/API/Common/AuditLogEntry.cs index 7458a19cb..9626ad67e 100644 --- a/src/Discord.Net.Rest/API/Common/AuditLogEntry.cs +++ b/src/Discord.Net.Rest/API/Common/AuditLogEntry.cs @@ -1,4 +1,4 @@ -using Newtonsoft.Json; +using Newtonsoft.Json; namespace Discord.API { diff --git a/src/Discord.Net.Rest/API/Common/AutocompleteInteractionData.cs b/src/Discord.Net.Rest/API/Common/AutocompleteInteractionData.cs index ea2cd4fd9..2184a0e98 100644 --- a/src/Discord.Net.Rest/API/Common/AutocompleteInteractionData.cs +++ b/src/Discord.Net.Rest/API/Common/AutocompleteInteractionData.cs @@ -1,9 +1,4 @@ using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Discord.API { diff --git a/src/Discord.Net.Rest/API/Common/AutocompleteInteractionDataOption.cs b/src/Discord.Net.Rest/API/Common/AutocompleteInteractionDataOption.cs index a9d5b66f0..1419f93b6 100644 --- a/src/Discord.Net.Rest/API/Common/AutocompleteInteractionDataOption.cs +++ b/src/Discord.Net.Rest/API/Common/AutocompleteInteractionDataOption.cs @@ -1,9 +1,4 @@ using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Discord.API { @@ -15,10 +10,13 @@ namespace Discord.API [JsonProperty("name")] public string Name { get; set; } + [JsonProperty("options")] + public Optional Options { get; set; } + [JsonProperty("value")] - public object Value { get; set; } + public Optional Value { get; set; } [JsonProperty("focused")] - public bool Focused { get; set; } + public Optional Focused { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/ButtonComponent.cs b/src/Discord.Net.Rest/API/Common/ButtonComponent.cs index 887040006..7f737d7ad 100644 --- a/src/Discord.Net.Rest/API/Common/ButtonComponent.cs +++ b/src/Discord.Net.Rest/API/Common/ButtonComponent.cs @@ -1,9 +1,4 @@ using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Discord.API { @@ -45,16 +40,16 @@ namespace Discord.API { if (c.Emote is Emote e) { - Emote = new Emoji() + Emote = new Emoji { Name = e.Name, Animated = e.Animated, - Id = e.Id, + Id = e.Id }; } else { - Emote = new Emoji() + Emote = new Emoji { Name = c.Emote.Name }; diff --git a/src/Discord.Net.Rest/API/Common/ChannelThreads.cs b/src/Discord.Net.Rest/API/Common/ChannelThreads.cs index eccac8e54..94b2396bf 100644 --- a/src/Discord.Net.Rest/API/Common/ChannelThreads.cs +++ b/src/Discord.Net.Rest/API/Common/ChannelThreads.cs @@ -1,9 +1,4 @@ using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Discord.API.Rest { diff --git a/src/Discord.Net.Rest/API/Common/GuildApplicationCommandPermissions.cs b/src/Discord.Net.Rest/API/Common/GuildApplicationCommandPermissions.cs index 3ab5ef650..cc74299f7 100644 --- a/src/Discord.Net.Rest/API/Common/GuildApplicationCommandPermissions.cs +++ b/src/Discord.Net.Rest/API/Common/GuildApplicationCommandPermissions.cs @@ -1,9 +1,4 @@ using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Discord.API { @@ -19,6 +14,6 @@ namespace Discord.API public ulong GuildId { get; set; } [JsonProperty("permissions")] - public API.ApplicationCommandPermissions[] Permissions { get; set; } + public ApplicationCommandPermissions[] Permissions { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/Interaction.cs b/src/Discord.Net.Rest/API/Common/Interaction.cs index ebbc5fc1b..7f953384d 100644 --- a/src/Discord.Net.Rest/API/Common/Interaction.cs +++ b/src/Discord.Net.Rest/API/Common/Interaction.cs @@ -1,9 +1,4 @@ using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Discord.API { diff --git a/src/Discord.Net.Rest/API/Common/InteractionCallbackData.cs b/src/Discord.Net.Rest/API/Common/InteractionCallbackData.cs index 38b35c8ec..b07ebff49 100644 --- a/src/Discord.Net.Rest/API/Common/InteractionCallbackData.cs +++ b/src/Discord.Net.Rest/API/Common/InteractionCallbackData.cs @@ -11,7 +11,7 @@ namespace Discord.API public Optional Content { get; set; } [JsonProperty("embeds")] - public Optional Embeds { get; set; } + public Optional Embeds { get; set; } [JsonProperty("allowed_mentions")] public Optional AllowedMentions { get; set; } @@ -20,9 +20,9 @@ namespace Discord.API public Optional Flags { get; set; } [JsonProperty("components")] - public Optional Components { get; set; } + public Optional Components { get; set; } [JsonProperty("choices")] - public Optional Choices { get; set; } + public Optional Choices { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/InteractionResponse.cs b/src/Discord.Net.Rest/API/Common/InteractionResponse.cs index e50e8076e..93d4cd307 100644 --- a/src/Discord.Net.Rest/API/Common/InteractionResponse.cs +++ b/src/Discord.Net.Rest/API/Common/InteractionResponse.cs @@ -1,9 +1,4 @@ using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Discord.API { diff --git a/src/Discord.Net.Rest/API/Common/Message.cs b/src/Discord.Net.Rest/API/Common/Message.cs index 3b9e5379a..d33a03fe5 100644 --- a/src/Discord.Net.Rest/API/Common/Message.cs +++ b/src/Discord.Net.Rest/API/Common/Message.cs @@ -32,7 +32,7 @@ namespace Discord.API [JsonProperty("mention_everyone")] public Optional MentionEveryone { get; set; } [JsonProperty("mentions")] - public Optional[]> UserMentions { get; set; } + public Optional UserMentions { get; set; } [JsonProperty("mention_roles")] public Optional RoleMentions { get; set; } [JsonProperty("attachments")] @@ -59,6 +59,7 @@ namespace Discord.API public Optional ReferencedMessage { get; set; } [JsonProperty("components")] public Optional Components { get; set; } + public Optional Interaction { get; set; } [JsonProperty("sticker_items")] public Optional StickerItems { get; set; } } diff --git a/src/Discord.Net.Rest/API/Common/MessageComponentInteractionData.cs b/src/Discord.Net.Rest/API/Common/MessageComponentInteractionData.cs index 5dc81e61e..a7760911c 100644 --- a/src/Discord.Net.Rest/API/Common/MessageComponentInteractionData.cs +++ b/src/Discord.Net.Rest/API/Common/MessageComponentInteractionData.cs @@ -1,9 +1,4 @@ using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Discord.API { diff --git a/src/Discord.Net.Rest/API/Common/MessageInteraction.cs b/src/Discord.Net.Rest/API/Common/MessageInteraction.cs new file mode 100644 index 000000000..48f278396 --- /dev/null +++ b/src/Discord.Net.Rest/API/Common/MessageInteraction.cs @@ -0,0 +1,21 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord.API +{ + internal class MessageInteraction + { + [JsonProperty("id")] + public ulong Id { get; set; } + [JsonProperty("type")] + public InteractionType Type { get; set; } + [JsonProperty("name")] + public string Name { get; set; } + [JsonProperty("user")] + public User User { get; set; } + } +} diff --git a/src/Discord.Net.Rest/API/Common/NitroStickerPacks.cs b/src/Discord.Net.Rest/API/Common/NitroStickerPacks.cs index ddb9b0bc5..cc2f0d963 100644 --- a/src/Discord.Net.Rest/API/Common/NitroStickerPacks.cs +++ b/src/Discord.Net.Rest/API/Common/NitroStickerPacks.cs @@ -1,9 +1,5 @@ using Newtonsoft.Json; -using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Discord.API { diff --git a/src/Discord.Net.Rest/API/Common/Role.cs b/src/Discord.Net.Rest/API/Common/Role.cs index 4c33956af..09fb99885 100644 --- a/src/Discord.Net.Rest/API/Common/Role.cs +++ b/src/Discord.Net.Rest/API/Common/Role.cs @@ -8,6 +8,8 @@ namespace Discord.API public ulong Id { get; set; } [JsonProperty("name")] public string Name { get; set; } + [JsonProperty("icon")] + public string Icon { get; set; } [JsonProperty("color")] public uint Color { get; set; } [JsonProperty("hoist")] diff --git a/src/Discord.Net.Rest/API/Common/SelectMenuComponent.cs b/src/Discord.Net.Rest/API/Common/SelectMenuComponent.cs index 16daa66ab..0886a8fe9 100644 --- a/src/Discord.Net.Rest/API/Common/SelectMenuComponent.cs +++ b/src/Discord.Net.Rest/API/Common/SelectMenuComponent.cs @@ -1,9 +1,5 @@ using Newtonsoft.Json; -using System; -using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Discord.API { diff --git a/src/Discord.Net.Rest/API/Common/SelectMenuOption.cs b/src/Discord.Net.Rest/API/Common/SelectMenuOption.cs index 06c8f7dc3..d0a25a829 100644 --- a/src/Discord.Net.Rest/API/Common/SelectMenuOption.cs +++ b/src/Discord.Net.Rest/API/Common/SelectMenuOption.cs @@ -1,9 +1,4 @@ using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Discord.API { @@ -36,23 +31,23 @@ namespace Discord.API { if (option.Emote is Emote e) { - Emoji = new Emoji() + Emoji = new Emoji { Name = e.Name, Animated = e.Animated, - Id = e.Id, + Id = e.Id }; } else { - Emoji = new Emoji() + Emoji = new Emoji { Name = option.Emote.Name }; } } - Default = option.IsDefault.HasValue ? option.IsDefault.Value : Optional.Unspecified; + Default = option.IsDefault ?? Optional.Unspecified; } } } diff --git a/src/Discord.Net.Rest/API/Common/StageInstance.cs b/src/Discord.Net.Rest/API/Common/StageInstance.cs index 4cb5f5823..3ec623949 100644 --- a/src/Discord.Net.Rest/API/Common/StageInstance.cs +++ b/src/Discord.Net.Rest/API/Common/StageInstance.cs @@ -1,9 +1,4 @@ using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Discord.API { diff --git a/src/Discord.Net.Rest/API/Common/Sticker.cs b/src/Discord.Net.Rest/API/Common/Sticker.cs index 45e6d18b3..b2c58d57c 100644 --- a/src/Discord.Net.Rest/API/Common/Sticker.cs +++ b/src/Discord.Net.Rest/API/Common/Sticker.cs @@ -11,7 +11,7 @@ namespace Discord.API [JsonProperty("name")] public string Name { get; set; } [JsonProperty("description")] - public string Desription { get; set; } + public string Description { get; set; } [JsonProperty("tags")] public Optional Tags { get; set; } [JsonProperty("type")] diff --git a/src/Discord.Net.Rest/API/Common/StickerItem.cs b/src/Discord.Net.Rest/API/Common/StickerItem.cs index 9ec0fb503..4b24f711b 100644 --- a/src/Discord.Net.Rest/API/Common/StickerItem.cs +++ b/src/Discord.Net.Rest/API/Common/StickerItem.cs @@ -1,9 +1,4 @@ using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Discord.API { diff --git a/src/Discord.Net.Rest/API/Common/StickerPack.cs b/src/Discord.Net.Rest/API/Common/StickerPack.cs index aa3314d7c..3daaac5bf 100644 --- a/src/Discord.Net.Rest/API/Common/StickerPack.cs +++ b/src/Discord.Net.Rest/API/Common/StickerPack.cs @@ -1,9 +1,4 @@ using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Discord.API { diff --git a/src/Discord.Net.Rest/API/Common/ThreadMember.cs b/src/Discord.Net.Rest/API/Common/ThreadMember.cs index d8ce15e92..3e30d2c95 100644 --- a/src/Discord.Net.Rest/API/Common/ThreadMember.cs +++ b/src/Discord.Net.Rest/API/Common/ThreadMember.cs @@ -1,9 +1,5 @@ using Newtonsoft.Json; using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Discord.API { @@ -18,7 +14,7 @@ namespace Discord.API [JsonProperty("join_timestamp")] public DateTimeOffset JoinTimestamp { get; set; } - [JsonProperty("presense")] + [JsonProperty("presence")] public Optional Presence { get; set; } [JsonProperty("member")] diff --git a/src/Discord.Net.Rest/API/Common/ThreadMetadata.cs b/src/Discord.Net.Rest/API/Common/ThreadMetadata.cs index 0bc068c1c..39e9bd13e 100644 --- a/src/Discord.Net.Rest/API/Common/ThreadMetadata.cs +++ b/src/Discord.Net.Rest/API/Common/ThreadMetadata.cs @@ -1,9 +1,5 @@ using Newtonsoft.Json; using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Discord.API { diff --git a/src/Discord.Net.Rest/API/Rest/CreateApplicationCommandParams.cs b/src/Discord.Net.Rest/API/Rest/CreateApplicationCommandParams.cs index 7fe8b10ad..82f0befcd 100644 --- a/src/Discord.Net.Rest/API/Rest/CreateApplicationCommandParams.cs +++ b/src/Discord.Net.Rest/API/Rest/CreateApplicationCommandParams.cs @@ -1,10 +1,4 @@ -using Discord.API; using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Discord.API.Rest { @@ -30,7 +24,7 @@ namespace Discord.API.Rest { Name = name; Description = description; - Options = Optional.Create(options); + Options = Optional.Create(options); Type = type; } } diff --git a/src/Discord.Net.Rest/API/Rest/CreateStageInstanceParams.cs b/src/Discord.Net.Rest/API/Rest/CreateStageInstanceParams.cs index 294f9e1a5..a1d59bb51 100644 --- a/src/Discord.Net.Rest/API/Rest/CreateStageInstanceParams.cs +++ b/src/Discord.Net.Rest/API/Rest/CreateStageInstanceParams.cs @@ -1,9 +1,4 @@ using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Discord.API.Rest { diff --git a/src/Discord.Net.Rest/API/Rest/CreateStickerParams.cs b/src/Discord.Net.Rest/API/Rest/CreateStickerParams.cs index 225caaaae..b330a0111 100644 --- a/src/Discord.Net.Rest/API/Rest/CreateStickerParams.cs +++ b/src/Discord.Net.Rest/API/Rest/CreateStickerParams.cs @@ -1,12 +1,6 @@ using Discord.Net.Rest; -using Newtonsoft.Json; -using System; using System.Collections.Generic; using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - namespace Discord.API.Rest { internal class CreateStickerParams @@ -30,7 +24,7 @@ namespace Discord.API.Rest if (File is FileStream fileStream) contentType = $"image/{Path.GetExtension(fileStream.Name)}"; - else if(FileName != null) + else if (FileName != null) contentType = $"image/{Path.GetExtension(FileName)}"; d["file"] = new MultipartFile(File, FileName ?? "image", contentType.Replace(".", "")); diff --git a/src/Discord.Net.Rest/API/Rest/ModifyApplicationCommandParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyApplicationCommandParams.cs index 2ed9466c0..5891c2c28 100644 --- a/src/Discord.Net.Rest/API/Rest/ModifyApplicationCommandParams.cs +++ b/src/Discord.Net.Rest/API/Rest/ModifyApplicationCommandParams.cs @@ -1,9 +1,4 @@ using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Discord.API.Rest { diff --git a/src/Discord.Net.Rest/API/Rest/ModifyGuildApplicationCommandPermissions.cs b/src/Discord.Net.Rest/API/Rest/ModifyGuildApplicationCommandPermissions.cs index 5e42ee4c4..a557061f3 100644 --- a/src/Discord.Net.Rest/API/Rest/ModifyGuildApplicationCommandPermissions.cs +++ b/src/Discord.Net.Rest/API/Rest/ModifyGuildApplicationCommandPermissions.cs @@ -1,9 +1,4 @@ using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Discord.API.Rest { diff --git a/src/Discord.Net.Rest/API/Rest/ModifyGuildApplicationCommandPermissionsParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyGuildApplicationCommandPermissionsParams.cs index af8ee95d4..322875b8e 100644 --- a/src/Discord.Net.Rest/API/Rest/ModifyGuildApplicationCommandPermissionsParams.cs +++ b/src/Discord.Net.Rest/API/Rest/ModifyGuildApplicationCommandPermissionsParams.cs @@ -1,9 +1,4 @@ using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Discord.API.Rest { diff --git a/src/Discord.Net.Rest/API/Rest/ModifyInteractionResponseParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyInteractionResponseParams.cs index b2800a066..a2c7cbee6 100644 --- a/src/Discord.Net.Rest/API/Rest/ModifyInteractionResponseParams.cs +++ b/src/Discord.Net.Rest/API/Rest/ModifyInteractionResponseParams.cs @@ -1,9 +1,4 @@ using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Discord.API.Rest { @@ -19,7 +14,7 @@ namespace Discord.API.Rest public Optional AllowedMentions { get; set; } [JsonProperty("components")] - public Optional Components { get; set; } + public Optional Components { get; set; } [JsonProperty("flags")] public Optional Flags { get; set; } diff --git a/src/Discord.Net.Rest/API/Rest/ModifyStageInstanceParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyStageInstanceParams.cs index df73954de..c09d8f216 100644 --- a/src/Discord.Net.Rest/API/Rest/ModifyStageInstanceParams.cs +++ b/src/Discord.Net.Rest/API/Rest/ModifyStageInstanceParams.cs @@ -1,15 +1,9 @@ using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Discord.API.Rest { internal class ModifyStageInstanceParams { - [JsonProperty("topic")] public Optional Topic { get; set; } diff --git a/src/Discord.Net.Rest/API/Rest/ModifyStickerParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyStickerParams.cs index 47331b5a0..bd538c72e 100644 --- a/src/Discord.Net.Rest/API/Rest/ModifyStickerParams.cs +++ b/src/Discord.Net.Rest/API/Rest/ModifyStickerParams.cs @@ -1,9 +1,4 @@ using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Discord.API.Rest { diff --git a/src/Discord.Net.Rest/API/Rest/ModifyThreadParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyThreadParams.cs index c62b3bfbb..8c9216c3f 100644 --- a/src/Discord.Net.Rest/API/Rest/ModifyThreadParams.cs +++ b/src/Discord.Net.Rest/API/Rest/ModifyThreadParams.cs @@ -1,9 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Discord; using Newtonsoft.Json; namespace Discord.API.Rest diff --git a/src/Discord.Net.Rest/API/Rest/StartThreadParams.cs b/src/Discord.Net.Rest/API/Rest/StartThreadParams.cs index ed3701cad..7810557eb 100644 --- a/src/Discord.Net.Rest/API/Rest/StartThreadParams.cs +++ b/src/Discord.Net.Rest/API/Rest/StartThreadParams.cs @@ -1,9 +1,4 @@ using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Discord.API.Rest { diff --git a/src/Discord.Net.Rest/ClientHelper.cs b/src/Discord.Net.Rest/ClientHelper.cs index 4581219bb..5debea27e 100644 --- a/src/Discord.Net.Rest/ClientHelper.cs +++ b/src/Discord.Net.Rest/ClientHelper.cs @@ -194,17 +194,17 @@ namespace Discord.Rest }; } - public static async Task> GetGlobalApplicationCommands(BaseDiscordClient client, + public static async Task> GetGlobalApplicationCommandsAsync(BaseDiscordClient client, RequestOptions options = null) { var response = await client.ApiClient.GetGlobalApplicationCommandsAsync(options).ConfigureAwait(false); if (!response.Any()) - return new RestGlobalCommand[0]; + return Array.Empty(); return response.Select(x => RestGlobalCommand.Create(client, x)).ToArray(); } - public static async Task GetGlobalApplicationCommand(BaseDiscordClient client, ulong id, + public static async Task GetGlobalApplicationCommandAsync(BaseDiscordClient client, ulong id, RequestOptions options = null) { var model = await client.ApiClient.GetGlobalApplicationCommandAsync(id, options); @@ -212,55 +212,55 @@ namespace Discord.Rest return model != null ? RestGlobalCommand.Create(client, model) : null; } - public static async Task> GetGuildApplicationCommands(BaseDiscordClient client, ulong guildId, + public static async Task> GetGuildApplicationCommandsAsync(BaseDiscordClient client, ulong guildId, RequestOptions options = null) { var response = await client.ApiClient.GetGuildApplicationCommandsAsync(guildId, options).ConfigureAwait(false); if (!response.Any()) - return new RestGuildCommand[0].ToImmutableArray(); + return ImmutableArray.Create(); return response.Select(x => RestGuildCommand.Create(client, x, guildId)).ToImmutableArray(); } - public static async Task GetGuildApplicationCommand(BaseDiscordClient client, ulong id, ulong guildId, + public static async Task GetGuildApplicationCommandAsync(BaseDiscordClient client, ulong id, ulong guildId, RequestOptions options = null) { var model = await client.ApiClient.GetGuildApplicationCommandAsync(guildId, id, options); return model != null ? RestGuildCommand.Create(client, model, guildId) : null; } - public static async Task CreateGuildApplicationCommand(BaseDiscordClient client, ulong guildId, ApplicationCommandProperties properties, + public static async Task CreateGuildApplicationCommandAsync(BaseDiscordClient client, ulong guildId, ApplicationCommandProperties properties, RequestOptions options = null) { - var model = await InteractionHelper.CreateGuildCommand(client, guildId, properties, options); + var model = await InteractionHelper.CreateGuildCommandAsync(client, guildId, properties, options); return RestGuildCommand.Create(client, model, guildId); } - public static async Task CreateGlobalApplicationCommand(BaseDiscordClient client, ApplicationCommandProperties properties, + public static async Task CreateGlobalApplicationCommandAsync(BaseDiscordClient client, ApplicationCommandProperties properties, RequestOptions options = null) { - var model = await InteractionHelper.CreateGlobalCommand(client, properties, options); + var model = await InteractionHelper.CreateGlobalCommandAsync(client, properties, options); return RestGlobalCommand.Create(client, model); } - public static async Task> BulkOverwriteGlobalApplicationCommand(BaseDiscordClient client, ApplicationCommandProperties[] properties, + public static async Task> BulkOverwriteGlobalApplicationCommandAsync(BaseDiscordClient client, ApplicationCommandProperties[] properties, RequestOptions options = null) { - var models = await InteractionHelper.BulkOverwriteGlobalCommands(client, properties, options); + var models = await InteractionHelper.BulkOverwriteGlobalCommandsAsync(client, properties, options); return models.Select(x => RestGlobalCommand.Create(client, x)).ToImmutableArray(); } - public static async Task> BulkOverwriteGuildApplicationCommand(BaseDiscordClient client, ulong guildId, + public static async Task> BulkOverwriteGuildApplicationCommandAsync(BaseDiscordClient client, ulong guildId, ApplicationCommandProperties[] properties, RequestOptions options = null) { - var models = await InteractionHelper.BulkOverwriteGuildCommands(client, guildId, properties, options); + var models = await InteractionHelper.BulkOverwriteGuildCommandsAsync(client, guildId, properties, options); return models.Select(x => RestGuildCommand.Create(client, x, guildId)).ToImmutableArray(); } public static Task AddRoleAsync(BaseDiscordClient client, ulong guildId, ulong userId, ulong roleId, RequestOptions options = null) => client.ApiClient.AddRoleAsync(guildId, userId, roleId, options); - + public static Task RemoveRoleAsync(BaseDiscordClient client, ulong guildId, ulong userId, ulong roleId, RequestOptions options = null) => client.ApiClient.RemoveRoleAsync(guildId, userId, roleId, options); #endregion diff --git a/src/Discord.Net.Rest/Discord.Net.Rest.csproj b/src/Discord.Net.Rest/Discord.Net.Rest.csproj index 3ed886616..8407abfd6 100644 --- a/src/Discord.Net.Rest/Discord.Net.Rest.csproj +++ b/src/Discord.Net.Rest/Discord.Net.Rest.csproj @@ -1,4 +1,4 @@ - + @@ -14,4 +14,4 @@ - \ No newline at end of file + diff --git a/src/Discord.Net.Rest/DiscordRestApiClient.cs b/src/Discord.Net.Rest/DiscordRestApiClient.cs index f986aecde..6979d7030 100644 --- a/src/Discord.Net.Rest/DiscordRestApiClient.cs +++ b/src/Discord.Net.Rest/DiscordRestApiClient.cs @@ -320,6 +320,7 @@ namespace Discord.API var model = await SendAsync("GET", () => $"channels/{channelId}", ids, options: options).ConfigureAwait(false); if (!model.GuildId.IsSpecified || model.GuildId.Value != guildId) return null; + return model; } catch (HttpException ex) when (ex.HttpCode == HttpStatusCode.NotFound) { return null; } @@ -338,11 +339,16 @@ namespace Discord.API Preconditions.NotNull(args, nameof(args)); Preconditions.GreaterThan(args.Bitrate, 0, nameof(args.Bitrate)); Preconditions.NotNullOrWhitespace(args.Name, nameof(args.Name)); + Preconditions.LessThan(args.Name.Length, 100, nameof(args.Name)); + if (args.Topic.IsSpecified) + Preconditions.LessThan(args.Topic.Value.Length, 1024, nameof(args.Name)); + options = RequestOptions.CreateOrClone(options); var ids = new BucketIds(guildId: guildId); return await SendJsonAsync("POST", () => $"guilds/{guildId}/channels", args, ids, options: options).ConfigureAwait(false); } + public async Task DeleteChannelAsync(ulong channelId, RequestOptions options = null) { Preconditions.NotEqual(channelId, 0, nameof(channelId)); @@ -366,18 +372,27 @@ namespace Discord.API Preconditions.NotEqual(channelId, 0, nameof(channelId)); Preconditions.NotNull(args, nameof(args)); Preconditions.AtLeast(args.Position, 0, nameof(args.Position)); - Preconditions.NotNullOrEmpty(args.Name, nameof(args.Name)); + Preconditions.NotNullOrWhitespace(args.Name, nameof(args.Name)); + Preconditions.LessThan(args.Name.Value.Length, 100, nameof(args.Name)); + options = RequestOptions.CreateOrClone(options); var ids = new BucketIds(channelId: channelId); return await SendJsonAsync("PATCH", () => $"channels/{channelId}", args, ids, options: options).ConfigureAwait(false); } + public async Task ModifyGuildChannelAsync(ulong channelId, Rest.ModifyTextChannelParams args, RequestOptions options = null) { Preconditions.NotEqual(channelId, 0, nameof(channelId)); Preconditions.NotNull(args, nameof(args)); Preconditions.AtLeast(args.Position, 0, nameof(args.Position)); - Preconditions.NotNullOrEmpty(args.Name, nameof(args.Name)); + Preconditions.NotNullOrWhitespace(args.Name, nameof(args.Name)); + + if(args.Name.IsSpecified) + Preconditions.LessThan(args.Name.Value.Length, 100, nameof(args.Name)); + if(args.Topic.IsSpecified) + Preconditions.LessThan(args.Topic.Value.Length, 1024, nameof(args.Name)); + Preconditions.AtLeast(args.SlowModeInterval, 0, nameof(args.SlowModeInterval)); Preconditions.AtMost(args.SlowModeInterval, 21600, nameof(args.SlowModeInterval)); options = RequestOptions.CreateOrClone(options); @@ -385,6 +400,7 @@ namespace Discord.API var ids = new BucketIds(channelId: channelId); return await SendJsonAsync("PATCH", () => $"channels/{channelId}", args, ids, options: options).ConfigureAwait(false); } + public async Task ModifyGuildChannelAsync(ulong channelId, Rest.ModifyVoiceChannelParams args, RequestOptions options = null) { Preconditions.NotEqual(channelId, 0, nameof(channelId)); @@ -392,12 +408,13 @@ namespace Discord.API Preconditions.AtLeast(args.Bitrate, 8000, nameof(args.Bitrate)); Preconditions.AtLeast(args.UserLimit, 0, nameof(args.UserLimit)); Preconditions.AtLeast(args.Position, 0, nameof(args.Position)); - Preconditions.NotNullOrEmpty(args.Name, nameof(args.Name)); + Preconditions.NotNullOrWhitespace(args.Name, nameof(args.Name)); options = RequestOptions.CreateOrClone(options); var ids = new BucketIds(channelId: channelId); return await SendJsonAsync("PATCH", () => $"channels/{channelId}", args, ids, options: options).ConfigureAwait(false); } + public async Task ModifyGuildChannelsAsync(ulong guildId, IEnumerable args, RequestOptions options = null) { Preconditions.NotEqual(guildId, 0, nameof(guildId)); @@ -441,7 +458,7 @@ namespace Discord.API return await SendJsonAsync("POST", () => $"channels/{channelId}/messages/{messageId}/threads", args, bucket, options: options).ConfigureAwait(false); } - + public async Task StartThreadAsync(ulong channelId, StartThreadParams args, RequestOptions options = null) { Preconditions.NotEqual(channelId, 0, nameof(channelId)); @@ -517,7 +534,7 @@ namespace Discord.API var bucket = new BucketIds(channelId: channelId); - return await SendAsync("GET", () => $"channels/{channelId}/threads/active", bucket); + return await SendAsync("GET", () => $"channels/{channelId}/threads/active", bucket, options: options); } public async Task GetPublicArchivedThreadsAsync(ulong channelId, DateTimeOffset? before = null, int? limit = null, RequestOptions options = null) @@ -592,7 +609,6 @@ namespace Discord.API #region Stage public async Task CreateStageInstanceAsync(CreateStageInstanceParams args, RequestOptions options = null) { - options = RequestOptions.CreateOrClone(options); var bucket = new BucketIds(); @@ -636,7 +652,7 @@ namespace Discord.API { return await SendAsync("POST", () => $"stage-instances/{channelId}", bucket, options: options).ConfigureAwait(false); } - catch(HttpException httpEx) when (httpEx.HttpCode == HttpStatusCode.NotFound) + catch (HttpException httpEx) when (httpEx.HttpCode == HttpStatusCode.NotFound) { return null; } @@ -1137,7 +1153,7 @@ namespace Discord.API { return await SendAsync("GET", () => $"applications/{CurrentUserId}/commands/{id}", new BucketIds(), options: options).ConfigureAwait(false); } - catch(HttpException x) when (x.HttpCode == HttpStatusCode.NotFound) { return null; } + catch (HttpException x) when (x.HttpCode == HttpStatusCode.NotFound) { return null; } } public async Task CreateGlobalApplicationCommandAsync(CreateApplicationCommandParams command, RequestOptions options = null) @@ -1208,7 +1224,7 @@ namespace Discord.API { return await SendAsync("GET", () => $"applications/{CurrentUserId}/guilds/{guildId}/commands/{commandId}", bucket, options: options); } - catch(HttpException x) when (x.HttpCode == HttpStatusCode.NotFound) { return null; } + catch (HttpException x) when (x.HttpCode == HttpStatusCode.NotFound) { return null; } } public async Task CreateGuildApplicationCommandAsync(CreateApplicationCommandParams command, ulong guildId, RequestOptions options = null) @@ -1236,7 +1252,7 @@ namespace Discord.API var bucket = new BucketIds(guildId: guildId); - return await TrySendApplicationCommandAsync(SendJsonAsync("PATCH", () => $"applications/{CurrentUserId}/guilds/{guildId}/commands/{commandId}", command, bucket, options: options)).ConfigureAwait(false); + return await TrySendApplicationCommandAsync(SendJsonAsync("PATCH", () => $"applications/{CurrentUserId}/guilds/{guildId}/commands/{commandId}", command, bucket, options: options)).ConfigureAwait(false); } public async Task DeleteGuildApplicationCommandAsync(ulong guildId, ulong commandId, RequestOptions options = null) { @@ -1260,7 +1276,7 @@ namespace Discord.API #region Interaction Responses public async Task CreateInteractionResponseAsync(InteractionResponse response, ulong interactionId, string interactionToken, RequestOptions options = null) { - if(response.Data.IsSpecified && response.Data.Value.Content.IsSpecified) + if (response.Data.IsSpecified && response.Data.Value.Content.IsSpecified) Preconditions.AtMost(response.Data.Value.Content.Value?.Length ?? 0, 2000, nameof(response.Data.Value.Content)); options = RequestOptions.CreateOrClone(options); @@ -1309,7 +1325,7 @@ namespace Discord.API Preconditions.NotNull(args, nameof(args)); Preconditions.NotEqual(id, 0, nameof(id)); - if(args.Content.IsSpecified) + if (args.Content.IsSpecified) if (args.Content.Value.Length > DiscordConfig.MaxMessageSize) throw new ArgumentException(message: $"Message content is too long, length must be less or equal to {DiscordConfig.MaxMessageSize}.", paramName: nameof(args.Content)); @@ -2102,7 +2118,7 @@ namespace Discord.API else return result; } - catch(HttpException x) + catch (HttpException x) { if (x.HttpCode == HttpStatusCode.BadRequest) { @@ -2214,7 +2230,7 @@ namespace Discord.API Array.Copy(elements, 0, methodArgs, 1, elements.Length); } - int endIndex = format.IndexOf('?'); //Dont include params + int endIndex = format.IndexOf('?'); //Don't include params if (endIndex == -1) endIndex = format.Length; diff --git a/src/Discord.Net.Rest/DiscordRestClient.cs b/src/Discord.Net.Rest/DiscordRestClient.cs index c8f16a1f0..847ba6835 100644 --- a/src/Discord.Net.Rest/DiscordRestClient.cs +++ b/src/Discord.Net.Rest/DiscordRestClient.cs @@ -110,17 +110,17 @@ namespace Discord.Rest => ClientHelper.GetWebhookAsync(this, id, options); public Task CreateGlobalCommand(ApplicationCommandProperties properties, RequestOptions options = null) - => ClientHelper.CreateGlobalApplicationCommand(this, properties, options); + => ClientHelper.CreateGlobalApplicationCommandAsync(this, properties, options); public Task CreateGuildCommand(ApplicationCommandProperties properties, ulong guildId, RequestOptions options = null) - => ClientHelper.CreateGuildApplicationCommand(this, guildId, properties, options); + => ClientHelper.CreateGuildApplicationCommandAsync(this, guildId, properties, options); public Task> GetGlobalApplicationCommands(RequestOptions options = null) - => ClientHelper.GetGlobalApplicationCommands(this, options); + => ClientHelper.GetGlobalApplicationCommandsAsync(this, options); public Task> GetGuildApplicationCommands(ulong guildId, RequestOptions options = null) - => ClientHelper.GetGuildApplicationCommands(this, guildId, options); + => ClientHelper.GetGuildApplicationCommandsAsync(this, guildId, options); public Task> BulkOverwriteGlobalCommands(ApplicationCommandProperties[] commandProperties, RequestOptions options = null) - => ClientHelper.BulkOverwriteGlobalApplicationCommand(this, commandProperties, options); + => ClientHelper.BulkOverwriteGlobalApplicationCommandAsync(this, commandProperties, options); public Task> BulkOverwriteGuildCommands(ApplicationCommandProperties[] commandProperties, ulong guildId, RequestOptions options = null) - => ClientHelper.BulkOverwriteGuildApplicationCommand(this, guildId, commandProperties, options); + => ClientHelper.BulkOverwriteGuildApplicationCommandAsync(this, guildId, commandProperties, options); public Task> BatchEditGuildCommandPermissions(ulong guildId, IDictionary permissions, RequestOptions options = null) => InteractionHelper.BatchEditGuildCommandPermissionsAsync(this, guildId, permissions, options); public Task DeleteAllGlobalCommandsAsync(RequestOptions options = null) @@ -231,7 +231,7 @@ namespace Discord.Rest => await GetGlobalApplicationCommands(options).ConfigureAwait(false); /// async Task IDiscordClient.GetGlobalApplicationCommandAsync(ulong id, RequestOptions options) - => await ClientHelper.GetGlobalApplicationCommand(this, id, options).ConfigureAwait(false); + => await ClientHelper.GetGlobalApplicationCommandAsync(this, id, options).ConfigureAwait(false); #endregion } } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/AuditLogHelper.cs b/src/Discord.Net.Rest/Entities/AuditLogs/AuditLogHelper.cs index 696917203..b3aaf582c 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/AuditLogHelper.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/AuditLogHelper.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using Model = Discord.API.AuditLog; @@ -51,6 +51,7 @@ namespace Discord.Rest [ActionType.MessageBulkDeleted] = MessageBulkDeleteAuditLogData.Create, [ActionType.MessagePinned] = MessagePinAuditLogData.Create, [ActionType.MessageUnpinned] = MessageUnpinAuditLogData.Create, + }; public static IAuditLogData CreateData(BaseDiscordClient discord, Model log, EntryModel entry) diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/StageInfo.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/StageInfo.cs new file mode 100644 index 000000000..3700796e6 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/StageInfo.cs @@ -0,0 +1,30 @@ +namespace Discord.Rest +{ + /// + /// Represents information for a stage. + /// + public class StageInfo + { + /// + /// Gets the topic of the stage channel. + /// + public string Topic { get; } + + /// + /// Gets the privacy level of the stage channel. + /// + public StagePrivacyLevel? PrivacyLevel { get; } + + /// + /// Gets the user who started the stage channel. + /// + public IUser User { get; } + + internal StageInfo(IUser user, StagePrivacyLevel? level, string topic) + { + Topic = topic; + PrivacyLevel = level; + User = user; + } + } +} diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/StageInstanceCreateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/StageInstanceCreateAuditLogData.cs new file mode 100644 index 000000000..eac99e87b --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/StageInstanceCreateAuditLogData.cs @@ -0,0 +1,50 @@ +using System.Linq; +using Model = Discord.API.AuditLog; +using EntryModel = Discord.API.AuditLogEntry; + +namespace Discord.Rest +{ + /// + /// Contains a piece of audit log data related to a stage going live. + /// + public class StageInstanceCreateAuditLogData : IAuditLogData + { + /// + /// Gets the topic of the stage channel. + /// + public string Topic { get; } + + /// + /// Gets the privacy level of the stage channel. + /// + public StagePrivacyLevel PrivacyLevel { get; } + + /// + /// Gets the user who started the stage channel. + /// + public IUser User { get; } + + /// + /// Gets the Id of the stage channel. + /// + public ulong StageChannelId { get; } + + internal StageInstanceCreateAuditLogData(string topic, StagePrivacyLevel privacyLevel, IUser user, ulong channelId) + { + Topic = topic; + PrivacyLevel = privacyLevel; + User = user; + StageChannelId = channelId; + } + + internal static StageInstanceCreateAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) + { + var topic = entry.Changes.FirstOrDefault(x => x.ChangedProperty == "topic").NewValue.ToObject(discord.ApiClient.Serializer); + var privacyLevel = entry.Changes.FirstOrDefault(x => x.ChangedProperty == "privacy_level").NewValue.ToObject(discord.ApiClient.Serializer); + var user = log.Users.FirstOrDefault(x => x.Id == entry.UserId); + var channelId = entry.Options.ChannelId; + + return new StageInstanceCreateAuditLogData(topic, privacyLevel, RestUser.Create(discord, user), channelId ?? 0); + } + } +} diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/StageInstanceDeleteAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/StageInstanceDeleteAuditLogData.cs new file mode 100644 index 000000000..d22c56010 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/StageInstanceDeleteAuditLogData.cs @@ -0,0 +1,50 @@ +using System.Linq; +using Model = Discord.API.AuditLog; +using EntryModel = Discord.API.AuditLogEntry; + +namespace Discord.Rest +{ + /// + /// Contains a piece of audit log data related to a stage instance deleted. + /// + public class StageInstanceDeleteAuditLogData + { + /// + /// Gets the topic of the stage channel. + /// + public string Topic { get; } + + /// + /// Gets the privacy level of the stage channel. + /// + public StagePrivacyLevel PrivacyLevel { get; } + + /// + /// Gets the user who started the stage channel. + /// + public IUser User { get; } + + /// + /// Gets the Id of the stage channel. + /// + public ulong StageChannelId { get; } + + internal StageInstanceDeleteAuditLogData(string topic, StagePrivacyLevel privacyLevel, IUser user, ulong channelId) + { + Topic = topic; + PrivacyLevel = privacyLevel; + User = user; + StageChannelId = channelId; + } + + internal static StageInstanceDeleteAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) + { + var topic = entry.Changes.FirstOrDefault(x => x.ChangedProperty == "topic").OldValue.ToObject(discord.ApiClient.Serializer); + var privacyLevel = entry.Changes.FirstOrDefault(x => x.ChangedProperty == "privacy_level").OldValue.ToObject(discord.ApiClient.Serializer); + var user = log.Users.FirstOrDefault(x => x.Id == entry.UserId); + var channelId = entry.Options.ChannelId; + + return new StageInstanceDeleteAuditLogData(topic, privacyLevel, RestUser.Create(discord, user), channelId ?? 0); + } + } +} diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/StageInstanceUpdatedAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/StageInstanceUpdatedAuditLogData.cs new file mode 100644 index 000000000..93a0344b2 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/StageInstanceUpdatedAuditLogData.cs @@ -0,0 +1,51 @@ +using System.Linq; +using Model = Discord.API.AuditLog; +using EntryModel = Discord.API.AuditLogEntry; + +namespace Discord.Rest +{ + /// + /// Contains a piece of audit log data related to a stage instance update. + /// + public class StageInstanceUpdatedAuditLogData + { + /// + /// Gets the Id of the stage channel. + /// + public ulong StageChannelId { get; } + + /// + /// Gets the stage information before the changes. + /// + public StageInfo Before { get; } + + /// + /// Gets the stage information after the changes. + /// + public StageInfo After { get; } + + internal StageInstanceUpdatedAuditLogData(ulong channelId, StageInfo before, StageInfo after) + { + StageChannelId = channelId; + Before = before; + After = after; + } + + internal static StageInstanceUpdatedAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) + { + var channelId = entry.Options.ChannelId.Value; + + var topic = entry.Changes.FirstOrDefault(x => x.ChangedProperty == "topic"); + var privacy = entry.Changes.FirstOrDefault(x => x.ChangedProperty == "privacy"); + + var user = RestUser.Create(discord, log.Users.FirstOrDefault(x => x.Id == entry.UserId)); + + var oldTopic = topic?.OldValue.ToObject(); + var newTopic = topic?.NewValue.ToObject(); + var oldPrivacy = privacy?.OldValue.ToObject(); + var newPrivacy = privacy?.NewValue.ToObject(); + + return new StageInstanceUpdatedAuditLogData(channelId, new StageInfo(user, oldPrivacy, oldTopic), new StageInfo(user, newPrivacy, newTopic)); + } + } +} diff --git a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs index c5d264ade..d02e273de 100644 --- a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs +++ b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs @@ -353,6 +353,7 @@ namespace Discord.Rest Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed."); Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed."); Preconditions.AtMost(embeds.Length, 10, nameof(embeds), "A max of 10 embeds are allowed."); + Preconditions.NotNullOrEmpty(filename, nameof(filename), "File Name must not be empty or null"); // check that user flag and user Id list are exclusive, same with role flag and role Id list if (allowedMentions != null && allowedMentions.AllowedTypes.HasValue) diff --git a/src/Discord.Net.Rest/Entities/Channels/RestStageChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestStageChannel.cs index 721e2d780..c01df96fd 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestStageChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestStageChannel.cs @@ -1,10 +1,8 @@ +using Discord.API; +using Discord.API.Rest; using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Threading.Tasks; using Model = Discord.API.Channel; -using StageInstance = Discord.API.StageInstance; namespace Discord.Rest { @@ -25,12 +23,9 @@ namespace Discord.Rest /// public bool IsLive { get; private set; } internal RestStageChannel(BaseDiscordClient discord, IGuild guild, ulong id) - : base(discord, guild, id) - { - - } + : base(discord, guild, id) { } - internal static new RestStageChannel Create(BaseDiscordClient discord, IGuild guild, Model model) + internal new static RestStageChannel Create(BaseDiscordClient discord, IGuild guild, Model model) { var entity = new RestStageChannel(discord, guild, model.Id); entity.Update(model); @@ -65,7 +60,7 @@ namespace Discord.Rest /// public async Task StartStageAsync(string topic, StagePrivacyLevel privacyLevel = StagePrivacyLevel.GuildOnly, RequestOptions options = null) { - var args = new API.Rest.CreateStageInstanceParams() + var args = new CreateStageInstanceParams { ChannelId = Id, PrivacyLevel = privacyLevel, @@ -82,7 +77,7 @@ namespace Discord.Rest { await Discord.ApiClient.DeleteStageInstanceAsync(Id, options); - Update(null, false); + Update(null); } /// @@ -98,7 +93,7 @@ namespace Discord.Rest /// public Task RequestToSpeakAsync(RequestOptions options = null) { - var args = new API.Rest.ModifyVoiceStateParams() + var args = new ModifyVoiceStateParams { ChannelId = Id, RequestToSpeakTimestamp = DateTimeOffset.UtcNow @@ -109,7 +104,7 @@ namespace Discord.Rest /// public Task BecomeSpeakerAsync(RequestOptions options = null) { - var args = new API.Rest.ModifyVoiceStateParams() + var args = new ModifyVoiceStateParams { ChannelId = Id, Suppressed = false @@ -120,7 +115,7 @@ namespace Discord.Rest /// public Task StopSpeakingAsync(RequestOptions options = null) { - var args = new API.Rest.ModifyVoiceStateParams() + var args = new ModifyVoiceStateParams { ChannelId = Id, Suppressed = true @@ -131,7 +126,7 @@ namespace Discord.Rest /// public Task MoveToSpeakerAsync(IGuildUser user, RequestOptions options = null) { - var args = new API.Rest.ModifyVoiceStateParams() + var args = new ModifyVoiceStateParams { ChannelId = Id, Suppressed = false @@ -143,7 +138,7 @@ namespace Discord.Rest /// public Task RemoveFromSpeakerAsync(IGuildUser user, RequestOptions options = null) { - var args = new API.Rest.ModifyVoiceStateParams() + var args = new ModifyVoiceStateParams { ChannelId = Id, Suppressed = true diff --git a/src/Discord.Net.Rest/Entities/Channels/RestThreadChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestThreadChannel.cs index 0fb55e797..63071b9a5 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestThreadChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestThreadChannel.cs @@ -2,15 +2,13 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; using System.IO; -using System.Linq; -using System.Text; using System.Threading.Tasks; using Model = Discord.API.Channel; namespace Discord.Rest { /// - /// Represents a thread channel recieved over REST. + /// Represents a thread channel received over REST. /// public class RestThreadChannel : RestTextChannel, IThreadChannel { @@ -42,10 +40,7 @@ namespace Discord.Rest public ulong ParentChannelId { get; private set; } internal RestThreadChannel(BaseDiscordClient discord, IGuild guild, ulong id) - : base(discord, guild, id) - { - - } + : base(discord, guild, id) { } internal new static RestThreadChannel Create(BaseDiscordClient discord, IGuild guild, Model model) { @@ -66,7 +61,6 @@ namespace Discord.Rest AutoArchiveDuration = model.ThreadMetadata.Value.AutoArchiveDuration; ArchiveTimestamp = model.ThreadMetadata.Value.ArchiveTimestamp; IsLocked = model.ThreadMetadata.Value.Locked.GetValueOrDefault(false); - } MemberCount = model.MemberCount.GetValueOrDefault(0); @@ -81,7 +75,7 @@ namespace Discord.Rest /// The id of the user to fetch. /// The options to be used when sending the request. /// - /// A task representing the asyncronous get operation. The task returns a + /// A task representing the asynchronous get operation. The task returns a /// if found, otherwise . /// public new Task GetUserAsync(ulong userId, RequestOptions options = null) @@ -92,7 +86,7 @@ namespace Discord.Rest /// /// The options to be used when sending the request. /// - /// A task representing the asyncronous get operation. The task returns a + /// A task representing the asynchronous get operation. The task returns a /// of 's. /// public new async Task> GetUsersAsync(RequestOptions options = null) @@ -110,107 +104,106 @@ namespace Discord.Rest /// This method is not supported in threads. /// public override Task AddPermissionOverwriteAsync(IRole role, OverwritePermissions permissions, RequestOptions options = null) - => throw new NotImplementedException(); + => throw new NotSupportedException("This method is not supported in threads."); /// /// /// This method is not supported in threads. /// public override Task AddPermissionOverwriteAsync(IUser user, OverwritePermissions permissions, RequestOptions options = null) - => throw new NotImplementedException(); + => throw new NotSupportedException("This method is not supported in threads."); /// /// /// This method is not supported in threads. /// public override Task CreateInviteAsync(int? maxAge = 86400, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null) - => throw new NotImplementedException(); + => throw new NotSupportedException("This method is not supported in threads."); /// /// /// This method is not supported in threads. /// public override Task CreateInviteToApplicationAsync(ulong applicationId, int? maxAge, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null) - => throw new NotImplementedException(); + => throw new NotSupportedException("This method is not supported in threads."); /// /// /// This method is not supported in threads. /// public override Task CreateInviteToStreamAsync(IUser user, int? maxAge, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null) - => throw new NotImplementedException(); + => throw new NotSupportedException("This method is not supported in threads."); /// /// /// This method is not supported in threads. /// public override Task CreateWebhookAsync(string name, Stream avatar = null, RequestOptions options = null) - => throw new NotImplementedException(); + => throw new NotSupportedException("This method is not supported in threads."); /// /// /// This method is not supported in threads. /// public override Task GetCategoryAsync(RequestOptions options = null) - => throw new NotImplementedException(); + => throw new NotSupportedException("This method is not supported in threads."); /// /// /// This method is not supported in threads. /// public override Task> GetInvitesAsync(RequestOptions options = null) - => throw new NotImplementedException(); + => throw new NotSupportedException("This method is not supported in threads."); /// /// /// This method is not supported in threads. /// public override OverwritePermissions? GetPermissionOverwrite(IRole role) - => throw new NotImplementedException(); + => throw new NotSupportedException("This method is not supported in threads."); /// /// /// This method is not supported in threads. /// public override OverwritePermissions? GetPermissionOverwrite(IUser user) - => throw new NotImplementedException(); + => throw new NotSupportedException("This method is not supported in threads."); /// /// /// This method is not supported in threads. /// public override Task GetWebhookAsync(ulong id, RequestOptions options = null) - => throw new NotImplementedException(); + => throw new NotSupportedException("This method is not supported in threads."); /// /// /// This method is not supported in threads. /// public override Task> GetWebhooksAsync(RequestOptions options = null) - => throw new NotImplementedException(); + => throw new NotSupportedException("This method is not supported in threads."); /// /// /// This method is not supported in threads. /// public override Task RemovePermissionOverwriteAsync(IRole role, RequestOptions options = null) - => throw new NotImplementedException(); + => throw new NotSupportedException("This method is not supported in threads."); /// /// /// This method is not supported in threads. /// public override Task RemovePermissionOverwriteAsync(IUser user, RequestOptions options = null) - => throw new NotImplementedException(); + => throw new NotSupportedException("This method is not supported in threads."); /// /// /// This method is not supported in threads. /// public override IReadOnlyCollection PermissionOverwrites - => throw new NotImplementedException(); + => throw new NotSupportedException("This method is not supported in threads."); - /// public Task JoinAsync(RequestOptions options = null) => Discord.ApiClient.JoinThreadAsync(Id, options); diff --git a/src/Discord.Net.Rest/Entities/Channels/ThreadHelper.cs b/src/Discord.Net.Rest/Entities/Channels/ThreadHelper.cs index f1bf238bc..69eb0d768 100644 --- a/src/Discord.Net.Rest/Entities/Channels/ThreadHelper.cs +++ b/src/Discord.Net.Rest/Entities/Channels/ThreadHelper.cs @@ -1,8 +1,6 @@ using Discord.API.Rest; using System; -using System.Collections.Generic; using System.Linq; -using System.Text; using System.Threading.Tasks; using Model = Discord.API.Channel; @@ -14,22 +12,22 @@ namespace Discord.Rest ThreadArchiveDuration autoArchiveDuration = ThreadArchiveDuration.OneDay, IMessage message = null, RequestOptions options = null) { if (autoArchiveDuration == ThreadArchiveDuration.OneWeek && !channel.Guild.Features.Contains("SEVEN_DAY_THREAD_ARCHIVE")) - throw new ArgumentException($"The guild {channel.Guild.Name} does not have the SEVEN_DAY_THREAD_ARCHIVE feature!"); + throw new ArgumentException($"The guild {channel.Guild.Name} does not have the SEVEN_DAY_THREAD_ARCHIVE feature!", nameof(autoArchiveDuration)); if (autoArchiveDuration == ThreadArchiveDuration.ThreeDays && !channel.Guild.Features.Contains("THREE_DAY_THREAD_ARCHIVE")) - throw new ArgumentException($"The guild {channel.Guild.Name} does not have the THREE_DAY_THREAD_ARCHIVE feature!"); + throw new ArgumentException($"The guild {channel.Guild.Name} does not have the THREE_DAY_THREAD_ARCHIVE feature!", nameof(autoArchiveDuration)); if (type == ThreadType.PrivateThread && !channel.Guild.Features.Contains("PRIVATE_THREADS")) - throw new ArgumentException($"The guild {channel.Guild.Name} does not have the PRIVATE_THREADS feature!"); + throw new ArgumentException($"The guild {channel.Guild.Name} does not have the PRIVATE_THREADS feature!", nameof(type)); - var args = new StartThreadParams() + var args = new StartThreadParams { Name = name, Duration = autoArchiveDuration, Type = type }; - Model model = null; + Model model; if (message != null) model = await client.ApiClient.StartThreadAsync(channel.Id, message.Id, args, options).ConfigureAwait(false); @@ -45,7 +43,7 @@ namespace Discord.Rest { var args = new TextChannelProperties(); func(args); - var apiArgs = new API.Rest.ModifyThreadParams + var apiArgs = new ModifyThreadParams { Name = args.Name, Archived = args.Archived, @@ -63,7 +61,7 @@ namespace Discord.Rest return users.Select(x => RestThreadUser.Create(client, channel.Guild, x, channel)).ToArray(); } - public static async Task GetUserAsync(ulong userdId, IThreadChannel channel, BaseDiscordClient client, RequestOptions options = null) - => (await GetUsersAsync(channel, client, options).ConfigureAwait(false)).FirstOrDefault(x => x.Id == userdId); + public static async Task GetUserAsync(ulong userId, IThreadChannel channel, BaseDiscordClient client, RequestOptions options = null) + => (await GetUsersAsync(channel, client, options).ConfigureAwait(false)).FirstOrDefault(x => x.Id == userId); } } diff --git a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs index 5e56fd56e..8b0659baf 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs @@ -578,7 +578,7 @@ namespace Discord.Rest /// The options to be used when sending the request. /// /// A task that represents the asynchronous get operation. The task result contains the text channel - /// where guild notices such as welcome messages and boost events are poste; if none is found. + /// where guild notices such as welcome messages and boost events are post; if none is found. /// public async Task GetSystemChannelAsync(RequestOptions options = null) { @@ -611,11 +611,11 @@ namespace Discord.Rest } /// - /// Gets the text channel channel where admins and moderators of Community guilds receive notices from Discord. + /// Gets the text channel where admins and moderators of Community guilds receive notices from Discord. /// /// The options to be used when sending the request. /// - /// A task that represents the asynchronous get operation. The task result contains the text channel channel where + /// A task that represents the asynchronous get operation. The task result contains the text channel where /// admins and moderators of Community guilds receive notices from Discord; if none is set. /// public async Task GetPublicUpdatesChannelAsync(RequestOptions options = null) @@ -914,7 +914,7 @@ namespace Discord.Rest #region Interactions /// - /// Gets this guilds slash commands commands + /// Gets this guilds slash commands /// /// The options to be used when sending the request. /// @@ -922,7 +922,7 @@ namespace Discord.Rest /// of application commands found within the guild. /// public async Task> GetApplicationCommandsAsync (RequestOptions options = null) - => await ClientHelper.GetGuildApplicationCommands(Discord, Id, options).ConfigureAwait(false); + => await ClientHelper.GetGuildApplicationCommandsAsync(Discord, Id, options).ConfigureAwait(false); /// /// Gets an application command within this guild with the specified id. /// @@ -933,7 +933,7 @@ namespace Discord.Rest /// if found, otherwise . /// public async Task GetApplicationCommandAsync(ulong id, RequestOptions options = null) - => await ClientHelper.GetGuildApplicationCommand(Discord, id, Id, options); + => await ClientHelper.GetGuildApplicationCommandAsync(Discord, id, Id, options); /// /// Creates an application command within this guild. /// @@ -944,7 +944,7 @@ namespace Discord.Rest /// public async Task CreateApplicationCommandAsync(ApplicationCommandProperties properties, RequestOptions options = null) { - var model = await InteractionHelper.CreateGuildCommand(Discord, Id, properties, options); + var model = await InteractionHelper.CreateGuildCommandAsync(Discord, Id, properties, options); return RestGuildCommand.Create(Discord, model, Id); } @@ -959,7 +959,7 @@ namespace Discord.Rest public async Task> BulkOverwriteApplicationCommandsAsync(ApplicationCommandProperties[] properties, RequestOptions options = null) { - var models = await InteractionHelper.BulkOverwriteGuildCommands(Discord, Id, properties, options); + var models = await InteractionHelper.BulkOverwriteGuildCommandsAsync(Discord, Id, properties, options); return models.Select(x => RestGuildCommand.Create(Discord, x, Id)).ToImmutableArray(); } diff --git a/src/Discord.Net.Rest/Entities/Interactions/InteractionHelper.cs b/src/Discord.Net.Rest/Entities/Interactions/InteractionHelper.cs index 6ec6819de..7cfc6a2ec 100644 --- a/src/Discord.Net.Rest/Entities/Interactions/InteractionHelper.cs +++ b/src/Discord.Net.Rest/Entities/Interactions/InteractionHelper.cs @@ -1,28 +1,40 @@ using Discord.API; using Discord.API.Rest; using Discord.Net; -//using Discord.Rest.Entities.Interactions; using System; using System.Collections.Generic; using System.Linq; +using System.Net; using System.Threading.Tasks; namespace Discord.Rest { internal static class InteractionHelper { + public const double ResponseTimeLimit = 3; + public const double ResponseAndFollowupLimit = 15; + #region InteractionHelper + public static bool CanSendResponse(IDiscordInteraction interaction) + { + return (DateTime.UtcNow - interaction.CreatedAt).TotalSeconds < ResponseTimeLimit; + } + public static bool CanRespondOrFollowup(IDiscordInteraction interaction) + { + return (DateTime.UtcNow - interaction.CreatedAt).TotalMinutes <= ResponseAndFollowupLimit; + } + public static Task DeleteAllGuildCommandsAsync(BaseDiscordClient client, ulong guildId, RequestOptions options = null) { - return client.ApiClient.BulkOverwriteGuildApplicationCommandsAsync(guildId, new CreateApplicationCommandParams[0], options); + return client.ApiClient.BulkOverwriteGuildApplicationCommandsAsync(guildId, Array.Empty(), options); } public static Task DeleteAllGlobalCommandsAsync(BaseDiscordClient client, RequestOptions options = null) { - return client.ApiClient.BulkOverwriteGlobalApplicationCommandsAsync(new CreateApplicationCommandParams[0], options); + return client.ApiClient.BulkOverwriteGlobalApplicationCommandsAsync(Array.Empty(), options); } - public static Task SendInteractionResponse(BaseDiscordClient client, InteractionResponse response, + public static Task SendInteractionResponseAsync(BaseDiscordClient client, InteractionResponse response, ulong interactionId, string interactionToken, RequestOptions options = null) { return client.ApiClient.CreateInteractionResponseAsync(response, interactionId, interactionToken, options); @@ -40,7 +52,7 @@ namespace Discord.Rest { var model = await client.ApiClient.CreateInteractionFollowupMessageAsync(args, token, options).ConfigureAwait(false); - RestFollowupMessage entity = RestFollowupMessage.Create(client, model, token, channel); + var entity = RestFollowupMessage.Create(client, model, token, channel); return entity; } #endregion @@ -51,22 +63,21 @@ namespace Discord.Rest { var model = await client.ApiClient.GetGlobalApplicationCommandAsync(id, options).ConfigureAwait(false); - return RestGlobalCommand.Create(client, model); } - public static Task CreateGlobalCommand(BaseDiscordClient client, + public static Task CreateGlobalCommandAsync(BaseDiscordClient client, Action func, RequestOptions options = null) where TArg : ApplicationCommandProperties { var args = Activator.CreateInstance(typeof(TArg)); func((TArg)args); - return CreateGlobalCommand(client, (TArg)args, options); + return CreateGlobalCommandAsync(client, (TArg)args, options); } - public static async Task CreateGlobalCommand(BaseDiscordClient client, + public static async Task CreateGlobalCommandAsync(BaseDiscordClient client, ApplicationCommandProperties arg, RequestOptions options = null) { Preconditions.NotNullOrEmpty(arg.Name, nameof(arg.Name)); - var model = new CreateApplicationCommandParams() + var model = new CreateApplicationCommandParams { Name = arg.Name.Value, Type = arg.Type, @@ -82,25 +93,25 @@ namespace Discord.Rest model.Description = slashProps.Description.Value; model.Options = slashProps.Options.IsSpecified - ? slashProps.Options.Value.Select(x => new Discord.API.ApplicationCommandOption(x)).ToArray() - : Optional.Unspecified; + ? slashProps.Options.Value.Select(x => new ApplicationCommandOption(x)).ToArray() + : Optional.Unspecified; } return await client.ApiClient.CreateGlobalApplicationCommandAsync(model, options).ConfigureAwait(false); } - public static async Task BulkOverwriteGlobalCommands(BaseDiscordClient client, + public static async Task BulkOverwriteGlobalCommandsAsync(BaseDiscordClient client, ApplicationCommandProperties[] args, RequestOptions options = null) { Preconditions.NotNull(args, nameof(args)); - List models = new List(); + var models = new List(); foreach (var arg in args) { Preconditions.NotNullOrEmpty(arg.Name, nameof(arg.Name)); - var model = new CreateApplicationCommandParams() + var model = new CreateApplicationCommandParams { Name = arg.Name.Value, Type = arg.Type, @@ -116,28 +127,28 @@ namespace Discord.Rest model.Description = slashProps.Description.Value; model.Options = slashProps.Options.IsSpecified - ? slashProps.Options.Value.Select(x => new Discord.API.ApplicationCommandOption(x)).ToArray() - : Optional.Unspecified; + ? slashProps.Options.Value.Select(x => new ApplicationCommandOption(x)).ToArray() + : Optional.Unspecified; } models.Add(model); } - return await client.ApiClient.BulkOverwriteGlobalApplicationCommandsAsync(models.ToArray(), options).ConfigureAwait(false); + return await client.ApiClient.BulkOverwriteGlobalApplicationCommandsAsync(models.ToArray(), options).ConfigureAwait(false); } - public static async Task> BulkOverwriteGuildCommands(BaseDiscordClient client, ulong guildId, + public static async Task> BulkOverwriteGuildCommandsAsync(BaseDiscordClient client, ulong guildId, ApplicationCommandProperties[] args, RequestOptions options = null) { Preconditions.NotNull(args, nameof(args)); - List models = new List(); + var models = new List(); foreach (var arg in args) { Preconditions.NotNullOrEmpty(arg.Name, nameof(arg.Name)); - var model = new CreateApplicationCommandParams() + var model = new CreateApplicationCommandParams { Name = arg.Name.Value, Type = arg.Type, @@ -153,8 +164,8 @@ namespace Discord.Rest model.Description = slashProps.Description.Value; model.Options = slashProps.Options.IsSpecified - ? slashProps.Options.Value.Select(x => new Discord.API.ApplicationCommandOption(x)).ToArray() - : Optional.Unspecified; + ? slashProps.Options.Value.Select(x => new ApplicationCommandOption(x)).ToArray() + : Optional.Unspecified; } models.Add(model); @@ -181,24 +192,24 @@ namespace Discord.Rest } } - public static Task ModifyGlobalCommand(BaseDiscordClient client, IApplicationCommand command, + public static Task ModifyGlobalCommandAsync(BaseDiscordClient client, IApplicationCommand command, Action func, RequestOptions options = null) where TArg : ApplicationCommandProperties { var arg = GetApplicationCommandProperties(command); func(arg); - return ModifyGlobalCommand(client, command, arg, options); + return ModifyGlobalCommandAsync(client, command, arg, options); } - public static async Task ModifyGlobalCommand(BaseDiscordClient client, IApplicationCommand command, + public static async Task ModifyGlobalCommandAsync(BaseDiscordClient client, IApplicationCommand command, ApplicationCommandProperties args, RequestOptions options = null) { if (args.Name.IsSpecified) { Preconditions.AtMost(args.Name.Value.Length, 32, nameof(args.Name)); - Preconditions.AtLeast(args.Name.Value.Length, 3, nameof(args.Name)); + Preconditions.AtLeast(args.Name.Value.Length, 1, nameof(args.Name)); } - var model = new Discord.API.Rest.ModifyApplicationCommandParams() + var model = new ModifyApplicationCommandParams { Name = args.Name, DefaultPermission = args.IsDefaultPermission.IsSpecified @@ -206,7 +217,7 @@ namespace Discord.Rest : Optional.Unspecified }; - if(args is SlashCommandProperties slashProps) + if (args is SlashCommandProperties slashProps) { if (slashProps.Description.IsSpecified) { @@ -223,15 +234,14 @@ namespace Discord.Rest model.Description = slashProps.Description; model.Options = slashProps.Options.IsSpecified - ? slashProps.Options.Value.Select(x => new Discord.API.ApplicationCommandOption(x)).ToArray() - : Optional.Unspecified; + ? slashProps.Options.Value.Select(x => new ApplicationCommandOption(x)).ToArray() + : Optional.Unspecified; } return await client.ApiClient.ModifyGlobalApplicationCommandAsync(model, command.Id, options).ConfigureAwait(false); } - - public static async Task DeleteGlobalCommand(BaseDiscordClient client, IApplicationCommand command, RequestOptions options = null) + public static async Task DeleteGlobalCommandAsync(BaseDiscordClient client, IApplicationCommand command, RequestOptions options = null) { Preconditions.NotNull(command, nameof(command)); Preconditions.NotEqual(command.Id, 0, nameof(command.Id)); @@ -241,18 +251,18 @@ namespace Discord.Rest #endregion #region Guild Commands - public static Task CreateGuildCommand(BaseDiscordClient client, ulong guildId, + public static Task CreateGuildCommandAsync(BaseDiscordClient client, ulong guildId, Action func, RequestOptions options) where TArg : ApplicationCommandProperties { var args = Activator.CreateInstance(typeof(TArg)); func((TArg)args); - return CreateGuildCommand(client, guildId, (TArg)args, options); + return CreateGuildCommandAsync(client, guildId, (TArg)args, options); } - public static async Task CreateGuildCommand(BaseDiscordClient client, ulong guildId, + public static async Task CreateGuildCommandAsync(BaseDiscordClient client, ulong guildId, ApplicationCommandProperties arg, RequestOptions options = null) { - var model = new CreateApplicationCommandParams() + var model = new CreateApplicationCommandParams { Name = arg.Name.Value, Type = arg.Type, @@ -268,25 +278,25 @@ namespace Discord.Rest model.Description = slashProps.Description.Value; model.Options = slashProps.Options.IsSpecified - ? slashProps.Options.Value.Select(x => new Discord.API.ApplicationCommandOption(x)).ToArray() - : Optional.Unspecified; + ? slashProps.Options.Value.Select(x => new ApplicationCommandOption(x)).ToArray() + : Optional.Unspecified; } return await client.ApiClient.CreateGuildApplicationCommandAsync(model, guildId, options).ConfigureAwait(false); } - public static Task ModifyGuildCommand(BaseDiscordClient client, IApplicationCommand command, ulong guildId, + public static Task ModifyGuildCommandAsync(BaseDiscordClient client, IApplicationCommand command, ulong guildId, Action func, RequestOptions options = null) where TArg : ApplicationCommandProperties { var arg = GetApplicationCommandProperties(command); func(arg); - return ModifyGuildCommand(client, command, guildId, arg, options); + return ModifyGuildCommandAsync(client, command, guildId, arg, options); } - public static async Task ModifyGuildCommand(BaseDiscordClient client, IApplicationCommand command, ulong guildId, + public static async Task ModifyGuildCommandAsync(BaseDiscordClient client, IApplicationCommand command, ulong guildId, ApplicationCommandProperties arg, RequestOptions options = null) { - var model = new ModifyApplicationCommandParams() + var model = new ModifyApplicationCommandParams { Name = arg.Name, DefaultPermission = arg.IsDefaultPermission.IsSpecified @@ -301,14 +311,14 @@ namespace Discord.Rest model.Description = slashProps.Description.Value; model.Options = slashProps.Options.IsSpecified - ? slashProps.Options.Value.Select(x => new Discord.API.ApplicationCommandOption(x)).ToArray() - : Optional.Unspecified; + ? slashProps.Options.Value.Select(x => new ApplicationCommandOption(x)).ToArray() + : Optional.Unspecified; } return await client.ApiClient.ModifyGuildApplicationCommandAsync(model, guildId, command.Id, options).ConfigureAwait(false); } - public static async Task DeleteGuildCommand(BaseDiscordClient client, ulong guildId, IApplicationCommand command, RequestOptions options = null) + public static async Task DeleteGuildCommandAsync(BaseDiscordClient client, ulong guildId, IApplicationCommand command, RequestOptions options = null) { Preconditions.NotNull(command, nameof(command)); Preconditions.NotEqual(command.Id, 0, nameof(command.Id)); @@ -316,21 +326,16 @@ namespace Discord.Rest await client.ApiClient.DeleteGuildApplicationCommandAsync(guildId, command.Id, options).ConfigureAwait(false); } - public static Task DeleteUnknownApplicationCommand(BaseDiscordClient client, ulong? guildId, IApplicationCommand command, RequestOptions options = null) + public static Task DeleteUnknownApplicationCommandAsync(BaseDiscordClient client, ulong? guildId, IApplicationCommand command, RequestOptions options = null) { - if (guildId.HasValue) - { - return DeleteGuildCommand(client, guildId.Value, command, options); - } - else - { - return DeleteGlobalCommand(client, command, options); - } + return guildId.HasValue + ? DeleteGuildCommandAsync(client, guildId.Value, command, options) + : DeleteGlobalCommandAsync(client, command, options); } #endregion #region Responses - public static async Task ModifyFollowupMessage(BaseDiscordClient client, RestFollowupMessage message, Action func, + public static async Task ModifyFollowupMessageAsync(BaseDiscordClient client, RestFollowupMessage message, Action func, RequestOptions options = null) { var args = new MessageProperties(); @@ -360,21 +365,19 @@ namespace Discord.Rest Preconditions.AtMost(apiEmbeds?.Count ?? 0, 10, nameof(args.Embeds), "A max of 10 embeds are allowed."); - var apiArgs = new API.Rest.ModifyInteractionResponseParams + var apiArgs = new ModifyInteractionResponseParams { Content = args.Content, 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, + Components = args.Components.IsSpecified ? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() : Optional.Unspecified }; return await client.ApiClient.ModifyInteractionFollowupMessageAsync(apiArgs, message.Id, message.Token, options).ConfigureAwait(false); } - - public static async Task DeleteFollowupMessage(BaseDiscordClient client, RestFollowupMessage message, RequestOptions options = null) + public static async Task DeleteFollowupMessageAsync(BaseDiscordClient client, RestFollowupMessage message, RequestOptions options = null) => await client.ApiClient.DeleteInteractionFollowupMessageAsync(message.Id, message.Token, options); - - public static async Task ModifyInteractionResponse(BaseDiscordClient client, string token, Action func, + public static async Task ModifyInteractionResponseAsync(BaseDiscordClient client, string token, Action func, RequestOptions options = null) { var args = new MessageProperties(); @@ -416,25 +419,24 @@ namespace Discord.Rest return await client.ApiClient.ModifyInteractionResponseAsync(apiArgs, token, options).ConfigureAwait(false); } - public static async Task DeletedInteractionResponse(BaseDiscordClient client, RestInteractionMessage message, RequestOptions options = null) + public static async Task DeleteInteractionResponseAsync(BaseDiscordClient client, RestInteractionMessage message, RequestOptions options = null) => await client.ApiClient.DeleteInteractionFollowupMessageAsync(message.Id, message.Token, options); - public static Task SendAutocompleteResult(BaseDiscordClient client, IEnumerable result, ulong interactionId, + public static Task SendAutocompleteResultAsync(BaseDiscordClient client, IEnumerable result, ulong interactionId, string interactionToken, RequestOptions options) { - if (result == null) - result = new AutocompleteResult[0]; + result ??= Array.Empty(); Preconditions.AtMost(result.Count(), 20, nameof(result), "A maximum of 20 choices are allowed!"); - var apiArgs = new InteractionResponse() + var apiArgs = new InteractionResponse { Type = InteractionResponseType.ApplicationCommandAutocompleteResult, - Data = new InteractionCallbackData() + Data = new InteractionCallbackData { Choices = result.Any() - ? result.Select(x => new API.ApplicationCommandOptionChoice() { Name = x.Name, Value = x.Value }).ToArray() - : new ApplicationCommandOptionChoice[0] + ? result.Select(x => new ApplicationCommandOptionChoice { Name = x.Name, Value = x.Value }).ToArray() + : Array.Empty() } }; @@ -449,7 +451,7 @@ namespace Discord.Rest var models = await client.ApiClient.GetGuildApplicationCommandPermissionsAsync(guildId, options); return models.Select(x => new GuildApplicationCommandPermission(x.Id, x.ApplicationId, guildId, x.Permissions.Select( - y => new Discord.ApplicationCommandPermission(y.Id, y.Type, y.Permission)) + y => new ApplicationCommandPermission(y.Id, y.Type, y.Permission)) .ToArray()) ).ToArray(); } @@ -465,7 +467,7 @@ namespace Discord.Rest } catch (HttpException x) { - if (x.HttpCode == System.Net.HttpStatusCode.NotFound) + if (x.HttpCode == HttpStatusCode.NotFound) return null; throw; } @@ -478,11 +480,11 @@ namespace Discord.Rest Preconditions.AtMost(args.Length, 10, nameof(args)); Preconditions.AtLeast(args.Length, 0, nameof(args)); - List permissionsList = new List(); + var permissionsList = new List(); foreach (var arg in args) { - var permissions = new ApplicationCommandPermissions() + var permissions = new ApplicationCommandPermissions { Id = arg.TargetId, Permission = arg.Permission, @@ -492,7 +494,7 @@ namespace Discord.Rest permissionsList.Add(permissions); } - ModifyGuildApplicationCommandPermissionsParams model = new ModifyGuildApplicationCommandPermissionsParams() + var model = new ModifyGuildApplicationCommandPermissionsParams { Permissions = permissionsList.ToArray() }; @@ -509,16 +511,16 @@ namespace Discord.Rest Preconditions.NotNull(args, nameof(args)); Preconditions.NotEqual(args.Count, 0, nameof(args)); - List models = new List(); + var models = new List(); foreach (var arg in args) { Preconditions.AtMost(arg.Value.Length, 10, nameof(args)); - var model = new ModifyGuildApplicationCommandPermissions() + var model = new ModifyGuildApplicationCommandPermissions { Id = arg.Key, - Permissions = arg.Value.Select(x => new ApplicationCommandPermissions() + Permissions = arg.Value.Select(x => new ApplicationCommandPermissions { Id = x.TargetId, Permission = x.Permission, diff --git a/src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommand.cs b/src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommand.cs index eb9cce07f..c3edaf6ff 100644 --- a/src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommand.cs +++ b/src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommand.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; -using System.Text; using System.Threading.Tasks; using Model = Discord.API.ApplicationCommand; @@ -38,21 +37,13 @@ namespace Discord.Rest => SnowflakeUtils.FromSnowflake(Id); internal RestApplicationCommand(BaseDiscordClient client, ulong id) - : base(client, id) - { - - } + : base(client, id) { } internal static RestApplicationCommand Create(BaseDiscordClient client, Model model, ulong? guildId) { - if (guildId.HasValue) - { - return RestGuildCommand.Create(client, model, guildId.Value); - } - else - { - return RestGlobalCommand.Create(client, model); - } + return guildId.HasValue + ? RestGuildCommand.Create(client, model, guildId.Value) + : RestGlobalCommand.Create(client, model); } internal virtual void Update(Model model) @@ -64,8 +55,8 @@ namespace Discord.Rest IsDefaultPermission = model.DefaultPermissions.GetValueOrDefault(true); Options = model.Options.IsSpecified - ? model.Options.Value.Select(x => RestApplicationCommandOption.Create(x)).ToImmutableArray() - : null; + ? model.Options.Value.Select(RestApplicationCommandOption.Create).ToImmutableArray() + : ImmutableArray.Create(); } /// @@ -76,7 +67,7 @@ namespace Discord.Rest { return ModifyAsync(func, options); } - + /// public abstract Task ModifyAsync(Action func, RequestOptions options = null) where TArg : ApplicationCommandProperties; diff --git a/src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommandChoice.cs b/src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommandChoice.cs index cd1f73768..a40491a2c 100644 --- a/src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommandChoice.cs +++ b/src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommandChoice.cs @@ -1,8 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using Model = Discord.API.ApplicationCommandOptionChoice; namespace Discord.Rest diff --git a/src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommandOption.cs b/src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommandOption.cs index 643cdcf3c..460ff872a 100644 --- a/src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommandOption.cs +++ b/src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommandOption.cs @@ -1,9 +1,6 @@ -using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; -using System.Text; -using System.Threading.Tasks; using Model = Discord.API.ApplicationCommandOption; namespace Discord.Rest @@ -39,6 +36,11 @@ namespace Discord.Rest /// public IReadOnlyCollection Options { get; private set; } + /// + /// The allowed channel types for this option. + /// + public IReadOnlyCollection ChannelTypes { get; private set; } + internal RestApplicationCommandOption() { } internal static RestApplicationCommandOption Create(Model model) @@ -61,12 +63,16 @@ namespace Discord.Rest IsRequired = model.Required.Value; Options = model.Options.IsSpecified - ? model.Options.Value.Select(x => Create(x)).ToImmutableArray() - : null; + ? model.Options.Value.Select(Create).ToImmutableArray() + : ImmutableArray.Create(); Choices = model.Choices.IsSpecified ? model.Choices.Value.Select(x => new RestApplicationCommandChoice(x)).ToImmutableArray() - : null; + : ImmutableArray.Create(); + + ChannelTypes = model.ChannelTypes.IsSpecified + ? model.ChannelTypes.Value.ToImmutableArray() + : ImmutableArray.Create(); } #endregion diff --git a/src/Discord.Net.Rest/Entities/Interactions/RestGlobalCommand.cs b/src/Discord.Net.Rest/Entities/Interactions/RestGlobalCommand.cs index bee1f39cd..c319bcf34 100644 --- a/src/Discord.Net.Rest/Entities/Interactions/RestGlobalCommand.cs +++ b/src/Discord.Net.Rest/Entities/Interactions/RestGlobalCommand.cs @@ -1,7 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Threading.Tasks; using Model = Discord.API.ApplicationCommand; @@ -13,10 +10,7 @@ namespace Discord.Rest public class RestGlobalCommand : RestApplicationCommand { internal RestGlobalCommand(BaseDiscordClient client, ulong id) - : base(client, id) - { - - } + : base(client, id) { } internal static RestGlobalCommand Create(BaseDiscordClient client, Model model) { @@ -27,7 +21,7 @@ namespace Discord.Rest /// public override async Task DeleteAsync(RequestOptions options = null) - => await InteractionHelper.DeleteGlobalCommand(Discord, this).ConfigureAwait(false); + => await InteractionHelper.DeleteGlobalCommandAsync(Discord, this).ConfigureAwait(false); /// /// Modifies this . @@ -39,7 +33,7 @@ namespace Discord.Rest /// public override async Task ModifyAsync(Action func, RequestOptions options = null) { - var cmd = await InteractionHelper.ModifyGlobalCommand(Discord, this, func, options).ConfigureAwait(false); + var cmd = await InteractionHelper.ModifyGlobalCommandAsync(Discord, this, func, options).ConfigureAwait(false); Update(cmd); } } diff --git a/src/Discord.Net.Rest/Entities/Interactions/RestGuildCommand.cs b/src/Discord.Net.Rest/Entities/Interactions/RestGuildCommand.cs index fb41c1812..00804e57e 100644 --- a/src/Discord.Net.Rest/Entities/Interactions/RestGuildCommand.cs +++ b/src/Discord.Net.Rest/Entities/Interactions/RestGuildCommand.cs @@ -1,7 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Threading.Tasks; using Model = Discord.API.ApplicationCommand; @@ -32,7 +29,7 @@ namespace Discord.Rest /// public override async Task DeleteAsync(RequestOptions options = null) - => await InteractionHelper.DeleteGuildCommand(Discord, GuildId, this).ConfigureAwait(false); + => await InteractionHelper.DeleteGuildCommandAsync(Discord, GuildId, this).ConfigureAwait(false); /// /// Modifies this . @@ -44,7 +41,7 @@ namespace Discord.Rest /// public override async Task ModifyAsync(Action func, RequestOptions options = null) { - var model = await InteractionHelper.ModifyGuildCommand(Discord, this, GuildId, func, options).ConfigureAwait(false); + var model = await InteractionHelper.ModifyGuildCommandAsync(Discord, this, GuildId, func, options).ConfigureAwait(false); Update(model); } diff --git a/src/Discord.Net.Rest/Entities/Messages/Attachment.cs b/src/Discord.Net.Rest/Entities/Messages/Attachment.cs index 1cd73518a..4e4849c51 100644 --- a/src/Discord.Net.Rest/Entities/Messages/Attachment.cs +++ b/src/Discord.Net.Rest/Entities/Messages/Attachment.cs @@ -21,8 +21,10 @@ namespace Discord public int? Height { get; } /// public int? Width { get; } + /// + public bool Ephemeral { get; } - internal Attachment(ulong id, string filename, string url, string proxyUrl, int size, int? height, int? width) + internal Attachment(ulong id, string filename, string url, string proxyUrl, int size, int? height, int? width, bool? ephemeral) { Id = id; Filename = filename; @@ -31,12 +33,14 @@ namespace Discord Size = size; Height = height; Width = width; + Ephemeral = ephemeral.GetValueOrDefault(false); } internal static Attachment Create(Model model) { return new Attachment(model.Id, model.Filename, model.Url, model.ProxyUrl, model.Size, model.Height.IsSpecified ? model.Height.Value : (int?)null, - model.Width.IsSpecified ? model.Width.Value : (int?)null); + model.Width.IsSpecified ? model.Width.Value : (int?)null, + model.Ephemeral.ToNullable()); } /// diff --git a/src/Discord.Net.Rest/Entities/Messages/CustomSticker.cs b/src/Discord.Net.Rest/Entities/Messages/CustomSticker.cs index 94caf0f3f..6fd0f7700 100644 --- a/src/Discord.Net.Rest/Entities/Messages/CustomSticker.cs +++ b/src/Discord.Net.Rest/Entities/Messages/CustomSticker.cs @@ -1,8 +1,5 @@ using System; -using System.Collections.Generic; using System.Diagnostics; -using System.Linq; -using System.Text; using System.Threading.Tasks; using Model = Discord.API.Sticker; @@ -26,7 +23,7 @@ namespace Discord.Rest /// Gets the guild that this custom sticker is in. /// /// - /// Note: This property can be if the sticker wasnt fetched from a guild. + /// Note: This property can be if the sticker wasn't fetched from a guild. /// public RestGuild Guild { get; private set; } diff --git a/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs b/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs index e10be2c0a..6b9e134c1 100644 --- a/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs +++ b/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs @@ -84,7 +84,7 @@ namespace Discord.Rest { Content = args.Content, Embeds = apiEmbeds?.ToArray() ?? Optional.Unspecified, - Components = args.Components.IsSpecified ? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? new API.ActionRowComponent[0] : Optional.Unspecified, + Components = args.Components.IsSpecified ? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Array.Empty() : Optional.Unspecified, Flags = args.Flags, AllowedMentions = args.AllowedMentions.IsSpecified ? args.AllowedMentions.Value.ToModel() : Optional.Unspecified, }; @@ -151,7 +151,7 @@ namespace Discord.Rest 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() ?? new API.ActionRowComponent[0] : Optional.Unspecified, + Components = args.Components.IsSpecified ? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Array.Empty() : Optional.Unspecified, }; return await client.ApiClient.ModifyMessageAsync(channelId, msgId, apiArgs, options).ConfigureAwait(false); } @@ -246,6 +246,12 @@ namespace Discord.Rest return System.Web.HttpUtility.UrlEncode(text); #endif } + public static string SanitizeMessage(IMessage message) + { + var newContent = MentionUtils.Resolve(message, 0, TagHandling.FullName, TagHandling.FullName, TagHandling.FullName, TagHandling.FullName, TagHandling.FullName); + newContent = Format.StripMarkDown(newContent); + return newContent; + } public static async Task PinAsync(IMessage msg, BaseDiscordClient client, RequestOptions options) diff --git a/src/Discord.Net.Rest/Entities/Messages/RestFollowupMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestFollowupMessage.cs index 103c856be..693d36e56 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestFollowupMessage.cs +++ b/src/Discord.Net.Rest/Entities/Messages/RestFollowupMessage.cs @@ -1,11 +1,7 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Threading.Tasks; using Model = Discord.API.Message; - namespace Discord.Rest { /// @@ -35,11 +31,11 @@ namespace Discord.Rest } /// - /// Deletes this object and all of it's childern. + /// Deletes this object and all of it's children. /// /// A task that represents the asynchronous delete operation. public Task DeleteAsync() - => InteractionHelper.DeleteFollowupMessage(Discord, this); + => InteractionHelper.DeleteFollowupMessageAsync(Discord, this); /// /// Modifies this interaction followup message. @@ -60,17 +56,17 @@ namespace Discord.Rest /// A task that represents the asynchronous modification operation. /// /// The token used to modify/delete this message expired. - /// /// Somthing went wrong during the request. + /// /// Something went wrong during the request. public new async Task ModifyAsync(Action func, RequestOptions options = null) { try { - var model = await InteractionHelper.ModifyFollowupMessage(Discord, this, func, options).ConfigureAwait(false); + var model = await InteractionHelper.ModifyFollowupMessageAsync(Discord, this, func, options).ConfigureAwait(false); Update(model); } - catch (Discord.Net.HttpException x) + catch (Net.HttpException x) { - if(x.HttpCode == System.Net.HttpStatusCode.NotFound) + if (x.HttpCode == System.Net.HttpStatusCode.NotFound) { throw new InvalidOperationException("The token of this message has expired!", x); } diff --git a/src/Discord.Net.Rest/Entities/Messages/RestInteractionMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestInteractionMessage.cs index ae1b5f2fb..26beb03b6 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestInteractionMessage.cs +++ b/src/Discord.Net.Rest/Entities/Messages/RestInteractionMessage.cs @@ -31,11 +31,11 @@ namespace Discord.Rest } /// - /// Deletes this object and all of it's childern. + /// Deletes this object and all of it's children. /// /// A task that represents the asynchronous delete operation. public Task DeleteAsync() - => InteractionHelper.DeletedInteractionResponse(Discord, this); + => InteractionHelper.DeleteInteractionResponseAsync(Discord, this); /// /// Modifies this interaction response @@ -56,15 +56,15 @@ namespace Discord.Rest /// A task that represents the asynchronous modification operation. /// /// The token used to modify/delete this message expired. - /// /// Somthing went wrong during the request. + /// /// Something went wrong during the request. public new async Task ModifyAsync(Action func, RequestOptions options = null) { try { - var model = await InteractionHelper.ModifyInteractionResponse(Discord, Token, func, options).ConfigureAwait(false); + var model = await InteractionHelper.ModifyInteractionResponseAsync(Discord, Token, func, options).ConfigureAwait(false); Update(model); } - catch (Discord.Net.HttpException x) + catch (Net.HttpException x) { if (x.HttpCode == System.Net.HttpStatusCode.NotFound) { diff --git a/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs index 09e262f00..c48a60aac 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs +++ b/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs @@ -15,6 +15,7 @@ namespace Discord.Rest { private long _timestampTicks; private ImmutableArray _reactions = ImmutableArray.Create(); + private ImmutableArray _userMentions = ImmutableArray.Create(); /// public IMessageChannel Channel { get; } @@ -28,6 +29,9 @@ namespace Discord.Rest /// public string Content { get; private set; } + /// + public string CleanContent => MessageHelper.SanitizeMessage(this); + /// public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); /// @@ -53,10 +57,6 @@ namespace Discord.Rest public virtual IReadOnlyCollection MentionedChannelIds => ImmutableArray.Create(); /// public virtual IReadOnlyCollection MentionedRoleIds => ImmutableArray.Create(); - /// - /// Gets a collection of the mentioned users in the message. - /// - public virtual IReadOnlyCollection MentionedUsers => ImmutableArray.Create(); /// public virtual IReadOnlyCollection Tags => ImmutableArray.Create(); /// @@ -70,6 +70,11 @@ namespace Discord.Rest public MessageApplication Application { get; private set; } /// public MessageReference Reference { get; private set; } + + /// + /// Gets the interaction this message is a response to. + /// + public MessageInteraction Interaction { get; private set; } /// public MessageFlags? Flags { get; private set; } /// @@ -77,6 +82,10 @@ namespace Discord.Rest /// public IReadOnlyCollection Components { get; private set; } + /// + /// Gets a collection of the mentioned users in the message. + /// + public IReadOnlyCollection MentionedUsers => _userMentions; internal RestMessage(BaseDiscordClient discord, ulong id, IMessageChannel channel, IUser author, MessageSource source) : base(discord, id) @@ -207,8 +216,31 @@ namespace Discord.Rest } else _reactions = ImmutableArray.Create(); - } + if (model.Interaction.IsSpecified) + { + Interaction = new MessageInteraction(model.Interaction.Value.Id, + model.Interaction.Value.Type, + model.Interaction.Value.Name, + RestUser.Create(Discord, model.Interaction.Value.User)); + } + + if (model.UserMentions.IsSpecified) + { + var value = model.UserMentions.Value; + if (value.Length > 0) + { + var newMentions = ImmutableArray.CreateBuilder(value.Length); + for (int i = 0; i < value.Length; i++) + { + var val = value[i]; + if (val != null) + newMentions.Add(RestUser.Create(Discord, val)); + } + _userMentions = newMentions.ToImmutable(); + } + } + } /// public async Task UpdateAsync(RequestOptions options = null) { @@ -238,6 +270,9 @@ namespace Discord.Rest /// IReadOnlyCollection IMessage.Components => Components; + /// + IMessageInteraction IMessage.Interaction => Interaction; + /// IReadOnlyCollection IMessage.Stickers => Stickers; diff --git a/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs index e6f3ac30d..083a8e72c 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs +++ b/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs @@ -20,7 +20,6 @@ namespace Discord.Rest private ImmutableArray _embeds = ImmutableArray.Create(); private ImmutableArray _tags = ImmutableArray.Create(); private ImmutableArray _roleMentionIds = ImmutableArray.Create(); - private ImmutableArray _userMentions = ImmutableArray.Create(); private ImmutableArray _stickers = ImmutableArray.Create(); /// @@ -42,8 +41,6 @@ namespace Discord.Rest /// public override IReadOnlyCollection MentionedRoleIds => _roleMentionIds; /// - public override IReadOnlyCollection MentionedUsers => _userMentions; - /// public override IReadOnlyCollection Tags => _tags; /// public override IReadOnlyCollection Stickers => _stickers; @@ -104,28 +101,12 @@ namespace Discord.Rest _embeds = ImmutableArray.Create(); } - if (model.UserMentions.IsSpecified) - { - var value = model.UserMentions.Value; - if (value.Length > 0) - { - var newMentions = ImmutableArray.CreateBuilder(value.Length); - for (int i = 0; i < value.Length; i++) - { - var val = value[i]; - if (val.Object != null) - newMentions.Add(RestUser.Create(Discord, val.Object)); - } - _userMentions = newMentions.ToImmutable(); - } - } - var guildId = (Channel as IGuildChannel)?.GuildId; var guild = guildId != null ? (Discord as IDiscordClient).GetGuildAsync(guildId.Value, CacheMode.CacheOnly).Result : null; if (model.Content.IsSpecified) { var text = model.Content.Value; - _tags = MessageHelper.ParseTags(text, null, guild, _userMentions); + _tags = MessageHelper.ParseTags(text, null, guild, MentionedUsers); model.Content = text; } diff --git a/src/Discord.Net.Rest/Entities/Messages/Sticker.cs b/src/Discord.Net.Rest/Entities/Messages/Sticker.cs index 75bc9bd38..accdbe66a 100644 --- a/src/Discord.Net.Rest/Entities/Messages/Sticker.cs +++ b/src/Discord.Net.Rest/Entities/Messages/Sticker.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; @@ -43,8 +44,8 @@ namespace Discord.Rest { PackId = model.PackId; Name = model.Name; - Description = model.Desription; - Tags = model.Tags.IsSpecified ? model.Tags.Value.Split(',').Select(x => x.Trim()).ToArray() : new string[0]; + Description = model.Description; + Tags = model.Tags.IsSpecified ? model.Tags.Value.Split(',').Select(x => x.Trim()).ToArray() : Array.Empty(); Type = model.Type; SortOrder = model.SortValue; IsAvailable = model.Available; diff --git a/src/Discord.Net.Rest/Entities/Messages/StickerItem.cs b/src/Discord.Net.Rest/Entities/Messages/StickerItem.cs index f1bcb9951..0ce4f634b 100644 --- a/src/Discord.Net.Rest/Entities/Messages/StickerItem.cs +++ b/src/Discord.Net.Rest/Entities/Messages/StickerItem.cs @@ -1,7 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Threading.Tasks; using Model = Discord.API.StickerItem; @@ -31,15 +27,13 @@ namespace Discord.Rest /// /// A task representing the download operation, the result of the task is a sticker object. /// - public async Task ResolveStickerAsync() { var model = await Discord.ApiClient.GetStickerAsync(Id); - if (model.GuildId.IsSpecified) - return CustomSticker.Create(Discord, model, model.GuildId.Value, model.User.IsSpecified ? model.User.Value.Id : null); - else - return Sticker.Create(Discord, model); + return model.GuildId.IsSpecified + ? CustomSticker.Create(Discord, model, model.GuildId.Value, model.User.IsSpecified ? model.User.Value.Id : null) + : Sticker.Create(Discord, model); } } } diff --git a/src/Discord.Net.Rest/Entities/Roles/RestRole.cs b/src/Discord.Net.Rest/Entities/Roles/RestRole.cs index 3b6d8d3f7..e0be8e998 100644 --- a/src/Discord.Net.Rest/Entities/Roles/RestRole.cs +++ b/src/Discord.Net.Rest/Entities/Roles/RestRole.cs @@ -24,6 +24,8 @@ namespace Discord.Rest /// public string Name { get; private set; } /// + public string Icon { get; private set; } + /// public GuildPermissions Permissions { get; private set; } /// public int Position { get; private set; } @@ -53,6 +55,7 @@ namespace Discord.Rest internal void Update(Model model) { Name = model.Name; + Icon = model.Icon; IsHoisted = model.Hoist; IsManaged = model.Managed; IsMentionable = model.Mentionable; @@ -73,6 +76,10 @@ namespace Discord.Rest public Task DeleteAsync(RequestOptions options = null) => RoleHelper.DeleteAsync(this, Discord, options); + /// + public string GetIconUrl() + => CDN.GetGuildRoleIconUrl(Id, Icon); + /// public int CompareTo(IRole role) => RoleUtils.Compare(this, role); diff --git a/src/Discord.Net.Rest/Entities/Users/RestThreadUser.cs b/src/Discord.Net.Rest/Entities/Users/RestThreadUser.cs index 4dd7f7aaf..82830dafd 100644 --- a/src/Discord.Net.Rest/Entities/Users/RestThreadUser.cs +++ b/src/Discord.Net.Rest/Entities/Users/RestThreadUser.cs @@ -1,8 +1,4 @@ using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using System.Text; using System.Threading.Tasks; using Model = Discord.API.ThreadMember; diff --git a/src/Discord.Net.Rest/Entities/Webhooks/WebhookHelper.cs b/src/Discord.Net.Rest/Entities/Webhooks/WebhookHelper.cs index 50e9cab78..0b61b6c22 100644 --- a/src/Discord.Net.Rest/Entities/Webhooks/WebhookHelper.cs +++ b/src/Discord.Net.Rest/Entities/Webhooks/WebhookHelper.cs @@ -33,6 +33,5 @@ namespace Discord.Rest { await client.ApiClient.DeleteWebhookAsync(webhook.Id, options).ConfigureAwait(false); } - } } diff --git a/src/Discord.Net.Rest/Net/Converters/InteractionConverter.cs b/src/Discord.Net.Rest/Net/Converters/InteractionConverter.cs index 8159165c9..ca8214682 100644 --- a/src/Discord.Net.Rest/Net/Converters/InteractionConverter.cs +++ b/src/Discord.Net.Rest/Net/Converters/InteractionConverter.cs @@ -1,10 +1,6 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Discord.Net.Converters { diff --git a/src/Discord.Net.Rest/Net/Converters/MessageComponentConverter.cs b/src/Discord.Net.Rest/Net/Converters/MessageComponentConverter.cs index 2203008d6..0bf11a369 100644 --- a/src/Discord.Net.Rest/Net/Converters/MessageComponentConverter.cs +++ b/src/Discord.Net.Rest/Net/Converters/MessageComponentConverter.cs @@ -1,10 +1,6 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Discord.Net.Converters { diff --git a/src/Discord.Net.Rest/Net/Queue/RequestQueueBucket.cs b/src/Discord.Net.Rest/Net/Queue/RequestQueueBucket.cs index 52fea021b..52debd87f 100644 --- a/src/Discord.Net.Rest/Net/Queue/RequestQueueBucket.cs +++ b/src/Discord.Net.Rest/Net/Queue/RequestQueueBucket.cs @@ -355,7 +355,7 @@ namespace Discord.Net.Queue if (info.Limit.HasValue && WindowCount != info.Limit.Value) { WindowCount = info.Limit.Value; - _semaphore = info.Remaining.Value; + _semaphore = is429 ? 0 : info.Remaining.Value; #if DEBUG_LIMITS Debug.WriteLine($"[{id}] Upgraded Semaphore to {info.Remaining.Value}/{WindowCount}"); #endif @@ -435,7 +435,7 @@ namespace Discord.Net.Queue if (!hasQueuedReset || resetTick > _resetTick) { _resetTick = resetTick; - LastAttemptAt = resetTick.Value; //Make sure we dont destroy this until after its been reset + LastAttemptAt = resetTick.Value; //Make sure we don't destroy this until after its been reset #if DEBUG_LIMITS Debug.WriteLine($"[{id}] Reset in {(int)Math.Ceiling((resetTick - DateTimeOffset.UtcNow).Value.TotalMilliseconds)} ms"); #endif @@ -456,7 +456,7 @@ namespace Discord.Net.Queue lock (_lock) { millis = (int)Math.Ceiling((_resetTick.Value - DateTimeOffset.UtcNow).TotalMilliseconds); - if (millis <= 0) //Make sure we havent gotten a more accurate reset time + if (millis <= 0) //Make sure we haven't gotten a more accurate reset time { #if DEBUG_LIMITS Debug.WriteLine($"[{id}] * Reset *"); diff --git a/src/Discord.Net.WebSocket/API/Gateway/ApplicationCommandCreatedUpdatedEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/ApplicationCommandCreatedUpdatedEvent.cs index 9d41ecf48..91dcbde11 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/ApplicationCommandCreatedUpdatedEvent.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/ApplicationCommandCreatedUpdatedEvent.cs @@ -1,13 +1,8 @@ using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Discord.API.Gateway { - internal class ApplicationCommandCreatedUpdatedEvent : API.ApplicationCommand + internal class ApplicationCommandCreatedUpdatedEvent : ApplicationCommand { [JsonProperty("guild_id")] public Optional GuildId { get; set; } diff --git a/src/Discord.Net.WebSocket/API/Gateway/GuildStickerUpdateEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/GuildStickerUpdateEvent.cs index 8f3ba683b..f0ecd3a4f 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/GuildStickerUpdateEvent.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/GuildStickerUpdateEvent.cs @@ -1,9 +1,4 @@ using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Discord.API.Gateway { diff --git a/src/Discord.Net.WebSocket/API/Gateway/ThreadListSyncEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/ThreadListSyncEvent.cs index ab7989268..5084f6c95 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/ThreadListSyncEvent.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/ThreadListSyncEvent.cs @@ -1,9 +1,4 @@ using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Discord.API.Gateway { diff --git a/src/Discord.Net.WebSocket/API/Gateway/ThreadMembersUpdate.cs b/src/Discord.Net.WebSocket/API/Gateway/ThreadMembersUpdate.cs index b6acba6ae..83d2c0edd 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/ThreadMembersUpdate.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/ThreadMembersUpdate.cs @@ -1,9 +1,4 @@ using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Discord.API.Gateway { diff --git a/src/Discord.Net.WebSocket/API/Voice/ReadyEvent.cs b/src/Discord.Net.WebSocket/API/Voice/ReadyEvent.cs index 10479cdb2..fb910573a 100644 --- a/src/Discord.Net.WebSocket/API/Voice/ReadyEvent.cs +++ b/src/Discord.Net.WebSocket/API/Voice/ReadyEvent.cs @@ -14,7 +14,7 @@ namespace Discord.API.Voice [JsonProperty("modes")] public string[] Modes { get; set; } [JsonProperty("heartbeat_interval")] - [Obsolete("This field is errorneous and should not be used", true)] + [Obsolete("This field is erroneous and should not be used", true)] public int HeartbeatInterval { get; set; } } } diff --git a/src/Discord.Net.WebSocket/Audio/Streams/BufferedWriteStream.cs b/src/Discord.Net.WebSocket/Audio/Streams/BufferedWriteStream.cs index a2de252a2..25afde784 100644 --- a/src/Discord.Net.WebSocket/Audio/Streams/BufferedWriteStream.cs +++ b/src/Discord.Net.WebSocket/Audio/Streams/BufferedWriteStream.cs @@ -125,7 +125,7 @@ namespace Discord.Audio.Streams timestamp += OpusEncoder.FrameSamplesPerChannel; } #if DEBUG - var _ = _logger?.DebugAsync("Buffer underrun"); + var _ = _logger?.DebugAsync("Buffer under run"); #endif } } diff --git a/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs b/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs index 58de002a0..b7bcfdf2e 100644 --- a/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs +++ b/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs @@ -469,7 +469,7 @@ namespace Discord.WebSocket #region Interactions /// - /// Fired when an Interaction is created. This event covers all types of interactions including but not limited to: buttons, select menus, slash commands. + /// Fired when an Interaction is created. This event covers all types of interactions including but not limited to: buttons, select menus, slash commands, autocompletes. /// /// /// @@ -535,6 +535,15 @@ namespace Discord.WebSocket remove => _messageCommandExecuted.Remove(value); } internal readonly AsyncEvent> _messageCommandExecuted = new AsyncEvent>(); + /// + /// Fired when an autocomplete is used and its interaction is received. + /// + public event Func AutocompleteExecuted + { + add => _autocompleteExecuted.Add(value); + remove => _autocompleteExecuted.Remove(value); + } + internal readonly AsyncEvent> _autocompleteExecuted = new AsyncEvent>(); /// /// Fired when a guild application command is created. diff --git a/src/Discord.Net.WebSocket/DiscordShardedClient.cs b/src/Discord.Net.WebSocket/DiscordShardedClient.cs index faafa918f..613864fdc 100644 --- a/src/Discord.Net.WebSocket/DiscordShardedClient.cs +++ b/src/Discord.Net.WebSocket/DiscordShardedClient.cs @@ -466,6 +466,7 @@ namespace Discord.WebSocket client.SlashCommandExecuted += (arg) => _slashCommandExecuted.InvokeAsync(arg); client.UserCommandExecuted += (arg) => _userCommandExecuted.InvokeAsync(arg); client.MessageCommandExecuted += (arg) => _messageCommandExecuted.InvokeAsync(arg); + client.AutocompleteExecuted += (arg) => _autocompleteExecuted.InvokeAsync(arg); client.ThreadUpdated += (thread1, thread2) => _threadUpdated.InvokeAsync(thread1, thread2); client.ThreadCreated += (thread) => _threadCreated.InvokeAsync(thread); diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs index 61ee5f3cf..264151e94 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -51,7 +51,7 @@ namespace Discord.WebSocket /// Provides access to a REST-only client with a shared state from this client. /// public override DiscordSocketRestClient Rest { get; } - /// Gets the shard of of this client. + /// Gets the shard of this client. public int ShardId { get; } /// Gets the current connection state of this client. public ConnectionState ConnectionState => _connection.State; @@ -434,11 +434,11 @@ namespace Discord.WebSocket public async Task CreateGlobalApplicationCommandAsync(ApplicationCommandProperties properties, RequestOptions options = null) { - var model = await InteractionHelper.CreateGlobalCommand(this, properties, options).ConfigureAwait(false); + var model = await InteractionHelper.CreateGlobalCommandAsync(this, properties, options).ConfigureAwait(false); var entity = State.GetOrAddCommand(model.Id, (id) => SocketApplicationCommand.Create(this, model)); - //Update it incase it was cached + //Update it in case it was cached entity.Update(model); return entity; @@ -446,7 +446,7 @@ namespace Discord.WebSocket public async Task> BulkOverwriteGlobalApplicationCommandsAsync( ApplicationCommandProperties[] properties, RequestOptions options = null) { - var models = await InteractionHelper.BulkOverwriteGlobalCommands(this, properties, options); + var models = await InteractionHelper.BulkOverwriteGlobalCommandsAsync(this, properties, options); var entities = models.Select(x => SocketApplicationCommand.Create(this, x)); @@ -1522,7 +1522,7 @@ namespace Discord.WebSocket } else { - //Edited message isnt in cache, create a detached one + //Edited message isn't in cache, create a detached one SocketUser author; if (data.Author.IsSpecified) { @@ -2125,6 +2125,9 @@ namespace Discord.WebSocket case SocketMessageCommand messageCommand: await TimedInvokeAsync(_messageCommandExecuted, nameof(MessageCommandExecuted), messageCommand).ConfigureAwait(false); break; + case SocketAutocompleteInteraction autocomplete: + await TimedInvokeAsync(_autocompleteExecuted, nameof(AutocompleteExecuted), autocomplete).ConfigureAwait(false); + break; } } else diff --git a/src/Discord.Net.WebSocket/DiscordSocketConfig.cs b/src/Discord.Net.WebSocket/DiscordSocketConfig.cs index e2e4f9897..6946a02b5 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketConfig.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketConfig.cs @@ -79,7 +79,7 @@ namespace Discord.WebSocket /// /// /// By default, the Discord gateway will only send offline members if a guild has less than a certain number - /// of members (determined by in this library). This behaviour is why + /// of members (determined by in this library). This behavior is why /// sometimes a user may be missing from the WebSocket cache for collections such as /// . /// diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketStageChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketStageChannel.cs index 890f58ed6..91bca5054 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketStageChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketStageChannel.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; -using System.Text; using System.Threading.Tasks; using Model = Discord.API.Channel; using StageInstance = Discord.API.StageInstance; @@ -11,7 +10,7 @@ using StageInstance = Discord.API.StageInstance; namespace Discord.WebSocket { /// - /// Represents a stage channel recieved over the gateway. + /// Represents a stage channel received over the gateway. /// public class SocketStageChannel : SocketVoiceChannel, IStageChannel { @@ -25,7 +24,7 @@ namespace Discord.WebSocket public bool? IsDiscoverableDisabled { get; private set; } /// - public bool IsLive { get; private set; } = false; + public bool IsLive { get; private set; } /// /// Returns if the current user is a speaker within the stage, otherwise . @@ -42,10 +41,7 @@ namespace Discord.WebSocket internal new SocketStageChannel Clone() => MemberwiseClone() as SocketStageChannel; internal SocketStageChannel(DiscordSocketClient discord, ulong id, SocketGuild guild) - : base(discord, id, guild) - { - - } + : base(discord, id, guild) { } internal new static SocketStageChannel Create(SocketGuild guild, ClientState state, Model model) { @@ -54,11 +50,6 @@ namespace Discord.WebSocket return entity; } - internal override void Update(ClientState state, Model model) - { - base.Update(state, model); - } - internal void Update(StageInstance model, bool isLive = false) { IsLive = isLive; @@ -79,11 +70,11 @@ namespace Discord.WebSocket /// public async Task StartStageAsync(string topic, StagePrivacyLevel privacyLevel = StagePrivacyLevel.GuildOnly, RequestOptions options = null) { - var args = new API.Rest.CreateStageInstanceParams() + var args = new API.Rest.CreateStageInstanceParams { ChannelId = Id, Topic = topic, - PrivacyLevel = privacyLevel, + PrivacyLevel = privacyLevel }; var model = await Discord.ApiClient.CreateStageInstanceAsync(args, options).ConfigureAwait(false); @@ -104,13 +95,13 @@ namespace Discord.WebSocket { await Discord.ApiClient.DeleteStageInstanceAsync(Id, options); - Update(null, false); + Update(null); } /// public Task RequestToSpeakAsync(RequestOptions options = null) { - var args = new API.Rest.ModifyVoiceStateParams() + var args = new API.Rest.ModifyVoiceStateParams { ChannelId = Id, RequestToSpeakTimestamp = DateTimeOffset.UtcNow @@ -121,7 +112,7 @@ namespace Discord.WebSocket /// public Task BecomeSpeakerAsync(RequestOptions options = null) { - var args = new API.Rest.ModifyVoiceStateParams() + var args = new API.Rest.ModifyVoiceStateParams { ChannelId = Id, Suppressed = false @@ -132,7 +123,7 @@ namespace Discord.WebSocket /// public Task StopSpeakingAsync(RequestOptions options = null) { - var args = new API.Rest.ModifyVoiceStateParams() + var args = new API.Rest.ModifyVoiceStateParams { ChannelId = Id, Suppressed = true @@ -143,7 +134,7 @@ namespace Discord.WebSocket /// public Task MoveToSpeakerAsync(IGuildUser user, RequestOptions options = null) { - var args = new API.Rest.ModifyVoiceStateParams() + var args = new API.Rest.ModifyVoiceStateParams { ChannelId = Id, Suppressed = false @@ -155,7 +146,7 @@ namespace Discord.WebSocket /// public Task RemoveFromSpeakerAsync(IGuildUser user, RequestOptions options = null) { - var args = new API.Rest.ModifyVoiceStateParams() + var args = new API.Rest.ModifyVoiceStateParams { ChannelId = Id, Suppressed = true diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketThreadChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketThreadChannel.cs index 3fd47ec87..7fcafc14a 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketThreadChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketThreadChannel.cs @@ -5,11 +5,9 @@ using System.Collections.Immutable; using System.Diagnostics; using System.IO; using System.Linq; -using System.Text; using System.Threading.Tasks; using Model = Discord.API.Channel; using ThreadMember = Discord.API.ThreadMember; -using MemberUpdates = Discord.API.Gateway.ThreadMembersUpdated; using System.Collections.Concurrent; namespace Discord.WebSocket @@ -72,14 +70,13 @@ namespace Discord.WebSocket public new IReadOnlyCollection Users => _members.Values.ToImmutableArray(); - - private ConcurrentDictionary _members; + private readonly ConcurrentDictionary _members; private string DebuggerDisplay => $"{Name} ({Id}, Thread)"; - private bool _usersDownloaded = false; + private bool _usersDownloaded; - private object _downloadLock = new object(); + private readonly object _downloadLock = new object(); internal SocketThreadChannel(DiscordSocketClient discord, SocketGuild guild, ulong id, SocketTextChannel parent) : base(discord, id, guild) @@ -103,12 +100,12 @@ namespace Discord.WebSocket Type = (ThreadType)model.Type; MessageCount = model.MessageCount.GetValueOrDefault(-1); MemberCount = model.MemberCount.GetValueOrDefault(-1); - + if (model.ThreadMetadata.IsSpecified) { IsArchived = model.ThreadMetadata.Value.Archived; ArchiveTimestamp = model.ThreadMetadata.Value.ArchiveTimestamp; - AutoArchiveDuration = (ThreadArchiveDuration)model.ThreadMetadata.Value.AutoArchiveDuration; + AutoArchiveDuration = model.ThreadMetadata.Value.AutoArchiveDuration; IsLocked = model.ThreadMetadata.Value.Locked.GetValueOrDefault(false); } @@ -173,12 +170,11 @@ namespace Discord.WebSocket return Users; } - /// /// Downloads all users that have access to this thread. /// /// The options to be used when sending the request. - /// A task representing the asyncronous download operation. + /// A task representing the asynchronous download operation. public async Task DownloadUsersAsync(RequestOptions options = null) { var users = await Discord.ApiClient.ListThreadMembersAsync(Id, options); @@ -193,7 +189,7 @@ namespace Discord.WebSocket } } } - + internal new SocketThreadChannel Clone() => MemberwiseClone() as SocketThreadChannel; /// @@ -210,7 +206,7 @@ namespace Discord.WebSocket /// The to add. /// The options to be used when sending the request. /// - /// A task that represents the asynchronous operation of adding a member to a thread. + /// A task that represents the asynchronous operation of adding a member to a thread. /// public Task AddUserAsync(IGuildUser user, RequestOptions options = null) => Discord.ApiClient.AddThreadMemberAsync(Id, user.Id, options); @@ -221,60 +217,59 @@ namespace Discord.WebSocket /// The to remove from this thread. /// The options to be used when sending the request. /// - /// A task that represents the asynchronous operation of removing a user from this thread. + /// A task that represents the asynchronous operation of removing a user from this thread. /// public Task RemoveUserAsync(IGuildUser user, RequestOptions options = null) => Discord.ApiClient.RemoveThreadMemberAsync(Id, user.Id, options); - /// /// /// This method is not supported in threads. /// public override Task AddPermissionOverwriteAsync(IRole role, OverwritePermissions permissions, RequestOptions options = null) - => throw new NotImplementedException(); + => throw new NotSupportedException("This method is not supported in threads."); /// /// /// This method is not supported in threads. /// public override Task AddPermissionOverwriteAsync(IUser user, OverwritePermissions permissions, RequestOptions options = null) - => throw new NotImplementedException(); + => throw new NotSupportedException("This method is not supported in threads."); /// /// /// This method is not supported in threads. /// public override Task CreateInviteAsync(int? maxAge = 86400, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null) - => throw new NotImplementedException(); + => throw new NotSupportedException("This method is not supported in threads."); /// /// /// This method is not supported in threads. /// public override Task CreateInviteToApplicationAsync(ulong applicationId, int? maxAge, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null) - => throw new NotImplementedException(); + => throw new NotSupportedException("This method is not supported in threads."); /// /// /// This method is not supported in threads. /// public override Task CreateInviteToStreamAsync(IUser user, int? maxAge, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null) - => throw new NotImplementedException(); + => throw new NotSupportedException("This method is not supported in threads."); /// /// /// This method is not supported in threads. /// public override Task CreateWebhookAsync(string name, Stream avatar = null, RequestOptions options = null) - => throw new NotImplementedException(); + => throw new NotSupportedException("This method is not supported in threads."); /// /// /// This method is not supported in threads. /// public override Task> GetInvitesAsync(RequestOptions options = null) - => throw new NotImplementedException(); + => throw new NotSupportedException("This method is not supported in threads."); /// /// @@ -295,14 +290,14 @@ namespace Discord.WebSocket /// This method is not supported in threads. /// public override Task GetWebhookAsync(ulong id, RequestOptions options = null) - => throw new NotImplementedException(); + => throw new NotSupportedException("This method is not supported in threads."); /// /// /// This method is not supported in threads. /// public override Task> GetWebhooksAsync(RequestOptions options = null) - => throw new NotImplementedException(); + => throw new NotSupportedException("This method is not supported in threads."); /// /// @@ -316,28 +311,28 @@ namespace Discord.WebSocket /// This method is not supported in threads. /// public override Task RemovePermissionOverwriteAsync(IRole role, RequestOptions options = null) - => throw new NotImplementedException(); + => throw new NotSupportedException("This method is not supported in threads."); /// /// /// This method is not supported in threads. /// public override Task RemovePermissionOverwriteAsync(IUser user, RequestOptions options = null) - => throw new NotImplementedException(); + => throw new NotSupportedException("This method is not supported in threads."); /// /// /// This method is not supported in threads. /// public override IReadOnlyCollection PermissionOverwrites - => throw new NotImplementedException(); + => throw new NotSupportedException("This method is not supported in threads."); /// /// /// This method is not supported in threads. /// public override Task SyncPermissionsAsync(RequestOptions options = null) - => throw new NotImplementedException(); + => throw new NotSupportedException("This method is not supported in threads."); string IChannel.Name => Name; } diff --git a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs index efc6f3ba5..23712be66 100644 --- a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs +++ b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs @@ -904,7 +904,7 @@ namespace Discord.WebSocket /// public async Task CreateApplicationCommandAsync(ApplicationCommandProperties properties, RequestOptions options = null) { - var model = await InteractionHelper.CreateGuildCommand(Discord, Id, properties, options); + var model = await InteractionHelper.CreateGuildCommandAsync(Discord, Id, properties, options); var entity = Discord.State.GetOrAddCommand(model.Id, (id) => SocketApplicationCommand.Create(Discord, model)); @@ -924,7 +924,7 @@ namespace Discord.WebSocket public async Task> BulkOverwriteApplicationCommandAsync(ApplicationCommandProperties[] properties, RequestOptions options = null) { - var models = await InteractionHelper.BulkOverwriteGuildCommands(Discord, Id, properties, options); + var models = await InteractionHelper.BulkOverwriteGuildCommandsAsync(Discord, Id, properties, options); var entities = models.Select(x => SocketApplicationCommand.Create(Discord, x)); @@ -1549,7 +1549,7 @@ namespace Discord.WebSocket } internal async Task FinishConnectAudio(string url, string token) { - //TODO: Mem Leak: Disconnected/Connected handlers arent cleaned up + //TODO: Mem Leak: Disconnected/Connected handlers aren't cleaned up var voiceState = GetVoiceState(Discord.CurrentUser.Id).Value; await _audioLock.WaitAsync().ConfigureAwait(false); diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/Message Commands/SocketMessageCommand.cs b/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/Message Commands/SocketMessageCommand.cs index efd99c2c4..6d0f5ced0 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/Message Commands/SocketMessageCommand.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/Message Commands/SocketMessageCommand.cs @@ -1,7 +1,3 @@ -using Discord.Rest; -using System; -using System.Linq; -using System.Threading.Tasks; using DataModel = Discord.API.ApplicationCommandInteractionData; using Model = Discord.API.Interaction; @@ -20,8 +16,8 @@ namespace Discord.WebSocket internal SocketMessageCommand(DiscordSocketClient client, Model model, ISocketMessageChannel channel) : base(client, model, channel) { - var dataModel = model.Data.IsSpecified ? - (DataModel)model.Data.Value + var dataModel = model.Data.IsSpecified + ? (DataModel)model.Data.Value : null; ulong? guildId = null; @@ -31,7 +27,7 @@ namespace Discord.WebSocket Data = SocketMessageCommandData.Create(client, dataModel, model.Id, guildId); } - new internal static SocketInteraction Create(DiscordSocketClient client, Model model, ISocketMessageChannel channel) + internal new static SocketInteraction Create(DiscordSocketClient client, Model model, ISocketMessageChannel channel) { var entity = new SocketMessageCommand(client, model, channel); entity.Update(model); diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/Message Commands/SocketMessageCommandData.cs b/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/Message Commands/SocketMessageCommandData.cs index e78da223d..fc3be82b7 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/Message Commands/SocketMessageCommandData.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/Message Commands/SocketMessageCommandData.cs @@ -10,7 +10,7 @@ namespace Discord.WebSocket public class SocketMessageCommandData : SocketCommandBaseData { /// - /// Gets the messagte associated with this message command. + /// Gets the message associated with this message command. /// public SocketMessage Message => ResolvableData?.Messages.FirstOrDefault().Value; diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/User Commands/SocketUserCommand.cs b/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/User Commands/SocketUserCommand.cs index a61279db9..a4e162039 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/User Commands/SocketUserCommand.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/User Commands/SocketUserCommand.cs @@ -1,7 +1,3 @@ -using Discord.Rest; -using System; -using System.Linq; -using System.Threading.Tasks; using DataModel = Discord.API.ApplicationCommandInteractionData; using Model = Discord.API.Interaction; @@ -20,8 +16,8 @@ namespace Discord.WebSocket internal SocketUserCommand(DiscordSocketClient client, Model model, ISocketMessageChannel channel) : base(client, model, channel) { - var dataModel = model.Data.IsSpecified ? - (DataModel)model.Data.Value + var dataModel = model.Data.IsSpecified + ? (DataModel)model.Data.Value : null; ulong? guildId = null; @@ -31,11 +27,11 @@ namespace Discord.WebSocket Data = SocketUserCommandData.Create(client, dataModel, model.Id, guildId); } - new internal static SocketInteraction Create(DiscordSocketClient client, Model model, ISocketMessageChannel channel) + internal new static SocketInteraction Create(DiscordSocketClient client, Model model, ISocketMessageChannel channel) { var entity = new SocketUserCommand(client, model, channel); 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 aa3a915ae..926563c4c 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/Message Components/SocketMessageComponent.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/Message Components/SocketMessageComponent.cs @@ -10,38 +10,40 @@ using System.IO; 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 { /// /// The data received with this interaction, contains the button that was clicked. /// - new public SocketMessageComponentData Data { get; } + public new SocketMessageComponentData Data { get; } /// /// The message that contained the trigger for this interaction. /// public SocketUserMessage Message { get; private set; } + private object _lock = new object(); + internal override bool _hasResponded { get; set; } = false; + internal SocketMessageComponent(DiscordSocketClient client, Model model, ISocketMessageChannel channel) : base(client, model.Id, channel) { - var dataModel = model.Data.IsSpecified ? - (DataModel)model.Data.Value + var dataModel = model.Data.IsSpecified + ? (DataModel)model.Data.Value : null; Data = new SocketMessageComponentData(dataModel); } - new internal static SocketMessageComponent Create(DiscordSocketClient client, Model model, ISocketMessageChannel channel) + internal new static SocketMessageComponent Create(DiscordSocketClient client, Model model, ISocketMessageChannel channel) { var entity = new SocketMessageComponent(client, model, channel); entity.Update(model); return entity; } - internal override void Update(Model model) { base.Update(model); @@ -69,7 +71,6 @@ namespace Discord.WebSocket } } } - /// public override async Task RespondAsync( string text = null, @@ -84,20 +85,16 @@ namespace Discord.WebSocket if (!IsValidToken) throw new InvalidOperationException("Interaction token is no longer valid"); + if (!InteractionHelper.CanSendResponse(this)) + throw new TimeoutException($"Cannot respond to an interaction after {InteractionHelper.ResponseTimeLimit} seconds!"); + + embeds ??= Array.Empty(); if (embed != null) - { - if (embeds == null) - embeds = new[] { embed }; - else - { - List listEmbeds = embeds.ToList(); - listEmbeds.Insert(0, embed); - } - } + embeds = new[] { embed }.Concat(embeds).ToArray(); Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed."); Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed."); - Preconditions.AtMost(embeds?.Length ?? 0, 10, nameof(embeds), "A max of 10 embeds are allowed."); + Preconditions.AtMost(embeds.Length, 10, nameof(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 (allowedMentions != null && allowedMentions.AllowedTypes.HasValue) @@ -122,7 +119,7 @@ namespace Discord.WebSocket { Content = text ?? Optional.Unspecified, AllowedMentions = allowedMentions?.ToModel(), - Embeds = embeds?.Select(x => x.ToModel()).ToArray() ?? Optional.Unspecified, + Embeds = embeds.Select(x => x.ToModel()).ToArray(), TTS = isTTS, Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional.Unspecified } @@ -131,14 +128,27 @@ namespace Discord.WebSocket if (ephemeral) response.Data.Value.Flags = MessageFlags.Ephemeral; - await InteractionHelper.SendInteractionResponse(Discord, response, Id, Token, options); + lock (_lock) + { + if (_hasResponded) + { + throw new InvalidOperationException("Cannot respond, update, or defer twice to the same interaction"); + } + } + + await InteractionHelper.SendInteractionResponseAsync(Discord, response, Id, Token, options).ConfigureAwait(false); + + lock (_lock) + { + _hasResponded = true; + } } /// /// Updates the message which this component resides in with the type /// /// A delegate containing the properties to modify the message with. - /// The request options for this async request. + /// The request options for this request. /// A task that represents the asynchronous operation of updating the message. public async Task UpdateAsync(Action func, RequestOptions options = null) { @@ -148,11 +158,14 @@ namespace Discord.WebSocket if (!IsValidToken) throw new InvalidOperationException("Interaction token is no longer valid"); + if (!InteractionHelper.CanSendResponse(this)) + throw new TimeoutException($"Cannot respond to an interaction after {InteractionHelper.ResponseTimeLimit} seconds!"); + if (args.AllowedMentions.IsSpecified) { - var allowedMentions = args.AllowedMentions.Value; - Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions), "A max of 100 role Ids are allowed."); - Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions), "A max of 100 user Ids are allowed."); + var allowedMentions = args.AllowedMentions.Value; + Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions), "A max of 100 role Ids are allowed."); + Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions), "A max of 100 user Ids are allowed."); } var embed = args.Embed; @@ -204,13 +217,26 @@ namespace Discord.WebSocket AllowedMentions = args.AllowedMentions.IsSpecified ? args.AllowedMentions.Value?.ToModel() : Optional.Unspecified, Embeds = apiEmbeds?.ToArray() ?? Optional.Unspecified, Components = args.Components.IsSpecified - ? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? new API.ActionRowComponent[0] + ? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Array.Empty() : Optional.Unspecified, Flags = args.Flags.IsSpecified ? args.Flags.Value ?? Optional.Unspecified : Optional.Unspecified } }; - await InteractionHelper.SendInteractionResponse(Discord, response, Id, Token, options); + lock (_lock) + { + if (_hasResponded) + { + throw new InvalidOperationException("Cannot respond, update, or defer twice to the same interaction"); + } + } + + await InteractionHelper.SendInteractionResponseAsync(Discord, response, Id, Token, options).ConfigureAwait(false); + + lock (_lock) + { + _hasResponded = true; + } } /// @@ -227,41 +253,34 @@ namespace Discord.WebSocket if (!IsValidToken) throw new InvalidOperationException("Interaction token is no longer valid"); + embeds ??= Array.Empty(); if (embed != null) - { - if (embeds == null) - embeds = new[] { embed }; - else - { - List listEmbeds = embeds.ToList(); - listEmbeds.Insert(0, embed); - } - } + embeds = new[] { embed }.Concat(embeds).ToArray(); Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed."); Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed."); - Preconditions.AtMost(embeds?.Length ?? 0, 10, nameof(embeds), "A max of 10 embeds are allowed."); + Preconditions.AtMost(embeds.Length, 10, nameof(embeds), "A max of 10 embeds are allowed."); var args = new API.Rest.CreateWebhookMessageParams { Content = text, 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, + Embeds = embeds.Select(x => x.ToModel()).ToArray(), + Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional.Unspecified }; if (ephemeral) args.Flags = MessageFlags.Ephemeral; - return await InteractionHelper.SendFollowupAsync(Discord.Rest, args, Token, Channel, options); + return await InteractionHelper.SendFollowupAsync(Discord.Rest, args, Token, Channel, options).ConfigureAwait(false); } /// public override async Task FollowupWithFileAsync( + Stream fileStream, + string fileName, string text = null, - Stream fileStream = null, - string fileName = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, @@ -273,20 +292,13 @@ namespace Discord.WebSocket if (!IsValidToken) throw new InvalidOperationException("Interaction token is no longer valid"); + embeds ??= Array.Empty(); if (embed != null) - { - if (embeds == null) - embeds = new[] { embed }; - else - { - List listEmbeds = embeds.ToList(); - listEmbeds.Insert(0, embed); - } - } + embeds = new[] { embed }.Concat(embeds).ToArray(); Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed."); Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed."); - Preconditions.AtMost(embeds?.Length ?? 0, 10, nameof(embeds), "A max of 10 embeds are allowed."); + Preconditions.AtMost(embeds.Length, 10, nameof(embeds), "A max of 10 embeds are allowed."); Preconditions.NotNull(fileStream, nameof(fileStream), "File Stream must have data"); Preconditions.NotNullOrWhitespace(fileName, nameof(fileName), "File Name must not be empty or null"); @@ -295,7 +307,7 @@ namespace Discord.WebSocket Content = text, AllowedMentions = allowedMentions?.ToModel() ?? Optional.Unspecified, IsTTS = isTTS, - Embeds = embeds?.Select(x => x.ToModel()).ToArray() ?? Optional.Unspecified, + Embeds = embeds.Select(x => x.ToModel()).ToArray(), Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional.Unspecified, File = fileStream is not null ? new MultipartFile(fileStream, fileName) : Optional.Unspecified }; @@ -303,13 +315,13 @@ namespace Discord.WebSocket if (ephemeral) args.Flags = MessageFlags.Ephemeral; - return await InteractionHelper.SendFollowupAsync(Discord.Rest, args, Token, Channel, options); + return await InteractionHelper.SendFollowupAsync(Discord.Rest, args, Token, Channel, options).ConfigureAwait(false); } /// public override async Task FollowupWithFileAsync( + string filePath, string text = null, - string filePath = null, string fileName = null, Embed[] embeds = null, bool isTTS = false, @@ -322,20 +334,13 @@ namespace Discord.WebSocket if (!IsValidToken) throw new InvalidOperationException("Interaction token is no longer valid"); + embeds ??= Array.Empty(); if (embed != null) - { - if (embeds == null) - embeds = new[] { embed }; - else - { - List listEmbeds = embeds.ToList(); - listEmbeds.Insert(0, embed); - } - } + embeds = new[] { embed }.Concat(embeds).ToArray(); Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed."); Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed."); - Preconditions.AtMost(embeds?.Length ?? 0, 10, nameof(embeds), "A max of 10 embeds are allowed."); + Preconditions.AtMost(embeds.Length, 10, nameof(embeds), "A max of 10 embeds are allowed."); Preconditions.NotNullOrWhitespace(filePath, nameof(filePath), "Path must exist"); var args = new API.Rest.CreateWebhookMessageParams @@ -343,7 +348,7 @@ namespace Discord.WebSocket Content = text, AllowedMentions = allowedMentions?.ToModel() ?? Optional.Unspecified, IsTTS = isTTS, - Embeds = embeds?.Select(x => x.ToModel()).ToArray() ?? Optional.Unspecified, + Embeds = embeds.Select(x => x.ToModel()).ToArray(), Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional.Unspecified, File = !string.IsNullOrEmpty(filePath) ? new MultipartFile(new MemoryStream(File.ReadAllBytes(filePath), false), fileName) : Optional.Unspecified }; @@ -351,40 +356,70 @@ namespace Discord.WebSocket if (ephemeral) args.Flags = MessageFlags.Ephemeral; - return await InteractionHelper.SendFollowupAsync(Discord.Rest, args, Token, Channel, options); + return await InteractionHelper.SendFollowupAsync(Discord.Rest, args, Token, Channel, options).ConfigureAwait(false); } /// /// Defers an interaction and responds with type 5 () /// /// to send this message ephemerally, otherwise . - /// The request options for this async request. + /// The request options for this request. /// /// A task that represents the asynchronous operation of acknowledging the interaction. /// - public Task DeferLoadingAsync(bool ephemeral = false, RequestOptions options = null) + public async Task DeferLoadingAsync(bool ephemeral = false, RequestOptions options = null) { - var response = new API.InteractionResponse() + if (!InteractionHelper.CanSendResponse(this)) + throw new TimeoutException($"Cannot defer an interaction after {InteractionHelper.ResponseTimeLimit} seconds of no response/acknowledgement"); + + var response = new API.InteractionResponse { Type = InteractionResponseType.DeferredChannelMessageWithSource, - Data = ephemeral ? new API.InteractionCallbackData() { Flags = MessageFlags.Ephemeral } : Optional.Unspecified - + Data = ephemeral ? new API.InteractionCallbackData { Flags = MessageFlags.Ephemeral } : Optional.Unspecified }; - return Discord.Rest.ApiClient.CreateInteractionResponseAsync(response, Id, Token, options); + lock (_lock) + { + if (_hasResponded) + { + throw new InvalidOperationException("Cannot respond or defer twice to the same interaction"); + } + } + + await Discord.Rest.ApiClient.CreateInteractionResponseAsync(response, Id, Token, options).ConfigureAwait(false); + + lock (_lock) + { + _hasResponded = true; + } } /// - public override Task DeferAsync(bool ephemeral = false, RequestOptions options = null) + public override async Task DeferAsync(bool ephemeral = false, RequestOptions options = null) { - var response = new API.InteractionResponse() + if (!InteractionHelper.CanSendResponse(this)) + throw new TimeoutException($"Cannot defer an interaction after {InteractionHelper.ResponseTimeLimit} seconds of no response/acknowledgement"); + + var response = new API.InteractionResponse { Type = InteractionResponseType.DeferredUpdateMessage, - Data = ephemeral ? new API.InteractionCallbackData() { Flags = MessageFlags.Ephemeral } : Optional.Unspecified - + Data = ephemeral ? new API.InteractionCallbackData { Flags = MessageFlags.Ephemeral } : Optional.Unspecified }; - return Discord.Rest.ApiClient.CreateInteractionResponseAsync(response, Id, Token, options); + lock (_lock) + { + if (_hasResponded) + { + throw new InvalidOperationException("Cannot respond or defer twice to the same interaction"); + } + } + + await Discord.Rest.ApiClient.CreateInteractionResponseAsync(response, Id, Token, options).ConfigureAwait(false); + + lock (_lock) + { + _hasResponded = true; + } } } } diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/Message Components/SocketMessageComponentData.cs b/src/Discord.Net.WebSocket/Entities/Interaction/Message Components/SocketMessageComponentData.cs index 098478612..408a7f853 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/Message Components/SocketMessageComponentData.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/Message Components/SocketMessageComponentData.cs @@ -1,8 +1,4 @@ -using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using Model = Discord.API.MessageComponentInteractionData; namespace Discord.WebSocket diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketAutocompleteInteraction.cs b/src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketAutocompleteInteraction.cs index 0eb22f29d..e58589c08 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketAutocompleteInteraction.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketAutocompleteInteraction.cs @@ -2,13 +2,10 @@ using Discord.Rest; using System; using System.Collections.Generic; using System.IO; -using System.Linq; -using System.Text; using System.Threading.Tasks; using Model = Discord.API.Interaction; using DataModel = Discord.API.AutocompleteInteractionData; - namespace Discord.WebSocket { /// @@ -21,6 +18,9 @@ namespace Discord.WebSocket /// public new SocketAutocompleteInteractionData Data { get; } + internal override bool _hasResponded { get; set; } + private object _lock = new object(); + internal SocketAutocompleteInteraction(DiscordSocketClient client, Model model, ISocketMessageChannel channel) : base(client, model.Id, channel) { @@ -34,7 +34,7 @@ namespace Discord.WebSocket internal new static SocketAutocompleteInteraction Create(DiscordSocketClient client, Model model, ISocketMessageChannel channel) { - var entity = new SocketAutocompleteInteraction(client, model, channel); + var entity = new SocketAutocompleteInteraction(client, model, channel); entity.Update(model); return entity; } @@ -53,8 +53,25 @@ namespace Discord.WebSocket /// /// A task that represents the asynchronous operation of responding to this interaction. /// - public Task RespondAsync(IEnumerable result, RequestOptions options = null) - => InteractionHelper.SendAutocompleteResult(Discord, result, Id, Token, options); + public async Task RespondAsync(IEnumerable result, RequestOptions options = null) + { + if (!InteractionHelper.CanSendResponse(this)) + throw new TimeoutException($"Cannot respond to an interaction after {InteractionHelper.ResponseTimeLimit} seconds!"); + + lock (_lock) + { + if (_hasResponded) + { + throw new InvalidOperationException("Cannot respond twice to the same interaction"); + } + } + + await InteractionHelper.SendAutocompleteResultAsync(Discord, result, Id, Token, options).ConfigureAwait(false); + lock (_lock) + { + _hasResponded = true; + } + } /// /// Responds to this interaction with a set of choices. @@ -71,27 +88,31 @@ namespace Discord.WebSocket /// A task that represents the asynchronous operation of responding to this interaction. /// public Task RespondAsync(RequestOptions options = null, params AutocompleteResult[] result) - => InteractionHelper.SendAutocompleteResult(Discord, result, Id, Token, options); - + => RespondAsync(result, options); /// - [Obsolete("Autocomplete interactions cannot be defered!", true)] - public override Task DeferAsync(bool ephemeral = false, RequestOptions options = null) => throw new NotSupportedException(); + [Obsolete("Autocomplete interactions cannot be deferred!", true)] + public override Task DeferAsync(bool ephemeral = false, RequestOptions options = null) + => throw new NotSupportedException("Autocomplete interactions cannot be deferred!"); /// [Obsolete("Autocomplete interactions cannot have followups!", true)] - public override 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) => throw new NotSupportedException(); + public override 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) + => throw new NotSupportedException("Autocomplete interactions cannot be deferred!"); /// [Obsolete("Autocomplete interactions cannot have followups!", true)] - public override Task FollowupWithFileAsync(string text = null, Stream fileStream = null, string fileName = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent component = null, Embed embed = null) => throw new NotSupportedException(); + public override Task FollowupWithFileAsync(Stream fileStream, string fileName, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent component = null, Embed embed = null) + => throw new NotSupportedException("Autocomplete interactions cannot be deferred!"); /// [Obsolete("Autocomplete interactions cannot have followups!", true)] - public override Task FollowupWithFileAsync(string text = null, string filePath = null, string fileName = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent component = null, Embed embed = null) => throw new NotSupportedException(); + public override Task FollowupWithFileAsync(string filePath, string text = null, string fileName = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent component = null, Embed embed = null) + => throw new NotSupportedException("Autocomplete interactions cannot be deferred!"); /// [Obsolete("Autocomplete interactions cannot have normal responses!", true)] - public override 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) => throw new NotSupportedException(); + public override 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) + => throw new NotSupportedException("Autocomplete interactions cannot be deferred!"); } } diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketAutocompleteInteractionData.cs b/src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketAutocompleteInteractionData.cs index 06e313b44..d0c44bab1 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketAutocompleteInteractionData.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketAutocompleteInteractionData.cs @@ -1,12 +1,8 @@ -using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; -using System.Text; -using System.Threading.Tasks; using DataModel = Discord.API.AutocompleteInteractionData; - namespace Discord.WebSocket { /// @@ -35,7 +31,7 @@ namespace Discord.WebSocket public ulong Version { get; } /// - /// Gets the current autocomplete option that is activly being filled out. + /// Gets the current autocomplete option that is actively being filled out. /// public AutocompleteOption Current { get; } @@ -46,15 +42,34 @@ namespace Discord.WebSocket internal SocketAutocompleteInteractionData(DataModel model) { - var options = model.Options.Select(x => new AutocompleteOption(x.Type, x.Name, x.Value, x.Focused)); + var options = model.Options.SelectMany(GetOptions); Current = options.FirstOrDefault(x => x.Focused); Options = options.ToImmutableArray(); + if (Options.Count == 1 && Current == null) + Current = Options.FirstOrDefault(); + CommandName = model.Name; CommandId = model.Id; Type = model.Type; Version = model.Version; } + + private List GetOptions(API.AutocompleteInteractionDataOption model) + { + var options = new List(); + + if (model.Options.IsSpecified) + { + options.AddRange(model.Options.Value.SelectMany(GetOptions)); + } + else if(model.Focused.IsSpecified) + { + options.Add(new AutocompleteOption(model.Type, model.Name, model.Value.GetValueOrDefault(null), model.Focused.Value)); + } + + return 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 db53f5d08..26614b572 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketSlashCommand.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketSlashCommand.cs @@ -1,7 +1,3 @@ -using Discord.Rest; -using System; -using System.Linq; -using System.Threading.Tasks; using DataModel = Discord.API.ApplicationCommandInteractionData; using Model = Discord.API.Interaction; @@ -15,13 +11,13 @@ namespace Discord.WebSocket /// /// The data associated with this interaction. /// - new public SocketSlashCommandData Data { get; } + public new SocketSlashCommandData Data { get; } internal SocketSlashCommand(DiscordSocketClient client, Model model, ISocketMessageChannel channel) : base(client, model, channel) { - var dataModel = model.Data.IsSpecified ? - (DataModel)model.Data.Value + var dataModel = model.Data.IsSpecified + ? (DataModel)model.Data.Value : null; ulong? guildId = null; @@ -31,7 +27,7 @@ namespace Discord.WebSocket Data = SocketSlashCommandData.Create(client, dataModel, guildId); } - new internal static SocketInteraction Create(DiscordSocketClient client, Model model, ISocketMessageChannel channel) + internal new static SocketInteraction Create(DiscordSocketClient client, Model model, ISocketMessageChannel channel) { var entity = new SocketSlashCommand(client, model, channel); entity.Update(model); diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketSlashCommandData.cs b/src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketSlashCommandData.cs index 7b504f119..39dc9a46a 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketSlashCommandData.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketSlashCommandData.cs @@ -1,4 +1,3 @@ -using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using Model = Discord.API.ApplicationCommandInteractionData; @@ -25,7 +24,7 @@ namespace Discord.WebSocket Options = model.Options.IsSpecified ? model.Options.Value.Select(x => new SocketSlashCommandDataOption(this, x)).ToImmutableArray() - : null; + : ImmutableArray.Create(); } } } 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 b2ea82856..265eda75b 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketSlashCommandDataOption.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketSlashCommandDataOption.cs @@ -6,7 +6,7 @@ using Model = Discord.API.ApplicationCommandInteractionDataOption; namespace Discord.WebSocket { /// - /// Represents a Websocket-based recieved by the gateway + /// Represents a Websocket-based received by the gateway. /// public class SocketSlashCommandDataOption : IApplicationCommandInteractionDataOption { @@ -61,7 +61,7 @@ namespace Discord.WebSocket break; case ApplicationCommandOptionType.Mentionable: { - if(data.ResolvableData.GuildMembers.Any(x => x.Key == valueId) || data.ResolvableData.Users.Any(x => x.Key == valueId)) + if (data.ResolvableData.GuildMembers.Any(x => x.Key == valueId) || data.ResolvableData.Users.Any(x => x.Key == valueId)) { var guildUser = data.ResolvableData.GuildMembers.FirstOrDefault(x => x.Key == valueId).Value; @@ -70,7 +70,7 @@ namespace Discord.WebSocket else Value = data.ResolvableData.Users.FirstOrDefault(x => x.Key == valueId).Value; } - else if(data.ResolvableData.Roles.Any(x => x.Key == valueId)) + else if (data.ResolvableData.Roles.Any(x => x.Key == valueId)) { Value = data.ResolvableData.Roles.FirstOrDefault(x => x.Key == valueId).Value; } @@ -110,14 +110,13 @@ namespace Discord.WebSocket } break; } - } Options = model.Options.IsSpecified ? model.Options.Value.Select(x => new SocketSlashCommandDataOption(data, x)).ToImmutableArray() - : null; + : ImmutableArray.Create(); } -#endregion + #endregion #region Converters public static explicit operator bool(SocketSlashCommandDataOption option) diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketApplicationCommand.cs b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketApplicationCommand.cs index c849341b8..d986a93f3 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketApplicationCommand.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketApplicationCommand.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; -using System.Text; using System.Threading.Tasks; using GatewayModel = Discord.API.Gateway.ApplicationCommandCreatedUpdatedEvent; using Model = Discord.API.ApplicationCommand; @@ -11,7 +10,7 @@ using Model = Discord.API.ApplicationCommand; namespace Discord.WebSocket { /// - /// Represends a Websocket-based . + /// Represents a Websocket-based . /// public class SocketApplicationCommand : SocketEntity, IApplicationCommand { @@ -82,35 +81,29 @@ namespace Discord.WebSocket Description = model.Description; Name = model.Name; IsDefaultPermission = model.DefaultPermissions.GetValueOrDefault(true); + Type = model.Type; Options = model.Options.IsSpecified - ? model.Options.Value.Select(x => SocketApplicationCommandOption.Create(x)).ToImmutableArray() - : new ImmutableArray(); + ? model.Options.Value.Select(SocketApplicationCommandOption.Create).ToImmutableArray() + : ImmutableArray.Create(); } /// public Task DeleteAsync(RequestOptions options = null) - => InteractionHelper.DeleteUnknownApplicationCommand(Discord, GuildId, this, options); + => InteractionHelper.DeleteUnknownApplicationCommandAsync(Discord, GuildId, this, options); /// public Task ModifyAsync(Action func, RequestOptions options = null) { return ModifyAsync(func, options); } - + /// public async Task ModifyAsync(Action func, RequestOptions options = null) where TArg : ApplicationCommandProperties { - Model command = null; - - if (IsGlobalCommand) - { - command = await InteractionHelper.ModifyGlobalCommand(Discord, this, func, options).ConfigureAwait(false); - } - else - { - command = await InteractionHelper.ModifyGuildCommand(Discord, this, GuildId.Value, func, options); - } + var command = IsGlobalCommand + ? await InteractionHelper.ModifyGlobalCommandAsync(Discord, this, func, options).ConfigureAwait(false) + : await InteractionHelper.ModifyGuildCommandAsync(Discord, this, GuildId.Value, func, options); Update(command); } diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketApplicationCommandChoice.cs b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketApplicationCommandChoice.cs index 0f36af0b8..e70efa27b 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketApplicationCommandChoice.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketApplicationCommandChoice.cs @@ -1,8 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using Model = Discord.API.ApplicationCommandOptionChoice; namespace Discord.WebSocket diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketApplicationCommandOption.cs b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketApplicationCommandOption.cs index ab1ead531..8fd5f449c 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketApplicationCommandOption.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketApplicationCommandOption.cs @@ -1,9 +1,6 @@ -using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; -using System.Text; -using System.Threading.Tasks; using Model = Discord.API.ApplicationCommandOption; namespace Discord.WebSocket @@ -38,6 +35,11 @@ namespace Discord.WebSocket /// public IReadOnlyCollection Options { get; private set; } + /// + /// The allowed channel types for this option. + /// + public IReadOnlyCollection ChannelTypes { get; private set; } + internal SocketApplicationCommandOption() { } internal static SocketApplicationCommandOption Create(Model model) { @@ -61,12 +63,16 @@ namespace Discord.WebSocket : null; Choices = model.Choices.IsSpecified - ? model.Choices.Value.Select(x => SocketApplicationCommandChoice.Create(x)).ToImmutableArray() - : new ImmutableArray(); + ? model.Choices.Value.Select(SocketApplicationCommandChoice.Create).ToImmutableArray() + : ImmutableArray.Create(); Options = model.Options.IsSpecified - ? model.Options.Value.Select(x => SocketApplicationCommandOption.Create(x)).ToImmutableArray() - : new ImmutableArray(); + ? model.Options.Value.Select(Create).ToImmutableArray() + : ImmutableArray.Create(); + + ChannelTypes = model.ChannelTypes.IsSpecified + ? model.ChannelTypes.Value.ToImmutableArray() + : ImmutableArray.Create(); } IReadOnlyCollection IApplicationCommandOption.Choices => Choices; diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBase.cs b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBase.cs index d0622196a..66d10fd3b 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBase.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBase.cs @@ -1,10 +1,8 @@ using Discord.Net.Rest; using Discord.Rest; using System; -using System.Collections.Generic; using System.IO; using System.Linq; -using System.Text; using System.Threading.Tasks; using DataModel = Discord.API.ApplicationCommandInteractionData; using Model = Discord.API.Interaction; @@ -31,13 +29,17 @@ namespace Discord.WebSocket /// /// The data associated with this interaction. /// - new internal SocketCommandBaseData Data { get; } + internal new SocketCommandBaseData Data { get; } + + internal override bool _hasResponded { get; set; } + + private object _lock = new object(); internal SocketCommandBase(DiscordSocketClient client, Model model, ISocketMessageChannel channel) : base(client, model.Id, channel) { - var dataModel = model.Data.IsSpecified ? - (DataModel)model.Data.Value + var dataModel = model.Data.IsSpecified + ? (DataModel)model.Data.Value : null; ulong? guildId = null; @@ -47,7 +49,7 @@ namespace Discord.WebSocket Data = SocketCommandBaseData.Create(client, dataModel, model.Id, guildId); } - new internal static SocketInteraction Create(DiscordSocketClient client, Model model, ISocketMessageChannel channel) + internal new static SocketInteraction Create(DiscordSocketClient client, Model model, ISocketMessageChannel channel) { var entity = new SocketCommandBase(client, model, channel); entity.Update(model); @@ -56,8 +58,8 @@ namespace Discord.WebSocket internal override void Update(Model model) { - var data = model.Data.IsSpecified ? - (DataModel)model.Data.Value + var data = model.Data.IsSpecified + ? (DataModel)model.Data.Value : null; Data.Update(data); @@ -79,20 +81,16 @@ namespace Discord.WebSocket if (!IsValidToken) throw new InvalidOperationException("Interaction token is no longer valid"); + if (!InteractionHelper.CanSendResponse(this)) + throw new TimeoutException($"Cannot respond to an interaction after {InteractionHelper.ResponseTimeLimit} seconds!"); + + embeds ??= Array.Empty(); if (embed != null) - { - if (embeds == null) - embeds = new[] { embed }; - else - { - List listEmbeds = embeds.ToList(); - listEmbeds.Insert(0, embed); - } - } + embeds = new[] { embed }.Concat(embeds).ToArray(); Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed."); Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed."); - Preconditions.AtMost(embeds?.Length ?? 0, 10, nameof(embeds), "A max of 10 embeds are allowed."); + Preconditions.AtMost(embeds.Length, 10, nameof(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 (allowedMentions != null && allowedMentions.AllowedTypes.HasValue) @@ -117,14 +115,27 @@ namespace Discord.WebSocket { Content = text, AllowedMentions = allowedMentions?.ToModel() ?? Optional.Unspecified, - Embeds = embeds?.Select(x => x.ToModel()).ToArray() ?? Optional.Unspecified, - TTS = isTTS ? true : Optional.Unspecified, + Embeds = embeds.Select(x => x.ToModel()).ToArray(), + TTS = isTTS, Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional.Unspecified, Flags = ephemeral ? MessageFlags.Ephemeral : Optional.Unspecified } }; - await InteractionHelper.SendInteractionResponse(Discord, response, Id, Token, options); + lock (_lock) + { + if (_hasResponded) + { + throw new InvalidOperationException("Cannot respond twice to the same interaction"); + } + } + + await InteractionHelper.SendInteractionResponseAsync(Discord, response, Id, Token, options).ConfigureAwait(false); + + lock (_lock) + { + _hasResponded = true; + } } /// @@ -141,27 +152,20 @@ namespace Discord.WebSocket if (!IsValidToken) throw new InvalidOperationException("Interaction token is no longer valid"); + embeds ??= Array.Empty(); if (embed != null) - { - if (embeds == null) - embeds = new[] { embed }; - else - { - List listEmbeds = embeds.ToList(); - listEmbeds.Insert(0, embed); - } - } + embeds = new[] { embed }.Concat(embeds).ToArray(); Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed."); Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed."); - Preconditions.AtMost(embeds?.Length ?? 0, 10, nameof(embeds), "A max of 10 embeds are allowed."); + Preconditions.AtMost(embeds.Length, 10, nameof(embeds), "A max of 10 embeds are allowed."); var args = new API.Rest.CreateWebhookMessageParams { Content = text, AllowedMentions = allowedMentions?.ToModel() ?? Optional.Unspecified, IsTTS = isTTS, - Embeds = embeds?.Select(x => x.ToModel()).ToArray() ?? Optional.Unspecified, + Embeds = embeds.Select(x => x.ToModel()).ToArray(), Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional.Unspecified }; @@ -173,8 +177,8 @@ namespace Discord.WebSocket /// public override async Task FollowupWithFileAsync( + Stream fileStream, string text = null, - Stream fileStream = null, string fileName = null, Embed[] embeds = null, bool isTTS = false, @@ -187,29 +191,22 @@ namespace Discord.WebSocket if (!IsValidToken) throw new InvalidOperationException("Interaction token is no longer valid"); + embeds ??= Array.Empty(); if (embed != null) - { - if (embeds == null) - embeds = new[] { embed }; - else - { - List listEmbeds = embeds.ToList(); - listEmbeds.Insert(0, embed); - } - } + embeds = new[] { embed }.Concat(embeds).ToArray(); Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed."); Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed."); - Preconditions.AtMost(embeds?.Length ?? 0, 10, nameof(embeds), "A max of 10 embeds are allowed."); + Preconditions.AtMost(embeds.Length, 10, nameof(embeds), "A max of 10 embeds are allowed."); Preconditions.NotNull(fileStream, nameof(fileStream), "File Stream must have data"); - Preconditions.NotNullOrWhitespace(fileName, nameof(fileName), "File Name must not be empty or null"); + Preconditions.NotNullOrEmpty(fileName, nameof(fileName), "File Name must not be empty or null"); var args = new API.Rest.CreateWebhookMessageParams { Content = text, AllowedMentions = allowedMentions?.ToModel() ?? Optional.Unspecified, IsTTS = isTTS, - Embeds = embeds?.Select(x => x.ToModel()).ToArray() ?? Optional.Unspecified, + Embeds = embeds.Select(x => x.ToModel()).ToArray(), Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional.Unspecified, File = fileStream is not null ? new MultipartFile(fileStream, fileName) : Optional.Unspecified }; @@ -222,8 +219,8 @@ namespace Discord.WebSocket /// public override async Task FollowupWithFileAsync( + string filePath, string text = null, - string filePath = null, string fileName = null, Embed[] embeds = null, bool isTTS = false, @@ -236,28 +233,24 @@ namespace Discord.WebSocket if (!IsValidToken) throw new InvalidOperationException("Interaction token is no longer valid"); + embeds ??= Array.Empty(); if (embed != null) - { - if (embeds == null) - embeds = new[] { embed }; - else - { - List listEmbeds = embeds.ToList(); - listEmbeds.Insert(0, embed); - } - } + embeds = new[] { embed }.Concat(embeds).ToArray(); Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed."); Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed."); - Preconditions.AtMost(embeds?.Length ?? 0, 10, nameof(embeds), "A max of 10 embeds are allowed."); - Preconditions.NotNullOrWhitespace(filePath, nameof(filePath), "Path must exist"); + Preconditions.AtMost(embeds.Length, 10, nameof(embeds), "A max of 10 embeds are allowed."); + Preconditions.NotNullOrEmpty(filePath, nameof(filePath), "Path must exist"); + + fileName ??= Path.GetFileName(filePath); + Preconditions.NotNullOrEmpty(fileName, nameof(fileName), "File Name must not be empty or null"); var args = new API.Rest.CreateWebhookMessageParams { Content = text, AllowedMentions = allowedMentions?.ToModel() ?? Optional.Unspecified, IsTTS = isTTS, - Embeds = embeds?.Select(x => x.ToModel()).ToArray() ?? Optional.Unspecified, + Embeds = embeds.Select(x => x.ToModel()).ToArray(), Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional.Unspecified, File = !string.IsNullOrEmpty(filePath) ? new MultipartFile(new MemoryStream(File.ReadAllBytes(filePath), false), fileName) : Optional.Unspecified }; @@ -274,8 +267,11 @@ namespace Discord.WebSocket /// /// A task that represents the asynchronous operation of acknowledging the interaction. /// - public override Task DeferAsync(bool ephemeral = false, RequestOptions options = null) + public override async Task DeferAsync(bool ephemeral = false, RequestOptions options = null) { + if (!InteractionHelper.CanSendResponse(this)) + throw new TimeoutException($"Cannot defer an interaction after {InteractionHelper.ResponseTimeLimit} seconds!"); + var response = new API.InteractionResponse { Type = InteractionResponseType.DeferredChannelMessageWithSource, @@ -285,7 +281,20 @@ namespace Discord.WebSocket } }; - return Discord.Rest.ApiClient.CreateInteractionResponseAsync(response, Id, Token, options); + lock (_lock) + { + if (_hasResponded) + { + throw new InvalidOperationException("Cannot respond or defer twice to the same interaction"); + } + } + + await Discord.Rest.ApiClient.CreateInteractionResponseAsync(response, Id, Token, options).ConfigureAwait(false); + + lock (_lock) + { + _hasResponded = true; + } } } } diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBaseData.cs b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBaseData.cs index cac1ce2df..cb2f01f5f 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBaseData.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBaseData.cs @@ -1,6 +1,4 @@ using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; using Model = Discord.API.ApplicationCommandInteractionData; namespace Discord.WebSocket diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketResolvableData.cs b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketResolvableData.cs index c76bc49c3..c065637ca 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketResolvableData.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketResolvableData.cs @@ -1,8 +1,4 @@ -using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Discord.WebSocket { diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs b/src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs index c8e07bf97..20d8fa0f5 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs @@ -8,7 +8,7 @@ using System.IO; namespace Discord.WebSocket { /// - /// Represents an Interaction recieved over the gateway. + /// Represents an Interaction received over the gateway. /// public abstract class SocketInteraction : SocketEntity, IDiscordInteraction { @@ -47,13 +47,13 @@ namespace Discord.WebSocket public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); + internal abstract bool _hasResponded { get; set; } + /// /// if the token is valid for replying to, otherwise . /// public bool IsValidToken - => CheckToken(); - - private ulong? GuildId { get; set; } + => InteractionHelper.CanRespondOrFollowup(this); internal SocketInteraction(DiscordSocketClient client, ulong id, ISocketMessageChannel channel) : base(client, id) @@ -65,9 +65,9 @@ namespace Discord.WebSocket { if (model.Type == InteractionType.ApplicationCommand) { - var dataModel = model.Data.IsSpecified ? - (DataModel)model.Data.Value - : null; + var dataModel = model.Data.IsSpecified + ? (DataModel)model.Data.Value + : null; if (dataModel == null) return null; @@ -77,15 +77,17 @@ namespace Discord.WebSocket ApplicationCommandType.Slash => SocketSlashCommand.Create(client, model, channel), ApplicationCommandType.Message => SocketMessageCommand.Create(client, model, channel), ApplicationCommandType.User => SocketUserCommand.Create(client, model, channel), - _ => null, + _ => null }; } - else if (model.Type == InteractionType.MessageComponent) + + if (model.Type == InteractionType.MessageComponent) return SocketMessageComponent.Create(client, model, channel); - else if (model.Type == InteractionType.ApplicationCommandAutocomplete) + + if (model.Type == InteractionType.ApplicationCommandAutocomplete) return SocketAutocompleteInteraction.Create(client, model, channel); - else - return null; + + return null; } internal virtual void Update(Model model) @@ -93,8 +95,6 @@ namespace Discord.WebSocket Data = model.Data.IsSpecified ? model.Data.Value : null; - - GuildId = model.GuildId.ToNullable(); Token = model.Token; Version = model.Version; Type = model.Type; @@ -103,7 +103,7 @@ namespace Discord.WebSocket { if (model.Member.IsSpecified && model.GuildId.IsSpecified) { - User = SocketGuildUser.Create(Discord.State.GetGuild(GuildId.Value), Discord.State, model.Member.Value); + User = SocketGuildUser.Create(Discord.State.GetGuild(model.GuildId.Value), Discord.State, model.Member.Value); } else { @@ -142,7 +142,7 @@ namespace Discord.WebSocket /// /// The sent message. /// - public abstract Task FollowupAsync(string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, + public abstract 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); /// @@ -161,7 +161,7 @@ namespace Discord.WebSocket /// /// The sent message. /// - public abstract Task FollowupWithFileAsync(string text = null, Stream fileStream = null, string fileName = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, + public abstract Task FollowupWithFileAsync(Stream fileStream, string fileName, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent component = null, Embed embed = null); /// @@ -180,13 +180,13 @@ namespace Discord.WebSocket /// /// The sent message. /// - public abstract Task FollowupWithFileAsync(string text = null, string filePath = null, string fileName = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, + public abstract Task FollowupWithFileAsync(string filePath, string text = null, string fileName = 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. + /// The request options for this request. /// A that represents the initial response. public Task GetOriginalResponseAsync(RequestOptions options = null) => InteractionHelper.GetOriginalResponseAsync(Discord, Channel, this, options); @@ -195,11 +195,11 @@ namespace Discord.WebSocket /// Edits original response for this interaction. /// /// A delegate containing the properties to modify the message with. - /// The request options for this async request. + /// The request options for this request. /// A that represents the initial response. public async Task ModifyOriginalResponseAsync(Action func, RequestOptions options = null) { - var model = await InteractionHelper.ModifyInteractionResponse(Discord, Token, func, options); + var model = await InteractionHelper.ModifyInteractionResponseAsync(Discord, Token, func, options); return RestInteractionMessage.Create(Discord, model, Token, Channel); } @@ -207,31 +207,26 @@ namespace Discord.WebSocket /// Acknowledges this interaction. /// /// to send this message ephemerally, otherwise . - /// The request options for this async request. + /// The request options for this request. /// /// A task that represents the asynchronous operation of acknowledging the interaction. /// 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 - CreatedAt.UtcDateTime).TotalMinutes <= 15d; - } -#endregion + + #endregion #region IDiscordInteraction /// - async Task IDiscordInteraction.FollowupAsync (string text, Embed[] embeds, bool isTTS, bool ephemeral, AllowedMentions allowedMentions, + 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) + async Task IDiscordInteraction.GetOriginalResponseAsync(RequestOptions options) => await GetOriginalResponseAsync(options).ConfigureAwait(false); /// - async Task IDiscordInteraction.ModifyOriginalResponseAsync (Action func, RequestOptions options) + async Task IDiscordInteraction.ModifyOriginalResponseAsync(Action func, RequestOptions options) => await ModifyOriginalResponseAsync(func, options).ConfigureAwait(false); #endregion } diff --git a/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs index acf60f371..4be9f4c5a 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs @@ -17,6 +17,7 @@ namespace Discord.WebSocket #region SocketMessage private long _timestampTicks; private readonly List _reactions = new List(); + private ImmutableArray _userMentions = ImmutableArray.Create(); /// /// Gets the author of this message. @@ -38,6 +39,9 @@ namespace Discord.WebSocket /// public string Content { get; private set; } + /// + public string CleanContent => MessageHelper.SanitizeMessage(this); + /// public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); /// @@ -63,6 +67,11 @@ namespace Discord.WebSocket /// public IReadOnlyCollection Components { get; private set; } + /// + /// Gets the interaction this message is a response to. + /// + public MessageInteraction Interaction { get; private set; } + /// public MessageFlags? Flags { get; private set; } @@ -97,20 +106,19 @@ namespace Discord.WebSocket /// Collection of WebSocket-based roles. /// public virtual IReadOnlyCollection MentionedRoles => ImmutableArray.Create(); - /// - /// Returns the users mentioned in this message. - /// - /// - /// Collection of WebSocket-based users. - /// - public virtual IReadOnlyCollection MentionedUsers => ImmutableArray.Create(); /// public virtual IReadOnlyCollection Tags => ImmutableArray.Create(); /// public virtual IReadOnlyCollection Stickers => ImmutableArray.Create(); /// public IReadOnlyDictionary Reactions => _reactions.GroupBy(r => r.Emote).ToDictionary(x => x.Key, x => new ReactionMetadata { ReactionCount = x.Count(), IsMe = x.Any(y => y.UserId == Discord.CurrentUser.Id) }); - + /// + /// Returns the users mentioned in this message. + /// + /// + /// Collection of WebSocket-based users. + /// + public IReadOnlyCollection MentionedUsers => _userMentions; /// public DateTimeOffset Timestamp => DateTimeUtils.FromTicks(_timestampTicks); @@ -139,7 +147,9 @@ namespace Discord.WebSocket _timestampTicks = model.Timestamp.Value.UtcTicks; if (model.Content.IsSpecified) + { Content = model.Content.Value; + } if (model.Application.IsSpecified) { @@ -225,6 +235,36 @@ namespace Discord.WebSocket else Components = new List(); + if (model.UserMentions.IsSpecified) + { + var value = model.UserMentions.Value; + if (value.Length > 0) + { + var newMentions = ImmutableArray.CreateBuilder(value.Length); + for (int i = 0; i < value.Length; i++) + { + var val = value[i]; + if (val != null) + { + var user = Channel.GetUserAsync(val.Id, CacheMode.CacheOnly).GetAwaiter().GetResult() as SocketUser; + if (user != null) + newMentions.Add(user); + else + newMentions.Add(SocketUnknownUser.Create(Discord, state, val)); + } + } + _userMentions = newMentions.ToImmutable(); + } + } + + if (model.Interaction.IsSpecified) + { + Interaction = new MessageInteraction(model.Interaction.Value.Id, + model.Interaction.Value.Type, + model.Interaction.Value.Name, + SocketGlobalUser.Create(Discord, state, model.Interaction.Value.User)); + } + if (model.Flags.IsSpecified) Flags = model.Flags.Value; } @@ -262,9 +302,13 @@ namespace Discord.WebSocket /// IReadOnlyCollection IMessage.Components => Components; + /// + IMessageInteraction IMessage.Interaction => Interaction; + /// IReadOnlyCollection IMessage.Stickers => Stickers; + internal void AddReaction(SocketReaction reaction) { _reactions.Add(reaction); diff --git a/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs index 7ff55c613..e8f5604c4 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs @@ -22,7 +22,6 @@ namespace Discord.WebSocket private ImmutableArray _embeds = ImmutableArray.Create(); private ImmutableArray _tags = ImmutableArray.Create(); private ImmutableArray _roleMentions = ImmutableArray.Create(); - private ImmutableArray _userMentions = ImmutableArray.Create(); private ImmutableArray _stickers = ImmutableArray.Create(); /// @@ -46,8 +45,6 @@ namespace Discord.WebSocket /// public override IReadOnlyCollection MentionedRoles => _roleMentions; /// - public override IReadOnlyCollection MentionedUsers => _userMentions; - /// public override IReadOnlyCollection Stickers => _stickers; /// public IUserMessage ReferencedMessage => _referencedMessage; @@ -108,32 +105,10 @@ namespace Discord.WebSocket _embeds = ImmutableArray.Create(); } - if (model.UserMentions.IsSpecified) - { - var value = model.UserMentions.Value; - if (value.Length > 0) - { - var newMentions = ImmutableArray.CreateBuilder(value.Length); - for (int i = 0; i < value.Length; i++) - { - var val = value[i]; - if (val.Object != null) - { - var user = Channel.GetUserAsync(val.Object.Id, CacheMode.CacheOnly).GetAwaiter().GetResult() as SocketUser; - if (user != null) - newMentions.Add(user); - else - newMentions.Add(SocketUnknownUser.Create(Discord, state, val.Object)); - } - } - _userMentions = newMentions.ToImmutable(); - } - } - if (model.Content.IsSpecified) { var text = model.Content.Value; - _tags = MessageHelper.ParseTags(text, Channel, guild, _userMentions); + _tags = MessageHelper.ParseTags(text, Channel, guild, MentionedUsers); model.Content = text; } diff --git a/src/Discord.Net.WebSocket/Entities/Roles/SocketRole.cs b/src/Discord.Net.WebSocket/Entities/Roles/SocketRole.cs index 20fa2a391..2ccade290 100644 --- a/src/Discord.Net.WebSocket/Entities/Roles/SocketRole.cs +++ b/src/Discord.Net.WebSocket/Entities/Roles/SocketRole.cs @@ -34,6 +34,8 @@ namespace Discord.WebSocket /// public string Name { get; private set; } /// + public string Icon { get; private set; } + /// public GuildPermissions Permissions { get; private set; } /// public int Position { get; private set; } @@ -72,6 +74,7 @@ namespace Discord.WebSocket internal void Update(ClientState state, Model model) { Name = model.Name; + Icon = model.Icon; IsHoisted = model.Hoist; IsManaged = model.Managed; IsMentionable = model.Mentionable; @@ -89,6 +92,10 @@ namespace Discord.WebSocket public Task DeleteAsync(RequestOptions options = null) => RoleHelper.DeleteAsync(this, Discord, options); + /// + public string GetIconUrl() + => CDN.GetGuildRoleIconUrl(Id, Icon); + /// /// Gets the name of the role. /// diff --git a/src/Discord.Net.WebSocket/Entities/Stickers/SocketCustomSticker.cs b/src/Discord.Net.WebSocket/Entities/Stickers/SocketCustomSticker.cs index 9acc20a14..6a5104012 100644 --- a/src/Discord.Net.WebSocket/Entities/Stickers/SocketCustomSticker.cs +++ b/src/Discord.Net.WebSocket/Entities/Stickers/SocketCustomSticker.cs @@ -1,13 +1,9 @@ using Discord.Rest; using System; -using System.Collections.Generic; using System.Diagnostics; -using System.Linq; -using System.Text; using System.Threading.Tasks; using Model = Discord.API.Sticker; - namespace Discord.WebSocket { /// @@ -23,7 +19,7 @@ namespace Discord.WebSocket /// /// /// This may return in the WebSocket implementation due to incomplete user collection in - /// large guilds, or the bot doesnt have the MANAGE_EMOJIS_AND_STICKERS permission. + /// large guilds, or the bot doesn't have the MANAGE_EMOJIS_AND_STICKERS permission. /// /// public SocketGuildUser Author @@ -54,7 +50,7 @@ namespace Discord.WebSocket /// public async Task ModifyAsync(Action func, RequestOptions options = null) { - if(!Guild.CurrentUser.GuildPermissions.Has(GuildPermission.ManageEmojisAndStickers)) + if (!Guild.CurrentUser.GuildPermissions.Has(GuildPermission.ManageEmojisAndStickers)) throw new InvalidOperationException($"Missing permission {nameof(GuildPermission.ManageEmojisAndStickers)}"); var model = await GuildHelper.ModifyStickerAsync(Discord, Guild.Id, this, func, options); @@ -72,7 +68,7 @@ namespace Discord.WebSocket internal SocketCustomSticker Clone() => MemberwiseClone() as SocketCustomSticker; private new string DebuggerDisplay => Guild == null ? base.DebuggerDisplay : $"{Name} in {Guild.Name} ({Id})"; -#endregion + #endregion #region ICustomSticker ulong? ICustomSticker.AuthorId diff --git a/src/Discord.Net.WebSocket/Entities/Stickers/SocketSticker.cs b/src/Discord.Net.WebSocket/Entities/Stickers/SocketSticker.cs index 758ecebb4..ee45720b5 100644 --- a/src/Discord.Net.WebSocket/Entities/Stickers/SocketSticker.cs +++ b/src/Discord.Net.WebSocket/Entities/Stickers/SocketSticker.cs @@ -1,11 +1,7 @@ -using Discord.Rest; -using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Linq; -using System.Text; -using System.Threading.Tasks; using Model = Discord.API.Sticker; namespace Discord.WebSocket @@ -49,12 +45,9 @@ namespace Discord.WebSocket internal static SocketSticker Create(DiscordSocketClient client, Model model) { - SocketSticker entity; - - if (model.GuildId.IsSpecified) - entity = new SocketCustomSticker(client, model.Id, client.GetGuild(model.GuildId.Value), model.User.IsSpecified ? model.User.Value.Id : null); - else - entity = new SocketSticker(client, model.Id); + var entity = model.GuildId.IsSpecified + ? new SocketCustomSticker(client, model.Id, client.GetGuild(model.GuildId.Value), model.User.IsSpecified ? model.User.Value.Id : null) + : new SocketSticker(client, model.Id); entity.Update(model); return entity; @@ -63,21 +56,16 @@ namespace Discord.WebSocket internal virtual void Update(Model model) { Name = model.Name; - Description = model.Desription; + Description = model.Description; PackId = model.PackId; IsAvailable = model.Available; Format = model.FormatType; Type = model.Type; SortOrder = model.SortValue; - if (model.Tags.IsSpecified) - { - Tags = model.Tags.Value.Split(',').Select(x => x.Trim()).ToImmutableArray(); - } - else - { - Tags = ImmutableArray.Empty; - } + Tags = model.Tags.IsSpecified + ? model.Tags.Value.Split(',').Select(x => x.Trim()).ToImmutableArray() + : ImmutableArray.Create(); } internal string DebuggerDisplay => $"{Name} ({Id})"; @@ -85,10 +73,10 @@ namespace Discord.WebSocket /// public override bool Equals(object obj) { - if (obj is API.Sticker stickerModel) + if (obj is Model stickerModel) { return stickerModel.Name == Name && - stickerModel.Desription == Description && + stickerModel.Description == Description && stickerModel.FormatType == Format && stickerModel.Id == Id && stickerModel.PackId == PackId && @@ -97,8 +85,8 @@ namespace Discord.WebSocket stickerModel.Available == IsAvailable && (!stickerModel.Tags.IsSpecified || stickerModel.Tags.Value == string.Join(", ", Tags)); } - else - return base.Equals(obj); + + return base.Equals(obj); } } } diff --git a/src/Discord.Net.WebSocket/Entities/Stickers/SocketUnknownSticker.cs b/src/Discord.Net.WebSocket/Entities/Stickers/SocketUnknownSticker.cs index 2e9514595..ca7d2d0f1 100644 --- a/src/Discord.Net.WebSocket/Entities/Stickers/SocketUnknownSticker.cs +++ b/src/Discord.Net.WebSocket/Entities/Stickers/SocketUnknownSticker.cs @@ -1,8 +1,5 @@ -using System; using System.Collections.Generic; using System.Diagnostics; -using System.Linq; -using System.Text; using System.Threading.Tasks; using Model = Discord.API.StickerItem; diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketThreadUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketThreadUser.cs index 2cec3d612..c91921379 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketThreadUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketThreadUser.cs @@ -1,11 +1,8 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; using System.Threading.Tasks; using Model = Discord.API.ThreadMember; -using MemberModel = Discord.API.GuildMember; -using Discord.API; using System.Collections.Immutable; namespace Discord.WebSocket @@ -163,7 +160,6 @@ namespace Discord.WebSocket } } - /// public ChannelPermissions GetPermissions(IGuildChannel channel) => GuildUser.GetPermissions(channel); diff --git a/src/Discord.Net.Webhook/Discord.Net.Webhook.csproj b/src/Discord.Net.Webhook/Discord.Net.Webhook.csproj index 036d8c9a8..24ae442d7 100644 --- a/src/Discord.Net.Webhook/Discord.Net.Webhook.csproj +++ b/src/Discord.Net.Webhook/Discord.Net.Webhook.csproj @@ -1,4 +1,4 @@ - + @@ -11,4 +11,4 @@ - \ No newline at end of file + diff --git a/test/Discord.Net.Analyzers.Tests/Discord.Net.Analyzers.Tests.csproj b/test/Discord.Net.Analyzers.Tests/Discord.Net.Analyzers.Tests.csproj index 8f69672f9..1257041e4 100644 --- a/test/Discord.Net.Analyzers.Tests/Discord.Net.Analyzers.Tests.csproj +++ b/test/Discord.Net.Analyzers.Tests/Discord.Net.Analyzers.Tests.csproj @@ -15,10 +15,10 @@ - - + + - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/Discord.Net.Tests.Integration/Discord.Net.Tests.Integration.csproj b/test/Discord.Net.Tests.Integration/Discord.Net.Tests.Integration.csproj index c571059ef..8b16b2971 100644 --- a/test/Discord.Net.Tests.Integration/Discord.Net.Tests.Integration.csproj +++ b/test/Discord.Net.Tests.Integration/Discord.Net.Tests.Integration.csproj @@ -15,9 +15,9 @@ - + - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/Discord.Net.Tests.Unit/Discord.Net.Tests.Unit.csproj b/test/Discord.Net.Tests.Unit/Discord.Net.Tests.Unit.csproj index 866041696..716c3ebc4 100644 --- a/test/Discord.Net.Tests.Unit/Discord.Net.Tests.Unit.csproj +++ b/test/Discord.Net.Tests.Unit/Discord.Net.Tests.Unit.csproj @@ -13,9 +13,9 @@ - + - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/Discord.Net.Tests.Unit/EmbedBuilderTests.cs b/test/Discord.Net.Tests.Unit/EmbedBuilderTests.cs index 4d0cc8a55..83c6ede19 100644 --- a/test/Discord.Net.Tests.Unit/EmbedBuilderTests.cs +++ b/test/Discord.Net.Tests.Unit/EmbedBuilderTests.cs @@ -171,7 +171,7 @@ namespace Discord } /// - /// Tests that valid urls do not throw any exceptions. + /// Tests that valid url's do not throw any exceptions. /// /// The url to set. [Theory] diff --git a/test/Discord.Net.Tests.Unit/FormatTests.cs b/test/Discord.Net.Tests.Unit/FormatTests.cs index 2a5adbaae..c015c7e15 100644 --- a/test/Discord.Net.Tests.Unit/FormatTests.cs +++ b/test/Discord.Net.Tests.Unit/FormatTests.cs @@ -59,5 +59,20 @@ namespace Discord { Assert.Equal(expected, Format.BlockQuote(input)); } + + [Theory] + [InlineData("", "")] + [InlineData("\n", "\n")] + [InlineData("**hi**", "hi")] + [InlineData("__uwu__", "uwu")] + [InlineData(">>__uwu__", "uwu")] + [InlineData("```uwu```", "uwu")] + [InlineData("~uwu~", "uwu")] + [InlineData("berries __and__ *Cream**, I'm a little lad who loves berries and cream", "berries and Cream, I'm a little lad who loves berries and cream")] + public void StripMarkdown(string input, string expected) + { + var test = Format.StripMarkDown(input); + Assert.Equal(expected, test); + } } }