Browse Source

Merge branch 'feature/context_commands' into release/3.x

pull/1923/head
quin lynch 4 years ago
parent
commit
efa58598ba
76 changed files with 2248 additions and 798 deletions
  1. +15
    -3
      Discord.Net.sln
  2. +25
    -0
      docs/guides/application-commands/01-getting-started.md
  3. +91
    -0
      docs/guides/application-commands/context-menu-commands/creating-context-menu-commands.md
  4. +40
    -0
      docs/guides/application-commands/context-menu-commands/receiving-context-menu-command-events.md
  5. +0
    -0
      docs/guides/application-commands/slash-commands/02-creating-slash-commands.md
  6. +0
    -0
      docs/guides/application-commands/slash-commands/03-responding-to-slash-commands.md
  7. +0
    -0
      docs/guides/application-commands/slash-commands/04-parameters.md
  8. +0
    -0
      docs/guides/application-commands/slash-commands/05-responding-ephemerally.md
  9. +0
    -0
      docs/guides/application-commands/slash-commands/06-subcommands.md
  10. +0
    -0
      docs/guides/application-commands/slash-commands/07-choice-slash-command.md
  11. +11
    -11
      docs/guides/application-commands/slash-commands/README.md
  12. +0
    -0
      docs/guides/application-commands/slash-commands/images/ephemeral1.png
  13. +0
    -0
      docs/guides/application-commands/slash-commands/images/feedback1.png
  14. +0
    -0
      docs/guides/application-commands/slash-commands/images/feedback2.png
  15. +0
    -0
      docs/guides/application-commands/slash-commands/images/listroles1.png
  16. +0
    -0
      docs/guides/application-commands/slash-commands/images/listroles2.png
  17. +0
    -0
      docs/guides/application-commands/slash-commands/images/oauth.png
  18. +0
    -0
      docs/guides/application-commands/slash-commands/images/settings1.png
  19. +0
    -0
      docs/guides/application-commands/slash-commands/images/settings2.png
  20. +0
    -0
      docs/guides/application-commands/slash-commands/images/settings3.png
  21. +0
    -0
      docs/guides/application-commands/slash-commands/images/slashcommand1.png
  22. +0
    -0
      docs/guides/application-commands/slash-commands/images/slashcommand2.png
  23. +0
    -23
      docs/guides/slash-commands/01-getting-started.md
  24. +1
    -1
      src/Discord.Net.Core/Discord.Net.Core.csproj
  25. +180
    -20
      src/Discord.Net.Core/Discord.Net.Core.xml
  26. +35
    -1
      src/Discord.Net.Core/Entities/Guilds/IGuild.cs
  27. +5
    -17
      src/Discord.Net.Core/Entities/Interactions/ApplicationCommandProperties.cs
  28. +29
    -0
      src/Discord.Net.Core/Entities/Interactions/ApplicationCommandTypes.cs
  29. +71
    -0
      src/Discord.Net.Core/Entities/Interactions/Context Menus/MessageCommandBuilder.cs
  30. +16
    -0
      src/Discord.Net.Core/Entities/Interactions/Context Menus/MessageCommandProperties.cs
  31. +69
    -0
      src/Discord.Net.Core/Entities/Interactions/Context Menus/UserCommandBuilder.cs
  32. +16
    -0
      src/Discord.Net.Core/Entities/Interactions/Context Menus/UserCommandProperties.cs
  33. +16
    -1
      src/Discord.Net.Core/Entities/Interactions/IApplicationCommand.cs
  34. +5
    -5
      src/Discord.Net.Core/Entities/Interactions/SlashCommandBuilder.cs
  35. +5
    -8
      src/Discord.Net.Core/Entities/Interactions/SlashCommandProperties.cs
  36. +41
    -0
      src/Discord.Net.Core/IDiscordClient.cs
  37. +8
    -0
      src/Discord.Net.Rest/API/Common/ApplicationCommand.cs
  38. +4
    -1
      src/Discord.Net.Rest/API/Common/ApplicationCommandInteractionData.cs
  39. +2
    -0
      src/Discord.Net.Rest/API/Common/ApplicationCommandInteractionDataResolved.cs
  40. +0
    -1
      src/Discord.Net.Rest/API/Common/InteractionCallbackData.cs
  41. +13
    -0
      src/Discord.Net.Rest/API/Net/IResolvable.cs
  42. +5
    -1
      src/Discord.Net.Rest/API/Rest/CreateApplicationCommandParams.cs
  43. +13
    -0
      src/Discord.Net.Rest/BaseDiscordClient.cs
  44. +45
    -2
      src/Discord.Net.Rest/ClientHelper.cs
  45. +60
    -22
      src/Discord.Net.Rest/Discord.Net.Rest.xml
  46. +65
    -18
      src/Discord.Net.Rest/DiscordRestApiClient.cs
  47. +16
    -12
      src/Discord.Net.Rest/DiscordRestClient.cs
  48. +58
    -1
      src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs
  49. +174
    -150
      src/Discord.Net.Rest/Entities/Interactions/InteractionHelper.cs
  50. +14
    -13
      src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommand.cs
  51. +5
    -2
      src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommandOption.cs
  52. +0
    -24
      src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommandType.cs
  53. +7
    -4
      src/Discord.Net.Rest/Entities/Interactions/RestGlobalCommand.cs
  54. +6
    -4
      src/Discord.Net.Rest/Entities/Interactions/RestGuildCommand.cs
  55. +2
    -20
      src/Discord.Net.WebSocket/API/Gateway/ApplicationCommandCreatedUpdatedEvent.cs
  56. +31
    -0
      src/Discord.Net.WebSocket/ClientState.cs
  57. +1
    -1
      src/Discord.Net.WebSocket/Discord.Net.WebSocket.csproj
  58. +186
    -49
      src/Discord.Net.WebSocket/Discord.Net.WebSocket.xml
  59. +111
    -14
      src/Discord.Net.WebSocket/DiscordSocketClient.cs
  60. +93
    -22
      src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs
  61. +41
    -0
      src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/Message Commands/SocketMessageCommand.cs
  62. +35
    -0
      src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/Message Commands/SocketMessageCommandData.cs
  63. +41
    -0
      src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/User Commands/SocketUserCommand.cs
  64. +35
    -0
      src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/User Commands/SocketUserCommandData.cs
  65. +0
    -77
      src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketApplicationCommand.cs
  66. +3
    -123
      src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketSlashCommand.cs
  67. +0
    -48
      src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketSlashCommandCache.cs
  68. +5
    -89
      src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketSlashCommandData.cs
  69. +12
    -10
      src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketSlashCommandDataOption.cs
  70. +132
    -0
      src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketApplicationCommand.cs
  71. +0
    -0
      src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketApplicationCommandChoice.cs
  72. +0
    -0
      src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketApplicationCommandOption.cs
  73. +165
    -0
      src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBase.cs
  74. +60
    -0
      src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBaseData.cs
  75. +113
    -0
      src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketResolvableData.cs
  76. +16
    -0
      src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs

+ 15
- 3
Discord.Net.sln View File

@@ -1,6 +1,6 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.28407.52
# Visual Studio Version 17
VisualStudioVersion = 17.0.31521.260
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discord.Net.Core", "src\Discord.Net.Core\Discord.Net.Core.csproj", "{91E9E7BD-75C9-4E98-84AA-2C271922E5C2}"
EndProject
@@ -40,7 +40,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discord.Net.Analyzers.Tests
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discord.Net.Examples", "src\Discord.Net.Examples\Discord.Net.Examples.csproj", "{47820065-3CFB-401C-ACEA-862BD564A404}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "idn", "samples\idn\idn.csproj", "{4A03840B-9EBE-47E3-89AB-E0914DF21AFB}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "idn", "samples\idn\idn.csproj", "{4A03840B-9EBE-47E3-89AB-E0914DF21AFB}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -232,6 +232,18 @@ Global
{4A03840B-9EBE-47E3-89AB-E0914DF21AFB}.Release|x64.Build.0 = Release|Any CPU
{4A03840B-9EBE-47E3-89AB-E0914DF21AFB}.Release|x86.ActiveCfg = Release|Any CPU
{4A03840B-9EBE-47E3-89AB-E0914DF21AFB}.Release|x86.Build.0 = Release|Any CPU
{0CC57A32-3AC7-489D-8DF5-C431925E4675}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0CC57A32-3AC7-489D-8DF5-C431925E4675}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0CC57A32-3AC7-489D-8DF5-C431925E4675}.Debug|x64.ActiveCfg = Debug|Any CPU
{0CC57A32-3AC7-489D-8DF5-C431925E4675}.Debug|x64.Build.0 = Debug|Any CPU
{0CC57A32-3AC7-489D-8DF5-C431925E4675}.Debug|x86.ActiveCfg = Debug|Any CPU
{0CC57A32-3AC7-489D-8DF5-C431925E4675}.Debug|x86.Build.0 = Debug|Any CPU
{0CC57A32-3AC7-489D-8DF5-C431925E4675}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0CC57A32-3AC7-489D-8DF5-C431925E4675}.Release|Any CPU.Build.0 = Release|Any CPU
{0CC57A32-3AC7-489D-8DF5-C431925E4675}.Release|x64.ActiveCfg = Release|Any CPU
{0CC57A32-3AC7-489D-8DF5-C431925E4675}.Release|x64.Build.0 = Release|Any CPU
{0CC57A32-3AC7-489D-8DF5-C431925E4675}.Release|x86.ActiveCfg = Release|Any CPU
{0CC57A32-3AC7-489D-8DF5-C431925E4675}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE


+ 25
- 0
docs/guides/application-commands/01-getting-started.md View File

@@ -0,0 +1,25 @@
# Getting started with application commands.

Welcome! This guide will show you how to use application commands. If you have extra questions that aren't covered here you can come to our [Discord](https://discord.com/invite/dvSfUTet3K) server and ask around there.

## What is an application command?

Application commands consist of three different types. Slash commands, context menu User commands and context menu Message commands.
Slash commands are made up of a name, description, and a block of options, which you can think of like arguments to a function. The name and description help users find your command among many others, and the options validate user input as they fill out your command.
Message and User commands are only a name, to the user. So try to make the name descriptive. They're accessed by right clicking (or long press, on mobile) a user or a message, respectively.

All three varieties of application commands have both Global and Guild variants. Your global commands are available in every guild that adds your application. You can also make commands for a specific guild; they're only available in that guild. The User and Message commands are more limited in quantity than the slash commands. For specifics, check out their respective guide pages.

An Interaction is the message that your application receives when a user uses a command. It includes the values that the user submitted, as well as some metadata about this particular instance of the command being used: the guild_id, channel_id, member and other fields. You can find all the values in our data models.

## Authorizing your bot for application commands

There is a new special OAuth2 scope for applications called `applications.commands`. In order to make Application Commands work within a guild, the guild must authorize your application with the `applications.commands` scope. The bot scope is not enough.

Head over to your discord applications OAuth2 screen and make sure to select the `application.commands` scope.

![OAuth2 scoping](images/oauth.png)

From there you can then use the link to add your bot to a server.

**Note**: In order for users in your guild to use your slash commands, they need to have the "Use Slash Command" permission on the guild.

+ 91
- 0
docs/guides/application-commands/context-menu-commands/creating-context-menu-commands.md View File

@@ -0,0 +1,91 @@
# Creating context menu commands.

There are two kinds of Context Menu Commands: User Commands and Message Commands.
Each of these have a Global and Guild variant.
Global menu commands are available for every guild that adds your app. An individual app's global commands are also available in DMs if that app has a bot that shares a mutual guild with the user.

Guild commands are specific to the guild you specify when making them. Guild commands are not available in DMs. Command names are unique per application within each scope (global and guild). That means:

- Your app cannot have two global commands with the same name
- Your app cannot have two guild commands within the same name on the same guild
- Your app can have a global and guild command with the same name
- Multiple apps can have commands with the same names

**Note**: Apps can have a maximum of 5 global context menu commands, and an additional 5 guild-specific context menu commands per guild.

If you don't have the code for a bot ready yet please follow [this guide](https://docs.stillu.cc/guides/getting_started/first-bot.html).

## UserCommandBuilder

The context menu user command builder will help you create user commands. The builder has these available fields and methods:

| Name | Type | Description |
| --------------------- | -------------------------------- | -------------------------------------------------------------------------------------------- |
| Name | string | The name of this context menu command. |
| WithName | Function | Sets the field name. |
| Build | Function | Builds the builder into the appropriate `CommandCreationProperties` class used to make Menu commands |

## MessageCommandBuilder

The context menu message command builder will help you create message commands. The builder has these available fields and methods:

| Name | Type | Description |
| --------------------- | -------------------------------- | -------------------------------------------------------------------------------------------- |
| Name | string | The name of this context menu command. |
| WithName | Function | Sets the field name. |
| Build | Function | Builds the builder into the appropriate `CommandCreationProperties` class used to make Menu commands |

**Note**: Context Menu command names can be upper and lowercase, and use spaces.

Let's use the user command builder to make a global and guild command.

```cs
// Let's hook the ready event for creating our commands in.
client.Ready += Client_Ready;

...

public async Task Client_Ready()
{
// Let's build a guild command! We're going to need a guild id so lets just put that in a variable.
ulong guildId = 848176216011046962;

// Next, lets create our user and message command builder. This is like the embed builder but for context menu commands.
var guildUserCommand = new UserCommandBuilder();
var guildMessageCommand = new MessageCommandBuilder();

// Note: Names have to be all lowercase and match the regular expression ^[\w -]{3,32}$
guildUserCommand.WithName("Guild User Command");
guildMessageCommand.WithName("Guild Message Command");

// Descriptions are not used with User and Message commands
//guildCommand.WithDescription("");

// Let's do our global commands
var globalCommand = new UserCommandBuilder();
globalCommand.WithName("Global User Command");
var globalMessageCommand = new MessageCommandBuilder();
globalMessageCommand.WithName("Global Message Command");

try
{
// Now that we have our builder, we can call the rest API to make our slash command.
await client.Rest.CreateGuildUserCommand(guildUserCommand.Build(), guildId);
await client.Rest.CreateGuildMessageCommand(guildMessageCommand.Build(), guildId);

// With global commands we dont need the guild id.
await client.Rest.CreateGlobalUserCommand(globalUserCommand.Build());
await client.Rest.CreateGlobalMessageCommand(globalMessageCommand.Build());
}
catch(ApplicationCommandException exception)
{
// If our command was invalid, we should catch an ApplicationCommandException. This exception contains the path of the error as well as the error message. You can serialize the Error field in the exception to get a visual of where your error is.
var json = JsonConvert.SerializeObject(exception.Error, Formatting.Indented);

// You can send this error somewhere or just print it to the console, for this example we're just going to print it.
Console.WriteLine(json);
}
}

```
**Note**: Application 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 application commands.

+ 40
- 0
docs/guides/application-commands/context-menu-commands/receiving-context-menu-command-events.md View File

@@ -0,0 +1,40 @@
# Receiving Context Menu events

User commands and Message commands have their own unique objects returned. Different from Slash commands. To get the appropriate object returned, you can use a similar method to the slash commands.

```cs
client.InteractionCreated += InteractionCreatedHandler;

...

public async Task InteractionCreatedHandler(SocketInteraction arg)
{
if ( arg.Type == InteractionType.ApplicationCommand)
Task.Run(() => ApplicationCommandHandler(arg));
}

public async Task ApplicationCommandHandler(SocketInteraction arg)
{
switch (arg)
{
case SocketSlashCommand slashCommand:
Console.Writeline("Slash command received!");
break;
case SocketUserCommand userCommand:
Console.Writeline("User command received!")
// userCommand.User = User who ran command.
// userCommand.Data.Member = User who was clicked.
break;
case SocketMessageCommand messageCommand:
Console.Writeline("Message command received!")
// messageCommand.User = User who ran command.
// messageCommand.Data.Message = Message that was clicked.
break;
}
}
```

User commands return a SocketUser object, showing the user that was clicked to run the command.
Message commands return a SocketMessage object, showing the message that was clicked to run the command.

Both return the user who ran the command, the guild (if any), channel, etc.

docs/guides/slash-commands/02-creating-slash-commands.md → docs/guides/application-commands/slash-commands/02-creating-slash-commands.md View File


docs/guides/slash-commands/03-responding-to-slash-commands.md → docs/guides/application-commands/slash-commands/03-responding-to-slash-commands.md View File


docs/guides/slash-commands/04-parameters.md → docs/guides/application-commands/slash-commands/04-parameters.md View File


docs/guides/slash-commands/05-responding-ephemerally.md → docs/guides/application-commands/slash-commands/05-responding-ephemerally.md View File


docs/guides/slash-commands/06-subcommands.md → docs/guides/application-commands/slash-commands/06-subcommands.md View File


docs/guides/slash-commands/07-choice-slash-command.md → docs/guides/application-commands/slash-commands/07-choice-slash-command.md View File


docs/guides/slash-commands/README.md → docs/guides/application-commands/slash-commands/README.md View File

@@ -1,11 +1,11 @@
## Slash command guides
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)
## Slash command guides
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)

docs/guides/slash-commands/images/ephemeral1.png → docs/guides/application-commands/slash-commands/images/ephemeral1.png View File


docs/guides/slash-commands/images/feedback1.png → docs/guides/application-commands/slash-commands/images/feedback1.png View File


docs/guides/slash-commands/images/feedback2.png → docs/guides/application-commands/slash-commands/images/feedback2.png View File


docs/guides/slash-commands/images/listroles1.png → docs/guides/application-commands/slash-commands/images/listroles1.png View File


docs/guides/slash-commands/images/listroles2.png → docs/guides/application-commands/slash-commands/images/listroles2.png View File


docs/guides/slash-commands/images/oauth.png → docs/guides/application-commands/slash-commands/images/oauth.png View File


docs/guides/slash-commands/images/settings1.png → docs/guides/application-commands/slash-commands/images/settings1.png View File


docs/guides/slash-commands/images/settings2.png → docs/guides/application-commands/slash-commands/images/settings2.png View File


docs/guides/slash-commands/images/settings3.png → docs/guides/application-commands/slash-commands/images/settings3.png View File


docs/guides/slash-commands/images/slashcommand1.png → docs/guides/application-commands/slash-commands/images/slashcommand1.png View File


docs/guides/slash-commands/images/slashcommand2.png → docs/guides/application-commands/slash-commands/images/slashcommand2.png View File


+ 0
- 23
docs/guides/slash-commands/01-getting-started.md View File

@@ -1,23 +0,0 @@
# Getting started with slash commands.

Welcome! This guide will show you how to use slash commands. If you have extra questions that aren't covered here you can come to our [Discord](https://discord.com/invite/dvSfUTet3K) server and ask around there.

## What is a slash command?

Slash Commands _(synonymous with application commands)_ are made up of a name, description, and a block of options, which you can think of like arguments to a function. The name and description help users find your command among many others, and the options validate user input as they fill out your command.

Your global commands are available in every guild that adds your application. You can also make commands for a specific guild; they're only available in that guild.

An Interaction is the message that your application receives when a user uses a command. It includes the values that the user submitted, as well as some metadata about this particular instance of the command being used: the guild_id, channel_id, member and other fields. You can find all the values in our data models.

## Authorizing your bot for slash commands

There is a new special OAuth2 scope for applications called `applications.commands`. In order to make Slash Commands work within a guild, the guild must authorize your application with the `applications.commands` scope. The bot scope is not enough.

Head over to your discord applications OAuth2 screen and make sure to select the `application.commands` scope.

![OAuth2 scoping](images/oauth.png)

From there you can then use the link to add your bot to a server.

**Note**: In order for users in your guild to use your slash commands, they need to have the "Use Slash Command" permission on the guild.

+ 1
- 1
src/Discord.Net.Core/Discord.Net.Core.csproj View File

@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="../../Discord.Net.targets" />
<Import Project="../../StyleAnalyzer.targets" />
<PropertyGroup>


+ 180
- 20
src/Discord.Net.Core/Discord.Net.Core.xml View File

@@ -4007,7 +4007,7 @@
</member>
<member name="M:Discord.IGuild.GetApplicationCommandsAsync(Discord.RequestOptions)">
<summary>
Gets this guilds slash commands commands
Gets this guilds application commands.
</summary>
<param name="options">The options to be used when sending the request.</param>
<returns>
@@ -4015,6 +4015,38 @@
of application commands found within the guild.
</returns>
</member>
<member name="M:Discord.IGuild.GetApplicationCommandAsync(System.UInt64,Discord.CacheMode,Discord.RequestOptions)">
<summary>
Gets an application command within this guild with the specified id.
</summary>
<param name="id">The id of the application command to get.</param>
<param name="mode">The <see cref="T:Discord.CacheMode" /> that determines whether the object should be fetched from cache.</param>
<param name="options">The options to be used when sending the request.</param>
<returns>
A ValueTask that represents the asynchronous get operation. The task result contains a <see cref="T:Discord.IApplicationCommand"/>
if found, otherwise <see langword="null"/>.
</returns>
</member>
<member name="M:Discord.IGuild.CreateApplicationCommandAsync(Discord.ApplicationCommandProperties,Discord.RequestOptions)">
<summary>
Creates an application command within this guild.
</summary>
<param name="properties">The properties to use when creating the command.</param>
<param name="options">The options to be used when sending the request.</param>
<returns>
A task that represents the asynchronous creation operation. The task result contains the command that was created.
</returns>
</member>
<member name="M:Discord.IGuild.BulkOverwriteApplicationCommandsAsync(Discord.ApplicationCommandProperties[],Discord.RequestOptions)">
<summary>
Overwrites the application commands within this guild.
</summary>
<param name="properties">A collection of properties to use when creating the commands.</param>
<param name="options">The options to be used when sending the request.</param>
<returns>
A task that represents the asynchronous creation operation. The task result contains a collection of commands that was created.
</returns>
</member>
<member name="T:Discord.IGuildIntegration">
<summary>
Holds information for a guild integration feature.
@@ -4552,7 +4584,7 @@
</member>
<member name="T:Discord.ApplicationCommandProperties">
<summary>
Provides properties that are used to modify a <see cref="T:Discord.IApplicationCommand" /> with the specified changes.
Represents the base class to create/modify application commands.
</summary>
</member>
<member name="P:Discord.ApplicationCommandProperties.Name">
@@ -4560,24 +4592,101 @@
Gets or sets the name of this command.
</summary>
</member>
<member name="P:Discord.ApplicationCommandProperties.Description">
<member name="T:Discord.ApplicationCommandType">
<summary>
Gets or sets the discription of this command.
ApplicationCommandType is enum of current valid Application Command Types: Slash, User, Message
</summary>
</member>
<member name="P:Discord.ApplicationCommandProperties.Options">
<member name="F:Discord.ApplicationCommandType.Slash">
<summary>
Gets or sets the options for this command.
ApplicationCommandType.Slash is Slash command type
</summary>
</member>
<member name="P:Discord.ApplicationCommandProperties.DefaultPermission">
<member name="F:Discord.ApplicationCommandType.User">
<summary>
Whether the command is enabled by default when the app is added to a guild. Default is <see langword="true"/>
ApplicationCommandType.User is Context Menu User command type
</summary>
</member>
<member name="F:Discord.ApplicationCommandType.Message">
<summary>
ApplicationCommandType.Message is Context Menu Message command type
</summary>
</member>
<member name="T:Discord.MessageCommandBuilder">
<summary>
A class used to build Message commands.
</summary>
</member>
<member name="F:Discord.MessageCommandBuilder.MaxNameLength">
<summary>
Returns the maximun length a commands name allowed by Discord
</summary>
</member>
<member name="P:Discord.MessageCommandBuilder.Name">
<summary>
The name of this Message command.
</summary>
</member>
<member name="M:Discord.MessageCommandBuilder.Build">
<summary>
Build the current builder into a <see cref="T:Discord.MessageCommandProperties"/> class.
</summary>
<returns>
A <see cref="T:Discord.MessageCommandProperties"/> that can be used to create message commands.
</returns>
</member>
<member name="M:Discord.MessageCommandBuilder.WithName(System.String)">
<summary>
Sets the field name.
</summary>
<param name="name">The value to set the field name to.</param>
<returns>
The current builder.
</returns>
</member>
<member name="T:Discord.MessageCommandProperties">
<summary>
A class used to create message commands.
</summary>
</member>
<member name="T:Discord.UserCommandBuilder">
<summary>
A class used to build user commands.
</summary>
</member>
<member name="F:Discord.UserCommandBuilder.MaxNameLength">
<summary>
Returns the maximun length a commands name allowed by Discord
</summary>
</member>
<member name="P:Discord.UserCommandBuilder.Name">
<summary>
The name of this User command.
</summary>
</member>
<member name="M:Discord.UserCommandBuilder.Build">
<summary>
Build the current builder into a <see cref="T:Discord.UserCommandProperties"/> class.
</summary>
<returns>A <see cref="T:Discord.UserCommandProperties"/> that can be used to create user commands.</returns>
</member>
<member name="M:Discord.UserCommandBuilder.WithName(System.String)">
<summary>
Sets the field name.
</summary>
<param name="name">The value to set the field name to.</param>
<returns>
The current builder.
</returns>
</member>
<member name="T:Discord.UserCommandProperties">
<summary>
A class used to create User commands.
</summary>
</member>
<member name="T:Discord.IApplicationCommand">
<summary>
The base command model that belongs to an application. see <see href="https://discord.com/developers/docs/interactions/slash-commands#applicationcommand"/>
The base command model that belongs to an application.
</summary>
</member>
<member name="P:Discord.IApplicationCommand.ApplicationId">
@@ -4585,6 +4694,11 @@
Gets the unique id of the parent application.
</summary>
</member>
<member name="P:Discord.IApplicationCommand.Type">
<summary>
The type of the command
</summary>
</member>
<member name="P:Discord.IApplicationCommand.Name">
<summary>
The name of the command.
@@ -4605,6 +4719,16 @@
If the option is a subcommand or subcommand group type, this nested options will be the parameters.
</summary>
</member>
<member name="M:Discord.IApplicationCommand.ModifyAsync(System.Action{Discord.ApplicationCommandProperties},Discord.RequestOptions)">
<summary>
Modifies the current application command.
</summary>
<param name="func">The new properties to use when modifying the command.</param>
<param name="options">The options to be used when sending the request.</param>
<returns>
A task that represents the asynchronous modification operation.
</returns>
</member>
<member name="T:Discord.IApplicationCommandInteractionData">
<summary>
Represents data of an Interaction Command, see <see href="https://discord.com/developers/docs/interactions/slash-commands#interaction-applicationcommandinteractiondata"/>.
@@ -5659,9 +5783,9 @@
</member>
<member name="M:Discord.SlashCommandBuilder.Build">
<summary>
Build the current builder into a <see cref="T:Discord.SlashCommandCreationProperties"/> class.
Build the current builder into a <see cref="T:Discord.SlashCommandProperties"/> class.
</summary>
<returns>A <see cref="T:Discord.SlashCommandCreationProperties"/> that can be used to create slash commands over rest.</returns>
<returns>A <see cref="T:Discord.SlashCommandProperties"/> that can be used to create slash commands over rest.</returns>
</member>
<member name="M:Discord.SlashCommandBuilder.WithName(System.String)">
<summary>
@@ -5849,27 +5973,22 @@
<param name="type">The type to set.</param>
<returns>The current builder.</returns>
</member>
<member name="T:Discord.SlashCommandCreationProperties">
<member name="T:Discord.SlashCommandProperties">
<summary>
A class used to create slash commands.
</summary>
</member>
<member name="P:Discord.SlashCommandCreationProperties.Name">
<summary>
The name of this command.
</summary>
</member>
<member name="P:Discord.SlashCommandCreationProperties.Description">
<member name="P:Discord.SlashCommandProperties.Description">
<summary>
The discription of this command.
</summary>
</member>
<member name="P:Discord.SlashCommandCreationProperties.Options">
<member name="P:Discord.SlashCommandProperties.Options">
<summary>
Gets or sets the options for this command.
</summary>
</member>
<member name="P:Discord.SlashCommandCreationProperties.DefaultPermission">
<member name="P:Discord.SlashCommandProperties.DefaultPermission">
<summary>
Whether the command is enabled by default when the app is added to a guild. Default is <see langword="true"/>
</summary>
@@ -11121,6 +11240,47 @@
A task that represents the asynchronous get operation. The task result contains a read-only collection of connections.
</returns>
</member>
<member name="M:Discord.IDiscordClient.GetGlobalApplicationCommandAsync(System.UInt64,Discord.RequestOptions)">
<summary>
Gets a global application command.
</summary>
<param name="id">The id of the command.</param>
<param name="options">The options to be used when sending the request.</param>
<returns>
A task that represents the asynchronous get operation. The task result contains the application command if found, otherwise
<see langword="null"/>.
</returns>
</member>
<member name="M:Discord.IDiscordClient.GetGlobalApplicationCommandsAsync(Discord.RequestOptions)">
<summary>
Gets a collection of all global commands.
</summary>
<param name="options">The options to be used when sending the request.</param>
<returns>
A task that represents the asynchronous get operation. The task result contains a read-only collection of global
application commands.
</returns>
</member>
<member name="M:Discord.IDiscordClient.CreateGlobalApplicationCommand(Discord.ApplicationCommandProperties,Discord.RequestOptions)">
<summary>
Creates a global application command.
</summary>
<param name="properties">The properties to use when creating the command.</param>
<param name="options">The options to be used when sending the request.</param>
<returns>
A task that represents the asynchronous creation operation. The task result contains the created application command.
</returns>
</member>
<member name="M:Discord.IDiscordClient.BulkOverwriteGlobalApplicationCommand(Discord.ApplicationCommandProperties[],Discord.RequestOptions)">
<summary>
Bulk overwrites all global application commands.
</summary>
<param name="properties">A collection of properties to use when creating the commands.</param>
<param name="options">The options to be used when sending the request.</param>
<returns>
A task that represents the asynchronous creation operation. The task result contains a collection of application commands that were created.
</returns>
</member>
<member name="M:Discord.IDiscordClient.GetGuildAsync(System.UInt64,Discord.CacheMode,Discord.RequestOptions)">
<summary>
Gets a guild.


+ 35
- 1
src/Discord.Net.Core/Entities/Guilds/IGuild.cs View File

@@ -1024,7 +1024,7 @@ namespace Discord
Task DeleteStickerAsync(ICustomSticker sticker, RequestOptions options = null);

/// <summary>
/// Gets this guilds slash commands commands
/// Gets this guilds application commands.
/// </summary>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
@@ -1032,5 +1032,39 @@ namespace Discord
/// of application commands found within the guild.
/// </returns>
Task<IReadOnlyCollection<IApplicationCommand>> GetApplicationCommandsAsync (RequestOptions options = null);

/// <summary>
/// Gets an application command within this guild with the specified id.
/// </summary>
/// <param name="id">The id of the application command to get.</param>
/// <param name="mode">The <see cref="CacheMode" /> that determines whether the object should be fetched from cache.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A ValueTask that represents the asynchronous get operation. The task result contains a <see cref="IApplicationCommand"/>
/// if found, otherwise <see langword="null"/>.
/// </returns>
Task<IApplicationCommand> GetApplicationCommandAsync(ulong id, CacheMode mode = CacheMode.AllowDownload,
RequestOptions options = null);

/// <summary>
/// Creates an application command within this guild.
/// </summary>
/// <param name="properties">The properties to use when creating the command.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous creation operation. The task result contains the command that was created.
/// </returns>
Task<IApplicationCommand> CreateApplicationCommandAsync(ApplicationCommandProperties properties, RequestOptions options = null);

/// <summary>
/// Overwrites the application commands within this guild.
/// </summary>
/// <param name="properties">A collection of properties to use when creating the commands.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous creation operation. The task result contains a collection of commands that was created.
/// </returns>
Task<IReadOnlyCollection<IApplicationCommand>> BulkOverwriteApplicationCommandsAsync(ApplicationCommandProperties[] properties,
RequestOptions options = null);
}
}

+ 5
- 17
src/Discord.Net.Core/Entities/Interactions/ApplicationCommandProperties.cs View File

@@ -7,29 +7,17 @@ using System.Threading.Tasks;
namespace Discord
{
/// <summary>
/// Provides properties that are used to modify a <see cref="IApplicationCommand" /> with the specified changes.
/// Represents the base class to create/modify application commands.
/// </summary>
public class ApplicationCommandProperties
public abstract class ApplicationCommandProperties
{
internal abstract ApplicationCommandType Type { get; }

/// <summary>
/// Gets or sets the name of this command.
/// </summary>
public Optional<string> Name { get; set; }

/// <summary>
/// Gets or sets the discription of this command.
/// </summary>
public Optional<string> Description { get; set; }

/// <summary>
/// Gets or sets the options for this command.
/// </summary>
public Optional<List<ApplicationCommandOptionProperties>> Options { get; set; }

/// <summary>
/// Whether the command is enabled by default when the app is added to a guild. Default is <see langword="true"/>
/// </summary>
public Optional<bool> DefaultPermission { get; set; }
internal ApplicationCommandProperties() { }
}
}

+ 29
- 0
src/Discord.Net.Core/Entities/Interactions/ApplicationCommandTypes.cs View File

@@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Discord
{
/// <summary>
/// ApplicationCommandType is enum of current valid Application Command Types: Slash, User, Message
/// </summary>
public enum ApplicationCommandType : byte
{
/// <summary>
/// ApplicationCommandType.Slash is Slash command type
/// </summary>
Slash = 1,

/// <summary>
/// ApplicationCommandType.User is Context Menu User command type
/// </summary>
User = 2,

/// <summary>
/// ApplicationCommandType.Message is Context Menu Message command type
/// </summary>
Message = 3
}
}

+ 71
- 0
src/Discord.Net.Core/Entities/Interactions/Context Menus/MessageCommandBuilder.cs View File

@@ -0,0 +1,71 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;

namespace Discord
{
/// <summary>
/// A class used to build Message commands.
/// </summary>
public class MessageCommandBuilder
{
/// <summary>
/// Returns the maximun length a commands name allowed by Discord
/// </summary>
public const int MaxNameLength = 32;

/// <summary>
/// The name of this Message command.
/// </summary>
public string Name
{
get
{
return _name;
}
set
{
Preconditions.NotNullOrEmpty(value, nameof(Name));
Preconditions.AtLeast(value.Length, 3, nameof(Name));
Preconditions.AtMost(value.Length, MaxNameLength, nameof(Name));

_name = value;
}
}

private string _name { get; set; }

/// <summary>
/// Build the current builder into a <see cref="MessageCommandProperties"/> class.
/// </summary>
/// <returns>
/// A <see cref="MessageCommandProperties"/> that can be used to create message commands.
/// </returns>
public MessageCommandProperties Build()
{
MessageCommandProperties props = new MessageCommandProperties()
{
Name = this.Name,
};

return props;

}

/// <summary>
/// Sets the field name.
/// </summary>
/// <param name="name">The value to set the field name to.</param>
/// <returns>
/// The current builder.
/// </returns>
public MessageCommandBuilder WithName(string name)
{
this.Name = name;
return this;
}
}
}

+ 16
- 0
src/Discord.Net.Core/Entities/Interactions/Context Menus/MessageCommandProperties.cs View File

@@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Discord
{
/// <summary>
/// A class used to create message commands.
/// </summary>
public class MessageCommandProperties : ApplicationCommandProperties
{
internal override ApplicationCommandType Type => ApplicationCommandType.Message;
}
}

+ 69
- 0
src/Discord.Net.Core/Entities/Interactions/Context Menus/UserCommandBuilder.cs View File

@@ -0,0 +1,69 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;

namespace Discord
{
/// <summary>
/// A class used to build user commands.
/// </summary>
public class UserCommandBuilder
{
/// <summary>
/// Returns the maximun length a commands name allowed by Discord
/// </summary>
public const int MaxNameLength = 32;

/// <summary>
/// The name of this User command.
/// </summary>
public string Name
{
get
{
return _name;
}
set
{
Preconditions.NotNullOrEmpty(value, nameof(Name));
Preconditions.AtLeast(value.Length, 3, nameof(Name));
Preconditions.AtMost(value.Length, MaxNameLength, nameof(Name));

_name = value;
}
}

private string _name { get; set; }

/// <summary>
/// Build the current builder into a <see cref="UserCommandProperties"/> class.
/// </summary>
/// <returns>A <see cref="UserCommandProperties"/> that can be used to create user commands.</returns>
public UserCommandProperties Build()
{
UserCommandProperties props = new UserCommandProperties()
{
Name = this.Name,
};

return props;

}

/// <summary>
/// Sets the field name.
/// </summary>
/// <param name="name">The value to set the field name to.</param>
/// <returns>
/// The current builder.
/// </returns>
public UserCommandBuilder WithName(string name)
{
this.Name = name;
return this;
}
}
}

+ 16
- 0
src/Discord.Net.Core/Entities/Interactions/Context Menus/UserCommandProperties.cs View File

@@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Discord
{
/// <summary>
/// A class used to create User commands.
/// </summary>
public class UserCommandProperties : ApplicationCommandProperties
{
internal override ApplicationCommandType Type => ApplicationCommandType.User;
}
}

+ 16
- 1
src/Discord.Net.Core/Entities/Interactions/IApplicationCommand.cs View File

@@ -7,7 +7,7 @@ using System.Threading.Tasks;
namespace Discord
{
/// <summary>
/// The base command model that belongs to an application. see <see href="https://discord.com/developers/docs/interactions/slash-commands#applicationcommand"/>
/// The base command model that belongs to an application.
/// </summary>
public interface IApplicationCommand : ISnowflakeEntity, IDeletable
{
@@ -16,6 +16,11 @@ namespace Discord
/// </summary>
ulong ApplicationId { get; }

/// <summary>
/// The type of the command
/// </summary>
ApplicationCommandType Type { get; }

/// <summary>
/// The name of the command.
/// </summary>
@@ -35,5 +40,15 @@ namespace Discord
/// If the option is a subcommand or subcommand group type, this nested options will be the parameters.
/// </summary>
IReadOnlyCollection<IApplicationCommandOption> Options { get; }

/// <summary>
/// Modifies the current application command.
/// </summary>
/// <param name="func">The new properties to use when modifying the command.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous modification operation.
/// </returns>
Task ModifyAsync(Action<ApplicationCommandProperties> func, RequestOptions options = null);
}
}

+ 5
- 5
src/Discord.Net.Core/Entities/Interactions/SlashCommandBuilder.cs View File

@@ -93,16 +93,16 @@ namespace Discord
private List<SlashCommandOptionBuilder> _options { get; set; }

/// <summary>
/// Build the current builder into a <see cref="SlashCommandCreationProperties"/> class.
/// Build the current builder into a <see cref="SlashCommandProperties"/> class.
/// </summary>
/// <returns>A <see cref="SlashCommandCreationProperties"/> that can be used to create slash commands over rest.</returns>
public SlashCommandCreationProperties Build()
/// <returns>A <see cref="SlashCommandProperties"/> that can be used to create slash commands over rest.</returns>
public SlashCommandProperties Build()
{
SlashCommandCreationProperties props = new SlashCommandCreationProperties()
SlashCommandProperties props = new SlashCommandProperties()
{
Name = this.Name,
Description = this.Description,
DefaultPermission = this.DefaultPermission
DefaultPermission = this.DefaultPermission,
};

if (this.Options != null && this.Options.Any())


src/Discord.Net.Core/Entities/Interactions/SlashCommandCreationProperties.cs → src/Discord.Net.Core/Entities/Interactions/SlashCommandProperties.cs View File

@@ -9,18 +9,13 @@ namespace Discord
/// <summary>
/// A class used to create slash commands.
/// </summary>
public class SlashCommandCreationProperties
public class SlashCommandProperties : ApplicationCommandProperties
{
/// <summary>
/// The name of this command.
/// </summary>
public string Name { get; set; }

internal override ApplicationCommandType Type => ApplicationCommandType.Slash;
/// <summary>
/// The discription of this command.
/// </summary>
public string Description { get; set; }

public Optional<string> Description { get; set; }

/// <summary>
/// Gets or sets the options for this command.
@@ -31,5 +26,7 @@ namespace Discord
/// Whether the command is enabled by default when the app is added to a guild. Default is <see langword="true"/>
/// </summary>
public Optional<bool> DefaultPermission { get; set; }

internal SlashCommandProperties() { }
}
}

+ 41
- 0
src/Discord.Net.Core/IDiscordClient.cs View File

@@ -141,6 +141,47 @@ namespace Discord
/// </returns>
Task<IReadOnlyCollection<IConnection>> GetConnectionsAsync(RequestOptions options = null);

/// <summary>
/// Gets a global application command.
/// </summary>
/// <param name="id">The id of the command.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous get operation. The task result contains the application command if found, otherwise
/// <see langword="null"/>.
/// </returns>
Task<IApplicationCommand> GetGlobalApplicationCommandAsync(ulong id, RequestOptions options = null);

/// <summary>
/// Gets a collection of all global commands.
/// </summary>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous get operation. The task result contains a read-only collection of global
/// application commands.
/// </returns>
Task<IReadOnlyCollection<IApplicationCommand>> GetGlobalApplicationCommandsAsync(RequestOptions options = null);

/// <summary>
/// Creates a global application command.
/// </summary>
/// <param name="properties">The properties to use when creating the command.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous creation operation. The task result contains the created application command.
/// </returns>
Task<IApplicationCommand> CreateGlobalApplicationCommand(ApplicationCommandProperties properties, RequestOptions options = null);

/// <summary>
/// Bulk overwrites all global application commands.
/// </summary>
/// <param name="properties">A collection of properties to use when creating the commands.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous creation operation. The task result contains a collection of application commands that were created.
/// </returns>
Task<IReadOnlyCollection<IApplicationCommand>> BulkOverwriteGlobalApplicationCommand(ApplicationCommandProperties[] properties, RequestOptions options = null);

/// <summary>
/// Gets a guild.
/// </summary>


+ 8
- 0
src/Discord.Net.Rest/API/Common/ApplicationCommand.cs View File

@@ -11,14 +11,22 @@ namespace Discord.API
{
[JsonProperty("id")]
public ulong Id { get; set; }

[JsonProperty("type")]
public ApplicationCommandType Type { get; set; } = ApplicationCommandType.Slash; // defaults to 1 which is slash.

[JsonProperty("application_id")]
public ulong ApplicationId { get; set; }

[JsonProperty("name")]
public string Name { get; set; }

[JsonProperty("description")]
public string Description { get; set; }

[JsonProperty("options")]
public Optional<ApplicationCommandOption[]> Options { get; set; }

[JsonProperty("default_permission")]
public Optional<bool> DefaultPermissions { get; set; }
}


+ 4
- 1
src/Discord.Net.Rest/API/Common/ApplicationCommandInteractionData.cs View File

@@ -3,7 +3,7 @@ using System.Collections.Generic;

namespace Discord.API
{
internal class ApplicationCommandInteractionData : IDiscordInteractionData
internal class ApplicationCommandInteractionData : IResolvable, IDiscordInteractionData
{
[JsonProperty("id")]
public ulong Id { get; set; }
@@ -17,5 +17,8 @@ namespace Discord.API
[JsonProperty("resolved")]
public Optional<ApplicationCommandInteractionDataResolved> Resolved { get; set; }

[JsonProperty("type")]
public ApplicationCommandType Type { get; set; }

}
}

+ 2
- 0
src/Discord.Net.Rest/API/Common/ApplicationCommandInteractionDataResolved.cs View File

@@ -16,5 +16,7 @@ namespace Discord.API

[JsonProperty("roles")]
public Optional<Dictionary<string, Role>> Roles { get; set; }
[JsonProperty("messages")]
public Optional<Dictionary<string, Message>> Messages { get; set; }
}
}

+ 0
- 1
src/Discord.Net.Rest/API/Common/InteractionCallbackData.cs View File

@@ -16,7 +16,6 @@ namespace Discord.API
[JsonProperty("allowed_mentions")]
public Optional<AllowedMentions> AllowedMentions { get; set; }

// New flags prop. this make the response "ephemeral". see https://discord.com/developers/docs/interactions/slash-commands#interaction-response-interactionapplicationcommandcallbackdata
[JsonProperty("flags")]
public Optional<MessageFlags> Flags { get; set; }



+ 13
- 0
src/Discord.Net.Rest/API/Net/IResolvable.cs View File

@@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Discord.API
{
internal interface IResolvable
{
Optional<ApplicationCommandInteractionDataResolved> Resolved { get; }
}
}

+ 5
- 1
src/Discord.Net.Rest/API/Rest/CreateApplicationCommandParams.cs View File

@@ -13,6 +13,9 @@ namespace Discord.API.Rest
[JsonProperty("name")]
public string Name { get; set; }

[JsonProperty("type")]
public ApplicationCommandType Type { get; set; }

[JsonProperty("description")]
public string Description { get; set; }

@@ -23,11 +26,12 @@ namespace Discord.API.Rest
public Optional<bool> DefaultPermission { get; set; }

public CreateApplicationCommandParams() { }
public CreateApplicationCommandParams(string name, string description, ApplicationCommandOption[] options = null)
public CreateApplicationCommandParams(string name, string description, ApplicationCommandType type, ApplicationCommandOption[] options = null)
{
this.Name = name;
this.Description = description;
this.Options = Optional.Create<ApplicationCommandOption[]>(options);
this.Type = type;
}
}
}

+ 13
- 0
src/Discord.Net.Rest/BaseDiscordClient.cs View File

@@ -216,6 +216,19 @@ namespace Discord.Rest
Task<IWebhook> IDiscordClient.GetWebhookAsync(ulong id, RequestOptions options)
=> Task.FromResult<IWebhook>(null);

/// <inheritdoc />
Task<IApplicationCommand> IDiscordClient.GetGlobalApplicationCommandAsync(ulong id, RequestOptions options)
=> Task.FromResult<IApplicationCommand>(null);

/// <inheritdoc />
Task<IReadOnlyCollection<IApplicationCommand>> IDiscordClient.GetGlobalApplicationCommandsAsync(RequestOptions options)
=> Task.FromResult<IReadOnlyCollection<IApplicationCommand>>(ImmutableArray.Create<IApplicationCommand>());
Task<IApplicationCommand> IDiscordClient.CreateGlobalApplicationCommand(ApplicationCommandProperties properties, RequestOptions options)
=> Task.FromResult<IApplicationCommand>(null);
Task<IReadOnlyCollection<IApplicationCommand>> IDiscordClient.BulkOverwriteGlobalApplicationCommand(ApplicationCommandProperties[] properties,
RequestOptions options)
=> Task.FromResult<IReadOnlyCollection<IApplicationCommand>>(ImmutableArray.Create<IApplicationCommand>());

/// <inheritdoc />
Task IDiscordClient.StartAsync()
=> Task.Delay(0);


+ 45
- 2
src/Discord.Net.Rest/ClientHelper.cs View File

@@ -194,7 +194,8 @@ namespace Discord.Rest
};
}

public static async Task<IReadOnlyCollection<RestGlobalCommand>> GetGlobalApplicationCommands(BaseDiscordClient client, RequestOptions options)
public static async Task<IReadOnlyCollection<RestGlobalCommand>> GetGlobalApplicationCommands(BaseDiscordClient client,
RequestOptions options = null)
{
var response = await client.ApiClient.GetGlobalApplicationCommandsAsync(options).ConfigureAwait(false);

@@ -203,8 +204,16 @@ namespace Discord.Rest

return response.Select(x => RestGlobalCommand.Create(client, x)).ToArray();
}
public static async Task<RestGlobalCommand> GetGlobalApplicationCommand(BaseDiscordClient client, ulong id,
RequestOptions options = null)
{
var model = await client.ApiClient.GetGlobalApplicationCommandAsync(id, options);

return model != null ? RestGlobalCommand.Create(client, model) : null;
}

public static async Task<IReadOnlyCollection<RestGuildCommand>> GetGuildApplicationCommands(BaseDiscordClient client, ulong guildId, RequestOptions options)
public static async Task<IReadOnlyCollection<RestGuildCommand>> GetGuildApplicationCommands(BaseDiscordClient client, ulong guildId,
RequestOptions options = null)
{
var response = await client.ApiClient.GetGuildApplicationCommandsAsync(guildId, options).ConfigureAwait(false);

@@ -213,7 +222,41 @@ namespace Discord.Rest

return response.Select(x => RestGuildCommand.Create(client, x, guildId)).ToImmutableArray();
}
public static async Task<RestGuildCommand> GetGuildApplicationCommand(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<RestGuildCommand> CreateGuildApplicationCommand(BaseDiscordClient client, ulong guildId, ApplicationCommandProperties properties,
RequestOptions options = null)
{
var model = await InteractionHelper.CreateGuildCommand(client, guildId, properties, options);

return RestGuildCommand.Create(client, model, guildId);
}
public static async Task<RestGlobalCommand> CreateGlobalApplicationCommand(BaseDiscordClient client, ApplicationCommandProperties properties,
RequestOptions options = null)
{
var model = await InteractionHelper.CreateGlobalCommand(client, properties, options);

return RestGlobalCommand.Create(client, model);
}
public static async Task<IReadOnlyCollection<RestGlobalCommand>> BulkOverwriteGlobalApplicationCommand(BaseDiscordClient client, ApplicationCommandProperties[] properties,
RequestOptions options = null)
{
var models = await InteractionHelper.BulkOverwriteGlobalCommands(client, properties, options);

return models.Select(x => RestGlobalCommand.Create(client, x)).ToImmutableArray();
}
public static async Task<IReadOnlyCollection<RestGuildCommand>> BulkOverwriteGuildApplicationCommand(BaseDiscordClient client, ulong guildId,
ApplicationCommandProperties[] properties, RequestOptions options = null)
{
var models = await InteractionHelper.BulkOverwriteGuildCommands(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);


+ 60
- 22
src/Discord.Net.Rest/Discord.Net.Rest.xml View File

@@ -226,6 +226,12 @@
<member name="M:Discord.Rest.BaseDiscordClient.Discord#IDiscordClient#GetWebhookAsync(System.UInt64,Discord.RequestOptions)">
<inheritdoc />
</member>
<member name="M:Discord.Rest.BaseDiscordClient.Discord#IDiscordClient#GetGlobalApplicationCommandAsync(System.UInt64,Discord.RequestOptions)">
<inheritdoc />
</member>
<member name="M:Discord.Rest.BaseDiscordClient.Discord#IDiscordClient#GetGlobalApplicationCommandsAsync(Discord.RequestOptions)">
<inheritdoc />
</member>
<member name="M:Discord.Rest.BaseDiscordClient.Discord#IDiscordClient#StartAsync">
<inheritdoc />
</member>
@@ -299,6 +305,12 @@
<member name="M:Discord.Rest.DiscordRestClient.Discord#IDiscordClient#GetWebhookAsync(System.UInt64,Discord.RequestOptions)">
<inheritdoc />
</member>
<member name="M:Discord.Rest.DiscordRestClient.Discord#IDiscordClient#GetGlobalApplicationCommandsAsync(Discord.RequestOptions)">
<inheritdoc />
</member>
<member name="M:Discord.Rest.DiscordRestClient.Discord#IDiscordClient#GetGlobalApplicationCommandAsync(System.UInt64,Discord.RequestOptions)">
<inheritdoc />
</member>
<member name="T:Discord.Rest.DiscordRestConfig">
<summary>
Represents a configuration class for <see cref="T:Discord.Rest.DiscordRestClient"/>.
@@ -3503,6 +3515,37 @@
of application commands found within the guild.
</returns>
</member>
<member name="M:Discord.Rest.RestGuild.GetApplicationCommandAsync(System.UInt64,Discord.RequestOptions)">
<summary>
Gets an application command within this guild with the specified id.
</summary>
<param name="id">The id of the application command to get.</param>
<param name="options">The options to be used when sending the request.</param>
<returns>
A ValueTask that represents the asynchronous get operation. The task result contains a <see cref="T:Discord.IApplicationCommand"/>
if found, otherwise <see langword="null"/>.
</returns>
</member>
<member name="M:Discord.Rest.RestGuild.CreateApplicationCommandAsync(Discord.ApplicationCommandProperties,Discord.RequestOptions)">
<summary>
Creates an application command within this guild.
</summary>
<param name="properties">The properties to use when creating the command.</param>
<param name="options">The options to be used when sending the request.</param>
<returns>
A task that represents the asynchronous creation operation. The task result contains the command that was created.
</returns>
</member>
<member name="M:Discord.Rest.RestGuild.BulkOverwriteApplicationCommandsAsync(Discord.ApplicationCommandProperties[],Discord.RequestOptions)">
<summary>
Overwrites the application commands within this guild.
</summary>
<param name="properties">A collection of properties to use when creating the commands.</param>
<param name="options">The options to be used when sending the request.</param>
<returns>
A task that represents the asynchronous creation operation. The task result contains a collection of commands that was created.
</returns>
</member>
<member name="M:Discord.Rest.RestGuild.ToString">
<summary>
Returns the name of the guild.
@@ -3752,6 +3795,15 @@
<member name="M:Discord.Rest.RestGuild.Discord#IGuild#DeleteStickerAsync(Discord.ICustomSticker,Discord.RequestOptions)">
<inheritdoc />
</member>
<member name="M:Discord.Rest.RestGuild.Discord#IGuild#CreateApplicationCommandAsync(Discord.ApplicationCommandProperties,Discord.RequestOptions)">
<inheritdoc />
</member>
<member name="M:Discord.Rest.RestGuild.Discord#IGuild#BulkOverwriteApplicationCommandsAsync(Discord.ApplicationCommandProperties[],Discord.RequestOptions)">
<inheritdoc />
</member>
<member name="M:Discord.Rest.RestGuild.Discord#IGuild#GetApplicationCommandAsync(System.UInt64,Discord.CacheMode,Discord.RequestOptions)">
<inheritdoc />
</member>
<member name="P:Discord.Rest.RestGuildIntegration.Name">
<inheritdoc />
</member>
@@ -3834,6 +3886,9 @@
<member name="P:Discord.Rest.RestApplicationCommand.ApplicationId">
<inheritdoc/>
</member>
<member name="P:Discord.Rest.RestApplicationCommand.Type">
<inheritdoc/>
</member>
<member name="P:Discord.Rest.RestApplicationCommand.Name">
<inheritdoc/>
</member>
@@ -3848,17 +3903,15 @@
The options of this command.
</summary>
</member>
<member name="P:Discord.Rest.RestApplicationCommand.CommandType">
<summary>
The type of this rest application command.
</summary>
</member>
<member name="P:Discord.Rest.RestApplicationCommand.CreatedAt">
<inheritdoc/>
</member>
<member name="M:Discord.Rest.RestApplicationCommand.DeleteAsync(Discord.RequestOptions)">
<inheritdoc/>
</member>
<member name="M:Discord.Rest.RestApplicationCommand.ModifyAsync(System.Action{Discord.ApplicationCommandProperties},Discord.RequestOptions)">
<inheritdoc/>
</member>
<member name="T:Discord.Rest.RestApplicationCommandChoice">
<summary>
Represents a Rest-based implementation of <see cref="T:Discord.IApplicationCommandOptionChoice"/>.
@@ -3900,24 +3953,9 @@
A collection of <see cref="T:Discord.Rest.RestApplicationCommandOption"/>'s for this command.
</summary>
</member>
<member name="T:Discord.Rest.RestApplicationCommandType">
<summary>
Represents a type of Rest-based command.
</summary>
</member>
<member name="F:Discord.Rest.RestApplicationCommandType.GlobalCommand">
<summary>
Specifies that this command is a Global command.
</summary>
</member>
<member name="F:Discord.Rest.RestApplicationCommandType.GuildCommand">
<summary>
Specifies that this command is a Guild specific command.
</summary>
</member>
<member name="T:Discord.Rest.RestGlobalCommand">
<summary>
Represents a global Slash command.
Represents a Rest-based global application command.
</summary>
</member>
<member name="M:Discord.Rest.RestGlobalCommand.DeleteAsync(Discord.RequestOptions)">
@@ -3935,7 +3973,7 @@
</member>
<member name="T:Discord.Rest.RestGuildCommand">
<summary>
Represents a Rest-based guild command.
Represents a Rest-based guild application command.
</summary>
</member>
<member name="P:Discord.Rest.RestGuildCommand.GuildId">


+ 65
- 18
src/Discord.Net.Rest/DiscordRestApiClient.cs View File

@@ -235,6 +235,7 @@ namespace Discord.API
options.BucketId = bucketId;

string json = payload != null ? SerializeJson(payload) : null;

var request = new JsonRestRequest(RestClient, method, endpoint, json, options);
return DeserializeJson<TResponse>(await SendInternalAsync(method, endpoint, request).ConfigureAwait(false));
}
@@ -1123,6 +1124,18 @@ namespace Discord.API
return await SendAsync<ApplicationCommand[]>("GET", () => $"applications/{this.CurrentUserId}/commands", new BucketIds(), options: options).ConfigureAwait(false);
}

public async Task<ApplicationCommand> GetGlobalApplicationCommandAsync(ulong id, RequestOptions options = null)
{
Preconditions.NotEqual(id, 0, nameof(id));

options = RequestOptions.CreateOrClone(options);

try
{
return await SendAsync<ApplicationCommand>("GET", () => $"applications/{this.CurrentUserId}/commands/{id}", new BucketIds(), options: options).ConfigureAwait(false);
}
catch(HttpException x) when (x.HttpCode == HttpStatusCode.NotFound) { return null; }
}

public async Task<ApplicationCommand> CreateGlobalApplicationCommandAsync(CreateApplicationCommandParams command, RequestOptions options = null)
{
@@ -1134,7 +1147,6 @@ namespace Discord.API

options = RequestOptions.CreateOrClone(options);


return await TrySendApplicationCommand(SendJsonAsync<ApplicationCommand>("POST", () => $"applications/{this.CurrentUserId}/commands", command, new BucketIds(), options: options)).ConfigureAwait(false);
}
public async Task<ApplicationCommand> ModifyGlobalApplicationCommandAsync(ModifyApplicationCommandParams command, ulong commandId, RequestOptions options = null)
@@ -1143,6 +1155,18 @@ namespace Discord.API

return await TrySendApplicationCommand(SendJsonAsync<ApplicationCommand>("PATCH", () => $"applications/{this.CurrentUserId}/commands/{commandId}", command, new BucketIds(), options: options)).ConfigureAwait(false);
}
public async Task<ApplicationCommand> ModifyGlobalApplicationUserCommandAsync(ModifyApplicationCommandParams command, ulong commandId, RequestOptions options = null)
{
options = RequestOptions.CreateOrClone(options);

return await TrySendApplicationCommand(SendJsonAsync<ApplicationCommand>("PATCH", () => $"applications/{this.CurrentUserId}/commands/{commandId}", command, new BucketIds(), options: options)).ConfigureAwait(false);
}
public async Task<ApplicationCommand> ModifyGlobalApplicationMessageCommandAsync(ModifyApplicationCommandParams command, ulong commandId, RequestOptions options = null)
{
options = RequestOptions.CreateOrClone(options);

return await TrySendApplicationCommand(SendJsonAsync<ApplicationCommand>("PATCH", () => $"applications/{this.CurrentUserId}/commands/{commandId}", command, new BucketIds(), options: options)).ConfigureAwait(false);
}
public async Task DeleteGlobalApplicationCommandAsync(ulong commandId, RequestOptions options = null)
{
options = RequestOptions.CreateOrClone(options);
@@ -1156,6 +1180,40 @@ namespace Discord.API

return await TrySendApplicationCommand(SendJsonAsync<ApplicationCommand[]>("PUT", () => $"applications/{this.CurrentUserId}/commands", commands, new BucketIds(), options: options)).ConfigureAwait(false);
}
public async Task<ApplicationCommand> CreateGlobalApplicationUserCommandAsync(CreateApplicationCommandParams command, RequestOptions options = null)
{
Preconditions.NotNull(command, nameof(command));
Preconditions.AtMost(command.Name.Length, 32, nameof(command.Name));
Preconditions.AtLeast(command.Name.Length, 3, nameof(command.Name));

options = RequestOptions.CreateOrClone(options);

return await TrySendApplicationCommand(SendJsonAsync<ApplicationCommand>("POST", () => $"applications/{this.CurrentUserId}/commands", command, new BucketIds(), options: options)).ConfigureAwait(false);
}
public async Task<ApplicationCommand> CreateGlobalApplicationMessageCommandAsync(CreateApplicationCommandParams command, RequestOptions options = null)
{
Preconditions.NotNull(command, nameof(command));
Preconditions.AtMost(command.Name.Length, 32, nameof(command.Name));
Preconditions.AtLeast(command.Name.Length, 3, nameof(command.Name));

options = RequestOptions.CreateOrClone(options);

return await TrySendApplicationCommand(SendJsonAsync<ApplicationCommand>("POST", () => $"applications/{this.CurrentUserId}/commands", command, new BucketIds(), options: options)).ConfigureAwait(false);
}

public async Task<ApplicationCommand[]> BulkOverwriteGlobalApplicationUserCommands(CreateApplicationCommandParams[] commands, RequestOptions options = null)
{
options = RequestOptions.CreateOrClone(options);

return await TrySendApplicationCommand(SendJsonAsync<ApplicationCommand[]>("PUT", () => $"applications/{this.CurrentUserId}/commands", commands, new BucketIds(), options: options)).ConfigureAwait(false);
}

public async Task<ApplicationCommand[]> BulkOverwriteGlobalApplicationMessageCommands(CreateApplicationCommandParams[] commands, RequestOptions options = null)
{
options = RequestOptions.CreateOrClone(options);

return await TrySendApplicationCommand(SendJsonAsync<ApplicationCommand[]>("PUT", () => $"applications/{this.CurrentUserId}/commands", commands, new BucketIds(), options: options)).ConfigureAwait(false);
}

public async Task<ApplicationCommand[]> GetGuildApplicationCommandsAsync(ulong guildId, RequestOptions options = null)
{
@@ -1172,7 +1230,11 @@ namespace Discord.API

var bucket = new BucketIds(guildId: guildId);

return await SendAsync<ApplicationCommand>("GET", () => $"applications/{this.CurrentUserId}/guilds/{guildId}/commands/{commandId}", bucket, options: options);
try
{
return await SendAsync<ApplicationCommand>("GET", () => $"applications/{this.CurrentUserId}/guilds/{guildId}/commands/{commandId}", bucket, options: options);
}
catch(HttpException x) when (x.HttpCode == HttpStatusCode.NotFound) { return null; }
}

public async Task<ApplicationCommand> CreateGuildApplicationCommandAsync(CreateApplicationCommandParams command, ulong guildId, RequestOptions options = null)
@@ -1182,7 +1244,6 @@ namespace Discord.API
var bucket = new BucketIds(guildId: guildId);

return await TrySendApplicationCommand(SendJsonAsync<ApplicationCommand>("POST", () => $"applications/{this.CurrentUserId}/guilds/{guildId}/commands", command, bucket, options: options)).ConfigureAwait(false);

}
public async Task<ApplicationCommand> ModifyGuildApplicationCommandAsync(ModifyApplicationCommandParams command, ulong guildId, ulong commandId, RequestOptions options = null)
{
@@ -1190,21 +1251,7 @@ namespace Discord.API

var bucket = new BucketIds(guildId: guildId);

try
{
return await SendJsonAsync<ApplicationCommand>("PATCH", () => $"applications/{this.CurrentUserId}/guilds/{guildId}/commands/{commandId}", command, bucket, options: options).ConfigureAwait(false);
}
catch (HttpException x)
{
if (x.HttpCode == HttpStatusCode.BadRequest)
{
var json = (x.Request as JsonRestRequest).Json;
throw new ApplicationCommandException(json, x);
}

// Re-throw the http exception
throw;
}
return await TrySendApplicationCommand(SendJsonAsync<ApplicationCommand>("PATCH", () => $"applications/{this.CurrentUserId}/guilds/{guildId}/commands/{commandId}", command, bucket, options: options)).ConfigureAwait(false);
}
public async Task DeleteGuildApplicationCommandAsync(ulong guildId, ulong commandId, RequestOptions options = null)
{


+ 16
- 12
src/Discord.Net.Rest/DiscordRestClient.cs View File

@@ -1,3 +1,4 @@
//using Discord.Rest.Entities.Interactions;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
@@ -107,22 +108,18 @@ namespace Discord.Rest
public Task<RestWebhook> GetWebhookAsync(ulong id, RequestOptions options = null)
=> ClientHelper.GetWebhookAsync(this, id, options);

public Task<RestGlobalCommand> CreateGlobalCommand(SlashCommandCreationProperties properties, RequestOptions options = null)
=> InteractionHelper.CreateGlobalCommand(this, properties, options);
public Task<RestGlobalCommand> CreateGlobalCommand(Action<SlashCommandCreationProperties> func, RequestOptions options = null)
=> InteractionHelper.CreateGlobalCommand(this, func, options);
public Task<RestGuildCommand> CreateGuildCommand(SlashCommandCreationProperties properties, ulong guildId, RequestOptions options = null)
=> InteractionHelper.CreateGuildCommand(this, guildId, properties, options);
public Task<RestGuildCommand> CreateGuildCommand(Action<SlashCommandCreationProperties> func, ulong guildId, RequestOptions options = null)
=> InteractionHelper.CreateGuildCommand(this, guildId, func, options);
public Task<RestGlobalCommand> CreateGlobalCommand(ApplicationCommandProperties properties, RequestOptions options = null)
=> ClientHelper.CreateGlobalApplicationCommand(this, properties, options);
public Task<RestGuildCommand> CreateGuildCommand(ApplicationCommandProperties properties, ulong guildId, RequestOptions options = null)
=> ClientHelper.CreateGuildApplicationCommand(this, guildId, properties, options);
public Task<IReadOnlyCollection<RestGlobalCommand>> GetGlobalApplicationCommands(RequestOptions options = null)
=> ClientHelper.GetGlobalApplicationCommands(this, options);
public Task<IReadOnlyCollection<RestGuildCommand>> GetGuildApplicationCommands(ulong guildId, RequestOptions options = null)
=> ClientHelper.GetGuildApplicationCommands(this, guildId, options);
public Task<IReadOnlyCollection<RestGlobalCommand>> BulkOverwriteGlobalCommands(SlashCommandCreationProperties[] commandProperties, RequestOptions options = null)
=> InteractionHelper.BulkOverwriteGlobalCommands(this, commandProperties, options);
public Task<IReadOnlyCollection<RestGuildCommand>> BulkOverwriteGuildCommands(SlashCommandCreationProperties[] commandProperties, ulong guildId, RequestOptions options = null)
=> InteractionHelper.BulkOverwriteGuildCommands(this, guildId, commandProperties, options);
public Task<IReadOnlyCollection<RestGlobalCommand>> BulkOverwriteGlobalCommands(ApplicationCommandProperties[] commandProperties, RequestOptions options = null)
=> ClientHelper.BulkOverwriteGlobalApplicationCommand(this, commandProperties, options);
public Task<IReadOnlyCollection<RestGuildCommand>> BulkOverwriteGuildCommands(ApplicationCommandProperties[] commandProperties, ulong guildId, RequestOptions options = null)
=> ClientHelper.BulkOverwriteGuildApplicationCommand(this, guildId, commandProperties, options);
public Task<IReadOnlyCollection<GuildApplicationCommandPermission>> BatchEditGuildCommandPermissions(ulong guildId, IDictionary<ulong, ApplicationCommandPermission[]> permissions, RequestOptions options = null)
=> InteractionHelper.BatchEditGuildCommandPermissionsAsync(this, guildId, permissions, options);
public Task DeleteAllGlobalCommandsAsync(RequestOptions options = null)
@@ -225,5 +222,12 @@ namespace Discord.Rest
/// <inheritdoc />
async Task<IWebhook> IDiscordClient.GetWebhookAsync(ulong id, RequestOptions options)
=> await GetWebhookAsync(id, options).ConfigureAwait(false);

/// <inheritdoc />
async Task<IReadOnlyCollection<IApplicationCommand>> IDiscordClient.GetGlobalApplicationCommandsAsync(RequestOptions options)
=> await GetGlobalApplicationCommands(options).ConfigureAwait(false);
/// <inheritdoc />
async Task<IApplicationCommand> IDiscordClient.GetGlobalApplicationCommandAsync(ulong id, RequestOptions options)
=> await ClientHelper.GetGlobalApplicationCommand(this, id, options).ConfigureAwait(false);
}
}

+ 58
- 1
src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs View File

@@ -898,8 +898,48 @@ namespace Discord.Rest
/// A task that represents the asynchronous get operation. The task result contains a read-only collection
/// of application commands found within the guild.
/// </returns>
public async Task<IReadOnlyCollection<RestApplicationCommand>> GetApplicationCommandsAsync (RequestOptions options = null)
public async Task<IReadOnlyCollection<RestGuildCommand>> GetApplicationCommandsAsync (RequestOptions options = null)
=> await ClientHelper.GetGuildApplicationCommands(Discord, Id, options).ConfigureAwait(false);
/// <summary>
/// Gets an application command within this guild with the specified id.
/// </summary>
/// <param name="id">The id of the application command to get.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A ValueTask that represents the asynchronous get operation. The task result contains a <see cref="IApplicationCommand"/>
/// if found, otherwise <see langword="null"/>.
/// </returns>
public async Task<RestGuildCommand> GetApplicationCommandAsync(ulong id, RequestOptions options = null)
=> await ClientHelper.GetGuildApplicationCommand(Discord, id, this.Id, options);
/// <summary>
/// Creates an application command within this guild.
/// </summary>
/// <param name="properties">The properties to use when creating the command.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous creation operation. The task result contains the command that was created.
/// </returns>
public async Task<RestGuildCommand> CreateApplicationCommandAsync(ApplicationCommandProperties properties, RequestOptions options = null)
{
var model = await InteractionHelper.CreateGuildCommand(Discord, this.Id, properties, options);

return RestGuildCommand.Create(Discord, model, this.Id);
}
/// <summary>
/// Overwrites the application commands within this guild.
/// </summary>
/// <param name="properties">A collection of properties to use when creating the commands.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous creation operation. The task result contains a collection of commands that was created.
/// </returns>
public async Task<IReadOnlyCollection<RestGuildCommand>> BulkOverwriteApplicationCommandsAsync(ApplicationCommandProperties[] properties,
RequestOptions options = null)
{
var models = await InteractionHelper.BulkOverwriteGuildCommands(Discord, this.Id, properties, options);

return models.Select(x => RestGuildCommand.Create(Discord, x, this.Id)).ToImmutableArray();
}

/// <summary>
/// Returns the name of the guild.
@@ -1327,5 +1367,22 @@ namespace Discord.Rest
/// <inheritdoc />
Task IGuild.DeleteStickerAsync(ICustomSticker sticker, RequestOptions options)
=> sticker.DeleteAsync();
/// <inheritdoc />
async Task<IApplicationCommand> IGuild.CreateApplicationCommandAsync(ApplicationCommandProperties properties, RequestOptions options)
=> await CreateApplicationCommandAsync(properties, options);
/// <inheritdoc />
async Task<IReadOnlyCollection<IApplicationCommand>> IGuild.BulkOverwriteApplicationCommandsAsync(ApplicationCommandProperties[] properties,
RequestOptions options)
=> await BulkOverwriteApplicationCommandsAsync(properties, options);
/// <inheritdoc />
async Task<IApplicationCommand> IGuild.GetApplicationCommandAsync(ulong id, CacheMode mode, RequestOptions options)
{
if (mode == CacheMode.AllowDownload)
{
return await GetApplicationCommandAsync(id, options);
}
else
return null;
}
}
}

+ 174
- 150
src/Discord.Net.Rest/Entities/Interactions/InteractionHelper.cs View File

@@ -1,6 +1,7 @@
using Discord.API;
using Discord.API.Rest;
using Discord.Net;
//using Discord.Rest.Entities.Interactions;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -43,40 +44,52 @@ namespace Discord.Rest
}

// Global commands
public static async Task<RestGlobalCommand> CreateGlobalCommand(BaseDiscordClient client,
Action<SlashCommandCreationProperties> func, RequestOptions options = null)
public static async Task<RestGlobalCommand> GetGlobalCommandAsync(BaseDiscordClient client, ulong id,
RequestOptions options = null)
{
var args = new SlashCommandCreationProperties();
func(args);
return await CreateGlobalCommand(client, args, options).ConfigureAwait(false);
var model = await client.ApiClient.GetGlobalApplicationCommandAsync(id, options).ConfigureAwait(false);

return RestGlobalCommand.Create(client, model);
}
public static async Task<RestGlobalCommand> CreateGlobalCommand(BaseDiscordClient client,
SlashCommandCreationProperties arg, RequestOptions options = null)
public static Task<ApplicationCommand> CreateGlobalCommand<TArg>(BaseDiscordClient client,
Action<TArg> func, RequestOptions options = null) where TArg : ApplicationCommandProperties
{
var args = Activator.CreateInstance(typeof(TArg));
func((TArg)args);
return CreateGlobalCommand(client, (TArg)args, options);
}
public static async Task<ApplicationCommand> CreateGlobalCommand(BaseDiscordClient client,
ApplicationCommandProperties arg, RequestOptions options = null)
{
Preconditions.NotNullOrEmpty(arg.Name, nameof(arg.Name));
Preconditions.NotNullOrEmpty(arg.Description, nameof(arg.Description));

if (arg.Options.IsSpecified)
Preconditions.AtMost(arg.Options.Value.Count, 25, nameof(arg.Options));

var model = new CreateApplicationCommandParams()
{
Name = arg.Name,
Description = arg.Description,
Options = arg.Options.IsSpecified
? arg.Options.Value.Select(x => new Discord.API.ApplicationCommandOption(x)).ToArray()
: Optional<Discord.API.ApplicationCommandOption[]>.Unspecified,
DefaultPermission = arg.DefaultPermission.IsSpecified
? arg.DefaultPermission.Value
: Optional<bool>.Unspecified
Name = arg.Name.Value,
Type = arg.Type,
};

var cmd = await client.ApiClient.CreateGlobalApplicationCommandAsync(model, options).ConfigureAwait(false);
return RestGlobalCommand.Create(client, cmd);
if (arg is SlashCommandProperties slashProps)
{
Preconditions.NotNullOrEmpty(slashProps.Description, nameof(slashProps.Description));

model.Description = slashProps.Description.Value;

model.Options = slashProps.Options.IsSpecified
? slashProps.Options.Value.Select(x => new Discord.API.ApplicationCommandOption(x)).ToArray()
: Optional<Discord.API.ApplicationCommandOption[]>.Unspecified;

model.DefaultPermission = slashProps.DefaultPermission.IsSpecified
? slashProps.DefaultPermission.Value
: Optional<bool>.Unspecified;
}

return await client.ApiClient.CreateGlobalApplicationCommandAsync(model, options).ConfigureAwait(false);
}

public static async Task<IReadOnlyCollection<RestGlobalCommand>> BulkOverwriteGlobalCommands(BaseDiscordClient client,
SlashCommandCreationProperties[] args, RequestOptions options = null)
public static async Task<ApplicationCommand[]> BulkOverwriteGlobalCommands(BaseDiscordClient client,
ApplicationCommandProperties[] args, RequestOptions options = null)
{
Preconditions.NotNull(args, nameof(args));

@@ -85,33 +98,36 @@ namespace Discord.Rest
foreach (var arg in args)
{
Preconditions.NotNullOrEmpty(arg.Name, nameof(arg.Name));
Preconditions.NotNullOrEmpty(arg.Description, nameof(arg.Description));

if (arg.Options.IsSpecified)
Preconditions.AtMost(arg.Options.Value.Count, 25, nameof(arg.Options));

var model = new CreateApplicationCommandParams()
{
Name = arg.Name,
Description = arg.Description,
Options = arg.Options.IsSpecified
? arg.Options.Value.Select(x => new Discord.API.ApplicationCommandOption(x)).ToArray()
: Optional<Discord.API.ApplicationCommandOption[]>.Unspecified,
DefaultPermission = arg.DefaultPermission.IsSpecified
? arg.DefaultPermission.Value
: Optional<bool>.Unspecified
Name = arg.Name.Value,
Type = arg.Type,
};

if (arg is SlashCommandProperties slashProps)
{
Preconditions.NotNullOrEmpty(slashProps.Description, nameof(slashProps.Description));

model.Description = slashProps.Description.Value;

model.Options = slashProps.Options.IsSpecified
? slashProps.Options.Value.Select(x => new Discord.API.ApplicationCommandOption(x)).ToArray()
: Optional<Discord.API.ApplicationCommandOption[]>.Unspecified;

model.DefaultPermission = slashProps.DefaultPermission.IsSpecified
? slashProps.DefaultPermission.Value
: Optional<bool>.Unspecified;
}

models.Add(model);
}

var apiModels = await client.ApiClient.BulkOverwriteGlobalApplicationCommands(models.ToArray(), options);

return apiModels.Select(x => RestGlobalCommand.Create(client, x)).ToArray();
return await client.ApiClient.BulkOverwriteGlobalApplicationCommands(models.ToArray(), options).ConfigureAwait(false);
}

public static async Task<IReadOnlyCollection<RestGuildCommand>> BulkOverwriteGuildCommands(BaseDiscordClient client, ulong guildId,
SlashCommandCreationProperties[] args, RequestOptions options = null)
public static async Task<IReadOnlyCollection<ApplicationCommand>> BulkOverwriteGuildCommands(BaseDiscordClient client, ulong guildId,
ApplicationCommandProperties[] args, RequestOptions options = null)
{
Preconditions.NotNull(args, nameof(args));

@@ -120,74 +136,86 @@ namespace Discord.Rest
foreach (var arg in args)
{
Preconditions.NotNullOrEmpty(arg.Name, nameof(arg.Name));
Preconditions.NotNullOrEmpty(arg.Description, nameof(arg.Description));

if (arg.Options.IsSpecified)
Preconditions.AtMost(arg.Options.Value.Count, 25, nameof(arg.Options));

var model = new CreateApplicationCommandParams()
{
Name = arg.Name,
Description = arg.Description,
Options = arg.Options.IsSpecified
? arg.Options.Value.Select(x => new Discord.API.ApplicationCommandOption(x)).ToArray()
: Optional<Discord.API.ApplicationCommandOption[]>.Unspecified,
DefaultPermission = arg.DefaultPermission.IsSpecified
? arg.DefaultPermission.Value
: Optional<bool>.Unspecified
Name = arg.Name.Value,
Type = arg.Type,
};

if (arg is SlashCommandProperties slashProps)
{
Preconditions.NotNullOrEmpty(slashProps.Description, nameof(slashProps.Description));

model.Description = slashProps.Description.Value;

model.Options = slashProps.Options.IsSpecified
? slashProps.Options.Value.Select(x => new Discord.API.ApplicationCommandOption(x)).ToArray()
: Optional<Discord.API.ApplicationCommandOption[]>.Unspecified;

model.DefaultPermission = slashProps.DefaultPermission.IsSpecified
? slashProps.DefaultPermission.Value
: Optional<bool>.Unspecified;
}

models.Add(model);
}

var apiModels = await client.ApiClient.BulkOverwriteGuildApplicationCommands(guildId, models.ToArray(), options);

return apiModels.Select(x => RestGuildCommand.Create(client, x, guildId)).ToArray();
return await client.ApiClient.BulkOverwriteGuildApplicationCommands(guildId, models.ToArray(), options).ConfigureAwait(false);
}

public static async Task<RestGlobalCommand> ModifyGlobalCommand(BaseDiscordClient client, RestGlobalCommand command,
Action<ApplicationCommandProperties> func, RequestOptions options = null)
public static Task<ApplicationCommand> ModifyGlobalCommand<TArg>(BaseDiscordClient client, IApplicationCommand command,
Action<TArg> func, RequestOptions options = null) where TArg : ApplicationCommandProperties
{
ApplicationCommandProperties args = new ApplicationCommandProperties();
func(args);
var arg = (TArg)Activator.CreateInstance(typeof(TArg));
func(arg);
return ModifyGlobalCommand(client, command, arg, options);
}

public static async Task<ApplicationCommand> ModifyGlobalCommand(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));
}
if (args.Description.IsSpecified)
{
Preconditions.AtMost(args.Description.Value.Length, 100, nameof(args.Description));
Preconditions.AtLeast(args.Description.Value.Length, 1, nameof(args.Description));
}


if (args.Options.IsSpecified)
{
if (args.Options.Value.Count > 10)
throw new ArgumentException("Option count must be 10 or less");
}

var model = new Discord.API.Rest.ModifyApplicationCommandParams()
{
Name = args.Name,
Description = args.Description,
Options = args.Options.IsSpecified
? args.Options.Value.Select(x => new Discord.API.ApplicationCommandOption(x)).ToArray()
: Optional<Discord.API.ApplicationCommandOption[]>.Unspecified,
DefaultPermission = args.DefaultPermission.IsSpecified
? args.DefaultPermission.Value
: Optional<bool>.Unspecified
};

var msg = await client.ApiClient.ModifyGlobalApplicationCommandAsync(model, command.Id, options).ConfigureAwait(false);
command.Update(msg);
return command;
if(args is SlashCommandProperties slashProps)
{
if (slashProps.Description.IsSpecified)
{
Preconditions.AtMost(slashProps.Description.Value.Length, 100, nameof(slashProps.Description));
Preconditions.AtLeast(slashProps.Description.Value.Length, 1, nameof(slashProps.Description));
}

if (slashProps.Options.IsSpecified)
{
if (slashProps.Options.Value.Count > 10)
throw new ArgumentException("Option count must be 10 or less");
}

model.Description = slashProps.Description;

model.Options = slashProps.Options.IsSpecified
? slashProps.Options.Value.Select(x => new Discord.API.ApplicationCommandOption(x)).ToArray()
: Optional<Discord.API.ApplicationCommandOption[]>.Unspecified;

model.DefaultPermission = slashProps.DefaultPermission.IsSpecified
? slashProps.DefaultPermission.Value
: Optional<bool>.Unspecified;
}

return await client.ApiClient.ModifyGlobalApplicationCommandAsync(model, command.Id, options).ConfigureAwait(false);
}


public static async Task DeleteGlobalCommand(BaseDiscordClient client, RestGlobalCommand command, RequestOptions options = null)
public static async Task DeleteGlobalCommand(BaseDiscordClient client, IApplicationCommand command, RequestOptions options = null)
{
Preconditions.NotNull(command, nameof(command));
Preconditions.NotEqual(command.Id, 0, nameof(command.Id));
@@ -196,90 +224,73 @@ namespace Discord.Rest
}

// Guild Commands
public static async Task<RestGuildCommand> CreateGuildCommand(BaseDiscordClient client, ulong guildId,
Action<SlashCommandCreationProperties> func, RequestOptions options = null)
public static Task<ApplicationCommand> CreateGuildCommand<TArg>(BaseDiscordClient client, ulong guildId,
Action<TArg> func, RequestOptions options) where TArg : ApplicationCommandProperties
{
var args = new SlashCommandCreationProperties();
func(args);

return await CreateGuildCommand(client, guildId, args, options).ConfigureAwait(false);
var args = Activator.CreateInstance(typeof(TArg));
func((TArg)args);
return CreateGuildCommand(client, guildId, (TArg)args, options);
}
public static async Task<RestGuildCommand> CreateGuildCommand(BaseDiscordClient client, ulong guildId,
SlashCommandCreationProperties args, RequestOptions options = null)
{
Preconditions.NotNullOrEmpty(args.Name, nameof(args.Name));
Preconditions.NotNullOrEmpty(args.Description, nameof(args.Description));

Preconditions.AtMost(args.Name.Length, 32, nameof(args.Name));
Preconditions.AtLeast(args.Name.Length, 3, nameof(args.Name));
Preconditions.AtMost(args.Description.Length, 100, nameof(args.Description));
Preconditions.AtLeast(args.Description.Length, 1, nameof(args.Description));
public static async Task<ApplicationCommand> CreateGuildCommand(BaseDiscordClient client, ulong guildId,
ApplicationCommandProperties arg, RequestOptions options = null)
{
var model = new CreateApplicationCommandParams()
{
Name = arg.Name.Value,
Type = arg.Type,
};

if (args.Options.IsSpecified)
if (arg is SlashCommandProperties slashProps)
{
if (args.Options.Value.Count > 10)
throw new ArgumentException("Option count must be 10 or less");
Preconditions.NotNullOrEmpty(slashProps.Description, nameof(slashProps.Description));

foreach (var item in args.Options.Value)
{
Preconditions.NotNullOrEmpty(item.Name, nameof(item.Name));
Preconditions.NotNullOrEmpty(item.Description, nameof(item.Description));
}
}
model.Description = slashProps.Description.Value;

var model = new CreateApplicationCommandParams()
{
Name = args.Name,
Description = args.Description,
Options = args.Options.IsSpecified
? args.Options.Value.Select(x => new Discord.API.ApplicationCommandOption(x)).ToArray()
: Optional<Discord.API.ApplicationCommandOption[]>.Unspecified,
DefaultPermission = args.DefaultPermission.IsSpecified
? args.DefaultPermission.Value
: Optional<bool>.Unspecified
};
model.Options = slashProps.Options.IsSpecified
? slashProps.Options.Value.Select(x => new Discord.API.ApplicationCommandOption(x)).ToArray()
: Optional<Discord.API.ApplicationCommandOption[]>.Unspecified;

var cmd = await client.ApiClient.CreateGuildApplicationCommandAsync(model, guildId, options).ConfigureAwait(false);
return RestGuildCommand.Create(client, cmd, guildId);
model.DefaultPermission = slashProps.DefaultPermission.IsSpecified
? slashProps.DefaultPermission.Value
: Optional<bool>.Unspecified;
}

return await client.ApiClient.CreateGuildApplicationCommandAsync(model, guildId, options).ConfigureAwait(false);
}
public static async Task<RestGuildCommand> ModifyGuildCommand(BaseDiscordClient client, RestGuildCommand command,
Action<ApplicationCommandProperties> func, RequestOptions options = null)

public static Task<ApplicationCommand> ModifyGuildCommand<TArg>(BaseDiscordClient client, IApplicationCommand command, ulong guildId,
Action<TArg> func, RequestOptions options = null) where TArg : ApplicationCommandProperties
{
ApplicationCommandProperties args = new ApplicationCommandProperties();
func(args);
var arg = (TArg)Activator.CreateInstance(typeof(TArg));
func(arg);
return ModifyGuildCommand(client, command, guildId, arg, options);
}

if (args.Name.IsSpecified)
{
Preconditions.AtMost(args.Name.Value.Length, 32, nameof(args.Name));
Preconditions.AtLeast(args.Name.Value.Length, 3, nameof(args.Name));
}
if (args.Description.IsSpecified)
public static async Task<ApplicationCommand> ModifyGuildCommand(BaseDiscordClient client, IApplicationCommand command, ulong guildId,
ApplicationCommandProperties arg, RequestOptions options = null)
{
var model = new ModifyApplicationCommandParams()
{
Preconditions.AtMost(args.Description.Value.Length, 100, nameof(args.Description));
Preconditions.AtLeast(args.Description.Value.Length, 1, nameof(args.Description));
}
Name = arg.Name.Value,
};

if (args.Options.IsSpecified)
if (arg is SlashCommandProperties slashProps)
{
if (args.Options.Value.Count > 10)
throw new ArgumentException("Option count must be 10 or less");
}
Preconditions.NotNullOrEmpty(slashProps.Description, nameof(slashProps.Description));

var model = new Discord.API.Rest.ModifyApplicationCommandParams()
{
Name = args.Name,
Description = args.Description,
Options = args.Options.IsSpecified
? args.Options.Value.Select(x => new Discord.API.ApplicationCommandOption(x)).ToArray()
: Optional<Discord.API.ApplicationCommandOption[]>.Unspecified,
DefaultPermission = args.DefaultPermission.IsSpecified
? args.DefaultPermission.Value
: Optional<bool>.Unspecified
};
model.Description = slashProps.Description.Value;

model.Options = slashProps.Options.IsSpecified
? slashProps.Options.Value.Select(x => new Discord.API.ApplicationCommandOption(x)).ToArray()
: Optional<Discord.API.ApplicationCommandOption[]>.Unspecified;

model.DefaultPermission = slashProps.DefaultPermission.IsSpecified
? slashProps.DefaultPermission.Value
: Optional<bool>.Unspecified;
}

var msg = await client.ApiClient.ModifyGuildApplicationCommandAsync(model, command.GuildId, command.Id, options).ConfigureAwait(false);
command.Update(msg);
return command;
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)
@@ -290,6 +301,19 @@ 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)
{
if (guildId.HasValue)
{
return DeleteGuildCommand(client, guildId.Value, command, options);
}
else
{
return DeleteGlobalCommand(client, command, options);
}
}

// Responses
public static async Task<Discord.API.Message> ModifyFollowupMessage(BaseDiscordClient client, RestFollowupMessage message, Action<MessageProperties> func,
RequestOptions options = null)
{


+ 14
- 13
src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommand.cs View File

@@ -16,6 +16,9 @@ namespace Discord.Rest
/// <inheritdoc/>
public ulong ApplicationId { get; private set; }

/// <inheritdoc/>
public ApplicationCommandType Type { get; private set; }

/// <inheritdoc/>
public string Name { get; private set; }

@@ -30,11 +33,6 @@ namespace Discord.Rest
/// </summary>
public IReadOnlyCollection<RestApplicationCommandOption> Options { get; private set; }

/// <summary>
/// The type of this rest application command.
/// </summary>
public RestApplicationCommandType CommandType { get; internal set; }

/// <inheritdoc/>
public DateTimeOffset CreatedAt
=> SnowflakeUtils.FromSnowflake(this.Id);
@@ -45,15 +43,16 @@ namespace Discord.Rest

}

internal static RestApplicationCommand Create(BaseDiscordClient client, Model model, RestApplicationCommandType type, ulong guildId = 0)
internal static RestApplicationCommand Create(BaseDiscordClient client, Model model, ulong? guildId)
{
if (type == RestApplicationCommandType.GlobalCommand)
if (guildId.HasValue)
{
return RestGuildCommand.Create(client, model, guildId.Value);
}
else
{
return RestGlobalCommand.Create(client, model);

if (type == RestApplicationCommandType.GuildCommand)
return RestGuildCommand.Create(client, model, guildId);

return null;
}
}

internal virtual void Update(Model model)
@@ -72,7 +71,9 @@ namespace Discord.Rest
/// <inheritdoc/>
public abstract Task DeleteAsync(RequestOptions options = null);

IReadOnlyCollection<IApplicationCommandOption> IApplicationCommand.Options => Options;
/// <inheritdoc/>
public abstract Task ModifyAsync(Action<ApplicationCommandProperties> func, RequestOptions options = null);

IReadOnlyCollection<IApplicationCommandOption> IApplicationCommand.Options => Options;
}
}

+ 5
- 2
src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommandOption.cs View File

@@ -68,7 +68,10 @@ namespace Discord.Rest
: null;
}

IReadOnlyCollection<IApplicationCommandOption> IApplicationCommandOption.Options => Options;
IReadOnlyCollection<IApplicationCommandOptionChoice> IApplicationCommandOption.Choices => Choices;
//IApplicationCommandOption
IReadOnlyCollection<IApplicationCommandOption> IApplicationCommandOption.Options
=> Options;
IReadOnlyCollection<IApplicationCommandOptionChoice> IApplicationCommandOption.Choices
=> Choices;
}
}

+ 0
- 24
src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommandType.cs View File

@@ -1,24 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Discord.Rest
{
/// <summary>
/// Represents a type of Rest-based command.
/// </summary>
public enum RestApplicationCommandType
{
/// <summary>
/// Specifies that this command is a Global command.
/// </summary>
GlobalCommand,

/// <summary>
/// Specifies that this command is a Guild specific command.
/// </summary>
GuildCommand
}
}

+ 7
- 4
src/Discord.Net.Rest/Entities/Interactions/RestGlobalCommand.cs View File

@@ -8,14 +8,14 @@ using Model = Discord.API.ApplicationCommand;
namespace Discord.Rest
{
/// <summary>
/// Represents a global Slash command.
/// Represents a Rest-based global application command.
/// </summary>
public class RestGlobalCommand : RestApplicationCommand
{
internal RestGlobalCommand(BaseDiscordClient client, ulong id)
: base(client, id)
{
this.CommandType = RestApplicationCommandType.GlobalCommand;
}

internal static RestGlobalCommand Create(BaseDiscordClient client, Model model)
@@ -37,7 +37,10 @@ namespace Discord.Rest
/// <returns>
/// The modified command.
/// </returns>
public async Task<RestGlobalCommand> ModifyAsync(Action<ApplicationCommandProperties> func, RequestOptions options = null)
=> await InteractionHelper.ModifyGlobalCommand(Discord, this, func, options).ConfigureAwait(false);
public override async Task ModifyAsync(Action<ApplicationCommandProperties> func, RequestOptions options = null)
{
var cmd = await InteractionHelper.ModifyGlobalCommand(Discord, this, func, options).ConfigureAwait(false);
this.Update(cmd);
}
}
}

+ 6
- 4
src/Discord.Net.Rest/Entities/Interactions/RestGuildCommand.cs View File

@@ -8,7 +8,7 @@ using Model = Discord.API.ApplicationCommand;
namespace Discord.Rest
{
/// <summary>
/// Represents a Rest-based guild command.
/// Represents a Rest-based guild application command.
/// </summary>
public class RestGuildCommand : RestApplicationCommand
{
@@ -20,7 +20,6 @@ namespace Discord.Rest
internal RestGuildCommand(BaseDiscordClient client, ulong id, ulong guildId)
: base(client, id)
{
this.CommandType = RestApplicationCommandType.GuildCommand;
this.GuildId = guildId;
}

@@ -43,8 +42,11 @@ namespace Discord.Rest
/// <returns>
/// The modified command
/// </returns>
public async Task<RestGuildCommand> ModifyAsync(Action<ApplicationCommandProperties> func, RequestOptions options = null)
=> await InteractionHelper.ModifyGuildCommand(Discord, this, func, options).ConfigureAwait(false);
public override async Task ModifyAsync(Action<ApplicationCommandProperties> func, RequestOptions options = null)
{
var model = await InteractionHelper.ModifyGuildCommand(Discord, this, GuildId, func, options).ConfigureAwait(false);
this.Update(model);
}

/// <summary>
/// Gets this commands permissions inside of the current guild.


+ 2
- 20
src/Discord.Net.WebSocket/API/Gateway/ApplicationCommandCreatedUpdatedEvent.cs View File

@@ -7,27 +7,9 @@ using System.Threading.Tasks;

namespace Discord.API.Gateway
{
internal class ApplicationCommandCreatedUpdatedEvent
internal class ApplicationCommandCreatedUpdatedEvent : API.ApplicationCommand
{
[JsonProperty("name")]
public string Name { get; set; }

[JsonProperty("id")]
public ulong Id { get; set; }

[JsonProperty("description")]
public string Description { get; set; }

[JsonProperty("application_id")]
public ulong ApplicationId { get; set; }

[JsonProperty("guild_id")]
public ulong GuildId { get; set; }

[JsonProperty("options")]
public Optional<List<Discord.API.ApplicationCommandOption>> Options { get; set; }

[JsonProperty("default_permission")]
public Optional<bool> DefaultPermission { get; set; }
public Optional<ulong> GuildId { get; set; }
}
}

+ 31
- 0
src/Discord.Net.WebSocket/ClientState.cs View File

@@ -16,12 +16,14 @@ namespace Discord.WebSocket
private readonly ConcurrentDictionary<ulong, SocketGuild> _guilds;
private readonly ConcurrentDictionary<ulong, SocketGlobalUser> _users;
private readonly ConcurrentHashSet<ulong> _groupChannels;
private readonly ConcurrentDictionary<ulong, SocketApplicationCommand> _commands;

internal IReadOnlyCollection<SocketChannel> Channels => _channels.ToReadOnlyCollection();
internal IReadOnlyCollection<SocketDMChannel> DMChannels => _dmChannels.ToReadOnlyCollection();
internal IReadOnlyCollection<SocketGroupChannel> GroupChannels => _groupChannels.Select(x => GetChannel(x) as SocketGroupChannel).ToReadOnlyCollection(_groupChannels);
internal IReadOnlyCollection<SocketGuild> Guilds => _guilds.ToReadOnlyCollection();
internal IReadOnlyCollection<SocketGlobalUser> Users => _users.ToReadOnlyCollection();
internal IReadOnlyCollection<SocketApplicationCommand> Commands => _commands.ToReadOnlyCollection();

internal IReadOnlyCollection<ISocketPrivateChannel> PrivateChannels =>
_dmChannels.Select(x => x.Value as ISocketPrivateChannel).Concat(
@@ -37,6 +39,7 @@ namespace Discord.WebSocket
_guilds = new ConcurrentDictionary<ulong, SocketGuild>(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(guildCount * CollectionMultiplier));
_users = new ConcurrentDictionary<ulong, SocketGlobalUser>(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(estimatedUsersCount * CollectionMultiplier));
_groupChannels = new ConcurrentHashSet<ulong>(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(10 * CollectionMultiplier));
_commands = new ConcurrentDictionary<ulong, SocketApplicationCommand>();
}

internal SocketChannel GetChannel(ulong id)
@@ -139,5 +142,33 @@ namespace Discord.WebSocket
foreach (var guild in _guilds.Values)
guild.PurgeGuildUserCache();
}

internal SocketApplicationCommand GetCommand(ulong id)
{
if (_commands.TryGetValue(id, out SocketApplicationCommand command))
return command;
return null;
}
internal void AddCommand(SocketApplicationCommand command)
{
_commands[command.Id] = command;
}
internal SocketApplicationCommand GetOrAddCommand(ulong id, Func<ulong, SocketApplicationCommand> commandFactory)
{
return _commands.GetOrAdd(id, commandFactory);
}
internal SocketApplicationCommand RemoveCommand(ulong id)
{
if (_commands.TryRemove(id, out SocketApplicationCommand command))
return command;
return null;
}
internal void PurgeCommands(Func<SocketApplicationCommand, bool> precondition)
{
var ids = _commands.Where(x => precondition(x.Value)).Select(x => x.Key);

foreach (var id in ids)
_commands.TryRemove(id, out var _);
}
}
}

+ 1
- 1
src/Discord.Net.WebSocket/Discord.Net.WebSocket.csproj View File

@@ -20,7 +20,7 @@
<FileVersion>3.0.1</FileVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DefineConstants>TRACE;DEBUG;DEBUG_PACKETS</DefineConstants>
<DefineConstants>TRACE</DefineConstants>
</PropertyGroup>
<ItemGroup>


+ 186
- 49
src/Discord.Net.WebSocket/Discord.Net.WebSocket.xml View File

@@ -1118,6 +1118,27 @@
<member name="M:Discord.WebSocket.DiscordSocketClient.GetUser(System.String,System.String)">
<inheritdoc />
</member>
<member name="M:Discord.WebSocket.DiscordSocketClient.GetGlobalApplicationCommandAsync(System.UInt64,Discord.RequestOptions)">
<summary>
Gets a global application command.
</summary>
<param name="id">The id of the command.</param>
<param name="options">The options to be used when sending the request.</param>
<returns>
A ValueTask that represents the asynchronous get operation. The task result contains the application command if found, otherwise
<see langword="null"/>.
</returns>
</member>
<member name="M:Discord.WebSocket.DiscordSocketClient.GetGlobalApplicationCommandsAsync(Discord.RequestOptions)">
<summary>
Gets a collection of all global commands.
</summary>
<param name="options">The options to be used when sending the request.</param>
<returns>
A task that represents the asynchronous get operation. The task result contains a read-only collection of global
application commands.
</returns>
</member>
<member name="M:Discord.WebSocket.DiscordSocketClient.PurgeUserCache">
<summary>
Clears cached users from the client.
@@ -1216,6 +1237,12 @@
<member name="M:Discord.WebSocket.DiscordSocketClient.Discord#IDiscordClient#GetVoiceRegionAsync(System.String,Discord.RequestOptions)">
<inheritdoc />
</member>
<member name="M:Discord.WebSocket.DiscordSocketClient.Discord#IDiscordClient#GetGlobalApplicationCommandAsync(System.UInt64,Discord.RequestOptions)">
<inheritdoc />
</member>
<member name="M:Discord.WebSocket.DiscordSocketClient.Discord#IDiscordClient#GetGlobalApplicationCommandsAsync(Discord.RequestOptions)">
<inheritdoc />
</member>
<member name="M:Discord.WebSocket.DiscordSocketClient.Discord#IDiscordClient#StartAsync">
<inheritdoc />
</member>
@@ -3299,16 +3326,16 @@
voice regions the guild can access.
</returns>
</member>
<member name="M:Discord.WebSocket.SocketGuild.DeleteSlashCommandsAsync(Discord.RequestOptions)">
<member name="M:Discord.WebSocket.SocketGuild.DeleteApplicationCommandsAsync(Discord.RequestOptions)">
<summary>
Deletes all slash commands in the current guild.
Deletes all application commands in the current guild.
</summary>
<param name="options">The options to be used when sending the request.</param>
<returns>
A task that represents the asynchronous delete operation.
</returns>
</member>
<member name="M:Discord.WebSocket.SocketGuild.GetSlashCommandsAsync(Discord.RequestOptions)">
<member name="M:Discord.WebSocket.SocketGuild.GetApplicationCommandsAsync(Discord.RequestOptions)">
<summary>
Gets a collection of slash commands created by the current user in this guild.
</summary>
@@ -3318,15 +3345,36 @@
slash commands created by the current user.
</returns>
</member>
<member name="M:Discord.WebSocket.SocketGuild.GetSlashCommandAsync(System.UInt64,Discord.RequestOptions)">
<member name="M:Discord.WebSocket.SocketGuild.GetApplicationCommandAsync(System.UInt64,Discord.CacheMode,Discord.RequestOptions)">
<summary>
Gets a slash command in the current guild.
Gets an application command within this guild with the specified id.
</summary>
<param name="id">The unique identifier of the slash command.</param>
<param name="id">The id of the application command to get.</param>
<param name="mode">The <see cref="T:Discord.CacheMode" /> that determines whether the object should be fetched from cache.</param>
<param name="options">The options to be used when sending the request.</param>
<returns>
A task that represents the asynchronous get operation. The task result contains a
slash command created by the current user.
A ValueTask that represents the asynchronous get operation. The task result contains a <see cref="T:Discord.IApplicationCommand"/>
if found, otherwise <see langword="null"/>.
</returns>
</member>
<member name="M:Discord.WebSocket.SocketGuild.CreateApplicationCommandAsync(Discord.ApplicationCommandProperties,Discord.RequestOptions)">
<summary>
Creates an application command within this guild.
</summary>
<param name="properties">The properties to use when creating the command.</param>
<param name="options">The options to be used when sending the request.</param>
<returns>
A task that represents the asynchronous creation operation. The task result contains the command that was created.
</returns>
</member>
<member name="M:Discord.WebSocket.SocketGuild.BulkOverwriteApplicationCommandAsync(Discord.ApplicationCommandProperties[],Discord.RequestOptions)">
<summary>
Overwrites the application commands within this guild.
</summary>
<param name="properties">A collection of properties to use when creating the commands.</param>
<param name="options">The options to be used when sending the request.</param>
<returns>
A task that represents the asynchronous creation operation. The task result contains a collection of commands that was created.
</returns>
</member>
<member name="M:Discord.WebSocket.SocketGuild.GetInvitesAsync(Discord.RequestOptions)">
@@ -3467,16 +3515,6 @@
of webhooks found within the guild.
</returns>
</member>
<member name="M:Discord.WebSocket.SocketGuild.GetApplicationCommandsAsync(Discord.RequestOptions)">
<summary>
Gets this guilds slash commands commands
</summary>
<param name="options">The options to be used when sending the request.</param>
<returns>
A task that represents the asynchronous get operation. The task result contains a read-only collection
of application commands found within the guild.
</returns>
</member>
<member name="M:Discord.WebSocket.SocketGuild.GetEmotesAsync(Discord.RequestOptions)">
<inheritdoc />
</member>
@@ -3713,6 +3751,58 @@
<member name="M:Discord.WebSocket.SocketGuild.Discord#IGuild#GetApplicationCommandsAsync(Discord.RequestOptions)">
<inheritdoc />
</member>
<member name="T:Discord.WebSocket.SocketMessageCommand">
<summary>
Represents a Websocket-based slash command received over the gateway.
</summary>
</member>
<member name="P:Discord.WebSocket.SocketMessageCommand.Data">
<summary>
The data associated with this interaction.
</summary>
</member>
<member name="T:Discord.WebSocket.SocketMessageCommandData">
<summary>
Represents the data tied with the <see cref="T:Discord.WebSocket.SocketMessageCommand"/> interaction.
</summary>
</member>
<member name="P:Discord.WebSocket.SocketMessageCommandData.Message">
<summary>
Gets the messagte associated with this message command.
</summary>
</member>
<member name="P:Discord.WebSocket.SocketMessageCommandData.Options">
<inheritdoc/>
<remarks>
<b>Note</b> Not implemented for <see cref="T:Discord.WebSocket.SocketMessageCommandData"/>
</remarks>
</member>
<member name="T:Discord.WebSocket.SocketUserCommand">
<summary>
Represents a Websocket-based slash command received over the gateway.
</summary>
</member>
<member name="P:Discord.WebSocket.SocketUserCommand.Data">
<summary>
The data associated with this interaction.
</summary>
</member>
<member name="T:Discord.WebSocket.SocketUserCommandData">
<summary>
Represents the data tied with the <see cref="T:Discord.WebSocket.SocketUserCommand"/> interaction.
</summary>
</member>
<member name="P:Discord.WebSocket.SocketUserCommandData.Member">
<summary>
Gets the user who this command targets.
</summary>
</member>
<member name="P:Discord.WebSocket.SocketUserCommandData.Options">
<inheritdoc/>
<remarks>
<b>Note</b> Not implemented for <see cref="T:Discord.WebSocket.SocketUserCommandData"/>
</remarks>
</member>
<member name="T:Discord.WebSocket.SocketMessageComponent">
<summary>
Represents a Websocket-based interaction type for Message Components.
@@ -3775,9 +3865,48 @@
The value(s) of a <see cref="T:Discord.SelectMenu"/> interaction response.
</summary>
</member>
<member name="T:Discord.WebSocket.SocketSlashCommand">
<summary>
Represents a Websocket-based slash command received over the gateway.
</summary>
</member>
<member name="P:Discord.WebSocket.SocketSlashCommand.Data">
<summary>
The data associated with this interaction.
</summary>
</member>
<member name="T:Discord.WebSocket.SocketSlashCommandData">
<summary>
Represents the data tied with the <see cref="T:Discord.WebSocket.SocketSlashCommand"/> interaction.
</summary>
</member>
<member name="T:Discord.WebSocket.SocketSlashCommandDataOption">
<summary>
Represents a Websocket-based <see cref="T:Discord.IApplicationCommandInteractionDataOption"/> recieved by the gateway
</summary>
</member>
<member name="P:Discord.WebSocket.SocketSlashCommandDataOption.Name">
<inheritdoc/>
</member>
<member name="P:Discord.WebSocket.SocketSlashCommandDataOption.Value">
<inheritdoc/>
</member>
<member name="P:Discord.WebSocket.SocketSlashCommandDataOption.Type">
<inheritdoc/>
</member>
<member name="P:Discord.WebSocket.SocketSlashCommandDataOption.Options">
<summary>
The sub command options received for this sub command group.
</summary>
</member>
<member name="T:Discord.WebSocket.SocketApplicationCommand">
<summary>
Represends a Websocket-based <see cref="T:Discord.IApplicationCommand"/> recieved over the gateway.
Represends a Websocket-based <see cref="T:Discord.IApplicationCommand"/>.
</summary>
</member>
<member name="P:Discord.WebSocket.SocketApplicationCommand.IsGlobalCommand">
<summary>
<see langword="true"/> if this command is a global command, otherwise <see langword="false"/>.
</summary>
</member>
<member name="P:Discord.WebSocket.SocketApplicationCommand.ApplicationId">
@@ -3786,6 +3915,9 @@
<member name="P:Discord.WebSocket.SocketApplicationCommand.Name">
<inheritdoc/>
</member>
<member name="P:Discord.WebSocket.SocketApplicationCommand.Type">
<inheritdoc/>
</member>
<member name="P:Discord.WebSocket.SocketApplicationCommand.Description">
<inheritdoc/>
</member>
@@ -3794,20 +3926,34 @@
</member>
<member name="P:Discord.WebSocket.SocketApplicationCommand.Options">
<summary>
A collection of <see cref="T:Discord.WebSocket.SocketApplicationCommandOption"/>'s recieved over the gateway.
A collection of <see cref="T:Discord.WebSocket.SocketApplicationCommandOption"/>'s for this command.
</summary>
<remarks>
If the <see cref="P:Discord.WebSocket.SocketApplicationCommand.Type"/> is not a slash command, this field will be an empty collection.
</remarks>
</member>
<member name="P:Discord.WebSocket.SocketApplicationCommand.CreatedAt">
<inheritdoc/>
</member>
<member name="P:Discord.WebSocket.SocketApplicationCommand.Guild">
<summary>
The <see cref="T:Discord.WebSocket.SocketGuild"/> where this application was created.
Returns the guild this command resides in, if this command is a global command then it will return <see langword="null"/>
</summary>
</member>
<member name="M:Discord.WebSocket.SocketApplicationCommand.DeleteAsync(Discord.RequestOptions)">
<inheritdoc/>
</member>
<member name="M:Discord.WebSocket.SocketApplicationCommand.ModifyAsync``1(System.Action{``0},Discord.RequestOptions)">
<summary>
Modifies the current application command.
</summary>
<param name="func">The new properties to use when modifying the command.</param>
<param name="options">The options to be used when sending the request.</param>
<returns>
A task that represents the asynchronous modification operation.
</returns>
<exception cref="T:System.InvalidOperationException">Thrown when you pass in an invalid <see cref="T:Discord.ApplicationCommandProperties"/> type.</exception>
</member>
<member name="T:Discord.WebSocket.SocketApplicationCommandChoice">
<summary>
Represents a choice for a <see cref="T:Discord.WebSocket.SocketApplicationCommandOption"/>.
@@ -3849,55 +3995,46 @@
If the option is a subcommand or subcommand group type, this nested options will be the parameters.
</summary>
</member>
<member name="T:Discord.WebSocket.SocketSlashCommand">
<member name="T:Discord.WebSocket.SocketCommandBase">
<summary>
Represents a Websocket-based slash command received over the gateway.
Base class for User, Message, and Slash command interactions
</summary>
</member>
<member name="P:Discord.WebSocket.SocketSlashCommand.Data">
<member name="P:Discord.WebSocket.SocketCommandBase.Data">
<summary>
The data associated with this interaction.
</summary>
</member>
<member name="M:Discord.WebSocket.SocketSlashCommand.RespondAsync(System.String,Discord.Embed[],System.Boolean,System.Boolean,Discord.AllowedMentions,Discord.RequestOptions,Discord.MessageComponent,Discord.Embed)">
<inheritdoc/>
</member>
<member name="M:Discord.WebSocket.SocketSlashCommand.FollowupAsync(System.String,Discord.Embed[],System.Boolean,System.Boolean,Discord.AllowedMentions,Discord.RequestOptions,Discord.MessageComponent,Discord.Embed)">
<member name="M:Discord.WebSocket.SocketCommandBase.RespondAsync(System.String,Discord.Embed[],System.Boolean,System.Boolean,Discord.AllowedMentions,Discord.RequestOptions,Discord.MessageComponent,Discord.Embed)">
<inheritdoc/>
</member>
<member name="M:Discord.WebSocket.SocketSlashCommand.DeferAsync(System.Boolean,Discord.RequestOptions)">
<member name="M:Discord.WebSocket.SocketCommandBase.FollowupAsync(System.String,Discord.Embed[],System.Boolean,System.Boolean,Discord.AllowedMentions,Discord.RequestOptions,Discord.MessageComponent,Discord.Embed)">
<inheritdoc/>
</member>
<member name="T:Discord.WebSocket.SocketSlashCommandData">
<member name="M:Discord.WebSocket.SocketCommandBase.DeferAsync(System.Boolean,Discord.RequestOptions)">
<summary>
Represents the data tied with the <see cref="T:Discord.WebSocket.SocketSlashCommand"/> interaction.
</summary>
</member>
<member name="P:Discord.WebSocket.SocketSlashCommandData.Name">
<inheritdoc/>
</member>
<member name="P:Discord.WebSocket.SocketSlashCommandData.Options">
<summary>
The <see cref="T:Discord.WebSocket.SocketSlashCommandDataOption"/>'s received with this interaction.
Acknowledges this interaction with the <see cref="F:Discord.InteractionResponseType.DeferredChannelMessageWithSource"/>.
</summary>
<returns>
A task that represents the asynchronous operation of acknowledging the interaction.
</returns>
</member>
<member name="T:Discord.WebSocket.SocketSlashCommandDataOption">
<member name="T:Discord.WebSocket.SocketCommandBaseData`1">
<summary>
Represents a Websocket-based <see cref="T:Discord.IApplicationCommandInteractionDataOption"/> recieved by the gateway
Represents the base data tied with the <see cref="T:Discord.WebSocket.SocketCommandBase"/> interaction.
</summary>
</member>
<member name="P:Discord.WebSocket.SocketSlashCommandDataOption.Name">
<inheritdoc/>
</member>
<member name="P:Discord.WebSocket.SocketSlashCommandDataOption.Value">
<member name="P:Discord.WebSocket.SocketCommandBaseData`1.Name">
<inheritdoc/>
</member>
<member name="P:Discord.WebSocket.SocketSlashCommandDataOption.Type">
<inheritdoc/>
<member name="P:Discord.WebSocket.SocketCommandBaseData`1.Options">
<summary>
The <typeparamref name="TOption"/> received with this interaction.
</summary>
</member>
<member name="P:Discord.WebSocket.SocketSlashCommandDataOption.Options">
<member name="T:Discord.WebSocket.SocketCommandBaseData">
<summary>
The sub command options received for this sub command group.
Represents the base data tied with the <see cref="T:Discord.WebSocket.SocketCommandBase"/> interaction.
</summary>
</member>
<member name="T:Discord.WebSocket.SocketInteraction">


+ 111
- 14
src/Discord.Net.WebSocket/DiscordSocketClient.cs View File

@@ -381,6 +381,83 @@ namespace Discord.WebSocket
/// <inheritdoc />
public override SocketUser GetUser(string username, string discriminator)
=> State.Users.FirstOrDefault(x => x.Discriminator == discriminator && x.Username == username);

/// <summary>
/// Gets a global application command.
/// </summary>
/// <param name="id">The id of the command.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A ValueTask that represents the asynchronous get operation. The task result contains the application command if found, otherwise
/// <see langword="null"/>.
/// </returns>
public async ValueTask<SocketApplicationCommand> GetGlobalApplicationCommandAsync(ulong id, RequestOptions options = null)
{
var command = State.GetCommand(id);

if (command != null)
return command;

var model = await ApiClient.GetGlobalApplicationCommandAsync(id, options);

if (model == null)
return null;

command = SocketApplicationCommand.Create(this, model);

State.AddCommand(command);

return command;
}
/// <summary>
/// Gets a collection of all global commands.
/// </summary>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous get operation. The task result contains a read-only collection of global
/// application commands.
/// </returns>
public async Task<IReadOnlyCollection<SocketApplicationCommand>> GetGlobalApplicationCommandsAsync(RequestOptions options = null)
{
var commands = (await ApiClient.GetGlobalApplicationCommandsAsync(options)).Select(x => SocketApplicationCommand.Create(this, x));

foreach(var command in commands)
{
State.AddCommand(command);
}

return commands.ToImmutableArray();
}

public async Task<SocketApplicationCommand> CreateGlobalApplicationCommandAsync(ApplicationCommandProperties properties, RequestOptions options = null)
{
var model = await InteractionHelper.CreateGlobalCommand(this, properties, options).ConfigureAwait(false);

var entity = State.GetOrAddCommand(model.Id, (id) => SocketApplicationCommand.Create(this, model));

// update it incase it was cached
entity.Update(model);

return entity;
}
public async Task<IReadOnlyCollection<SocketApplicationCommand>> BulkOverwriteGlobalApplicationCommandsAsync(
ApplicationCommandProperties[] properties, RequestOptions options = null)
{
var models = await InteractionHelper.BulkOverwriteGlobalCommands(this, properties, options);

var entities = models.Select(x => SocketApplicationCommand.Create(this, x));

// purge our previous commands
State.PurgeCommands(x => x.IsGlobalCommand);

foreach(var entity in entities)
{
State.AddCommand(entity);
}

return entities.ToImmutableArray();
}

/// <summary>
/// Clears cached users from the client.
/// </summary>
@@ -1974,8 +2051,6 @@ namespace Discord.WebSocket
{
await _gatewayLogger.DebugAsync("Received Dispatch (INTERACTION_CREATE)").ConfigureAwait(false);

// 0x546861742062696720656e6469616e20656e636f64696e67206d616b6573206d79316d687a20636c6f636b207469636b

var data = (payload as JToken).ToObject<API.Interaction>(_serializer);

SocketChannel channel = null;
@@ -2029,15 +2104,20 @@ namespace Discord.WebSocket

var data = (payload as JToken).ToObject<API.Gateway.ApplicationCommandCreatedUpdatedEvent>(_serializer);

var guild = State.GetGuild(data.GuildId);
if(guild == null)
if (data.GuildId.IsSpecified)
{
await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false);
return;
var guild = State.GetGuild(data.GuildId.Value);
if (guild == null)
{
await UnknownGuildAsync(type, data.GuildId.Value).ConfigureAwait(false);
return;
}
}

var applicationCommand = SocketApplicationCommand.Create(this, data);

State.AddCommand(applicationCommand);

await TimedInvokeAsync(_applicationCommandCreated, nameof(ApplicationCommandCreated), applicationCommand).ConfigureAwait(false);
}
break;
@@ -2047,15 +2127,20 @@ namespace Discord.WebSocket

var data = (payload as JToken).ToObject<API.Gateway.ApplicationCommandCreatedUpdatedEvent>(_serializer);

var guild = State.GetGuild(data.GuildId);
if (guild == null)
if (data.GuildId.IsSpecified)
{
await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false);
return;
var guild = State.GetGuild(data.GuildId.Value);
if (guild == null)
{
await UnknownGuildAsync(type, data.GuildId.Value).ConfigureAwait(false);
return;
}
}

var applicationCommand = SocketApplicationCommand.Create(this, data);

State.AddCommand(applicationCommand);

await TimedInvokeAsync(_applicationCommandUpdated, nameof(ApplicationCommandUpdated), applicationCommand).ConfigureAwait(false);
}
break;
@@ -2065,15 +2150,20 @@ namespace Discord.WebSocket

var data = (payload as JToken).ToObject<API.Gateway.ApplicationCommandCreatedUpdatedEvent>(_serializer);

var guild = State.GetGuild(data.GuildId);
if (guild == null)
if (data.GuildId.IsSpecified)
{
await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false);
return;
var guild = State.GetGuild(data.GuildId.Value);
if (guild == null)
{
await UnknownGuildAsync(type, data.GuildId.Value).ConfigureAwait(false);
return;
}
}

var applicationCommand = SocketApplicationCommand.Create(this, data);

State.RemoveCommand(applicationCommand.Id);

await TimedInvokeAsync(_applicationCommandDeleted, nameof(ApplicationCommandDeleted), applicationCommand).ConfigureAwait(false);
}
break;
@@ -2738,6 +2828,13 @@ namespace Discord.WebSocket
async Task<IVoiceRegion> IDiscordClient.GetVoiceRegionAsync(string id, RequestOptions options)
=> await GetVoiceRegionAsync(id, options).ConfigureAwait(false);

/// <inheritdoc />
async Task<IApplicationCommand> IDiscordClient.GetGlobalApplicationCommandAsync(ulong id, RequestOptions options)
=> await GetGlobalApplicationCommandAsync(id, options);
/// <inheritdoc />
async Task<IReadOnlyCollection<IApplicationCommand>> IDiscordClient.GetGlobalApplicationCommandsAsync(RequestOptions options)
=> await GetGlobalApplicationCommandsAsync(options);

/// <inheritdoc />
async Task IDiscordClient.StartAsync()
=> await StartAsync().ConfigureAwait(false);


+ 93
- 22
src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs View File

@@ -815,13 +815,13 @@ namespace Discord.WebSocket

//Interactions
/// <summary>
/// Deletes all slash commands in the current guild.
/// Deletes all application commands in the current guild.
/// </summary>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous delete operation.
/// </returns>
public Task DeleteSlashCommandsAsync(RequestOptions options = null)
public Task DeleteApplicationCommandsAsync(RequestOptions options = null)
=> InteractionHelper.DeleteAllGuildCommandsAsync(Discord, this.Id, options);

/// <summary>
@@ -832,20 +832,93 @@ namespace Discord.WebSocket
/// A task that represents the asynchronous get operation. The task result contains a read-only collection of
/// slash commands created by the current user.
/// </returns>
public Task<IReadOnlyCollection<RestGuildCommand>> GetSlashCommandsAsync(RequestOptions options = null)
=> GuildHelper.GetSlashCommandsAsync(this, Discord, options);
public async Task<IReadOnlyCollection<SocketApplicationCommand>> GetApplicationCommandsAsync(RequestOptions options = null)
{
var commands = (await Discord.ApiClient.GetGuildApplicationCommandsAsync(this.Id, options)).Select(x => SocketApplicationCommand.Create(Discord, x, this.Id));

foreach (var command in commands)
{
Discord.State.AddCommand(command);
}

return commands.ToImmutableArray();
}

/// <summary>
/// Gets an application command within this guild with the specified id.
/// </summary>
/// <param name="id">The id of the application command to get.</param>
/// <param name="mode">The <see cref="CacheMode" /> that determines whether the object should be fetched from cache.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A ValueTask that represents the asynchronous get operation. The task result contains a <see cref="IApplicationCommand"/>
/// if found, otherwise <see langword="null"/>.
/// </returns>
public async ValueTask<SocketApplicationCommand> GetApplicationCommandAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null)
{
var command = Discord.State.GetCommand(id);

if (command != null)
return command;

if (mode == CacheMode.CacheOnly)
return null;

var model = await Discord.ApiClient.GetGlobalApplicationCommandAsync(id, options);

if (model == null)
return null;

command = SocketApplicationCommand.Create(Discord, model, this.Id);

Discord.State.AddCommand(command);

return command;
}

/// <summary>
/// Creates an application command within this guild.
/// </summary>
/// <param name="properties">The properties to use when creating the command.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous creation operation. The task result contains the command that was created.
/// </returns>
public async Task<SocketApplicationCommand> CreateApplicationCommandAsync(ApplicationCommandProperties properties, RequestOptions options = null)
{
var model = await InteractionHelper.CreateGuildCommand(Discord, this.Id, properties, options);

var entity = Discord.State.GetOrAddCommand(model.Id, (id) => SocketApplicationCommand.Create(Discord, model));

entity.Update(model);

return entity;
}

/// <summary>
/// Gets a slash command in the current guild.
/// Overwrites the application commands within this guild.
/// </summary>
/// <param name="id">The unique identifier of the slash command.</param>
/// <param name="properties">A collection of properties to use when creating the commands.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous get operation. The task result contains a
/// slash command created by the current user.
/// A task that represents the asynchronous creation operation. The task result contains a collection of commands that was created.
/// </returns>
public Task<RestGuildCommand> GetSlashCommandAsync(ulong id, RequestOptions options = null)
=> GuildHelper.GetSlashCommandAsync(this, id, Discord, options);
public async Task<IReadOnlyCollection<SocketApplicationCommand>> BulkOverwriteApplicationCommandAsync(ApplicationCommandProperties[] properties,
RequestOptions options = null)
{
var models = await InteractionHelper.BulkOverwriteGuildCommands(Discord, this.Id, properties, options);

var entities = models.Select(x => SocketApplicationCommand.Create(Discord, x));

Discord.State.PurgeCommands(x => !x.IsGlobalCommand && x.Guild.Id == this.Id);

foreach(var entity in entities)
{
Discord.State.AddCommand(entity);
}

return entities.ToImmutableArray();
}

//Invites
/// <summary>
@@ -1136,18 +1209,6 @@ namespace Discord.WebSocket
public Task<IReadOnlyCollection<RestWebhook>> GetWebhooksAsync(RequestOptions options = null)
=> GuildHelper.GetWebhooksAsync(this, Discord, options);

//Interactions
/// <summary>
/// Gets this guilds slash commands commands
/// </summary>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous get operation. The task result contains a read-only collection
/// of application commands found within the guild.
/// </returns>
public async Task<IReadOnlyCollection<RestApplicationCommand>> GetApplicationCommandsAsync(RequestOptions options = null)
=> await Discord.Rest.GetGuildApplicationCommands(this.Id, options);

//Emotes
/// <inheritdoc />
public Task<IReadOnlyCollection<GuildEmote>> GetEmotesAsync(RequestOptions options = null)
@@ -1687,6 +1748,16 @@ namespace Discord.WebSocket
/// <inheritdoc />
Task IGuild.DeleteStickerAsync(ICustomSticker sticker, RequestOptions options)
=> DeleteStickerAsync(_stickers[sticker.Id], options);
/// <inheritdoc />
async Task<IApplicationCommand> IGuild.GetApplicationCommandAsync(ulong id, CacheMode mode, RequestOptions options)
=> await GetApplicationCommandAsync(id, mode, options);
/// <inheritdoc />
async Task<IApplicationCommand> IGuild.CreateApplicationCommandAsync(ApplicationCommandProperties properties, RequestOptions options)
=> await CreateApplicationCommandAsync(properties, options);
/// <inheritdoc />
async Task<IReadOnlyCollection<IApplicationCommand>> IGuild.BulkOverwriteApplicationCommandsAsync(ApplicationCommandProperties[] properties,
RequestOptions options)
=> await BulkOverwriteApplicationCommandAsync(properties, options);

void IDisposable.Dispose()
{


+ 41
- 0
src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/Message Commands/SocketMessageCommand.cs View File

@@ -0,0 +1,41 @@
using Discord.Rest;
using System;
using System.Linq;
using System.Threading.Tasks;
using DataModel = Discord.API.ApplicationCommandInteractionData;
using Model = Discord.API.Interaction;

namespace Discord.WebSocket
{
/// <summary>
/// Represents a Websocket-based slash command received over the gateway.
/// </summary>
public class SocketMessageCommand : SocketCommandBase
{
/// <summary>
/// The data associated with this interaction.
/// </summary>
public new SocketMessageCommandData Data { get; }

internal SocketMessageCommand(DiscordSocketClient client, Model model, ISocketMessageChannel channel)
: base(client, model, channel)
{
var dataModel = model.Data.IsSpecified ?
(DataModel)model.Data.Value
: null;

ulong? guildId = null;
if (this.Channel is SocketGuildChannel guildChannel)
guildId = guildChannel.Guild.Id;

Data = SocketMessageCommandData.Create(client, dataModel, model.Id, guildId);
}

new internal static SocketInteraction Create(DiscordSocketClient client, Model model, ISocketMessageChannel channel)
{
var entity = new SocketMessageCommand(client, model, channel);
entity.Update(model);
return entity;
}
}
}

+ 35
- 0
src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/Message Commands/SocketMessageCommandData.cs View File

@@ -0,0 +1,35 @@
using System.Collections.Generic;
using System.Linq;
using Model = Discord.API.ApplicationCommandInteractionData;

namespace Discord.WebSocket
{
/// <summary>
/// Represents the data tied with the <see cref="SocketMessageCommand"/> interaction.
/// </summary>
public class SocketMessageCommandData : SocketCommandBaseData
{
/// <summary>
/// Gets the messagte associated with this message command.
/// </summary>
public SocketMessage Message
=> ResolvableData?.Messages.FirstOrDefault().Value;

/// <inheritdoc/>
/// <remarks>
/// <b>Note</b> Not implemented for <see cref="SocketMessageCommandData"/>
/// </remarks>
public override IReadOnlyCollection<IApplicationCommandInteractionDataOption> Options
=> throw new System.NotImplementedException();

internal SocketMessageCommandData(DiscordSocketClient client, Model model, ulong? guildId)
: base(client, model, guildId) { }

internal new static SocketMessageCommandData Create(DiscordSocketClient client, Model model, ulong id, ulong? guildId)
{
var entity = new SocketMessageCommandData(client, model, guildId);
entity.Update(model);
return entity;
}
}
}

+ 41
- 0
src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/User Commands/SocketUserCommand.cs View File

@@ -0,0 +1,41 @@
using Discord.Rest;
using System;
using System.Linq;
using System.Threading.Tasks;
using DataModel = Discord.API.ApplicationCommandInteractionData;
using Model = Discord.API.Interaction;

namespace Discord.WebSocket
{
/// <summary>
/// Represents a Websocket-based slash command received over the gateway.
/// </summary>
public class SocketUserCommand : SocketCommandBase
{
/// <summary>
/// The data associated with this interaction.
/// </summary>
public new SocketUserCommandData Data { get; }

internal SocketUserCommand(DiscordSocketClient client, Model model, ISocketMessageChannel channel)
: base(client, model, channel)
{
var dataModel = model.Data.IsSpecified ?
(DataModel)model.Data.Value
: null;

ulong? guildId = null;
if (this.Channel is SocketGuildChannel guildChannel)
guildId = guildChannel.Guild.Id;

Data = SocketUserCommandData.Create(client, dataModel, model.Id, guildId);
}

new internal static SocketInteraction Create(DiscordSocketClient client, Model model, ISocketMessageChannel channel)
{
var entity = new SocketUserCommand(client, model, channel);
entity.Update(model);
return entity;
}
}
}

+ 35
- 0
src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/User Commands/SocketUserCommandData.cs View File

@@ -0,0 +1,35 @@
using System.Collections.Generic;
using System.Linq;
using Model = Discord.API.ApplicationCommandInteractionData;

namespace Discord.WebSocket
{
/// <summary>
/// Represents the data tied with the <see cref="SocketUserCommand"/> interaction.
/// </summary>
public class SocketUserCommandData : SocketCommandBaseData
{
/// <summary>
/// Gets the user who this command targets.
/// </summary>
public SocketUser Member
=> (SocketUser)ResolvableData.GuildMembers.Values.FirstOrDefault() ?? ResolvableData.Users.Values.FirstOrDefault();

/// <inheritdoc/>
/// <remarks>
/// <b>Note</b> Not implemented for <see cref="SocketUserCommandData"/>
/// </remarks>
public override IReadOnlyCollection<IApplicationCommandInteractionDataOption> Options
=> throw new System.NotImplementedException();

internal SocketUserCommandData(DiscordSocketClient client, Model model, ulong? guildId)
: base(client, model, guildId) { }

internal new static SocketUserCommandData Create(DiscordSocketClient client, Model model, ulong id, ulong? guildId)
{
var entity = new SocketUserCommandData(client, model, guildId);
entity.Update(model);
return entity;
}
}
}

+ 0
- 77
src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketApplicationCommand.cs View File

@@ -1,77 +0,0 @@
using Discord.Rest;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Model = Discord.API.Gateway.ApplicationCommandCreatedUpdatedEvent;

namespace Discord.WebSocket
{
/// <summary>
/// Represends a Websocket-based <see cref="IApplicationCommand"/> recieved over the gateway.
/// </summary>
public class SocketApplicationCommand : SocketEntity<ulong>, IApplicationCommand
{
/// <inheritdoc/>
public ulong ApplicationId { get; private set; }

/// <inheritdoc/>
public string Name { get; private set; }

/// <inheritdoc/>
public string Description { get; private set; }

/// <inheritdoc/>
public bool DefaultPermission { get; private set; }

/// <summary>
/// A collection of <see cref="SocketApplicationCommandOption"/>'s recieved over the gateway.
/// </summary>
public IReadOnlyCollection<SocketApplicationCommandOption> Options { get; private set; }

/// <inheritdoc/>
public DateTimeOffset CreatedAt
=> SnowflakeUtils.FromSnowflake(this.Id);

/// <summary>
/// The <see cref="SocketGuild"/> where this application was created.
/// </summary>
public SocketGuild Guild
=> Discord.GetGuild(this.GuildId);
private ulong GuildId { get; set; }

internal SocketApplicationCommand(DiscordSocketClient client, ulong id)
: base(client, id)
{

}
internal static SocketApplicationCommand Create(DiscordSocketClient client, Model model)
{
var entity = new SocketApplicationCommand(client, model.Id);
entity.Update(model);
return entity;
}

internal void Update(Model model)
{
this.ApplicationId = model.ApplicationId;
this.Description = model.Description;
this.Name = model.Name;
this.GuildId = model.GuildId;
this.DefaultPermission = model.DefaultPermission.GetValueOrDefault(true);


this.Options = model.Options.IsSpecified
? model.Options.Value.Select(x => SocketApplicationCommandOption.Create(x)).ToImmutableArray()
: new ImmutableArray<SocketApplicationCommandOption>();
}

/// <inheritdoc/>
public Task DeleteAsync(RequestOptions options = null)
=> InteractionHelper.DeleteGuildCommand(Discord, this.GuildId, this, options);

IReadOnlyCollection<IApplicationCommandOption> IApplicationCommand.Options => Options;
}
}

+ 3
- 123
src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketSlashCommand.cs View File

@@ -10,7 +10,7 @@ namespace Discord.WebSocket
/// <summary>
/// Represents a Websocket-based slash command received over the gateway.
/// </summary>
public class SocketSlashCommand : SocketInteraction
public class SocketSlashCommand : SocketCommandBase
{
/// <summary>
/// The data associated with this interaction.
@@ -18,7 +18,7 @@ namespace Discord.WebSocket
new public SocketSlashCommandData Data { get; }

internal SocketSlashCommand(DiscordSocketClient client, Model model, ISocketMessageChannel channel)
: base(client, model.Id, channel)
: base(client, model, channel)
{
var dataModel = model.Data.IsSpecified ?
(DataModel)model.Data.Value
@@ -28,7 +28,7 @@ namespace Discord.WebSocket
if (this.Channel is SocketGuildChannel guildChannel)
guildId = guildChannel.Guild.Id;

Data = SocketSlashCommandData.Create(client, dataModel, model.Id, guildId);
Data = SocketSlashCommandData.Create(client, dataModel, guildId);
}

new internal static SocketInteraction Create(DiscordSocketClient client, Model model, ISocketMessageChannel channel)
@@ -37,125 +37,5 @@ namespace Discord.WebSocket
entity.Update(model);
return entity;
}

internal override void Update(Model model)
{
var data = model.Data.IsSpecified ?
(DataModel)model.Data.Value
: null;

this.Data.Update(data);

base.Update(model);
}

/// <inheritdoc/>
public override async 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)
{
if (!IsValidToken)
throw new InvalidOperationException("Interaction token is no longer valid");

if (embeds == null && embed != null)
embeds = new[] { embed };

if (Discord.AlwaysAcknowledgeInteractions)
{
await FollowupAsync(text, embeds, isTTS, ephemeral, allowedMentions, options, component);
return;
}

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.");

// check that user flag and user Id list are exclusive, same with role flag and role Id list
if (allowedMentions != null && allowedMentions.AllowedTypes.HasValue)
{
if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Users) &&
allowedMentions.UserIds != null && allowedMentions.UserIds.Count > 0)
{
throw new ArgumentException("The Users flag is mutually exclusive with the list of User Ids.", nameof(allowedMentions));
}

if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Roles) &&
allowedMentions.RoleIds != null && allowedMentions.RoleIds.Count > 0)
{
throw new ArgumentException("The Roles flag is mutually exclusive with the list of Role Ids.", nameof(allowedMentions));
}
}
var response = new API.InteractionResponse
{
Type = InteractionResponseType.ChannelMessageWithSource,
Data = new API.InteractionCallbackData
{
Content = text,
AllowedMentions = allowedMentions?.ToModel() ?? Optional<API.AllowedMentions>.Unspecified,
Embeds = embeds?.Select(x => x.ToModel()).ToArray() ?? Optional<API.Embed[]>.Unspecified,
TTS = isTTS ? true : Optional<bool>.Unspecified,
Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified
}
};

if (ephemeral)
response.Data.Value.Flags = MessageFlags.Ephemeral;

await InteractionHelper.SendInteractionResponse(this.Discord, response, this.Id, Token, options);
}

/// <inheritdoc/>
public override async Task<RestFollowupMessage> 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)
{
if (!IsValidToken)
throw new InvalidOperationException("Interaction token is no longer valid");

if (embeds == null && embed != null)
embeds = new[] { embed };
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.");

var args = new API.Rest.CreateWebhookMessageParams
{
Content = text,
AllowedMentions = allowedMentions?.ToModel() ?? Optional<API.AllowedMentions>.Unspecified,
IsTTS = isTTS,
Embeds = embeds?.Select(x => x.ToModel()).ToArray() ?? Optional<API.Embed[]>.Unspecified,
Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified
};

if (ephemeral)
args.Flags = MessageFlags.Ephemeral;

return await InteractionHelper.SendFollowupAsync(Discord.Rest, args, Token, Channel, options);
}

/// <inheritdoc/>
public override Task DeferAsync(bool ephemeral = false, RequestOptions options = null)
{
var response = new API.InteractionResponse
{
Type = InteractionResponseType.DeferredChannelMessageWithSource,
Data = ephemeral ? new API.InteractionCallbackData() { Flags = MessageFlags.Ephemeral } : Optional<API.InteractionCallbackData>.Unspecified
};

return Discord.Rest.ApiClient.CreateInteractionResponse(response, this.Id, this.Token, options);
}
}
}

+ 0
- 48
src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketSlashCommandCache.cs View File

@@ -1,48 +0,0 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Discord.WebSocket.Entities.Interaction
{
internal class SlashCommandCache
{
private readonly ConcurrentDictionary<ulong, SocketSlashCommand> _slashCommands;
private readonly ConcurrentQueue<ulong> _orderedSlashCommands;
private readonly int _size;

public IReadOnlyCollection<SocketSlashCommand> Messages => _slashCommands.ToReadOnlyCollection();

public SlashCommandCache(DiscordSocketClient client)
{
_size = 256;
_slashCommands = new ConcurrentDictionary<ulong, SocketSlashCommand>();

}

public void Add(SocketSlashCommand slashCommand)
{
if (_slashCommands.TryAdd(slashCommand.Id, slashCommand))
{
_orderedSlashCommands.Enqueue(slashCommand.Id);

while (_orderedSlashCommands.Count > _size && _orderedSlashCommands.TryDequeue(out ulong msgId))
_slashCommands.TryRemove(msgId, out _);
}
}

public SocketSlashCommand Remove(ulong id)
{
_slashCommands.TryRemove(id, out var slashCommand);
return slashCommand;
}

public SocketSlashCommand Get(ulong id)
{
_slashCommands.TryGetValue(id, out var slashCommands);
return slashCommands;
}
}
}

+ 5
- 89
src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketSlashCommandData.cs View File

@@ -8,108 +8,24 @@ namespace Discord.WebSocket
/// <summary>
/// Represents the data tied with the <see cref="SocketSlashCommand"/> interaction.
/// </summary>
public class SocketSlashCommandData : SocketEntity<ulong>, IApplicationCommandInteractionData
public class SocketSlashCommandData : SocketCommandBaseData<SocketSlashCommandDataOption>
{
/// <inheritdoc/>
public string Name { get; private set; }

/// <summary>
/// The <see cref="SocketSlashCommandDataOption"/>'s received with this interaction.
/// </summary>
public IReadOnlyCollection<SocketSlashCommandDataOption> Options { get; private set; }

internal Dictionary<ulong, SocketGuildUser> guildMembers { get; private set; }
= new Dictionary<ulong, SocketGuildUser>();
internal Dictionary<ulong, SocketGlobalUser> users { get; private set; }
= new Dictionary<ulong, SocketGlobalUser>();
internal Dictionary<ulong, SocketChannel> channels { get; private set; }
= new Dictionary<ulong, SocketChannel>();
internal Dictionary<ulong, SocketRole> roles { get; private set; }
= new Dictionary<ulong, SocketRole>();

private ulong? guildId;

internal SocketSlashCommandData(DiscordSocketClient client, Model model, ulong? guildId)
: base(client, model.Id)
{
this.guildId = guildId;

if (model.Resolved.IsSpecified)
{
var guild = this.guildId.HasValue ? Discord.GetGuild(this.guildId.Value) : null;

var resolved = model.Resolved.Value;

if (resolved.Users.IsSpecified)
{
foreach (var user in resolved.Users.Value)
{
var socketUser = Discord.GetOrCreateUser(this.Discord.State, user.Value);

this.users.Add(ulong.Parse(user.Key), socketUser);
}
}
: base(client, model, guildId) { }

if (resolved.Channels.IsSpecified)
{
foreach (var channel in resolved.Channels.Value)
{
SocketChannel socketChannel = guild != null
? guild.GetChannel(channel.Value.Id)
: Discord.GetChannel(channel.Value.Id);

if (socketChannel == null)
{
var channelModel = guild != null
? Discord.Rest.ApiClient.GetChannelAsync(guild.Id, channel.Value.Id).ConfigureAwait(false).GetAwaiter().GetResult()
: Discord.Rest.ApiClient.GetChannelAsync(channel.Value.Id).ConfigureAwait(false).GetAwaiter().GetResult();

socketChannel = guild != null
? SocketGuildChannel.Create(guild, Discord.State, channelModel)
: (SocketChannel)SocketChannel.CreatePrivate(Discord, Discord.State, channelModel);
}

Discord.State.AddChannel(socketChannel);
this.channels.Add(ulong.Parse(channel.Key), socketChannel);
}
}

if (resolved.Members.IsSpecified)
{
foreach (var member in resolved.Members.Value)
{
member.Value.User = resolved.Users.Value[member.Key];
var user = guild.AddOrUpdateUser(member.Value);
this.guildMembers.Add(ulong.Parse(member.Key), user);
}
}

if (resolved.Roles.IsSpecified)
{
foreach (var role in resolved.Roles.Value)
{
var socketRole = guild.AddOrUpdateRole(role.Value);
this.roles.Add(ulong.Parse(role.Key), socketRole);
}
}
}
}

internal static SocketSlashCommandData Create(DiscordSocketClient client, Model model, ulong id, ulong? guildId)
internal static SocketSlashCommandData Create(DiscordSocketClient client, Model model, ulong? guildId)
{
var entity = new SocketSlashCommandData(client, model, guildId);
entity.Update(model);
return entity;
}
internal void Update(Model model)
internal override void Update(Model model)
{
this.Name = model.Name;
base.Update(model);

this.Options = model.Options.IsSpecified
? model.Options.Value.Select(x => new SocketSlashCommandDataOption(this, x)).ToImmutableArray()
: null;
}

IReadOnlyCollection<IApplicationCommandInteractionDataOption> IApplicationCommandInteractionData.Options => Options;
}
}

+ 12
- 10
src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketSlashCommandDataOption.cs View File

@@ -44,34 +44,34 @@ namespace Discord.WebSocket
{
case ApplicationCommandOptionType.User:
{
var guildUser = data.guildMembers.FirstOrDefault(x => x.Key == valueId).Value;
var guildUser = data.ResolvableData.GuildMembers.FirstOrDefault(x => x.Key == valueId).Value;

if (guildUser != null)
this.Value = guildUser;
else
this.Value = data.users.FirstOrDefault(x => x.Key == valueId).Value;
this.Value = data.ResolvableData.Users.FirstOrDefault(x => x.Key == valueId).Value;
}
break;
case ApplicationCommandOptionType.Channel:
this.Value = data.channels.FirstOrDefault(x => x.Key == valueId).Value;
this.Value = data.ResolvableData.Channels.FirstOrDefault(x => x.Key == valueId).Value;
break;
case ApplicationCommandOptionType.Role:
this.Value = data.roles.FirstOrDefault(x => x.Key == valueId).Value;
this.Value = data.ResolvableData.Roles.FirstOrDefault(x => x.Key == valueId).Value;
break;
case ApplicationCommandOptionType.Mentionable:
{
if(data.guildMembers.Any(x => x.Key == valueId) || data.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.guildMembers.FirstOrDefault(x => x.Key == valueId).Value;
var guildUser = data.ResolvableData.GuildMembers.FirstOrDefault(x => x.Key == valueId).Value;

if (guildUser != null)
this.Value = guildUser;
else
this.Value = data.users.FirstOrDefault(x => x.Key == valueId).Value;
this.Value = data.ResolvableData.Users.FirstOrDefault(x => x.Key == valueId).Value;
}
else if(data.roles.Any(x => x.Key == valueId))
else if(data.ResolvableData.Roles.Any(x => x.Key == valueId))
{
this.Value = data.roles.FirstOrDefault(x => x.Key == valueId).Value;
this.Value = data.ResolvableData.Roles.FirstOrDefault(x => x.Key == valueId).Value;
}
}
break;
@@ -125,6 +125,8 @@ namespace Discord.WebSocket
public static explicit operator string(SocketSlashCommandDataOption option)
=> option.Value.ToString();

IReadOnlyCollection<IApplicationCommandInteractionDataOption> IApplicationCommandInteractionDataOption.Options => this.Options;
// IApplicationCommandInteractionDataOption
IReadOnlyCollection<IApplicationCommandInteractionDataOption> IApplicationCommandInteractionDataOption.Options
=> this.Options;
}
}

+ 132
- 0
src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketApplicationCommand.cs View File

@@ -0,0 +1,132 @@
using Discord.Rest;
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;

namespace Discord.WebSocket
{
/// <summary>
/// Represends a Websocket-based <see cref="IApplicationCommand"/>.
/// </summary>
public class SocketApplicationCommand : SocketEntity<ulong>, IApplicationCommand
{
/// <summary>
/// <see langword="true"/> if this command is a global command, otherwise <see langword="false"/>.
/// </summary>
public bool IsGlobalCommand
=> Guild == null;

/// <inheritdoc/>
public ulong ApplicationId { get; private set; }

/// <inheritdoc/>
public string Name { get; private set; }

/// <inheritdoc/>
public ApplicationCommandType Type { get; private set; }

/// <inheritdoc/>
public string Description { get; private set; }

/// <inheritdoc/>
public bool DefaultPermission { get; private set; }

/// <summary>
/// A collection of <see cref="SocketApplicationCommandOption"/>'s for this command.
/// </summary>
/// <remarks>
/// If the <see cref="Type"/> is not a slash command, this field will be an empty collection.
/// </remarks>
public IReadOnlyCollection<SocketApplicationCommandOption> Options { get; private set; }

/// <inheritdoc/>
public DateTimeOffset CreatedAt
=> SnowflakeUtils.FromSnowflake(this.Id);

/// <summary>
/// Returns the guild this command resides in, if this command is a global command then it will return <see langword="null"/>
/// </summary>
public SocketGuild Guild
=> GuildId.HasValue ? Discord.GetGuild(this.GuildId.Value) : null;

private ulong? GuildId { get; set; }

internal SocketApplicationCommand(DiscordSocketClient client, ulong id, ulong? guildId)
: base(client, id)
{
this.GuildId = guildId;
}
internal static SocketApplicationCommand Create(DiscordSocketClient client, GatewayModel model)
{
var entity = new SocketApplicationCommand(client, model.Id, model.GuildId.ToNullable());
entity.Update(model);
return entity;
}

internal static SocketApplicationCommand Create(DiscordSocketClient client, Model model, ulong? guildId = null)
{
var entity = new SocketApplicationCommand(client, model.Id, guildId);
entity.Update(model);
return entity;
}

internal void Update(Model model)
{
this.ApplicationId = model.ApplicationId;
this.Description = model.Description;
this.Name = model.Name;
this.DefaultPermission = model.DefaultPermissions.GetValueOrDefault(true);

this.Options = model.Options.IsSpecified
? model.Options.Value.Select(x => SocketApplicationCommandOption.Create(x)).ToImmutableArray()
: new ImmutableArray<SocketApplicationCommandOption>();
}

/// <inheritdoc/>
public Task DeleteAsync(RequestOptions options = null)
=> InteractionHelper.DeleteUnknownApplicationCommand(Discord, this.GuildId, this, options);

/// <summary>
/// Modifies the current application command.
/// </summary>
/// <param name="func">The new properties to use when modifying the command.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous modification operation.
/// </returns>
/// <exception cref="InvalidOperationException">Thrown when you pass in an invalid <see cref="ApplicationCommandProperties"/> type.</exception>
public async Task ModifyAsync<TArg>(Action<TArg> func, RequestOptions options = null) where TArg : ApplicationCommandProperties
{
switch (typeof(TArg))
{
case Type messageCommand when messageCommand == typeof(MessageCommandProperties) && this.Type != ApplicationCommandType.Message:
case Type slashCommand when slashCommand == typeof(SlashCommandProperties) && this.Type != ApplicationCommandType.Slash:
case Type userCommand when userCommand == typeof(UserCommandProperties) && this.Type != ApplicationCommandType.User:
throw new InvalidOperationException($"Cannot modify this application command with the parameter type {nameof(TArg)}");
}

Model command = null;

if (this.IsGlobalCommand)
{
command = await InteractionHelper.ModifyGlobalCommand(Discord, this, func, options).ConfigureAwait(false);
}
else
{
command = await InteractionHelper.ModifyGuildCommand(Discord, this, this.GuildId.Value, func, options);
}

this.Update(command);
}

// IApplicationCommand
IReadOnlyCollection<IApplicationCommandOption> IApplicationCommand.Options => Options;
Task IApplicationCommand.ModifyAsync(Action<ApplicationCommandProperties> func, RequestOptions options)
=> ModifyAsync(func, options);
}
}

src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketApplicationCommandChoice.cs → src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketApplicationCommandChoice.cs View File


src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketApplicationCommandOption.cs → src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketApplicationCommandOption.cs View File


+ 165
- 0
src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBase.cs View File

@@ -0,0 +1,165 @@
using Discord.Rest;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using DataModel = Discord.API.ApplicationCommandInteractionData;
using Model = Discord.API.Interaction;

namespace Discord.WebSocket
{
/// <summary>
/// Base class for User, Message, and Slash command interactions
/// </summary>
public class SocketCommandBase : SocketInteraction
{
/// <summary>
/// The data associated with this interaction.
/// </summary>
new internal SocketCommandBaseData Data { get; }

internal SocketCommandBase(DiscordSocketClient client, Model model, ISocketMessageChannel channel)
: base(client, model.Id, channel)
{
var dataModel = model.Data.IsSpecified ?
(DataModel)model.Data.Value
: null;

ulong? guildId = null;
if (this.Channel is SocketGuildChannel guildChannel)
guildId = guildChannel.Guild.Id;

Data = SocketCommandBaseData.Create(client, dataModel, model.Id, guildId);
}

new internal static SocketInteraction Create(DiscordSocketClient client, Model model, ISocketMessageChannel channel)
{
var entity = new SocketCommandBase(client, model, channel);
entity.Update(model);
return entity;
}

internal override void Update(Model model)
{
var data = model.Data.IsSpecified ?
(DataModel)model.Data.Value
: null;

this.Data.Update(data);

base.Update(model);
}

/// <inheritdoc/>
public override async 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)
{
if (!IsValidToken)
throw new InvalidOperationException("Interaction token is no longer valid");

if (embeds == null && embed != null)
embeds = new[] { embed };

if (Discord.AlwaysAcknowledgeInteractions)
{
await FollowupAsync(text, embeds, isTTS, ephemeral, allowedMentions, options, component);
return;
}

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.");

// check that user flag and user Id list are exclusive, same with role flag and role Id list
if (allowedMentions != null && allowedMentions.AllowedTypes.HasValue)
{
if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Users) &&
allowedMentions.UserIds != null && allowedMentions.UserIds.Count > 0)
{
throw new ArgumentException("The Users flag is mutually exclusive with the list of User Ids.", nameof(allowedMentions));
}

if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Roles) &&
allowedMentions.RoleIds != null && allowedMentions.RoleIds.Count > 0)
{
throw new ArgumentException("The Roles flag is mutually exclusive with the list of Role Ids.", nameof(allowedMentions));
}
}

var response = new API.InteractionResponse
{
Type = InteractionResponseType.ChannelMessageWithSource,
Data = new API.InteractionCallbackData
{
Content = text,
AllowedMentions = allowedMentions?.ToModel() ?? Optional<API.AllowedMentions>.Unspecified,
Embeds = embeds?.Select(x => x.ToModel()).ToArray() ?? Optional<API.Embed[]>.Unspecified,
TTS = isTTS ? true : Optional<bool>.Unspecified,
Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified,
Flags = ephemeral ? MessageFlags.Ephemeral : Optional<MessageFlags>.Unspecified
}
};

await InteractionHelper.SendInteractionResponse(this.Discord, response, this.Id, Token, options);
}

/// <inheritdoc/>
public override async Task<RestFollowupMessage> 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)
{
if (!IsValidToken)
throw new InvalidOperationException("Interaction token is no longer valid");

if (embeds == null && embed != null)
embeds = new[] { embed };
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.");

var args = new API.Rest.CreateWebhookMessageParams
{
Content = text,
AllowedMentions = allowedMentions?.ToModel() ?? Optional<API.AllowedMentions>.Unspecified,
IsTTS = isTTS,
Embeds = embeds?.Select(x => x.ToModel()).ToArray() ?? Optional<API.Embed[]>.Unspecified,
Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified
};

if (ephemeral)
args.Flags = MessageFlags.Ephemeral;

return await InteractionHelper.SendFollowupAsync(Discord.Rest, args, Token, Channel, options);
}

/// <summary>
/// Acknowledges this interaction with the <see cref="InteractionResponseType.DeferredChannelMessageWithSource"/>.
/// </summary>
/// <returns>
/// A task that represents the asynchronous operation of acknowledging the interaction.
/// </returns>
public override Task DeferAsync(bool ephemeral = false, RequestOptions options = null)
{
var response = new API.InteractionResponse
{
Type = InteractionResponseType.DeferredChannelMessageWithSource,
};

return Discord.Rest.ApiClient.CreateInteractionResponse(response, this.Id, this.Token, options);
}
}
}

+ 60
- 0
src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBaseData.cs View File

@@ -0,0 +1,60 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Model = Discord.API.ApplicationCommandInteractionData;

namespace Discord.WebSocket
{
/// <summary>
/// Represents the base data tied with the <see cref="SocketCommandBase"/> interaction.
/// </summary>
public class SocketCommandBaseData<TOption> : SocketEntity<ulong>, IApplicationCommandInteractionData where TOption : IApplicationCommandInteractionDataOption
{
/// <inheritdoc/>
public string Name { get; private set; }

/// <summary>
/// The <typeparamref name="TOption"/> received with this interaction.
/// </summary>
public virtual IReadOnlyCollection<TOption> Options { get; internal set; }

internal readonly SocketResolvableData<Model> ResolvableData;

private ApplicationCommandType Type { get; set; }

internal SocketCommandBaseData(DiscordSocketClient client, Model model, ulong? guildId)
: base(client, model.Id)
{
this.Type = model.Type;

if (model.Resolved.IsSpecified)
{
ResolvableData = new SocketResolvableData<Model>(client, guildId, model);
}
}

internal static SocketCommandBaseData Create(DiscordSocketClient client, Model model, ulong id, ulong? guildId)
{
var entity = new SocketCommandBaseData(client, model, guildId);
entity.Update(model);
return entity;
}

internal virtual void Update(Model model)
{
this.Name = model.Name;
}

IReadOnlyCollection<IApplicationCommandInteractionDataOption> IApplicationCommandInteractionData.Options
=> (IReadOnlyCollection<IApplicationCommandInteractionDataOption>)Options;
}

/// <summary>
/// Represents the base data tied with the <see cref="SocketCommandBase"/> interaction.
/// </summary>
public class SocketCommandBaseData : SocketCommandBaseData<IApplicationCommandInteractionDataOption>
{
internal SocketCommandBaseData(DiscordSocketClient client, Model model, ulong? guildId)
: base(client, model, guildId) { }
}
}

+ 113
- 0
src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketResolvableData.cs View File

@@ -0,0 +1,113 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Discord.WebSocket
{
internal class SocketResolvableData<T> where T : API.IResolvable
{
internal readonly Dictionary<ulong, SocketGuildUser> GuildMembers
= new Dictionary<ulong, SocketGuildUser>();
internal readonly Dictionary<ulong, SocketGlobalUser> Users
= new Dictionary<ulong, SocketGlobalUser>();
internal readonly Dictionary<ulong, SocketChannel> Channels
= new Dictionary<ulong, SocketChannel>();
internal readonly Dictionary<ulong, SocketRole> Roles
= new Dictionary<ulong, SocketRole>();

internal readonly Dictionary<ulong, SocketMessage> Messages
= new Dictionary<ulong, SocketMessage>();

internal SocketResolvableData(DiscordSocketClient discord, ulong? guildId, T model)
{
var guild = guildId.HasValue ? discord.GetGuild(guildId.Value) : null;

var resolved = model.Resolved.Value;

if (resolved.Users.IsSpecified)
{
foreach (var user in resolved.Users.Value)
{
var socketUser = discord.GetOrCreateUser(discord.State, user.Value);

this.Users.Add(ulong.Parse(user.Key), socketUser);
}
}

if (resolved.Channels.IsSpecified)
{
foreach (var channel in resolved.Channels.Value)
{
SocketChannel socketChannel = guild != null
? guild.GetChannel(channel.Value.Id)
: discord.GetChannel(channel.Value.Id);

if (socketChannel == null)
{
var channelModel = guild != null
? discord.Rest.ApiClient.GetChannelAsync(guild.Id, channel.Value.Id).ConfigureAwait(false).GetAwaiter().GetResult()
: discord.Rest.ApiClient.GetChannelAsync(channel.Value.Id).ConfigureAwait(false).GetAwaiter().GetResult();

socketChannel = guild != null
? SocketGuildChannel.Create(guild, discord.State, channelModel)
: (SocketChannel)SocketChannel.CreatePrivate(discord, discord.State, channelModel);
}

discord.State.AddChannel(socketChannel);
this.Channels.Add(ulong.Parse(channel.Key), socketChannel);
}
}

if (resolved.Members.IsSpecified)
{
foreach (var member in resolved.Members.Value)
{
member.Value.User = resolved.Users.Value[member.Key];
var user = guild.AddOrUpdateUser(member.Value);
this.GuildMembers.Add(ulong.Parse(member.Key), user);
}
}

if (resolved.Roles.IsSpecified)
{
foreach (var role in resolved.Roles.Value)
{
var socketRole = guild.AddOrUpdateRole(role.Value);
this.Roles.Add(ulong.Parse(role.Key), socketRole);
}
}

if (resolved.Messages.IsSpecified)
{
foreach (var msg in resolved.Messages.Value)
{
var channel = discord.GetChannel(msg.Value.ChannelId) as ISocketMessageChannel;

SocketUser author;
if (guild != null)
{
if (msg.Value.WebhookId.IsSpecified)
author = SocketWebhookUser.Create(guild, discord.State, msg.Value.Author.Value, msg.Value.WebhookId.Value);
else
author = guild.GetUser(msg.Value.Author.Value.Id);
}
else
author = (channel as SocketChannel).GetUser(msg.Value.Author.Value.Id);

if (channel == null)
{
if (!msg.Value.GuildId.IsSpecified) // assume it is a DM
{
channel = discord.CreateDMChannel(msg.Value.ChannelId, msg.Value.Author.Value, discord.State);
}
}

var message = SocketMessage.Create(discord, discord.State, author, channel, msg.Value);
this.Messages.Add(message.Id, message);
}
}
}
}
}

+ 16
- 0
src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs View File

@@ -2,6 +2,7 @@ using Discord.Rest;
using System;
using System.Threading.Tasks;
using Model = Discord.API.Interaction;
using DataModel = Discord.API.ApplicationCommandInteractionData;

namespace Discord.WebSocket
{
@@ -61,7 +62,22 @@ namespace Discord.WebSocket
internal static SocketInteraction Create(DiscordSocketClient client, Model model, ISocketMessageChannel channel)
{
if (model.Type == InteractionType.ApplicationCommand)
{
if (model.ApplicationId != null)
{
var dataModel = model.Data.IsSpecified ?
(DataModel)model.Data.Value
: null;
if (dataModel != null)
{
if (dataModel.Type.Equals(ApplicationCommandType.User))
return SocketUserCommand.Create(client, model, channel);
if (dataModel.Type.Equals(ApplicationCommandType.Message))
return SocketMessageCommand.Create(client, model, channel);
}
}
return SocketSlashCommand.Create(client, model, channel);
}
if (model.Type == InteractionType.MessageComponent)
return SocketMessageComponent.Create(client, model, channel);
else


Loading…
Cancel
Save