Browse Source

Slash commands docs part 1

pull/1923/head
quin lynch 4 years ago
parent
commit
8800ed8f95
34 changed files with 795 additions and 318 deletions
  1. +0
    -217
      docs/guides/commands/application-commands.md
  2. +23
    -0
      docs/guides/slash-commands/01-getting-started.md
  3. +83
    -0
      docs/guides/slash-commands/02-creating-slash-commands.md
  4. +50
    -0
      docs/guides/slash-commands/03-responding-to-slash-commands.md
  5. +100
    -0
      docs/guides/slash-commands/04-parameters.md
  6. +29
    -0
      docs/guides/slash-commands/05-responding-ephemerally.md
  7. +217
    -0
      docs/guides/slash-commands/06-subcommands.md
  8. +83
    -0
      docs/guides/slash-commands/07-choice-slash-command.md
  9. +0
    -0
      docs/guides/slash-commands/README.md
  10. BIN
      docs/guides/slash-commands/images/ephemeral1.png
  11. BIN
      docs/guides/slash-commands/images/feedback1.png
  12. BIN
      docs/guides/slash-commands/images/feedback2.png
  13. BIN
      docs/guides/slash-commands/images/listroles1.png
  14. BIN
      docs/guides/slash-commands/images/listroles2.png
  15. BIN
      docs/guides/slash-commands/images/oauth.png
  16. BIN
      docs/guides/slash-commands/images/settings1.png
  17. BIN
      docs/guides/slash-commands/images/settings2.png
  18. BIN
      docs/guides/slash-commands/images/settings3.png
  19. BIN
      docs/guides/slash-commands/images/slashcommand1.png
  20. BIN
      docs/guides/slash-commands/images/slashcommand2.png
  21. +1
    -1
      src/Discord.Net.Core/Discord.Net.Core.csproj
  22. +29
    -13
      src/Discord.Net.Core/Discord.Net.Core.xml
  23. +1
    -1
      src/Discord.Net.Core/Entities/Interactions/ApplicationCommandOptionType.cs
  24. +63
    -15
      src/Discord.Net.Core/Entities/Interactions/SlashCommandBuilder.cs
  25. +1
    -1
      src/Discord.Net.Core/Extensions/EmbedBuilderExtensions.cs
  26. +1
    -1
      src/Discord.Net.Rest/Discord.Net.Rest.csproj
  27. +23
    -10
      src/Discord.Net.Rest/DiscordRestApiClient.cs
  28. +13
    -3
      src/Discord.Net.Rest/Entities/Interactions/InteractionHelper.cs
  29. +1
    -1
      src/Discord.Net.WebSocket/Discord.Net.WebSocket.csproj
  30. +0
    -6
      src/Discord.Net.WebSocket/Discord.Net.WebSocket.xml
  31. +0
    -14
      src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketSlashCommand.cs
  32. +66
    -22
      src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketSlashCommandDataOption.cs
  33. +1
    -3
      src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs
  34. +10
    -10
      src/Discord.Net/Discord.Net.nuspec

+ 0
- 217
docs/guides/commands/application-commands.md View File

@@ -1,217 +0,0 @@
# Warning

Slash commands and interactions are still under development and are subject to change at any time. This doccument is temporary and is only here to help those who wish to use the interaction features in the development state.

# Application commands

Application commands are a new feature thats still a work in progress, this guide will show you how to make the best of em.

## Getting started

### Configuring

There is a new configuration setting for your DiscordSocketClient called `AlwaysAcknowledgeInteractions`, It's default value is true.
Interactions work off of the Recieve -> Respond pipeline, meaning if you dont acknowledge the interaction within 3 seconds its gone forever.
With `AlwaysAcknowledgeInteractions` set to true, the client will automatically acknowledge the interaction as its recieved,
letting you wait up to 15 minutes before responding with a message.

With `AlwaysAcknowledgeInteractions` set to false you will have to acknowledge the interaction yourself via the `InteractionCreated` event

### Registering commands

While there is no "easy" way to register command right now, in the future I plan to write a command service to help with that, but right now you have to use the rest
client to create your command:

```cs
_client.Ready += RegisterCommands

...

private async Task RegisterCommands()
{
// Creating a global command
var myGlobalCommand = await _client.Rest.CreateGlobalCommand(new Discord.SlashCommandCreationProperties()
{
Name = "example",
Description = "Runs the example command",
Options = new List<Discord.ApplicationCommandOptionProperties>()
{
new ApplicationCommandOptionProperties()
{
Name = "example_option",
Required = false,
Description = "Option Description",
Type = Discord.ApplicationCommandOptionType.String,
}
}
});

// Creating a guild command
var myGuildCommand = await _client.Rest.CreateGuildCommand(new Discord.SlashCommandCreationProperties()
{
Name = "guildExample",
Description = "Runs the guild example command",
Options = new List<Discord.ApplicationCommandOptionProperties>()
{
new ApplicationCommandOptionProperties()
{
Name = "Guild example option",
Required = false,
Description = "Guild option description",
Type = Discord.ApplicationCommandOptionType.String,
}
}
}, 1234567890); // <- the guild id
}
```
CreateGuildCommand returns a `RestGuildCommand` class which can be used to modify/delete your command on the fly, it also contains details about your command.
CreateGlobalCommand returns a `RestGlobalCommand` class which can be used to modify/delete your command on the fly, it also contains details about your command.

### Getting a list of all your commands
You can fetch a list of all your global commands via rest:
```cs
var commands = _client.Rest.GetGlobalApplicationCommands();
```
This returns a `IReadOnlyCollection<RestGlobalCommand>`.

You can also fetch guild specific commands:
```cs
var commands = _client.Rest.GetGuildApplicationCommands(1234567890)
```
This returns all the application commands in that guild.

### Responding

First thing we want to do is listen to the `InteractionCreated` event. This event is fired when a socket interaction is recieved via the gateway, It looks somthing like this
```cs
_client.InteractionCreated += MyEventHandler;

...

private async Task MyEventHandler(SocketInteraction arg)
{
// handle the interaction here
}
```

A socket interaction is made up of these properties and methods:

| Name | Description |
|--------|--------------|
| Guild | The `SocketGuild` this interaction was used in |
| Channel | The `SocketTextChannel` this interaction was used in |
| Member | The `SocketGuildUser` that executed the interaction |
| Type | The [InteractionType](https://discord.com/developers/docs/interactions/slash-commands#interaction-interactiontype) of this interaction |
| Data | The `SocketInteractionData` associated with this interaction |
| Token | The token used to respond to this interaction |
| Version | The version of this interaction |
| CreatedAt | The time this interaction was created |
| IsValidToken | Whether or not the token to respond to this interaction is still valid |
| RespondAsync | Responds to the interaction |
| FollowupAsync | Sends a followup message to the interaction |



#### Whats the difference between `FollowupAsync` and `RespondAsync`?
RespondAsync is the initial responce to the interaction, its used to "capture" the interaction, while followup is used to send more messages to the interaction.
Basically, you want to first use `RespondAsync` to acknowledge the interaction, then if you need to send anything else regarding that interaction you would use `FollowupAsync`
If you have `AlwaysAcknowledgeInteractions` set to true in your client config then it will automatically acknowledge the interaction without sending a message,
in this case you can use either or to respond.

#### Example ping pong command
```cs
_client.InteractionCreated += MyEventHandler;
_client.Ready += CreateCommands

...

private async Task CreateCommands()
{
await _client.Rest.CreateGlobalCommand(new Discord.SlashCommandCreationProperties()
{
Name = "ping",
Description = "ping for a pong!",
});
}

private async Task MyEventHandler(SocketInteraction arg)
{
switch(arg.Type) // We want to check the type of this interaction
{
case InteractionType.ApplicationCommand: // If it is a command
await MySlashCommandHandler(arg); // Handle the command somewhere
break;
default: // We dont support it
Console.WriteLine("Unsupported interaction type: " + arg.Type);
break;
}
}

private async Task MySlashCommandHandler(SocketInteraction arg)
{
switch(arg.Name)
{
case "ping":
await arg.RespondAsync("Pong!");
break;
}
}
```

#### Example hug command
```cs
_client.InteractionCreated += MyEventHandler;
_client.Ready += CreateCommands;

...

private async Task CreateCommands()
{
await _client.Rest.CreateGlobalCommand(new Discord.SlashCommandCreationProperties()
{
Name = "hug",
Description = "Hugs a user!",
Options = new List<Discord.ApplicationCommandOptionProperties>()
{
new ApplicationCommandOptionProperties()
{
Name = "User",
Required = true,
Description = "The user to hug",
Type = Discord.ApplicationCommandOptionType.User,
}
}
});
}

private async Task MyEventHandler(SocketInteraction arg)
{
switch(arg.Type) // We want to check the type of this interaction
{
case InteractionType.ApplicationCommand: // If it is a command
await MySlashCommandHandler(arg); // Handle the command somewhere
break;
default: // We dont support it
Console.WriteLine("Unsupported interaction type: " + arg.Type);
break;
}
}

private async Task MySlashCommandHandler(SocketInteraction arg)
{
switch(arg.Name)
{
case "hug":
// Get the user argument
var option = arg.Data.Options.First(x => x.Name == "user");
// We know that the options value must be a user. Types of user, channel, and role return the snowflake id of that object.
// For this case we can just mention the user with the id
await arg.RespondAsync($"Hugged <@{user.Value}>");
break;
}
}
```

# Wrapping up
These examples are soon to change as there is currently a command service being written that will support slash commands. These examples are written for [#1717](https://github.com/discord-net/Discord.Net/pull/1717). You can see the progress of slash commands on pull [#1733](https://github.com/discord-net/Discord.Net/pull/1733)

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

@@ -0,0 +1,23 @@
# 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.

+ 83
- 0
docs/guides/slash-commands/02-creating-slash-commands.md View File

@@ -0,0 +1,83 @@
# Creating your first slash commands.

There are two kinds of Slash Commands: global commands and guild commands.
Global 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 100 global commands, and an additional 100 guild-specific 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).

## SlashCommandBuilder

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

| Name | Type | Description |
| --------------------- | -------------------------------- | -------------------------------------------------------------------------------------------- |
| MaxNameLength | const int | The maximum length of a name for a slash command allowed by Discord. |
| MaxDescriptionLength | const int | The maximum length of a commands description allowed by Discord. |
| MaxOptionsCount | const int | The maximum count of command options allowed by Discord |
| Name | string | The name of this slash command. |
| Description | string | A 1-100 length description of this slash command |
| Options | List\<SlashCommandOptionBuilder> | The options for this command. |
| DefaultPermission | bool | Whether the command is enabled by default when the app is added to a guild. |
| WithName | Function | Sets the field name. |
| WithDescription | Function | Sets the description of the current command. |
| WithDefaultPermission | Function | Sets the default permission of the current command. |
| AddOption | Function | Adds an option to the current slash command. |
| Build | Function | Builds the builder into a `SlashCommandCreationProperties` class used to make slash commands |

**Note**: Slash command names must be all lowercase!

Let's use the slash 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 slash command builder. This is like the embed builder but for slash commands.
var guildCommand = new SlashCommandBuilder();

// Note: Names have to be all lowercase and match the regular expression ^[\w-]{3,32}$
guildCommand.WithName("first-command");

// Descriptions can have a max length of 100.
guildCommand.WithDescription("This is my first guild slash command!");

// Let's do our global command
var globalCommand = new SlashCommandBuilder();
globalCommand.WithName("first-global-command");
globalCommand.WithDescription("This is my frist global slash command");

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

// With global commands we dont need the guild id.
await client.Rest.CreateGlobalCommand(globalCommand.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);
}
}

```

+ 50
- 0
docs/guides/slash-commands/03-responding-to-slash-commands.md View File

@@ -0,0 +1,50 @@
# Responding to interactions.

Interactions are the base thing sent over by discord. Slash commands are one of the interaction types. In order to receive a slash command we have to listen to the `InteractionCreated` event. Let's add this to our code.

```cs
client.InteractionCreated += Client_InteractionCreated;

...

private async Task Client_InteractionCreated(SocketInteraction arg)
{

}
```

Now that we have the interaction event, Let's talk about the `SocketInteraction` argument. The interaction can be cast to either a `SocketSlashCommand` or a `SocketMessageComponent`. In our case we're trying to use slash commands so Let's cast it to a `SocketSlashCommand`.

```cs
private async Task Client_InteractionCreated(SocketInteraction arg)
{
if(arg is SocketSlashCommand command)
{
// we now have an instance of a SocketSlashCommand named command.
}
}
```

With every type of interaction there is a `Data` field. this is where the relevant information lives about our command that was executed. In our case, `Data` is a `SocketSlashCommandData` class. In the data class, we can access the name of the command triggered as well as the options if there were any. For this example, we're just going to respond with the name of the command executed.

```cs
private async Task Client_InteractionCreated(SocketInteraction arg)
{
if(arg is SocketSlashCommand command)
{
await command.RespondAsync($"You executed {command.Data.Name}");
}
}
```

Let's try this out!

![slash command picker](images/slashcommand1.png)

![slash command result](images/slashcommand2.png)

Let's go over the response types quickly, as you would only change them for style points :P

> After receiving an interaction, you must respond to acknowledge it. You can choose to respond with a message immediately using `ChannelMessageWithSource` or you can choose to send a deferred response with `DeferredChannelMessageWithSource`. If choosing a deferred response, the user will see a loading state for the interaction, and you'll have up to 15 minutes to edit the original deferred response using Edit Original Interaction Response. You can read more about Response types [here](https://discord.com/developers/docs/interactions/slash-commands#interaction-response)

This seems to be working! Next, we will look at parameters for slash commands.

+ 100
- 0
docs/guides/slash-commands/04-parameters.md View File

@@ -0,0 +1,100 @@
# Slash command parameters

Slash commands can have a bunch of parameters, each their own type. Let's first go over the types of parameters we can have.

| Name | Description |
| --------------- | -------------------------------------------------- |
| SubCommand | A subcommand inside of a subcommand group. |
| SubCommandGroup | The parent command group of subcommands. |
| String | A string of text. |
| Integer | A number. |
| Boolean | True or False. |
| User | A user |
| Channel | A channel, this includes voice text and categories |
| Role | A role. |
| Mentionable | A role or a user. |

Each one of the parameter types has its own DNET type in the `SocketSlashCommandDataOption`'s Value field:
| Name | C# Type |
| --------------- | ------------------------------------------------ |
| SubCommand | NA |
| SubCommandGroup | NA |
| String | `string` |
| Integer | `int` |
| Boolean | `bool` |
| User | `SocketGuildUser` or `SocketUser` |
| Role | `SocketRole` |
| Channel | `SocketChannel` |
| Mentionable | `SocketUser`, `SocketGuildUser`, or `SocketRole` |

Let's start by making a command that takes in a user and lists their roles.

```cs
client.Ready += Client_Ready;

...

public async Task Client_Ready()
{
ulong guildId = 848176216011046962;

var guildCommand = new SlashCommandBuilder()
.WithName("list-roles")
.WithDescription("Lists all roles of a user.")
.AddOption("user", ApplicationCommandOptionType.User, "The users whos roles you want to be listed", required: true);

try
{
await client.Rest.CreateGuildCommand(guildCommand.Build(), guildId);
}
catch(ApplicationCommandException exception)
{
var json = JsonConvert.SerializeObject(exception.Error, Formatting.Indented);
Console.WriteLine(json);
}
}

```

![list roles command](images/listroles1.png)

That seems to be working, now Let's handle the interaction.

```cs
private async Task Client_InteractionCreated(SocketInteraction arg)
{
if(arg is SocketSlashCommand command)
{
// Let's add a switch statement for the command name so we can handle multiple commands in one event.
switch(command.Data.Name)
{
case "list-roles":
await HandleListRoleCommand(command);
break;
}
}
}

private async Task HandleListRoleCommand(SocketSlashCommandData command)
{
// We need to extract the user parameter from the command. since we only have one option and it's required, we can just use the first option.
var guildUser = (SocketGuildUser)command.Data.Options.First().Value;

// We remove the everyone role and select the mention of each role.
var roleList = string.Join(",\n", guildUser.Roles.Where(x => !x.IsEveryone).Select(x => x.Mention));

var embedBuiler = new EmbedBuilder()
.WithAuthor(guildUser.ToString(), guildUser.GetAvatarUrl() ?? guildUser.GetDefaultAvatarUrl())
.WithTitle("Roles")
.WithDescription(roleList)
.WithColor(Color.Green)
.WithCurrentTimestamp();

// Now, Let's respond with the embed.
await command.RespondAsync(embed: embedBuiler.Build());
}
```

![working list roles](images/listroles2.png)

That has worked! Next, we will go over responding ephemerally.

+ 29
- 0
docs/guides/slash-commands/05-responding-ephemerally.md View File

@@ -0,0 +1,29 @@
# Responding ephemerally

What is an ephemeral response? Basically, only the user who executed the command can see the result of it. In labs this is pretty simple to do.

First, we need to talk about `AlwaysAcknowledgeInteractions` in the discord config. `AlwaysAcknowledgeInteractions` will always acknowledge the message non-ephemerally, meaning any follow-up messages or responses will also be non-ephemeral. If you set `AlwaysAcknowledgeInteractions` to false, you can acknowledge interactions yourself with the ephemeral field set to your discretion.

**Note**: You don't have to run arg.AcknowledgeAsync() to capture the interaction, you can use arg.RespondAsync with a message to capture it, this also follows the ephemeral rule.

Let's start by changing our client config.

```cs
client = new DiscordSocketClient(new DiscordSocketConfig()
{
// Add this!
AlwaysAcknowledgeInteractions = false,
});
```

When responding with either `FollowupAsync` or `RespondAsync` you can pass in an `ephemeral` property. When setting it to true it will respond ephemerally, false and it will respond non-ephemerally.

Let's use this in our list role command.

```cs
await command.RespondAsync(embed: embedBuiler.Build(), ephemeral: true);
```

Running the command now only shows the message to us!

![ephemeral command](images/ephemeral1.png)

+ 217
- 0
docs/guides/slash-commands/06-subcommands.md View File

@@ -0,0 +1,217 @@
# Subcommands

Sub commands allow you to have multiple commands available in a single command. They can be useful for representing sub options for a command. for example: a settings command. Let's first look at some limitations with sub commands set by discord.

- An app can have up to 25 subcommand groups on a top-level command
- An app can have up to 25 subcommands within a subcommand group
- commands can have up to 25 `options`
- options can have up to 25 `choices`

```
VALID

command
|
|__ subcommand
|
|__ subcommand

----

command
|
|__ subcommand-group
|
|__ subcommand
|
|__ subcommand-group
|
|__ subcommand


-------

INVALID


command
|
|__ subcommand-group
|
|__ subcommand-group
|
|__ subcommand-group
|
|__ subcommand-group

----

INVALID

command
|
|__ subcommand
|
|__ subcommand-group
|
|__ subcommand
|
|__ subcommand-group
```

Let's write a settings command that can change 2 fields in our bot.

```cs
public string FieldA { get; set; } = "test";
public int FieldB { get; set; } = 10;
public bool FieldC { get; set; } = true;

public async Task Client_Ready()
{
ulong guildId = 848176216011046962;

var guildCommand = new SlashCommandBuilder()
.WithName("settings")
.WithDescription("Changes some settings within the bot.")
.AddOption(new SlashCommandOptionBuilder()
.WithName("field-a")
.WithDescription("Gets or sets the field A")
.WithType(ApplicationCommandOptionType.SubCommandGroup)
.AddOption(new SlashCommandOptionBuilder()
.WithName("set")
.WithDescription("Sets the field A")
.WithType(ApplicationCommandOptionType.SubCommand)
.AddOption("value", ApplicationCommandOptionType.String, "the value to set the field ", required: true)
).AddOption(new SlashCommandOptionBuilder()
.WithName("get")
.WithDescription("Gets the value of field A.")
.WithType(ApplicationCommandOptionType.SubCommand)
)
).AddOption(new SlashCommandOptionBuilder()
.WithName("field-b")
.WithDescription("Gets or sets the field B")
.WithType(ApplicationCommandOptionType.SubCommandGroup)
.AddOption(new SlashCommandOptionBuilder()
.WithName("set")
.WithDescription("Sets the field B")
.WithType(ApplicationCommandOptionType.SubCommand)
.AddOption("value", ApplicationCommandOptionType.Integer, "the value to set the fie to.", required: true)
).AddOption(new SlashCommandOptionBuilder()
.WithName("get")
.WithDescription("Gets the value of field B.")
.WithType(ApplicationCommandOptionType.SubCommand)
)
).AddOption(new SlashCommandOptionBuilder()
.WithName("field-c")
.WithDescription("Gets or sets the field C")
.WithType(ApplicationCommandOptionType.SubCommandGroup)
.AddOption(new SlashCommandOptionBuilder()
.WithName("set")
.WithDescription("Sets the field C")
.WithType(ApplicationCommandOptionType.SubCommand)
.AddOption("value", ApplicationCommandOptionType.Boolean, "the value to set the fie to.", required: true)
).AddOption(new SlashCommandOptionBuilder()
.WithName("get")
.WithDescription("Gets the value of field C.")
.WithType(ApplicationCommandOptionType.SubCommand)
)
);

try
{
await client.Rest.CreateGuildCommand(guildCommand.Build(), guildId);
}
catch(ApplicationCommandException exception)
{
var json = JsonConvert.SerializeObject(exception.Error, Formatting.Indented);
Console.WriteLine(json);
}
}
```

All that code generates a command that looks like this:
![settings](images/settings1.png)

Now that we have our command made, we need to handle the multiple options with this command. so lets add this into our handler

```cs
private async Task Client_InteractionCreated(SocketInteraction arg)
{
if(arg is SocketSlashCommand command)
{
// Let's add a switch statement for the command name so we can handle multiple commands in one event.
switch(command.Data.Name)
{
case "list-roles":
await HandleListRoleCommand(command);
break;
case "settings":
await HandleSettingsCommand(command);
break;
}
}
}

private async Task HandleSettingsCommand(SocketSlashCommand command)
{
// First lets extract our variables
var fieldName = command.Data.Options.First().Name;
var getOrSet = command.Data.Options.First().Options.First().Name;
// since there is no value on a get command, we use the ? operator because "Options" can be null.
var value = command.Data.Options.First().Options.First().Options?.FirstOrDefault().Value;

switch (fieldName)
{
case "field-a":
{
if(getOrSet == "get")
{
await command.RespondAsync($"The value of `field-a` is `{FieldA}`");
}
else if (getOrSet == "set")
{
this.FieldA = (string)value;
await command.RespondAsync($"`field-a` has been set to `{FieldA}`");
}
}
break;
case "field-b":
{
if (getOrSet == "get")
{
await command.RespondAsync($"The value of `field-b` is `{FieldB}`");
}
else if (getOrSet == "set")
{
this.FieldB = (int)value;
await command.RespondAsync($"`field-b` has been set to `{FieldB}`");
}
}
break;
case "field-c":
{
if (getOrSet == "get")
{
await command.RespondAsync($"The value of `field-c` is `{FieldC}`");
}
else if (getOrSet == "set")
{
this.FieldC = (bool)value;
await command.RespondAsync($"`field-c` has been set to `{FieldC}`");
}
}
break;
}
}

```

Now, let't try this out! Running the 3 get commands seems to get the default values we set.

![settings get](images/settings2.png)

Now lets try changing each to a different value.

![settings set](images/settings3.png)

That has worked! Next, let't look at choices in commands.

+ 83
- 0
docs/guides/slash-commands/07-choice-slash-command.md View File

@@ -0,0 +1,83 @@
# Slash Command Choices.

With slash command options you can add choices, making the user select between some set values. Lets create a command that ask how much they like our bot!

Lets set up our slash command:

```cs
private async Task Client_Ready()
{
ulong guildId = 848176216011046962;

var guildCommand = new SlashCommandBuilder()
.WithName("feedback")
.WithDescription("Tell us how much you are enjoying this bot!")
.AddOption(new SlashCommandOptionBuilder()
.WithName("rating")
.WithDescription("The rating your willing to give our bot")
.WithRequired(true)
.AddChoice("Terrible", 1)
.AddChoice("Meh", 2)
.AddChoice("Good", 3)
.AddChoice("Lovely", 4)
.AddChoice("Excellent!", 5)
.WithType(ApplicationCommandOptionType.Integer)
).Build();

try
{
await client.Rest.CreateGuildCommand(guildCommand.Build(), guildId);
}
catch(ApplicationCommandException exception)
{
var json = JsonConvert.SerializeObject(exception.Error, Formatting.Indented);
Console.WriteLine(json);
}
}
```

> **Note:** Your `ApplicationCommandOptionType` specifies which type your choices are, you need to use `ApplicationCommandOptionType.Integer` if choices whos value are numbers and `ApplicationCommandOptionType.String` for string values.

We have defined 5 choices for the user to pick from, each choice has a value assigned to it. The value can either be a string or an int. In our case we're going to use an int. This is what the command looks like:

![feedback style](images/feedback1.png)

Lets add our code for handling the interaction.

```cs
private async Task Client_InteractionCreated(SocketInteraction arg)
{
if(arg is SocketSlashCommand command)
{
// Let's add a switch statement for the command name so we can handle multiple commands in one event.
switch(command.Data.Name)
{
case "list-roles":
await HandleListRoleCommand(command);
break;
case "settings":
await HandleSettingsCommand(HandleFeedbackCommand);
break;
case "feedback":
await HandleFeedbackCommand(command);
break;
}
}
}

private async Task HandleFeedbackCommand(SocketSlashCommand command)
{
var embedBuilder = new EmbedBuilder()
.WithAuthor(command.User)
.WithTitle("Feedback")
.WithDescription($"Thanks for your feedback! You rated us {command.Data.Options.First().Value}/5")
.WithColor(Color.Green)
.WithCurrentTimestamp();

await command.RespondAsync(embed: embedBuilder.Build());
}
```

And this is the result!

![feedback working](images/feedback2.png)

+ 0
- 0
docs/guides/slash-commands/README.md View File


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

Before After
Width: 482  |  Height: 366  |  Size: 37 KiB

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

Before After
Width: 1670  |  Height: 412  |  Size: 31 KiB

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

Before After
Width: 499  |  Height: 265  |  Size: 28 KiB

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

Before After
Width: 1672  |  Height: 294  |  Size: 37 KiB

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

Before After
Width: 472  |  Height: 314  |  Size: 32 KiB

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

Before After
Width: 1715  |  Height: 751  |  Size: 111 KiB

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

Before After
Width: 1667  |  Height: 549  |  Size: 74 KiB

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

Before After
Width: 467  |  Height: 331  |  Size: 50 KiB

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

Before After
Width: 552  |  Height: 322  |  Size: 52 KiB

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

Before After
Width: 330  |  Height: 132  |  Size: 11 KiB

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

Before After
Width: 434  |  Height: 106  |  Size: 18 KiB

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

@@ -8,7 +8,7 @@
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">net461;netstandard2.0;netstandard2.1</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' != 'Windows_NT' ">netstandard2.0;netstandard2.1</TargetFrameworks>
<PackageId>Discord.Net.Labs.Core</PackageId>
<Version>2.4.1</Version>
<Version>2.4.2</Version>
<Product>Discord.Net.Labs.Core</Product>
<RepositoryUrl>https://github.com/Discord-Net-Labs/Discord.Net-Labs</RepositoryUrl>
<PackageIcon>Temporary.png</PackageIcon>


+ 29
- 13
src/Discord.Net.Core/Discord.Net.Core.xml View File

@@ -1081,6 +1081,21 @@
<member name="F:Discord.ChannelType.News">
<summary> The channel is a news channel. </summary>
</member>
<member name="F:Discord.ChannelType.Store">
<summary> The channel is a store channel. </summary>
</member>
<member name="F:Discord.ChannelType.NewsThread">
<summary> The channel is a temporary thread channel under a news channel. </summary>
</member>
<member name="F:Discord.ChannelType.PublicThread">
<summary> The channel is a temporary thread channel under a text channel. </summary>
</member>
<member name="F:Discord.ChannelType.PrivateThread">
<summary> The channel is a private temporary thread channel under a text channel. </summary>
</member>
<member name="F:Discord.ChannelType.Stage">
<summary> The channel is a stage voice channel. </summary>
</member>
<member name="T:Discord.Direction">
<summary>
Specifies the direction of where message(s) should be retrieved from.
@@ -3934,7 +3949,7 @@
</member>
<member name="F:Discord.ApplicationCommandOptionType.Mentionable">
<summary>
A <see cref="T:Discord.IUser"/> or <see cref="T:Discord.IRole"/>.
</summary>
</member>
<member name="T:Discord.ApplicationCommandProperties">
@@ -4933,18 +4948,6 @@
<param name="choices">The choices of this option.</param>
<returns>The current builder.</returns>
</member>
<member name="M:Discord.SlashCommandBuilder.AddOption(System.String,Discord.ApplicationCommandOptionType,System.String,System.Boolean,System.Boolean,Discord.ApplicationCommandOptionChoiceProperties[])">
<summary>
Adds an option to the current slash command.
</summary>
<param name="name">The name of the option to add.</param>
<param name="type">The type of this option.</param>
<param name="description">The description of this option.</param>
<param name="required">If this option is required for this command.</param>
<param name="isDefault">If this option is the default option.</param>
<param name="choices">The choices of this option.</param>
<returns>The current builder.</returns>
</member>
<member name="M:Discord.SlashCommandBuilder.AddOption(System.String,Discord.ApplicationCommandOptionType,System.String)">
<summary>
Adds an option to the current slash command.
@@ -5024,6 +5027,19 @@
</summary>
<returns>The built version of this option.</returns>
</member>
<member name="M:Discord.SlashCommandOptionBuilder.AddOption(System.String,Discord.ApplicationCommandOptionType,System.String,System.Boolean,System.Boolean,System.Collections.Generic.List{Discord.SlashCommandOptionBuilder},Discord.ApplicationCommandOptionChoiceProperties[])">
<summary>
Adds an option to the current slash command.
</summary>
<param name="name">The name of the option to add.</param>
<param name="type">The type of this option.</param>
<param name="description">The description of this option.</param>
<param name="required">If this option is required for this command.</param>
<param name="isDefault">If this option is the default option.</param>
<param name="options">The options of the option to add.</param>
<param name="choices">The choices of this option.</param>
<returns>The current builder.</returns>
</member>
<member name="M:Discord.SlashCommandOptionBuilder.AddOption(Discord.SlashCommandOptionBuilder)">
<summary>
Adds a sub option to the current option.


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

@@ -52,7 +52,7 @@ namespace Discord
Role = 8,

/// <summary>
///
/// A <see cref="IUser"/> or <see cref="IRole"/>.
/// </summary>
Mentionable = 9
}


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

@@ -202,19 +202,19 @@ namespace Discord
return AddOption(option);
}

/// <summary>
/// Adds an option to the current slash command.
/// </summary>
/// <param name="name">The name of the option to add.</param>
/// <param name="type">The type of this option.</param>
/// <param name="description">The description of this option.</param>
/// <param name="required">If this option is required for this command.</param>
/// <param name="isDefault">If this option is the default option.</param>
/// <param name="choices">The choices of this option.</param>
/// <returns>The current builder.</returns>
public SlashCommandBuilder AddOption(string name, ApplicationCommandOptionType type,
string description, bool required = true, bool isDefault = false, params ApplicationCommandOptionChoiceProperties[] choices)
=> AddOption(name, type, description, required, isDefault, null, choices);
///// <summary>
///// Adds an option to the current slash command.
///// </summary>
///// <param name="name">The name of the option to add.</param>
///// <param name="type">The type of this option.</param>
///// <param name="description">The description of this option.</param>
///// <param name="required">If this option is required for this command.</param>
///// <param name="isDefault">If this option is the default option.</param>
///// <param name="choices">The choices of this option.</param>
///// <returns>The current builder.</returns>
//public SlashCommandBuilder AddOption(string name, ApplicationCommandOptionType type,
// string description, bool required = true, bool isDefault = false, params ApplicationCommandOptionChoiceProperties[] choices)
// => AddOption(name, type, description, required, isDefault, null, choices);

/// <summary>
/// Adds an option to the current slash command.
@@ -356,12 +356,12 @@ namespace Discord
/// <returns>The built version of this option.</returns>
public ApplicationCommandOptionProperties Build()
{
bool isSubType = this.Type == ApplicationCommandOptionType.SubCommand || this.Type == ApplicationCommandOptionType.SubCommandGroup;
bool isSubType = this.Type == ApplicationCommandOptionType.SubCommandGroup;

if (isSubType && (Options == null || !Options.Any()))
throw new ArgumentException(nameof(Options), "SubCommands/SubCommandGroups must have at least one option");

if (!isSubType && (Options != null && Options.Any()))
if (!isSubType && (Options != null && Options.Any()) && Type != ApplicationCommandOptionType.SubCommand)
throw new ArgumentException(nameof(Options), $"Cannot have options on {Type} type");

return new ApplicationCommandOptionProperties()
@@ -376,6 +376,54 @@ namespace Discord
};
}

/// <summary>
/// Adds an option to the current slash command.
/// </summary>
/// <param name="name">The name of the option to add.</param>
/// <param name="type">The type of this option.</param>
/// <param name="description">The description of this option.</param>
/// <param name="required">If this option is required for this command.</param>
/// <param name="isDefault">If this option is the default option.</param>
/// <param name="options">The options of the option to add.</param>
/// <param name="choices">The choices of this option.</param>
/// <returns>The current builder.</returns>
public SlashCommandOptionBuilder AddOption(string name, ApplicationCommandOptionType type,
string description, bool required = true, bool isDefault = false, List<SlashCommandOptionBuilder> options = null, params ApplicationCommandOptionChoiceProperties[] choices)
{
// Make sure the name matches the requirements from discord
Preconditions.NotNullOrEmpty(name, nameof(name));
Preconditions.AtLeast(name.Length, 3, nameof(name));
Preconditions.AtMost(name.Length, SlashCommandBuilder.MaxNameLength, nameof(name));

// Discord updated the docs, this regex prevents special characters like @!$%( and s p a c e s.. etc,
// https://discord.com/developers/docs/interactions/slash-commands#applicationcommand
if (!Regex.IsMatch(name, @"^[\w-]{3,32}$"))
throw new ArgumentException("Command name cannot contian any special characters or whitespaces!", nameof(name));

// same with description
Preconditions.NotNullOrEmpty(description, nameof(description));
Preconditions.AtLeast(description.Length, 3, nameof(description));
Preconditions.AtMost(description.Length, SlashCommandBuilder.MaxDescriptionLength, nameof(description));

// make sure theres only one option with default set to true
if (isDefault)
{
if (this.Options != null)
if (this.Options.Any(x => x.Default.HasValue && x.Default.Value))
throw new ArgumentException("There can only be one command option with default set to true!", nameof(isDefault));
}

SlashCommandOptionBuilder option = new SlashCommandOptionBuilder();
option.Name = name;
option.Description = description;
option.Required = required;
option.Default = isDefault;
option.Options = options;
option.Type = type;
option.Choices = choices != null ? new List<ApplicationCommandOptionChoiceProperties>(choices) : null;

return AddOption(option);
}
/// <summary>
/// Adds a sub option to the current option.
/// </summary>


+ 1
- 1
src/Discord.Net.Core/Extensions/EmbedBuilderExtensions.cs View File

@@ -27,7 +27,7 @@ namespace Discord

/// <summary> Fills the embed author field with the provided user's full username and avatar URL. </summary>
public static EmbedBuilder WithAuthor(this EmbedBuilder builder, IUser user) =>
builder.WithAuthor($"{user.Username}#{user.Discriminator}", user.GetAvatarUrl());
builder.WithAuthor($"{user.Username}#{user.Discriminator}", user.GetAvatarUrl() ?? user.GetDefaultAvatarUrl());

/// <summary> Converts a <see cref="EmbedType.Rich"/> <see cref="IEmbed"/> object to a <see cref="EmbedBuilder"/>. </summary>
/// <exception cref="InvalidOperationException">The embed type is not <see cref="EmbedType.Rich"/>.</exception>


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

@@ -9,7 +9,7 @@
<TargetFrameworks Condition=" '$(OS)' != 'Windows_NT' ">netstandard2.0;netstandard2.1</TargetFrameworks>
<PackageIcon>Temporary.png</PackageIcon>
<PackageProjectUrl>https://github.com/Discord-Net-Labs/Discord.Net-Labs</PackageProjectUrl>
<Version>2.4.2</Version>
<Version>2.4.3</Version>
<PackageId>Discord.Net.Labs.Rest</PackageId>
<RepositoryUrl>https://github.com/Discord-Net-Labs/Discord.Net-Labs</RepositoryUrl>
<AssemblyVersion>2.3.4</AssemblyVersion>


+ 23
- 10
src/Discord.Net.Rest/DiscordRestApiClient.cs View File

@@ -1718,23 +1718,36 @@ namespace Discord.API

protected async Task<T> TrySendApplicationCommand<T>(Task<T> sendTask)
{
var result = await sendTask.ConfigureAwait(false);

if (sendTask.Exception != null)
try
{
if (sendTask.Exception.InnerException is HttpException x)
var result = await sendTask.ConfigureAwait(false);

if (sendTask.Exception != null)
{
if (x.HttpCode == HttpStatusCode.BadRequest)
if (sendTask.Exception.InnerException is HttpException x)
{
var json = (x.Request as JsonRestRequest).Json;
throw new ApplicationCommandException(json, x);
if (x.HttpCode == HttpStatusCode.BadRequest)
{
var json = (x.Request as JsonRestRequest).Json;
throw new ApplicationCommandException(json, x);
}
}

throw sendTask.Exception;
}
else
return result;
}
catch(HttpException x)
{
if (x.HttpCode == HttpStatusCode.BadRequest)
{
var json = (x.Request as JsonRestRequest).Json;
throw new ApplicationCommandException(json, x);
}

throw sendTask.Exception;
throw;
}
else
return result;
}

internal class BucketIds


+ 13
- 3
src/Discord.Net.Rest/Entities/Interactions/InteractionHelper.cs View File

@@ -1,5 +1,6 @@
using Discord.API;
using Discord.API.Rest;
using Discord.Net;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -341,9 +342,18 @@ namespace Discord.Rest
public static async Task<GuildApplicationCommandPermission> GetGuildCommandPermissionAsync(BaseDiscordClient client,
ulong guildId, ulong commandId, RequestOptions options)
{
var model = await client.ApiClient.GetGuildApplicationCommandPermission(guildId, commandId, options);
return new GuildApplicationCommandPermission(model.Id, model.ApplicationId, guildId, model.Permissions.Select(
y => new ApplicationCommandPermission(y.Id, y.Type, y.Permission)).ToArray());
try
{
var model = await client.ApiClient.GetGuildApplicationCommandPermission(guildId, commandId, options);
return new GuildApplicationCommandPermission(model.Id, model.ApplicationId, guildId, model.Permissions.Select(
y => new ApplicationCommandPermission(y.Id, y.Type, y.Permission)).ToArray());
}
catch(HttpException x)
{
if (x.HttpCode == System.Net.HttpStatusCode.NotFound)
return null;
throw;
}
}

public static async Task<GuildApplicationCommandPermission> ModifyGuildCommandPermissionsAsync(BaseDiscordClient client, ulong guildId, ulong commandId,


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

@@ -8,7 +8,7 @@
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">net461;netstandard2.0;netstandard2.1</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' != 'Windows_NT' ">netstandard2.0;netstandard2.1</TargetFrameworks>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Version>2.4.3</Version>
<Version>2.4.4</Version>
<RepositoryUrl>https://github.com/Discord-Net-Labs/Discord.Net-Labs</RepositoryUrl>
<PackageProjectUrl>https://github.com/Discord-Net-Labs/Discord.Net-Labs</PackageProjectUrl>
<PackageIcon>Temporary.png</PackageIcon>


+ 0
- 6
src/Discord.Net.WebSocket/Discord.Net.WebSocket.xml View File

@@ -3334,12 +3334,6 @@
The data associated with this interaction.
</summary>
</member>
<member name="M:Discord.WebSocket.SocketSlashCommand.GetOriginalResponse">
<summary>
Gets the original response to this slash command.
</summary>
<returns>A <see cref="T:Discord.Rest.RestInteractionMessage"/> that represents the initial response to this interaction.</returns>
</member>
<member name="M:Discord.WebSocket.SocketSlashCommand.RespondAsync(System.String,System.Boolean,Discord.Embed,Discord.InteractionResponseType,System.Boolean,Discord.AllowedMentions,Discord.RequestOptions,Discord.MessageComponent)">
<inheritdoc/>
</member>


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

@@ -50,20 +50,6 @@ namespace Discord.WebSocket
base.Update(model);
}

/// <summary>
/// Gets the original response to this slash command.
/// </summary>
/// <returns>A <see cref="RestInteractionMessage"/> that represents the initial response to this interaction.</returns>
public async Task<RestInteractionMessage> GetOriginalResponse()
{
// get the original message
var msg = await Discord.ApiClient.GetInteractionResponse(this.Token).ConfigureAwait(false);

var entity = RestInteractionMessage.Create(Discord, msg, this.Token, this.Channel);

return entity;
}

/// <inheritdoc/>
public override async Task RespondAsync(string text = null, bool isTTS = false, Embed embed = null, InteractionResponseType type = InteractionResponseType.ChannelMessageWithSource,
bool ephemeral = false, AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent component = null)


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

@@ -32,32 +32,76 @@ namespace Discord.WebSocket

if (model.Value.IsSpecified)
{
if (ulong.TryParse($"{model.Value.Value}", out var valueId))
switch (Type)
{
switch (this.Type)
{
case ApplicationCommandOptionType.User:
var guildUser = data.guildMembers.FirstOrDefault(x => x.Key == valueId).Value;
case ApplicationCommandOptionType.User:
case ApplicationCommandOptionType.Role:
case ApplicationCommandOptionType.Channel:
case ApplicationCommandOptionType.Mentionable:
if (ulong.TryParse($"{model.Value.Value}", out var valueId))
{
switch (this.Type)
{
case ApplicationCommandOptionType.User:
{
var guildUser = data.guildMembers.FirstOrDefault(x => x.Key == valueId).Value;

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

break;
case ApplicationCommandOptionType.Channel:
this.Value = data.channels.FirstOrDefault(x => x.Key == valueId).Value;
break;
case ApplicationCommandOptionType.Role:
this.Value = data.roles.FirstOrDefault(x => x.Key == valueId).Value;
break;
default:
this.Value = model.Value.Value;
break;
}
if (guildUser != null)
this.Value = guildUser;
else
this.Value = data.users.FirstOrDefault(x => x.Key == valueId).Value;
}
else if(data.roles.Any(x => x.Key == valueId))
{
this.Value = data.roles.FirstOrDefault(x => x.Key == valueId).Value;
}
}
break;
default:
this.Value = model.Value.Value;
break;
}
}
break;
case ApplicationCommandOptionType.String:
this.Value = model.Value.ToString();
break;
case ApplicationCommandOptionType.Integer:
{
if (model.Value.Value is int val)
this.Value = val;
else if (int.TryParse(model.Value.Value.ToString(), out int res))
this.Value = res;
}
break;
case ApplicationCommandOptionType.Boolean:
{
if (model.Value.Value is bool val)
this.Value = val;
else if (bool.TryParse(model.Value.Value.ToString(), out var res))
this.Value = res;
}
break;
}
else
this.Value = model.Value.Value;
}

this.Options = model.Options.IsSpecified


+ 1
- 3
src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs View File

@@ -140,9 +140,7 @@ namespace Discord.WebSocket
/// <param name="options">The request options for this async request.</param>
/// <returns>A <see cref="RestInteractionMessage"/> that represents the intitial response, or <see langword="null"/> if there is no response.</returns>
public Task<RestInteractionMessage> GetOriginalResponseAsync(RequestOptions options = null)
{
return InteractionHelper.GetOriginalResponseAsync(this.Discord, this.Channel, this, options);
}
=> InteractionHelper.GetOriginalResponseAsync(this.Discord, this.Channel, this, options);

/// <summary>
/// Acknowledges this interaction with the <see cref="InteractionResponseType.DeferredChannelMessageWithSource"/>.


+ 10
- 10
src/Discord.Net/Discord.Net.nuspec View File

@@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata>
<id>Discord.Net.Labs</id>
<version>2.4.3$suffix$</version>
<version>2.4.4$suffix$</version>
<title>Discord.Net Labs</title>
<authors>Discord.Net Contributors</authors>
<owners>quinchs</owners>
@@ -14,23 +14,23 @@
<iconUrl>https://avatars.githubusercontent.com/u/84047264</iconUrl>
<dependencies>
<group targetFramework="net461">
<dependency id="Discord.Net.Labs.Core" version="2.4.1$suffix$" />
<dependency id="Discord.Net.Labs.Rest" version="2.4.2$suffix$" />
<dependency id="Discord.Net.Labs.WebSocket" version="2.4.3$suffix$" />
<dependency id="Discord.Net.Labs.Core" version="2.4.2$suffix$" />
<dependency id="Discord.Net.Labs.Rest" version="2.4.3$suffix$" />
<dependency id="Discord.Net.Labs.WebSocket" version="2.4.4$suffix$" />
<dependency id="Discord.Net.Labs.Commands" version="2.3.5$suffix$" />
<dependency id="Discord.Net.Labs.Webhook" version="2.3.4$suffix$" />
</group>
<group targetFramework="netstandard2.0">
<dependency id="Discord.Net.Labs.Core" version="2.4.1$suffix$" />
<dependency id="Discord.Net.Labs.Rest" version="2.4.2$suffix$" />
<dependency id="Discord.Net.Labs.WebSocket" version="2.4.3$suffix$" />
<dependency id="Discord.Net.Labs.Core" version="2.4.2$suffix$" />
<dependency id="Discord.Net.Labs.Rest" version="2.4.3$suffix$" />
<dependency id="Discord.Net.Labs.WebSocket" version="2.4.4$suffix$" />
<dependency id="Discord.Net.Labs.Commands" version="2.3.5$suffix$" />
<dependency id="Discord.Net.Labs.Webhook" version="2.3.4$suffix$" />
</group>
<group targetFramework="netstandard2.1">
<dependency id="Discord.Net.Labs.Core" version="2.4.1$suffix$" />
<dependency id="Discord.Net.Labs.Rest" version="2.4.2$suffix$" />
<dependency id="Discord.Net.Labs.WebSocket" version="2.4.3$suffix$" />
<dependency id="Discord.Net.Labs.Core" version="2.4.2$suffix$" />
<dependency id="Discord.Net.Labs.Rest" version="2.4.3$suffix$" />
<dependency id="Discord.Net.Labs.WebSocket" version="2.4.4$suffix$" />
<dependency id="Discord.Net.Labs.Commands" version="2.3.5$suffix$" />
<dependency id="Discord.Net.Labs.Webhook" version="2.3.4$suffix$" />
</group>


Loading…
Cancel
Save