Browse Source

Merge branch 'dev' of https://github.com/discord-net/Discord.Net into dev

pull/1958/head
quin lynch 3 years ago
parent
commit
ff0bbbd4d3
100 changed files with 9081 additions and 183 deletions
  1. +1
    -1
      Discord.Net.sln
  2. +1
    -1
      LICENSE
  3. +49
    -0
      docs/guides/concepts/ratelimits.md
  4. +5
    -3
      docs/guides/emoji/emoji.md
  5. +31
    -0
      docs/guides/guild_events/creating-guild-events.md
  6. +16
    -0
      docs/guides/guild_events/getting-event-users.md
  7. +41
    -0
      docs/guides/guild_events/intro.md
  8. +23
    -0
      docs/guides/guild_events/modifying-events.md
  9. +32
    -0
      docs/guides/interactions/application-commands/01-getting-started.md
  10. +105
    -0
      docs/guides/interactions/application-commands/context-menu-commands/creating-context-menu-commands.md
  11. +33
    -0
      docs/guides/interactions/application-commands/context-menu-commands/receiving-context-menu-command-events.md
  12. +98
    -0
      docs/guides/interactions/application-commands/slash-commands/02-creating-slash-commands.md
  13. +40
    -0
      docs/guides/interactions/application-commands/slash-commands/03-responding-to-slash-commands.md
  14. +102
    -0
      docs/guides/interactions/application-commands/slash-commands/04-parameters.md
  15. +23
    -0
      docs/guides/interactions/application-commands/slash-commands/05-responding-ephemerally.md
  16. +219
    -0
      docs/guides/interactions/application-commands/slash-commands/06-subcommands.md
  17. +85
    -0
      docs/guides/interactions/application-commands/slash-commands/07-choice-slash-command.md
  18. +40
    -0
      docs/guides/interactions/application-commands/slash-commands/08-bulk-overwrite-of-global-slash-commands.md
  19. BIN
      docs/guides/interactions/application-commands/slash-commands/images/ephemeral1.png
  20. BIN
      docs/guides/interactions/application-commands/slash-commands/images/feedback1.png
  21. BIN
      docs/guides/interactions/application-commands/slash-commands/images/feedback2.png
  22. BIN
      docs/guides/interactions/application-commands/slash-commands/images/listroles1.png
  23. BIN
      docs/guides/interactions/application-commands/slash-commands/images/listroles2.png
  24. BIN
      docs/guides/interactions/application-commands/slash-commands/images/oauth.png
  25. BIN
      docs/guides/interactions/application-commands/slash-commands/images/settings1.png
  26. BIN
      docs/guides/interactions/application-commands/slash-commands/images/settings2.png
  27. BIN
      docs/guides/interactions/application-commands/slash-commands/images/settings3.png
  28. BIN
      docs/guides/interactions/application-commands/slash-commands/images/slashcommand1.png
  29. BIN
      docs/guides/interactions/application-commands/slash-commands/images/slashcommand2.png
  30. +10
    -0
      docs/guides/interactions/intro.md
  31. +66
    -0
      docs/guides/interactions/message-components/01-getting-started.md
  32. +37
    -0
      docs/guides/interactions/message-components/02-responding-to-buttons.md
  33. +45
    -0
      docs/guides/interactions/message-components/03-buttons-in-depth.md
  34. +76
    -0
      docs/guides/interactions/message-components/04-select-menus.md
  35. +87
    -0
      docs/guides/interactions/message-components/05-advanced.md
  36. BIN
      docs/guides/interactions/message-components/images/image1.png
  37. BIN
      docs/guides/interactions/message-components/images/image2.png
  38. BIN
      docs/guides/interactions/message-components/images/image3.png
  39. BIN
      docs/guides/interactions/message-components/images/image4.png
  40. BIN
      docs/guides/interactions/message-components/images/image5.png
  41. BIN
      docs/guides/interactions/message-components/images/image6.png
  42. +46
    -21
      docs/guides/toc.yml
  43. +1
    -1
      docs/guides/voice/sending-voice.md
  44. +1
    -1
      samples/02_commands_framework/02_commands_framework.csproj
  45. +1
    -1
      samples/02_commands_framework/Modules/PublicModule.cs
  46. +1
    -1
      samples/02_commands_framework/Program.cs
  47. +1
    -1
      samples/03_sharded_client/03_sharded_client.csproj
  48. +1
    -1
      samples/03_sharded_client/Services/CommandHandlingService.cs
  49. +1
    -1
      samples/idn/Inspector.cs
  50. +1
    -1
      samples/idn/Program.cs
  51. +1
    -1
      samples/idn/idn.csproj
  52. +2
    -2
      src/Discord.Net.Analyzers/Discord.Net.Analyzers.csproj
  53. +1
    -1
      src/Discord.Net.Commands/Attributes/AliasAttribute.cs
  54. +8
    -3
      src/Discord.Net.Commands/Builders/CommandBuilder.cs
  55. +7
    -2
      src/Discord.Net.Commands/Builders/ModuleBuilder.cs
  56. +3
    -3
      src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs
  57. +9
    -4
      src/Discord.Net.Commands/Builders/ParameterBuilder.cs
  58. +103
    -52
      src/Discord.Net.Commands/CommandService.cs
  59. +1
    -1
      src/Discord.Net.Commands/Discord.Net.Commands.csproj
  60. +1
    -2
      src/Discord.Net.Commands/Extensions/MessageExtensions.cs
  61. +3
    -3
      src/Discord.Net.Commands/Info/CommandInfo.cs
  62. +2
    -2
      src/Discord.Net.Commands/Info/ParameterInfo.cs
  63. +10
    -3
      src/Discord.Net.Commands/ModuleBase.cs
  64. +39
    -19
      src/Discord.Net.Commands/Readers/TimeSpanTypeReader.cs
  65. +47
    -0
      src/Discord.Net.Commands/Results/MatchResult.cs
  66. +1
    -1
      src/Discord.Net.Commands/RunMode.cs
  67. +70
    -16
      src/Discord.Net.Core/CDN.cs
  68. +3
    -3
      src/Discord.Net.Core/Discord.Net.Core.csproj
  69. +19
    -0
      src/Discord.Net.Core/DiscordConfig.cs
  70. +197
    -0
      src/Discord.Net.Core/DiscordErrorCode.cs
  71. +53
    -0
      src/Discord.Net.Core/DiscordJsonError.cs
  72. +13
    -1
      src/Discord.Net.Core/Entities/Activities/ActivityProperties.cs
  73. +4
    -0
      src/Discord.Net.Core/Entities/Activities/ActivityType.cs
  74. +86
    -0
      src/Discord.Net.Core/Entities/Activities/DefaultApplications.cs
  75. +23
    -0
      src/Discord.Net.Core/Entities/ApplicationFlags.cs
  76. +31
    -0
      src/Discord.Net.Core/Entities/ApplicationInstallParams.cs
  77. +50
    -0
      src/Discord.Net.Core/Entities/AuditLogs/ActionType.cs
  78. +13
    -1
      src/Discord.Net.Core/Entities/Channels/ChannelType.cs
  79. +70
    -3
      src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs
  80. +17
    -9
      src/Discord.Net.Core/Entities/Channels/INestedChannel.cs
  81. +114
    -0
      src/Discord.Net.Core/Entities/Channels/IStageChannel.cs
  82. +35
    -0
      src/Discord.Net.Core/Entities/Channels/ITextChannel.cs
  83. +89
    -0
      src/Discord.Net.Core/Entities/Channels/IThreadChannel.cs
  84. +1
    -1
      src/Discord.Net.Core/Entities/Channels/IVoiceChannel.cs
  85. +18
    -0
      src/Discord.Net.Core/Entities/Channels/StageInstanceProperties.cs
  86. +17
    -0
      src/Discord.Net.Core/Entities/Channels/StagePrivacyLevel.cs
  87. +16
    -0
      src/Discord.Net.Core/Entities/Channels/TextChannelProperties.cs
  88. +34
    -0
      src/Discord.Net.Core/Entities/Channels/ThreadArchiveDuration.cs
  89. +23
    -0
      src/Discord.Net.Core/Entities/Channels/ThreadType.cs
  90. +4
    -0
      src/Discord.Net.Core/Entities/Channels/VoiceChannelProperties.cs
  91. +5937
    -8
      src/Discord.Net.Core/Entities/Emotes/Emoji.cs
  92. +6
    -0
      src/Discord.Net.Core/Entities/Emotes/Emote.cs
  93. +105
    -0
      src/Discord.Net.Core/Entities/Guilds/GuildFeature.cs
  94. +46
    -0
      src/Discord.Net.Core/Entities/Guilds/GuildFeatures.cs
  95. +7
    -2
      src/Discord.Net.Core/Entities/Guilds/GuildProperties.cs
  96. +25
    -0
      src/Discord.Net.Core/Entities/Guilds/GuildScheduledEventPrivacyLevel.cs
  97. +34
    -0
      src/Discord.Net.Core/Entities/Guilds/GuildScheduledEventStatus.cs
  98. +34
    -0
      src/Discord.Net.Core/Entities/Guilds/GuildScheduledEventType.cs
  99. +58
    -0
      src/Discord.Net.Core/Entities/Guilds/GuildScheduledEventsProperties.cs
  100. +272
    -6
      src/Discord.Net.Core/Entities/Guilds/IGuild.cs

+ 1
- 1
Discord.Net.sln View File

@@ -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


+ 1
- 1
LICENSE View File

@@ -1,6 +1,6 @@
The MIT License (MIT)

Copyright (c) 2015-2019 Discord.Net Contributors
Copyright (c) 2015-2021 Discord.Net Contributors

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal


+ 49
- 0
docs/guides/concepts/ratelimits.md View File

@@ -0,0 +1,49 @@
# Ratelimits

Ratelimits are a core concept of any API - Discords API is no exception. each verified library must follow the ratelimit guidelines.

### Using the ratelimit callback

There is a new property within `RequestOptions` called RatelimitCallback. This callback is called when a request is made via the rest api. The callback is called with a `IRateLimitInfo` parameter:

| Name | Type | Description |
| ---------- | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| IsGlobal | bool | Whether or not this ratelimit info is global. |
| Limit | int? | The number of requests that can be made. |
| Remaining | int? | The number of remaining requests that can be made. |
| RetryAfter | int? | The total time (in seconds) of when the current rate limit bucket will reset. Can have decimals to match previous millisecond ratelimit precision. |
| Reset | DateTimeOffset? | The time at which the rate limit resets. |
| ResetAfter | TimeSpan? | The absolute time when this ratelimit resets. |
| Bucket | string | A unique string denoting the rate limit being encountered (non-inclusive of major parameters in the route path). |
| Lag | TimeSpan? | The amount of lag for the request. This is used to denote the precise time of when the ratelimit expires. |
| Endpoint | string | The endpoint that this ratelimit info came from. |

Let's set up a ratelimit callback that will print out the ratelimit info to the console.

```cs
public async Task MyRatelimitCallback(IRateLimitInfo info)
{
Console.WriteLine($"{info.IsGlobal} {info.Limit} {info.Remaining} {info.RetryAfter} {info.Reset} {info.ResetAfter} {info.Bucket} {info.Lag} {info.Endpoint}");
}
```

Let's use this callback in a send message function

```cs
[Command("ping")]
public async Task ping()
{
var options = new RequestOptions()
{
RatelimitCallback = MyRatelimitCallback
};

await Context.Channel.SendMessageAsync("Pong!", options: options);
}
```

Running this produces the following output:

```
False 5 4 2021-09-09 3:48:14 AM +00:00 00:00:05 a06de0de4a08126315431cc0c55ee3dc 00:00:00.9891364 channels/848511736872828929/messages
```

+ 5
- 3
docs/guides/emoji/emoji.md View File

@@ -46,14 +46,16 @@ form; this can be obtained in several different ways.
### Emoji Declaration

After obtaining the Unicode representation of the emoji, you may
create the @Discord.Emoji object by passing the string into its
create the @Discord.Emoji object by passing the string with unicode into its
constructor (e.g. `new Emoji("👌");` or `new Emoji("\uD83D\uDC4C");`).

Your method of declaring an @Discord.Emoji should look similar to
this:

[!code-csharp[Emoji Sample](samples/emoji-sample.cs)]

Also you can use `Emoji.Parse()` or `Emoji.TryParse()` methods
for parsing emojis from strings like `:heart:`, `<3` or `❤`.

[FileFormat.Info]: https://www.fileformat.info/info/emoji/list.htm

## Emote
@@ -97,4 +99,4 @@ this:
## Additional Information

To learn more about emote and emojis and how they could be used,
see the documentation of @Discord.IEmote.
see the documentation of @Discord.IEmote.

+ 31
- 0
docs/guides/guild_events/creating-guild-events.md View File

@@ -0,0 +1,31 @@
---
uid: Guides.GuildEvents.Creating
title: Creating Guild Events
---

# Creating guild events

You can create new guild events by using the `CreateEventAsync` function on a guild.

### Parameters

| Name | Type | Summary |
| ------------- | --------------------------------- | ---------------------------------------------------------------------------- |
| name | `string` | Sets the name of the event. |
| startTime | `DateTimeOffset` | Sets the start time of the event. |
| type | `GuildScheduledEventType` | Sets the type of the event. |
| privacyLevel? | `GuildScheduledEventPrivacyLevel` | Sets the privacy level of the event |
| description? | `string` | Sets the description of the event. |
| endTime? | `DateTimeOffset?` | Sets the end time of the event. |
| channelId? | `ulong?` | Sets the channel id of the event, only valid on stage or voice channel types |
| location? | `string` | Sets the location of the event, only valid on external types |

Lets create a basic test event.

```cs
var guild = client.GetGuild(guildId);

var guildEvent = await guild.CreateEventAsync("test event", DateTimeOffset.UtcNow.AddDays(1), GuildScheduledEventType.External, endTime: DateTimeOffset.UtcNow.AddDays(2), location: "Space");
```

This code will create an event that lasts a day and starts tomorrow. It will be an external event thats in space.

+ 16
- 0
docs/guides/guild_events/getting-event-users.md View File

@@ -0,0 +1,16 @@
---
uid: Guides.GuildEvents.GettingUsers
title: Getting Guild Event Users
---

# Getting Event Users

You can get a collection of users who are currently interested in the event by calling `GetUsersAsync`. This method works like any other get users method as in it returns an async enumerable. This method also supports pagination by user id.

```cs
// get all users and flatten the result into one collection.
var users = await event.GetUsersAsync().FlattenAsync();

// get users around the 613425648685547541 id.
var aroundUsers = await event.GetUsersAsync(613425648685547541, Direction.Around).FlattenAsync();
```

+ 41
- 0
docs/guides/guild_events/intro.md View File

@@ -0,0 +1,41 @@
---
uid: Guides.GuildEvents.Intro
title: Introduction to Guild Events
---

# Guild Events

Guild events are a way to host events within a guild. They offer alot of features and flexibility.

## Getting started with guild events

You can access any events within a guild by calling `GetEventsAsync` on a guild.

```cs
var guildEvents = await guild.GetEventsAsync();
```

If your working with socket guilds you can just use the `Events` property:

```cs
var guildEvents = guild.Events;
```

There are also new gateway events that you can hook to receive guild scheduled events on.

```cs
// Fired when a guild event is cancelled.
client.GuildScheduledEventCancelled += ...

// Fired when a guild event is completed.
client.GuildScheduledEventCompleted += ...

// Fired when a guild event is started.
client.GuildScheduledEventStarted += ...

// Fired when a guild event is created.
client.GuildScheduledEventCreated += ...

// Fired when a guild event is updated.
client.GuildScheduledEventUpdated += ...
```

+ 23
- 0
docs/guides/guild_events/modifying-events.md View File

@@ -0,0 +1,23 @@
---
uid: Guides.GuildEvents.Modifying
title: Modifying Guild Events
---

# Modifying Events

You can modify events using the `ModifyAsync` method to modify the event, heres the properties you can modify:

| Name | Type | Description |
| ------------ | --------------------------------- | -------------------------------------------- |
| ChannelId | `ulong?` | Gets or sets the channel id of the event. |
| string | `string` | Gets or sets the location of this event. |
| Name | `string` | Gets or sets the name of the event. |
| PrivacyLevel | `GuildScheduledEventPrivacyLevel` | Gets or sets the privacy level of the event. |
| StartTime | `DateTimeOffset` | Gets or sets the start time of the event. |
| EndTime | `DateTimeOffset` | Gets or sets the end time of the event. |
| Description | `string` | Gets or sets the description of the event. |
| Type | `GuildScheduledEventType` | Gets or sets the type of the event. |
| Status | `GuildScheduledEventStatus` | Gets or sets the status of the event. |

> [!NOTE]
> All of these properties are optional.

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

@@ -0,0 +1,32 @@
---
uid: Guides.SlashCommands.Intro
title: Introduction to slash commands
---


# Getting started with application commands.

Welcome! This guide will show you how to use application commands.

## 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](slash-commands/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.

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

@@ -0,0 +1,105 @@
---
uid: Guides.ContextCommands.Creating
title: Creating Context Commands
---

# 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 `UserCommandProperties` 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 `MessageCommandProperties` 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 so lets just put that in a variable.
var guild = client.GetGuild(guildId);

// 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 globalUserCommand = 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 BulkOverwriteApplicationCommandAsync to make our context commands. Note: this will overwrite all your previous commands with this array.
await guild.BulkOverwriteApplicationCommandAsync(new ApplicationCommandProperties[]
{
guildUserCommand.Build(),
guildMessageCommand.Build()
});

// With global commands we dont need the guild.
await client.BulkOverwriteGlobalApplicationCommandsAsync(new ApplicationCommandProperties[]
{
globalUserCommand.Build(),
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.

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

@@ -0,0 +1,33 @@
---
uid: Guides.ContextCommands.Reveiving
title: Receiving Context Commands
---

# Receiving Context Menu events

User commands and Message commands have their own unique event just like the other interaction types. For user commands the event is `UserCommandExecuted` and for message commands the event is `MessageCommandExecuted`.

```cs
// For message commands
client.MessageCommandExecuted += MessageCommandHandler;

// For user commands
client.UserCommandExecuted += UserCommandHandler;

...

public async Task MessageCommandHandler(SocketMessageCommand arg)
{
Console.Writeline("Message command received!");
}

public async Task UserCommandHandler(SocketUserCommand arg)
{
Console.Writeline("User command received!");
}
```

User commands contain a SocketUser object called `Member` in their data class, showing the user that was clicked to run the command.
Message commands contain a SocketMessage object called `Message` in their data class, showing the message that was clicked to run the command.

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

+ 98
- 0
docs/guides/interactions/application-commands/slash-commands/02-creating-slash-commands.md View File

@@ -0,0 +1,98 @@
---
uid: Guides.SlashCommands.Creating
title: Creating Slash Commands
---

# Creating your first slash commands.

There are two kinds of Slash Commands: global commands and guild commands.
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.

**Note**: Global commands will take up to 1 hour to create, delete or modify on guilds. If you need to update a command quickly for testing you can create it as a guild command.

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!

## Creating a Slash Command

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 so lets just put that in a variable.
var guild = client.GetGuild(guildId);

// 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 CreateApplicationCommandAsync method to make our slash command.
await guild.CreateApplicationCommandAsync(guildCommand.Build());

// With global commands we dont need the guild.
await client.CreateGlobalApplicationCommandAsync(globalCommand.Build());
// Using the ready event is a simple implementation for the sake of the example. Suitable for testing and development.
// For a production bot, it is recommended to only run the CreateGlobalApplicationCommandAsync() once for each command.
}
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]
> Slash commands only need to be created once. They do _not_ have to be 'created' on every startup or connection. The example simple shows creating them in the ready event as it's simpler than creating normal bot commands to register slash commands. The global commands take up to an hour to register every time the CreateGlobalApplicationCommandAsync() is called for a given command.

+ 40
- 0
docs/guides/interactions/application-commands/slash-commands/03-responding-to-slash-commands.md View File

@@ -0,0 +1,40 @@
---
uid: Guides.SlashCommands.Receiving
title: Receiving and Responding to Slash Commands
---

# Responding to interactions.

Interactions are the base thing sent over by Discord. Slash commands are one of the interaction types. We can listen to the `SlashCommandExecuted` event to respond to them. Lets add this to our code:

```cs
client.SlashCommandExecuted += SlashCommandHandler;

...

private async Task SlashCommandHandler(SocketSlashCommand 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` instance. 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 SlashCommandHandler(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)

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

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

+ 102
- 0
docs/guides/interactions/application-commands/slash-commands/04-parameters.md View File

@@ -0,0 +1,102 @@
---
uid: Guides.SlashCommands.Parameters
title: Slash Command Parameters
---

# Slash command parameters

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

| 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", isRequired: 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 SlashCommandHandler(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(SocketSlashCommand 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.

+ 23
- 0
docs/guides/interactions/application-commands/slash-commands/05-responding-ephemerally.md View File

@@ -0,0 +1,23 @@
---
uid: Guides.SlashCommands.Ephemeral
title: Ephemeral Responses
---

# Responding ephemerally

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

> [!NOTE]
> You don't have to run arg.DeferAsync() to capture the interaction, you can use arg.RespondAsync() with a message to capture it, this also follows the ephemeral rule.

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

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)

+ 219
- 0
docs/guides/interactions/application-commands/slash-commands/06-subcommands.md View File

@@ -0,0 +1,219 @@
---
uid: Guides.SlashCommands.SubCommand
title: Sub Commands
---

# Subcommands

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

- 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 3 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", isRequired: 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.", isRequired: 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.", isRequired: 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 SlashCommandHandler(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's try this out! Running the 3 get commands seems to get the default values we set.

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

Now let's try changing each to a different value.

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

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

+ 85
- 0
docs/guides/interactions/application-commands/slash-commands/07-choice-slash-command.md View File

@@ -0,0 +1,85 @@
---
uid: Guides.SlashCommands.Choices
title: Slash Command Choices
---

# Slash Command Choices.

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

Let's 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` for choices whos values are whole numbers, `ApplicationCommandOptionType.Number` for choices whos values are doubles, 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 SlashCommandHandler(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;
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)

+ 40
- 0
docs/guides/interactions/application-commands/slash-commands/08-bulk-overwrite-of-global-slash-commands.md View File

@@ -0,0 +1,40 @@
---
uid: Guides.SlashCommands.BulkOverwrite
title: Slash Command Bulk Overwrites
---

If you have too many global commands then you might want to consider using the bulk overwrite function.

```cs
public async Task Client_Ready()
{
List<ApplicationCommandProperties> applicationCommandProperties = new();
try
{
// Simple help slash command.
SlashCommandBuilder globalCommandHelp = new SlashCommandBuilder();
globalCommandHelp.WithName("help");
globalCommandHelp.WithDescription("Shows information about the bot.");
applicationCommandProperties.Add(globalCommandHelp.Build());

// Slash command with name as its parameter.
SlashCommandOptionBuilder slashCommandOptionBuilder = new();
slashCommandOptionBuilder.WithName("name");
slashCommandOptionBuilder.WithType(ApplicationCommandOptionType.String);
slashCommandOptionBuilder.WithDescription("Add a family");
slashCommandOptionBuilder.WithRequired(true); // Only add this if you want it to be required

SlashCommandBuilder globalCommandAddFamily = new SlashCommandBuilder();
globalCommandAddFamily.WithName("add-family");
globalCommandAddFamily.WithDescription("Add a family");
applicationCommandProperties.Add(globalCommandAddFamily.Build());

await _client.BulkOverwriteGlobalApplicationCommandsAsync(applicationCommandProperties.ToArray());
}
catch (ApplicationCommandException exception)
{
var json = JsonConvert.SerializeObject(exception.Error, Formatting.Indented);
Console.WriteLine(json);
}
}
```

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

+ 10
- 0
docs/guides/interactions/intro.md View File

@@ -0,0 +1,10 @@
---
uid: Guides.Interactions.Intro
title: Introduction to Interactions
---

# Interactions

Placeholder text does the brrr.

Links to different sections of guides: msg comp / slash commands.

+ 66
- 0
docs/guides/interactions/message-components/01-getting-started.md View File

@@ -0,0 +1,66 @@
---
uid: Guides.MessageComponents.GettingStarted
title: Getting Started with Components
---

# Message Components

Message components are a framework for adding interactive elements to a message your app or bot sends. They're accessible, customizable, and easy to use.

## What is a Component

Components are a new parameter you can use when sending messages with your bot. There are currently 2 different types of components you can use: Buttons and Select Menus.

## Creating components

Lets create a simple component that has a button. First thing we need is a way to trigger the message, this can be done via commands or simply a ready event. Lets make a command that triggers our button message.

```cs
[Command("spawner")]
public async Task Spawn()
{
// Reply with some components
}
```

We now have our command, but we need to actually send the buttons with the command. To do that, lets look at the `ComponentBuilder` class:

| Name | Description |
| ---------------- | --------------------------------------------------------------------------- |
| `FromMessage` | Creates a new builder from a message. |
| `FromComponents` | Creates a new builder from the provided list of components. |
| `WithSelectMenu` | Adds a `SelectMenuBuilder` to the `ComponentBuilder` at the specific row. |
| `WithButton` | Adds a `ButtonBuilder` to the `ComponentBuilder` at the specific row. |
| `Build` | Builds this builder into a `MessageComponent` used to send your components. |

We see that we can use the `WithButton` function so lets do that. looking at its parameters it takes:

- `label` - The display text of the button.
- `customId` - The custom id of the button, this is whats sent by discord when your button is clicked.
- `style` - The discord defined style of the button.
- `emote` - An emote to be displayed with the button.
- `url` - The url of the button if its a link button.
- `disabled` - Whether or not the button is disabled.
- `row` - The row the button will occupy.

Since were just making a busic button, we dont have to specify anything else besides the label and custom id.

```cs
var builder = new ComponentBuilder()
.WithButton("label", "custom-id");
```

Lets add this to our command:

```cs
[Command("spawner")]
public async Task Spawn()
{
var builder = new ComponentBuilder()
.WithButton("label", "custom-id");

await ReplyAsync("Here is a button!", components: builder.Build());
}
```

![](images\image1.png)

+ 37
- 0
docs/guides/interactions/message-components/02-responding-to-buttons.md View File

@@ -0,0 +1,37 @@
---
uid: Guides.MessageComponents.Responding
title: Responding to Components
---

# Responding to button clicks

Responding to buttons is pretty simple, there are a couple ways of doing it and we can cover both.

### Method 1: Hooking the InteractionCreated Event

We can hook the `ButtonExecuted` event for button type interactions:

```cs
client.ButtonExecuted += MyButtonHandler;
```

Now, lets write our handler.

```cs
public async Task MyButtonHandler(SocketMessageComponent component)
{
// We can now check for our custom id
switch(component.Data.CustomId)
{
// Since we set our buttons custom id as 'custom-id', we can check for it like this:
case "custom-id":
// Lets respond by sending a message saying they clicked the button
await component.RespondAsync($"{component.User.Mention} has clicked the button!");
break;
}
}
```

Running it and clicking the button:

![](Images/image2.png)

+ 45
- 0
docs/guides/interactions/message-components/03-buttons-in-depth.md View File

@@ -0,0 +1,45 @@
---
uid: Guides.MessageComponents.Buttons
title: Buttons in Depth
---

# Buttons in depth

There are many changes you can make to buttons, lets take a look at the parameters in the `WithButton` function"
| Name | Type | Description |
|----------|---------------|----------------------------------------------------------------|
| label | `string` | The label text for the button. |
| customId | `string` | The custom id of the button. |
| style | `ButtonStyle` | The style of the button. |
| emote | `IEmote` | A IEmote to be used with this button. |
| url | `string` | A URL to be used only if the `ButtonStyle` is a Link. |
| disabled | `bool` | Whether or not the button is disabled. |
| row | `int` | The row to place the button if it has enough room, otherwise 0 |

### Label

This is the front facing text that the user sees. The maximum length is 80 characters.

### CustomId

This is the property sent to you by discord when a button is clicked. It is not required for link buttons as they do not emit an event. The maximum length is 100 characters.

### Style

Styling your buttons are important for indicating different actions:

![](Images/image3.png)

You can do this by using the `ButtonStyle` which has all the styles defined.

### Emote

You can specify an `IEmote` when creating buttons to add them to your button. They have the same restrictions as putting guild based emotes in messages.

### Url

If you use the link style with your button you can specify a url. When this button is clicked the user is taken to that url.

### Disabled

You can specify if your button is disabled, meaning users won't be able to click on it.

+ 76
- 0
docs/guides/interactions/message-components/04-select-menus.md View File

@@ -0,0 +1,76 @@
---
uid: Guides.MessageComponents.SelectMenus
title: Select Menus
---

# Select menus

Select menus allow users to select from a range of options, this can be quite useful with configuration commands etc.

## Creating a select menu

We can use a `SelectMenuBuilder` to create our menu.

```cs
var menuBuilder = new SelectMenuBuilder()
.WithPlaceholder("Select an option")
.WithCustomId("menu-1")
.WithMinValues(1)
.WithMaxValues(1)
.AddOption("Option A", "opt-a", "Option B is lying!")
.AddOption("Option B", "opt-b", "Option A is telling the truth!");

var builder = new ComponentBuilder()
.WithSelectMenu(menuBuilder);
```

Lets add this to a command:

```cs
[Command("spawner")]
public async Task Spawn()
{
var menuBuilder = new SelectMenuBuilder()
.WithPlaceholder("Select an option")
.WithCustomId("menu-1")
.WithMinValues(1)
.WithMaxValues(1)
.AddOption("Option A", "opt-a", "Option B is lying!")
.AddOption("Option B", "opt-b", "Option A is telling the truth!");

var builder = new ComponentBuilder()
.WithSelectMenu(menuBuilder);

await ReplyAsync("Whos really lying?", components: builder.Build());
}
```

Running this produces this result:

![](Images/image4.png)

And opening the menu we see:

![](Images/image5.png)

Lets handle the selection of an option, We can hook the `SelectMenuExecuted` event to handle our select menu:

```cs
client.SelectMenuExecuted += MyMenuHandler;
```

The `SelectMenuExecuted` also supplies a `SocketMessageComponent` argument, we can confirm that its a select menu by checking the `ComponentType` inside of the data field if we need, but the library will do that for us and only execute our handler if its a select menu.

The values that the user has selected will be inside of the `Values` collection in the Data field. we can list all of them back to the user for this example.

```cs
public async Task MyMenuHandler(SocketMessageComponent arg)
{
var text = string.Join(", ", arg.Data.Values);
await arg.RespondAsync($"You have selected {text}");
}
```

Running this produces this result:

![](Images/image6.png)

+ 87
- 0
docs/guides/interactions/message-components/05-advanced.md View File

@@ -0,0 +1,87 @@
---
uid: Guides.MessageComponents.Advanced
title: Advanced Concepts
---

# Advanced

Lets say you have some components on an ephemeral slash command, and you want to modify the message that the button is on. The issue with this is that ephemeral messages are not stored and can not be get via rest or other means.

Luckily, Discord thought of this and introduced a way to modify them with interactions.

### Using the UpdateAsync method

Components come with an `UpdateAsync` method that can update the message that the component was on. You can use it like a `ModifyAsync` method.

Lets use it with a command, we first create our command, in this example im just going to use a message command:

```cs
var command = new MessageCommandBuilder()
.WithName("testing").Build();

await client.GetGuild(guildId).BulkOverwriteApplicationCommandAsync(new [] { command, buttonCommand });
```

Next, we listen for this command, and respond with some components when its used:

```cs
var menu = new SelectMenuBuilder()
{
CustomId = "select-1",
Placeholder = "Select Somthing!",
MaxValues = 1,
MinValues = 1,
};

menu.AddOption("Meh", "1", "Its not gaming.")
.AddOption("Ish", "2", "Some would say that this is gaming.")
.AddOption("Moderate", "3", "It could pass as gaming")
.AddOption("Confirmed", "4", "We are gaming")
.AddOption("Excellent", "5", "It is renowned as gaming nation wide", new Emoji("🔥"));

var components = new ComponentBuilder()
.WithSelectMenu(menu);


await arg.RespondAsync("On a scale of one to five, how gaming is this?", component: componBuild(), ephemeral: true);
break;
```

Now, let's listen to the select menu executed event and add a case for `select-1`

```cs
client.SelectMenuExecuted += SelectMenuHandler;

...

public async Task SelectMenuHandler(SocketMessageComponent arg)
{
switch (arg.Data.CustomId)
{
case "select-1":
var value = arg.Data.Values.First();
var menu = new SelectMenuBuilder()
{
CustomId = "select-1",
Placeholder = $"{(arg.Message.Components.First().Components.First() as SelectMenu).Options.FirstOrDefault(x => x.Value == value).Label}",
MaxValues = 1,
MinValues = 1,
Disabled = true
};
menu.AddOption("Meh", "1", "Its not gaming.")
.AddOption("Ish", "2", "Some would say that this is gaming.")
.AddOption("Moderate", "3", "It could pass as gaming")
.AddOption("Confirmed", "4", "We are gaming")
.AddOption("Excellent", "5", "It is renowned as gaming nation wide", new Emoji("🔥"));
// We use UpdateAsync to update the message and its original content and components.
await arg.UpdateAsync(x =>
{
x.Content = $"Thank you {arg.User.Mention} for rating us {value}/5 on the gaming scale";
x.Components = new ComponentBuilder().WithSelectMenu(menu).Build();
});
break;
}
}
```

BIN
docs/guides/interactions/message-components/images/image1.png View File

Before After
Width: 390  |  Height: 154  |  Size: 17 KiB

BIN
docs/guides/interactions/message-components/images/image2.png View File

Before After
Width: 413  |  Height: 100  |  Size: 19 KiB

BIN
docs/guides/interactions/message-components/images/image3.png View File

Before After
Width: 849  |  Height: 508  |  Size: 40 KiB

BIN
docs/guides/interactions/message-components/images/image4.png View File

Before After
Width: 584  |  Height: 161  |  Size: 20 KiB

BIN
docs/guides/interactions/message-components/images/image5.png View File

Before After
Width: 587  |  Height: 201  |  Size: 17 KiB

BIN
docs/guides/interactions/message-components/images/image6.png View File

Before After
Width: 620  |  Height: 288  |  Size: 38 KiB

+ 46
- 21
docs/guides/toc.yml View File

@@ -1,26 +1,15 @@
- name: Introduction
topicUid: Guides.Introduction
- name: Getting Started
- name: "Working with Guild Events"
items:
- name: Installation
topicUid: Guides.GettingStarted.Installation
items:
- name: Nightly Builds
topicUid: Guides.GettingStarted.Installation.Nightlies
- name: Your First Bot
topicUid: Guides.GettingStarted.FirstBot
- name: Terminology
topicUid: Guides.GettingStarted.Terminology
- name: Basic Concepts
items:
- name: Logging Data
topicUid: Guides.Concepts.Logging
- name: Working with Events
topicUid: Guides.Concepts.Events
- name: Managing Connections
topicUid: Guides.Concepts.ManageConnections
- name: Entities
topicUid: Guides.Concepts.Entities
- name: Introduction
topicUid: Guides.GuildEvents.Intro
- name: Creating Events
topicUid: Guides.GuildEvents.Creating
- name: Getting Event Users
topicUid: Guides.GuildEvents.GettingUsers
- name: Modifying Events
topicUid: Guides.GuildEvents.Modifying
- name: Working with Commands
items:
- name: Introduction
@@ -35,9 +24,45 @@
topicUid: Guides.Commands.DI
- name: Post-execution Handling
topicUid: Guides.Commands.PostExecution
- name: Working with Slash Commands
items:
- name: Introduction
topicUid: Guides.SlashCommands.Intro
- name: Creating slash commands
topicUid: Guides.SlashCommands.Creating
- name: Receiving and responding to slash commands
topicUid: Guides.SlashCommands.Receiving
- name: Slash command parameters
topicUid: Guides.SlashCommands.Parameters
- name: Ephemeral responses
topicUid: Guides.SlashCommands.Ephemeral
- name: Sub commands
topicUid: Guides.SlashCommands.SubCommand
- name: Slash command choices
topicUid: Guides.SlashCommands.Choices
- name: Slash commands Bulk Overwrites
topicUid: Guides.SlashCommands.BulkOverwrite
- name: Working with Context commands
items:
- name: Creating Context Commands
topicUid: Guides.ContextCommands.Creating
- name: Receiving Context Commands
topicUid: Guides.ContextCommands.Reveiving
- name: Working with Message Components
items:
- name: Getting started
topicUid: Guides.MessageComponents.GettingStarted
- name: Responding to Components
topicUid: Guides.MessageComponents.Responding
- name: Buttons in depth
topicUid: Guides.MessageComponents.Buttons
- name: Select menus
topicUid: Guides.MessageComponents.SelectMenus
- name: Advanced Concepts
topicUid: Guides.MessageComponents.Advanced
- name: Emoji
topicUid: Guides.Emoji
- name: Voice
topicUid: Guides.Voice.SendingVoice
- name: Deployment
topicUid: Guides.Deployment
topicUid: Guides.Deployment

+ 1
- 1
docs/guides/voice/sending-voice.md View File

@@ -18,7 +18,7 @@ when developing on .NET Core, this is where you execute `dotnet run`
from; typically the same directory as your csproj).

For Windows Users, precompiled binaries are available for your
convienence [here](https://discord.foxbot.me/binaries/).
convienence [here](https://github.com/discord-net/Discord.Net/tree/dev/voice-natives).

For Linux Users, you will need to compile [Sodium] and [Opus] from
source, or install them from your package manager.


+ 1
- 1
samples/02_commands_framework/02_commands_framework.csproj View File

@@ -6,7 +6,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="5.0.2" />
</ItemGroup>

<ItemGroup>


+ 1
- 1
samples/02_commands_framework/Modules/PublicModule.cs View File

@@ -31,7 +31,7 @@ namespace _02_commands_framework.Modules
[Command("userinfo")]
public async Task UserInfoAsync(IUser user = null)
{
user = user ?? Context.User;
user ??= Context.User;

await ReplyAsync(user.ToString());
}


+ 1
- 1
samples/02_commands_framework/Program.cs View File

@@ -39,7 +39,7 @@ namespace _02_commands_framework
services.GetRequiredService<CommandService>().Log += LogAsync;

// Tokens should be considered secret data and never hard-coded.
// We can read from the environment variable to avoid hardcoding.
// We can read from the environment variable to avoid hard coding.
await client.LoginAsync(TokenType.Bot, Environment.GetEnvironmentVariable("token"));
await client.StartAsync();



+ 1
- 1
samples/03_sharded_client/03_sharded_client.csproj View File

@@ -7,7 +7,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="5.0.2" />
</ItemGroup>

<ItemGroup>


+ 1
- 1
samples/03_sharded_client/Services/CommandHandlingService.cs View File

@@ -54,7 +54,7 @@ namespace _03_sharded_client.Services
if (!command.IsSpecified)
return;

// the command was succesful, we don't care about this result, unless we want to log that a command succeeded.
// the command was successful, we don't care about this result, unless we want to log that a command succeeded.
if (result.IsSuccess)
return;



+ 1
- 1
samples/idn/Inspector.cs View File

@@ -3,7 +3,7 @@ using System.Linq;
using System.Reflection;
using System.Text;

namespace idn
namespace Idn
{
public static class Inspector
{


+ 1
- 1
samples/idn/Program.cs View File

@@ -13,7 +13,7 @@ using System.Threading;
using System.Text;
using System.Diagnostics;

namespace idn
namespace Idn
{
public class Program
{


+ 1
- 1
samples/idn/idn.csproj View File

@@ -6,7 +6,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="3.5.0" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="3.11.0" />
</ItemGroup>

<ItemGroup>


+ 2
- 2
src/Discord.Net.Analyzers/Discord.Net.Analyzers.csproj View File

@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="../../Discord.Net.targets" />
<PropertyGroup>
<AssemblyName>Discord.Net.Analyzers</AssemblyName>
@@ -7,7 +7,7 @@
<TargetFrameworks>netstandard2.0;netstandard2.1</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis" Version="3.3.1" />
<PackageReference Include="Microsoft.CodeAnalysis" Version="3.11.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Discord.Net.Commands\Discord.Net.Commands.csproj" />


+ 1
- 1
src/Discord.Net.Commands/Attributes/AliasAttribute.cs View File

@@ -16,7 +16,7 @@ namespace Discord.Commands
/// <code language="cs">
/// [Command("stats")]
/// [Alias("stat", "info")]
/// public async Task GetStatsAsync(IUser user)
/// public <see langword="async"/> Task GetStatsAsync(IUser user)
/// {
/// // ...pull stats
/// }


+ 8
- 3
src/Discord.Net.Commands/Builders/CommandBuilder.cs View File

@@ -7,6 +7,7 @@ namespace Discord.Commands.Builders
{
public class CommandBuilder
{
#region CommandBuilder
private readonly List<PreconditionAttribute> _preconditions;
private readonly List<ParameterBuilder> _parameters;
private readonly List<Attribute> _attributes;
@@ -27,8 +28,9 @@ namespace Discord.Commands.Builders
public IReadOnlyList<ParameterBuilder> Parameters => _parameters;
public IReadOnlyList<Attribute> Attributes => _attributes;
public IReadOnlyList<string> Aliases => _aliases;
#endregion

//Automatic
#region Automatic
internal CommandBuilder(ModuleBuilder module)
{
Module = module;
@@ -38,7 +40,9 @@ namespace Discord.Commands.Builders
_attributes = new List<Attribute>();
_aliases = new List<string>();
}
//User-defined
#endregion

#region User-defined
internal CommandBuilder(ModuleBuilder module, string primaryAlias, Func<ICommandContext, object[], IServiceProvider, CommandInfo, Task> callback)
: this(module)
{
@@ -132,7 +136,7 @@ namespace Discord.Commands.Builders
var firstMultipleParam = _parameters.FirstOrDefault(x => x.IsMultiple);
if ((firstMultipleParam != null) && (firstMultipleParam != lastParam))
throw new InvalidOperationException($"Only the last parameter in a command may have the Multiple flag. Parameter: {firstMultipleParam.Name} in {PrimaryAlias}");
var firstRemainderParam = _parameters.FirstOrDefault(x => x.IsRemainder);
if ((firstRemainderParam != null) && (firstRemainderParam != lastParam))
throw new InvalidOperationException($"Only the last parameter in a command may have the Remainder flag. Parameter: {firstRemainderParam.Name} in {PrimaryAlias}");
@@ -140,5 +144,6 @@ namespace Discord.Commands.Builders

return new CommandInfo(this, info, service);
}
#endregion
}
}

+ 7
- 2
src/Discord.Net.Commands/Builders/ModuleBuilder.cs View File

@@ -7,6 +7,7 @@ namespace Discord.Commands.Builders
{
public class ModuleBuilder
{
#region ModuleBuilder
private readonly List<CommandBuilder> _commands;
private readonly List<ModuleBuilder> _submodules;
private readonly List<PreconditionAttribute> _preconditions;
@@ -27,8 +28,9 @@ namespace Discord.Commands.Builders
public IReadOnlyList<string> Aliases => _aliases;

internal TypeInfo TypeInfo { get; set; }
#endregion

//Automatic
#region Automatic
internal ModuleBuilder(CommandService service, ModuleBuilder parent)
{
Service = service;
@@ -40,7 +42,9 @@ namespace Discord.Commands.Builders
_attributes = new List<Attribute>();
_aliases = new List<string>();
}
//User-defined
#endregion

#region User-defined
internal ModuleBuilder(CommandService service, ModuleBuilder parent, string primaryAlias)
: this(service, parent)
{
@@ -132,5 +136,6 @@ namespace Discord.Commands.Builders
public ModuleInfo Build(CommandService service, IServiceProvider services) => BuildImpl(service, services);

internal ModuleInfo Build(CommandService service, IServiceProvider services, ModuleInfo parent) => BuildImpl(service, services, parent);
#endregion
}
}

+ 3
- 3
src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs View File

@@ -116,7 +116,7 @@ namespace Discord.Commands
builder.AddAliases(alias.Aliases);
break;
case GroupAttribute group:
builder.Name = builder.Name ?? group.Prefix;
builder.Name ??= group.Prefix;
builder.Group = group.Prefix;
builder.AddAliases(group.Prefix);
break;
@@ -158,7 +158,7 @@ namespace Discord.Commands
case CommandAttribute command:
builder.AddAliases(command.Text);
builder.RunMode = command.RunMode;
builder.Name = builder.Name ?? command.Text;
builder.Name ??= command.Text;
builder.IgnoreExtraArgs = command.IgnoreExtraArgs ?? service._ignoreExtraArgs;
break;
case NameAttribute name:
@@ -291,7 +291,7 @@ namespace Discord.Commands
return reader;
}

//We dont have a cached type reader, create one
//We don't have a cached type reader, create one
reader = ReflectionUtils.CreateObject<TypeReader>(typeReaderType.GetTypeInfo(), service, services);
service.AddTypeReader(paramType, reader, false);



+ 9
- 4
src/Discord.Net.Commands/Builders/ParameterBuilder.cs View File

@@ -8,6 +8,7 @@ namespace Discord.Commands.Builders
{
public class ParameterBuilder
{
#region ParameterBuilder
private readonly List<ParameterPreconditionAttribute> _preconditions;
private readonly List<Attribute> _attributes;

@@ -24,8 +25,9 @@ namespace Discord.Commands.Builders

public IReadOnlyList<ParameterPreconditionAttribute> Preconditions => _preconditions;
public IReadOnlyList<Attribute> Attributes => _attributes;
#endregion

//Automatic
#region Automatic
internal ParameterBuilder(CommandBuilder command)
{
_preconditions = new List<ParameterPreconditionAttribute>();
@@ -33,7 +35,9 @@ namespace Discord.Commands.Builders

Command = command;
}
//User-defined
#endregion

#region User-defined
internal ParameterBuilder(CommandBuilder command, string name, Type type)
: this(command)
{
@@ -50,7 +54,7 @@ namespace Discord.Commands.Builders
if (type.GetTypeInfo().IsValueType)
DefaultValue = Activator.CreateInstance(type);
else if (type.IsArray)
type = ParameterType.GetElementType();
DefaultValue = Array.CreateInstance(type.GetElementType(), 0);
ParameterType = type;
}

@@ -127,10 +131,11 @@ namespace Discord.Commands.Builders

internal ParameterInfo Build(CommandInfo info)
{
if ((TypeReader ?? (TypeReader = GetReader(ParameterType))) == null)
if ((TypeReader ??= GetReader(ParameterType)) == null)
throw new InvalidOperationException($"No type reader found for type {ParameterType.Name}, one must be specified");

return new ParameterInfo(this, info, Command.Module.Service);
}
#endregion
}
}

+ 103
- 52
src/Discord.Net.Commands/CommandService.cs View File

@@ -29,6 +29,7 @@ namespace Discord.Commands
/// </remarks>
public class CommandService : IDisposable
{
#region CommandService
/// <summary>
/// Occurs when a command-related information is received.
/// </summary>
@@ -131,8 +132,9 @@ namespace Discord.Commands
entityTypeReaders.Add((typeof(IUser), typeof(UserTypeReader<>)));
_entityTypeReaders = entityTypeReaders.ToImmutable();
}
#endregion

//Modules
#region Modules
public async Task<ModuleInfo> CreateModuleAsync(string primaryAlias, Action<ModuleBuilder> buildFunc)
{
await _moduleLock.WaitAsync().ConfigureAwait(false);
@@ -187,7 +189,7 @@ namespace Discord.Commands
/// </returns>
public async Task<ModuleInfo> AddModuleAsync(Type type, IServiceProvider services)
{
services = services ?? EmptyServiceProvider.Instance;
services ??= EmptyServiceProvider.Instance;

await _moduleLock.WaitAsync().ConfigureAwait(false);
try
@@ -222,7 +224,7 @@ namespace Discord.Commands
/// </returns>
public async Task<IEnumerable<ModuleInfo>> AddModulesAsync(Assembly assembly, IServiceProvider services)
{
services = services ?? EmptyServiceProvider.Instance;
services ??= EmptyServiceProvider.Instance;

await _moduleLock.WaitAsync().ConfigureAwait(false);
try
@@ -322,8 +324,9 @@ namespace Discord.Commands

return true;
}
#endregion

//Type Readers
#region Type Readers
/// <summary>
/// Adds a custom <see cref="TypeReader" /> to this <see cref="CommandService" /> for the supplied object
/// type.
@@ -435,6 +438,13 @@ namespace Discord.Commands
_defaultTypeReaders[type] = reader;
return reader;
}
var underlyingType = Nullable.GetUnderlyingType(type);
if (underlyingType != null && underlyingType.IsEnum)
{
reader = NullableTypeReader.Create(underlyingType, EnumTypeReader.GetReader(underlyingType));
_defaultTypeReaders[type] = reader;
return reader;
}

//Is this an entity?
for (int i = 0; i < _entityTypeReaders.Count; i++)
@@ -448,8 +458,9 @@ namespace Discord.Commands
}
return null;
}
#endregion

//Execution
#region Execution
/// <summary>
/// Searches for the command.
/// </summary>
@@ -503,22 +514,86 @@ namespace Discord.Commands
/// </returns>
public async Task<IResult> ExecuteAsync(ICommandContext context, string input, IServiceProvider services, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception)
{
services = services ?? EmptyServiceProvider.Instance;
services ??= EmptyServiceProvider.Instance;

var searchResult = Search(input);
if (!searchResult.IsSuccess)

var validationResult = await ValidateAndGetBestMatch(searchResult, context, services, multiMatchHandling);

if (validationResult is SearchResult result)
{
await _commandExecutedEvent.InvokeAsync(Optional.Create<CommandInfo>(), context, result).ConfigureAwait(false);
return result;
}

if (validationResult is MatchResult matchResult)
{
return await HandleCommandPipeline(matchResult, context, services);
}

return validationResult;
}

private async Task<IResult> HandleCommandPipeline(MatchResult matchResult, ICommandContext context, IServiceProvider services)
{
if (!matchResult.IsSuccess)
return matchResult;

if (matchResult.Pipeline is ParseResult parseResult)
{
var executeResult = await matchResult.Match.Value.ExecuteAsync(context, parseResult, services);

if (!executeResult.IsSuccess && !(executeResult is RuntimeResult || executeResult is ExecuteResult)) // succesful results raise the event in CommandInfo#ExecuteInternalAsync (have to raise it there b/c deffered execution)
await _commandExecutedEvent.InvokeAsync(matchResult.Match.Value.Command, context, executeResult);
return executeResult;
}

if (matchResult.Pipeline is PreconditionResult preconditionResult)
{
await _commandExecutedEvent.InvokeAsync(matchResult.Match.Value.Command, context, preconditionResult).ConfigureAwait(false);
}

return matchResult;
}

// Calculates the 'score' of a command given a parse result
float CalculateScore(CommandMatch match, ParseResult parseResult)
{
float argValuesScore = 0, paramValuesScore = 0;

if (match.Command.Parameters.Count > 0)
{
await _commandExecutedEvent.InvokeAsync(Optional.Create<CommandInfo>(), context, searchResult).ConfigureAwait(false);
return searchResult;
var argValuesSum = parseResult.ArgValues?.Sum(x => x.Values.OrderByDescending(y => y.Score).FirstOrDefault().Score) ?? 0;
var paramValuesSum = parseResult.ParamValues?.Sum(x => x.Values.OrderByDescending(y => y.Score).FirstOrDefault().Score) ?? 0;

argValuesScore = argValuesSum / match.Command.Parameters.Count;
paramValuesScore = paramValuesSum / match.Command.Parameters.Count;
}

var totalArgsScore = (argValuesScore + paramValuesScore) / 2;
return match.Command.Priority + totalArgsScore * 0.99f;
}

/// <summary>
/// Validates and gets the best <see cref="CommandMatch"/> from a specified <see cref="SearchResult"/>
/// </summary>
/// <param name="matches">The SearchResult.</param>
/// <param name="context">The context of the command.</param>
/// <param name="provider">The service provider to be used on the command's dependency injection.</param>
/// <param name="multiMatchHandling">The handling mode when multiple command matches are found.</param>
/// <returns>A task that represents the asynchronous validation operation. The task result contains the result of the
/// command validation as a <see cref="MatchResult"/> or a <see cref="SearchResult"/> if no matches were found.</returns>
public async Task<IResult> ValidateAndGetBestMatch(SearchResult matches, ICommandContext context, IServiceProvider provider, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception)
{
if (!matches.IsSuccess)
return matches;

var commands = searchResult.Commands;
var commands = matches.Commands;
var preconditionResults = new Dictionary<CommandMatch, PreconditionResult>();

foreach (var match in commands)
foreach (var command in commands)
{
preconditionResults[match] = await match.Command.CheckPreconditionsAsync(context, services).ConfigureAwait(false);
preconditionResults[command] = await command.CheckPreconditionsAsync(context, provider);
}

var successfulPreconditions = preconditionResults
@@ -529,19 +604,16 @@ namespace Discord.Commands
{
//All preconditions failed, return the one from the highest priority command
var bestCandidate = preconditionResults
.OrderByDescending(x => x.Key.Command.Priority)
.FirstOrDefault(x => !x.Value.IsSuccess);

await _commandExecutedEvent.InvokeAsync(bestCandidate.Key.Command, context, bestCandidate.Value).ConfigureAwait(false);
return bestCandidate.Value;
.OrderByDescending(x => x.Key.Command.Priority)
.FirstOrDefault(x => !x.Value.IsSuccess);
return MatchResult.FromSuccess(bestCandidate.Key,bestCandidate.Value);
}

//If we get this far, at least one precondition was successful.
var parseResults = new Dictionary<CommandMatch, ParseResult>();

var parseResultsDict = new Dictionary<CommandMatch, ParseResult>();
foreach (var pair in successfulPreconditions)
{
var parseResult = await pair.Key.ParseAsync(context, searchResult, pair.Value, services).ConfigureAwait(false);
var parseResult = await pair.Key.ParseAsync(context, matches, pair.Value, provider).ConfigureAwait(false);

if (parseResult.Error == CommandError.MultipleMatches)
{
@@ -556,53 +628,31 @@ namespace Discord.Commands
}
}

parseResultsDict[pair.Key] = parseResult;
parseResults[pair.Key] = parseResult;
}

// Calculates the 'score' of a command given a parse result
float CalculateScore(CommandMatch match, ParseResult parseResult)
{
float argValuesScore = 0, paramValuesScore = 0;

if (match.Command.Parameters.Count > 0)
{
var argValuesSum = parseResult.ArgValues?.Sum(x => x.Values.OrderByDescending(y => y.Score).FirstOrDefault().Score) ?? 0;
var paramValuesSum = parseResult.ParamValues?.Sum(x => x.Values.OrderByDescending(y => y.Score).FirstOrDefault().Score) ?? 0;
var weightedParseResults = parseResults
.OrderByDescending(x => CalculateScore(x.Key, x.Value));

argValuesScore = argValuesSum / match.Command.Parameters.Count;
paramValuesScore = paramValuesSum / match.Command.Parameters.Count;
}

var totalArgsScore = (argValuesScore + paramValuesScore) / 2;
return match.Command.Priority + totalArgsScore * 0.99f;
}

//Order the parse results by their score so that we choose the most likely result to execute
var parseResults = parseResultsDict
.OrderByDescending(x => CalculateScore(x.Key, x.Value));

var successfulParses = parseResults
var successfulParses = weightedParseResults
.Where(x => x.Value.IsSuccess)
.ToArray();

if (successfulParses.Length == 0)
if(successfulParses.Length == 0)
{
//All parses failed, return the one from the highest priority command, using score as a tie breaker
var bestMatch = parseResults
.FirstOrDefault(x => !x.Value.IsSuccess);

await _commandExecutedEvent.InvokeAsync(bestMatch.Key.Command, context, bestMatch.Value).ConfigureAwait(false);
return bestMatch.Value;
return MatchResult.FromSuccess(bestMatch.Key,bestMatch.Value);
}

//If we get this far, at least one parse was successful. Execute the most likely overload.
var chosenOverload = successfulParses[0];
var result = await chosenOverload.Key.ExecuteAsync(context, chosenOverload.Value, services).ConfigureAwait(false);
if (!result.IsSuccess && !(result is RuntimeResult || result is ExecuteResult)) // succesful results raise the event in CommandInfo#ExecuteInternalAsync (have to raise it there b/c deffered execution)
await _commandExecutedEvent.InvokeAsync(chosenOverload.Key.Command, context, result);
return result;

return MatchResult.FromSuccess(chosenOverload.Key,chosenOverload.Value);
}
#endregion

#region Dispose
protected virtual void Dispose(bool disposing)
{
if (!_isDisposed)
@@ -620,5 +670,6 @@ namespace Discord.Commands
{
Dispose(true);
}
#endregion
}
}

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

@@ -12,4 +12,4 @@
<ProjectReference Include="..\Discord.Net.Core\Discord.Net.Core.csproj" />
</ItemGroup>

</Project>
</Project>

+ 1
- 2
src/Discord.Net.Commands/Extensions/MessageExtensions.cs View File

@@ -51,8 +51,7 @@ namespace Discord.Commands
if (endPos == -1) return false;
if (text.Length < endPos + 2 || text[endPos + 1] != ' ') return false; //Must end in "> "

ulong userId;
if (!MentionUtils.TryParseUser(text.Substring(0, endPos + 1), out userId)) return false;
if (!MentionUtils.TryParseUser(text.Substring(0, endPos + 1), out ulong userId)) return false;
if (userId == user.Id)
{
argPos = endPos + 2;


+ 3
- 3
src/Discord.Net.Commands/Info/CommandInfo.cs View File

@@ -123,7 +123,7 @@ namespace Discord.Commands

public async Task<PreconditionResult> CheckPreconditionsAsync(ICommandContext context, IServiceProvider services = null)
{
services = services ?? EmptyServiceProvider.Instance;
services ??= EmptyServiceProvider.Instance;

async Task<PreconditionResult> CheckGroups(IEnumerable<PreconditionAttribute> preconditions, string type)
{
@@ -164,7 +164,7 @@ namespace Discord.Commands

public async Task<ParseResult> ParseAsync(ICommandContext context, int startIndex, SearchResult searchResult, PreconditionResult preconditionResult = null, IServiceProvider services = null)
{
services = services ?? EmptyServiceProvider.Instance;
services ??= EmptyServiceProvider.Instance;

if (!searchResult.IsSuccess)
return ParseResult.FromError(searchResult);
@@ -201,7 +201,7 @@ namespace Discord.Commands
}
public async Task<IResult> ExecuteAsync(ICommandContext context, IEnumerable<object> argList, IEnumerable<object> paramList, IServiceProvider services)
{
services = services ?? EmptyServiceProvider.Instance;
services ??= EmptyServiceProvider.Instance;

try
{


+ 2
- 2
src/Discord.Net.Commands/Info/ParameterInfo.cs View File

@@ -75,7 +75,7 @@ namespace Discord.Commands

public async Task<PreconditionResult> CheckPreconditionsAsync(ICommandContext context, object arg, IServiceProvider services = null)
{
services = services ?? EmptyServiceProvider.Instance;
services ??= EmptyServiceProvider.Instance;

foreach (var precondition in Preconditions)
{
@@ -89,7 +89,7 @@ namespace Discord.Commands

public async Task<TypeReaderResult> ParseAsync(ICommandContext context, string input, IServiceProvider services = null)
{
services = services ?? EmptyServiceProvider.Instance;
services ??= EmptyServiceProvider.Instance;
return await _reader.ReadAsync(context, input, services).ConfigureAwait(false);
}



+ 10
- 3
src/Discord.Net.Commands/ModuleBase.cs View File

@@ -16,6 +16,7 @@ namespace Discord.Commands
public abstract class ModuleBase<T> : IModuleBase
where T : class, ICommandContext
{
#region ModuleBase
/// <summary>
/// The underlying context of the command.
/// </summary>
@@ -35,10 +36,14 @@ namespace Discord.Commands
/// Specifies if notifications are sent for mentioned users and roles in the <paramref name="message"/>.
/// If <c>null</c>, all mentioned roles and users will be notified.
/// </param>
/// <param name="options">The request options for this <see langword="async"/> request.</param>
/// <param name="messageReference">The message references to be included. Used to reply to specific messages.</param>
protected virtual async Task<IUserMessage> ReplyAsync(string message = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null)
/// <param name="component">The message components to be included with this message. Used for interactions.</param>
/// <param name="stickers">A collection of stickers to send with the file.</param>
/// <param name="embeds">A array of <see cref="Embed"/>s to send with this response. Max 10.</param>
protected virtual async Task<IUserMessage> ReplyAsync(string message = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null, ISticker[] stickers = null, Embed[] embeds = null)
{
return await Context.Channel.SendMessageAsync(message, isTTS, embed, options, allowedMentions, messageReference).ConfigureAwait(false);
return await Context.Channel.SendMessageAsync(message, isTTS, embed, options, allowedMentions, messageReference, component, stickers, embeds).ConfigureAwait(false);
}
/// <summary>
/// The method to execute before executing the command.
@@ -63,8 +68,9 @@ namespace Discord.Commands
protected virtual void OnModuleBuilding(CommandService commandService, ModuleBuilder builder)
{
}
#endregion

//IModuleBase
#region IModuleBase
void IModuleBase.SetContext(ICommandContext context)
{
var newValue = context as T;
@@ -73,5 +79,6 @@ namespace Discord.Commands
void IModuleBase.BeforeExecute(CommandInfo command) => BeforeExecute(command);
void IModuleBase.AfterExecute(CommandInfo command) => AfterExecute(command);
void IModuleBase.OnModuleBuilding(CommandService commandService, ModuleBuilder builder) => OnModuleBuilding(commandService, builder);
#endregion
}
}

+ 39
- 19
src/Discord.Net.Commands/Readers/TimeSpanTypeReader.cs View File

@@ -6,30 +6,50 @@ namespace Discord.Commands
{
internal class TimeSpanTypeReader : TypeReader
{
private static readonly string[] Formats = {
"%d'd'%h'h'%m'm'%s's'", //4d3h2m1s
"%d'd'%h'h'%m'm'", //4d3h2m
"%d'd'%h'h'%s's'", //4d3h 1s
"%d'd'%h'h'", //4d3h
"%d'd'%m'm'%s's'", //4d 2m1s
"%d'd'%m'm'", //4d 2m
"%d'd'%s's'", //4d 1s
"%d'd'", //4d
"%h'h'%m'm'%s's'", // 3h2m1s
"%h'h'%m'm'", // 3h2m
"%h'h'%s's'", // 3h 1s
"%h'h'", // 3h
"%m'm'%s's'", // 2m1s
"%m'm'", // 2m
"%s's'", // 1s
/// <summary>
/// TimeSpan try parse formats.
/// </summary>
private static readonly string[] Formats =
{
"%d'd'%h'h'%m'm'%s's'", // 4d3h2m1s
"%d'd'%h'h'%m'm'", // 4d3h2m
"%d'd'%h'h'%s's'", // 4d3h 1s
"%d'd'%h'h'", // 4d3h
"%d'd'%m'm'%s's'", // 4d 2m1s
"%d'd'%m'm'", // 4d 2m
"%d'd'%s's'", // 4d 1s
"%d'd'", // 4d
"%h'h'%m'm'%s's'", // 3h2m1s
"%h'h'%m'm'", // 3h2m
"%h'h'%s's'", // 3h 1s
"%h'h'", // 3h
"%m'm'%s's'", // 2m1s
"%m'm'", // 2m
"%s's'", // 1s
};

/// <inheritdoc />
public override Task<TypeReaderResult> ReadAsync(ICommandContext context, string input, IServiceProvider services)
{
return (TimeSpan.TryParseExact(input.ToLowerInvariant(), Formats, CultureInfo.InvariantCulture, out var timeSpan))
? Task.FromResult(TypeReaderResult.FromSuccess(timeSpan))
: Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "Failed to parse TimeSpan"));
if (string.IsNullOrEmpty(input))
throw new ArgumentException(message: $"{nameof(input)} must not be null or empty.", paramName: nameof(input));

var isNegative = input[0] == '-'; // Char for CultureInfo.InvariantCulture.NumberFormat.NegativeSign
if (isNegative)
{
input = input.Substring(1);
}

if (TimeSpan.TryParseExact(input.ToLowerInvariant(), Formats, CultureInfo.InvariantCulture, out var timeSpan))
{
return isNegative
? Task.FromResult(TypeReaderResult.FromSuccess(-timeSpan))
: Task.FromResult(TypeReaderResult.FromSuccess(timeSpan));
}
else
{
return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "Failed to parse TimeSpan"));
}
}
}
}

+ 47
- 0
src/Discord.Net.Commands/Results/MatchResult.cs View File

@@ -0,0 +1,47 @@
using System;

namespace Discord.Commands
{
public class MatchResult : IResult
{
/// <summary>
/// Gets the command that may have matched during the command execution.
/// </summary>
public CommandMatch? Match { get; }

/// <summary>
/// Gets on which pipeline stage the command may have matched or failed.
/// </summary>
public IResult? Pipeline { get; }

/// <inheritdoc />
public CommandError? Error { get; }
/// <inheritdoc />
public string ErrorReason { get; }
/// <inheritdoc />
public bool IsSuccess => !Error.HasValue;

private MatchResult(CommandMatch? match, IResult? pipeline, CommandError? error, string errorReason)
{
Match = match;
Error = error;
Pipeline = pipeline;
ErrorReason = errorReason;
}

public static MatchResult FromSuccess(CommandMatch match, IResult pipeline)
=> new MatchResult(match,pipeline,null, null);
public static MatchResult FromError(CommandError error, string reason)
=> new MatchResult(null,null,error, reason);
public static MatchResult FromError(Exception ex)
=> FromError(CommandError.Exception, ex.Message);
public static MatchResult FromError(IResult result)
=> new MatchResult(null, null,result.Error, result.ErrorReason);
public static MatchResult FromError(IResult pipeline, CommandError error, string reason)
=> new MatchResult(null, pipeline, error, reason);

public override string ToString() => IsSuccess ? "Success" : $"{Error}: {ErrorReason}";
private string DebuggerDisplay => IsSuccess ? "Success" : $"{Error}: {ErrorReason}";

}
}

+ 1
- 1
src/Discord.Net.Commands/RunMode.cs View File

@@ -8,7 +8,7 @@ namespace Discord.Commands
public enum RunMode
{
/// <summary>
/// The default behaviour set in <see cref="CommandServiceConfig"/>.
/// The default behavior set in <see cref="CommandServiceConfig"/>.
/// </summary>
Default,
/// <summary>


+ 70
- 16
src/Discord.Net.Core/CDN.cs View File

@@ -46,6 +46,32 @@ namespace Discord
string extension = FormatToExtension(format, avatarId);
return $"{DiscordConfig.CDNUrl}avatars/{userId}/{avatarId}.{extension}?size={size}";
}

public static string GetGuildUserAvatarUrl(ulong userId, ulong guildId, string avatarId, ushort size, ImageFormat format)
{
if (avatarId == null)
return null;
string extension = FormatToExtension(format, avatarId);
return $"{DiscordConfig.CDNUrl}guilds/{guildId}/users/{userId}/avatars/{avatarId}.{extension}?size={size}";
}

/// <summary>
/// Returns a user banner URL.
/// </summary>
/// <param name="userId">The user snowflake identifier.</param>
/// <param name="bannerId">The banner identifier.</param>
/// <param name="size">The size of the image to return in horizontal pixels. This can be any power of two between 16 and 2048.</param>
/// <param name="format">The format to return.</param>
/// <returns>
/// A URL pointing to the user's banner in the specified size.
/// </returns>
public static string GetUserBannerUrl(ulong userId, string bannerId, ushort size, ImageFormat format)
{
if (bannerId == null)
return null;
string extension = FormatToExtension(format, bannerId);
return $"{DiscordConfig.CDNUrl}banners/{userId}/{bannerId}.{extension}?size={size}";
}
/// <summary>
/// Returns the default user avatar URL.
/// </summary>
@@ -68,6 +94,16 @@ namespace Discord
public static string GetGuildIconUrl(ulong guildId, string iconId)
=> iconId != null ? $"{DiscordConfig.CDNUrl}icons/{guildId}/{iconId}.jpg" : null;
/// <summary>
/// Returns a guild role's icon URL.
/// </summary>
/// <param name="roleId">The role identifier.</param>
/// <param name="roleHash">The icon hash.</param>
/// <returns>
/// A URL pointing to the guild role's icon.
/// </returns>
public static string GetGuildRoleIconUrl(ulong roleId, string roleHash)
=> roleHash != null ? $"{DiscordConfig.CDNUrl}role-icons/{roleId}/{roleHash}.png" : null;
/// <summary>
/// Returns a guild splash URL.
/// </summary>
/// <param name="guildId">The guild snowflake identifier.</param>
@@ -103,15 +139,17 @@ namespace Discord
/// </summary>
/// <param name="guildId">The guild snowflake identifier.</param>
/// <param name="bannerId">The banner image identifier.</param>
/// <param name="format">The format to return.</param>
/// <param name="size">The size of the image to return in horizontal pixels. This can be any power of two between 16 and 2048 inclusive.</param>
/// <returns>
/// A URL pointing to the guild's banner image.
/// </returns>
public static string GetGuildBannerUrl(ulong guildId, string bannerId, ushort? size = null)
public static string GetGuildBannerUrl(ulong guildId, string bannerId, ImageFormat format, ushort? size = null)
{
if (!string.IsNullOrEmpty(bannerId))
return $"{DiscordConfig.CDNUrl}banners/{guildId}/{bannerId}.jpg" + (size.HasValue ? $"?size={size}" : string.Empty);
return null;
if (string.IsNullOrEmpty(bannerId))
return null;
string extension = FormatToExtension(format, bannerId);
return $"{DiscordConfig.CDNUrl}banners/{guildId}/{bannerId}.{extension}" + (size.HasValue ? $"?size={size}" : string.Empty);
}
/// <summary>
/// Returns an emoji URL.
@@ -159,23 +197,39 @@ namespace Discord
public static string GetSpotifyDirectUrl(string trackId)
=> $"https://open.spotify.com/track/{trackId}";

/// <summary>
/// Gets a stickers url based off the id and format.
/// </summary>
/// <param name="stickerId">The id of the sticker.</param>
/// <param name="format">The format of the sticker.</param>
/// <returns>
/// A URL to the sticker.
/// </returns>
public static string GetStickerUrl(ulong stickerId, StickerFormatType format = StickerFormatType.Png)
=> $"{DiscordConfig.CDNUrl}stickers/{stickerId}.{FormatToExtension(format)}";

private static string FormatToExtension(StickerFormatType format)
{
return format switch
{
StickerFormatType.None or StickerFormatType.Png or StickerFormatType.Apng => "png", // In the case of the Sticker endpoint, the sticker will be available as PNG if its format_type is PNG or APNG, and as Lottie if its format_type is LOTTIE.
StickerFormatType.Lottie => "lottie",
_ => throw new ArgumentException(nameof(format)),
};
}

private static string FormatToExtension(ImageFormat format, string imageId)
{
if (format == ImageFormat.Auto)
format = imageId.StartsWith("a_") ? ImageFormat.Gif : ImageFormat.Png;
switch (format)
return format switch
{
case ImageFormat.Gif:
return "gif";
case ImageFormat.Jpeg:
return "jpeg";
case ImageFormat.Png:
return "png";
case ImageFormat.WebP:
return "webp";
default:
throw new ArgumentException(nameof(format));
}
ImageFormat.Gif => "gif",
ImageFormat.Jpeg => "jpeg",
ImageFormat.Png => "png",
ImageFormat.WebP => "webp",
_ => throw new ArgumentException(nameof(format)),
};
}
}
}

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

@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="../../Discord.Net.targets" />
<Import Project="../../StyleAnalyzer.targets"/>
<Import Project="../../StyleAnalyzer.targets" />
<PropertyGroup>
<AssemblyName>Discord.Net.Core</AssemblyName>
<RootNamespace>Discord</RootNamespace>
@@ -16,4 +16,4 @@
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
</Project>
</Project>

+ 19
- 0
src/Discord.Net.Core/DiscordConfig.cs View File

@@ -94,6 +94,13 @@ namespace Discord
/// The maximum number of users that can be gotten per-batch.
/// </returns>
public const int MaxUsersPerBatch = 1000;
/// <summary>
/// Returns the max users allowed to be in a request for guild event users.
/// </summary>
/// <returns>
/// The maximum number of users that can be gotten per-batch.
/// </returns>
public const int MaxGuildEventUsersPerBatch = 100;
/// <summary>
/// Returns the max guilds allowed to be in a request.
/// </summary>
@@ -158,5 +165,17 @@ namespace Discord
/// clock. Your system will still need a stable clock.
/// </remarks>
public bool UseSystemClock { get; set; } = true;

/// <summary>
/// Gets or sets whether or not the internal experation check uses the system date
/// + snowflake date to check if an interaction can be responded to.
/// </summary>
/// <remarks>
/// If set to <see langword="false"/> then the CreatedAt property in an interaction
/// will be set to when it was received instead of the snowflakes date.
/// <br/>
/// <b>This will still require a stable clock on your system.</b>
/// </remarks>
public bool UseInteractionSnowflakeDate { get; set; } = true;
}
}

+ 197
- 0
src/Discord.Net.Core/DiscordErrorCode.cs View File

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

namespace Discord
{
/// <summary>
/// Represents a set of json error codes received by discord.
/// </summary>
public enum DiscordErrorCode
{
GeneralError = 0,

#region UnknownXYZ (10XXX)
UnknownAccount = 10001,
UnknownApplication = 10002,
UnknownChannel = 10003,
UnknownGuild = 10004,
UnknownIntegration = 10005,
UnknownInvite = 10006,
UnknownMember = 10007,
UnknownMessage = 10008,
UnknownPermissionOverwrite = 10009,
UnknownProvider = 10010,
UnknownRole = 10011,
UnknownToken = 10012,
UnknownUser = 10013,
UnknownEmoji = 10014,
UnknownWebhook = 10015,
UnknownWebhookService = 10016,
UnknownSession = 10020,
UnknownBan = 10026,
UnknownSKU = 10027,
UnknownStoreListing = 10028,
UnknownEntitlement = 10029,
UnknownBuild = 10030,
UnknownLobby = 10031,
UnknownBranch = 10032,
UnknownStoreDirectoryLayout = 10033,
UnknownRedistributable = 10036,
UnknownGiftCode = 10038,
UnknownStream = 10049,
UnknownPremiumServerSubscribeCooldown = 10050,
UnknownGuildTemplate = 10057,
UnknownDiscoverableServerCategory = 10059,
UnknownSticker = 10060,
UnknownInteraction = 10062,
UnknownApplicationCommand = 10063,
UnknownApplicationCommandPermissions = 10066,
UnknownStageInstance = 10067,
UnknownGuildMemberVerificationForm = 10068,
UnknownGuildWelcomeScreen = 10069,
UnknownGuildScheduledEvent = 10070,
UnknownGuildScheduledEventUser = 10071,
#endregion

#region General Actions (20XXX)
BotsCannotUse = 20001,
OnlyBotsCanUse = 20002,
CannotSendExplicitContent = 20009,
ApplicationActionUnauthorized = 20012,
ActionSlowmode = 20016,
OnlyOwnerAction = 20018,
AnnouncementEditRatelimit = 20022,
ChannelWriteRatelimit = 20028,
WordsNotAllowed = 20031,
GuildPremiumTooLow = 20035,
#endregion

#region Numeric Limits Reached (30XXX)
MaximumGuildsReached = 30001,
MaximumFriendsReached = 30002,
MaximumPinsReached = 30003,
MaximumRecipientsReached = 30004,
MaximumGuildRolesReached = 30005,
MaximumWebhooksReached = 30007,
MaximumEmojisReached = 30008,
MaximumReactionsReached = 30010,
MaximumGuildChannelsReached = 30013,
MaximumAttachmentsReached = 30015,
MaximumInvitesReached = 30016,
MaximumAnimatedEmojisReached = 30018,
MaximumServerMembersReached = 30019,
MaximumServerCategoriesReached = 30030,
GuildTemplateAlreadyExists = 30031,
MaximumThreadMembersReached = 30033,
MaximumBansForNonGuildMembersReached = 30035,
MaximumBanFetchesReached = 30037,
MaximumUncompleteGuildScheduledEvents = 30038,
MaximumStickersReached = 30039,
MaximumPruneRequestReached = 30040,
MaximumGuildWigitsReached = 30042,
#endregion

#region General Request Errors (40XXX)
TokenUnauthorized = 40001,
InvalidVerification = 40002,
OpeningDMTooFast = 40003,
RequestEntityTooLarge = 40005,
FeatureDisabled = 40006,
UserBanned = 40007,
TargetUserNotInVoice = 40032,
MessageAlreadyCrossposted = 40033,
ApplicationNameAlreadyExists = 40041,
#endregion

#region Action Preconditions/Checks (50XXX)
MissingPermissions = 50001,
InvalidAccountType = 50002,
CannotExecuteForDM = 50003,
GuildWigitDisabled = 50004,
CannotEditOtherUsersMessage = 50005,
CannotSendEmptyMessage = 50006,
CannotSendMessageToUser = 50007,
CannotSendMessageToVoiceChannel = 50008,
ChannelVerificationTooHight = 50009,
OAuth2ApplicationDoesntHaveBot = 50010,
OAuth2ApplicationLimitReached = 50011,
InvalidOAuth2State = 50012,
InsufficientPermissions = 50013,
InvalidAuthenticationToken = 50014,
NoteTooLong = 50015,
ProvidedMessageDeleteCountOutOfBounds = 50016,
InvalidPinChannel = 50019,
InvalidInvite = 50020,
CannotExecuteOnSystemMessage = 50021,
CannotExecuteOnChannelType = 50024,
InvalidOAuth2Token = 50025,
MissingOAuth2Scope = 50026,
InvalidWebhookToken = 50027,
InvalidRole = 50028,
InvalidRecipients = 50033,
BulkDeleteMessageTooOld = 50034,
InvalidFormBody = 50035,
InviteAcceptedForGuildThatBotIsntIn = 50036,
InvalidAPIVersion = 50041,
FileUploadTooBig = 50045,
InvalidFileUpload = 50046,
CannotSelfRedeemGift = 50054,
PaymentSourceRequiredForGift = 50070,
CannotDeleteRequiredCommunityChannel = 50074,
InvalidSticker = 50081,
CannotExecuteOnArchivedThread = 50083,
InvalidThreadNotificationSettings = 50084,
BeforeValueEarlierThanThreadCreation = 50085,
ServerLocaleUnavailable = 50095,
ServerRequiresMonetization = 50097,
ServerRequiresBoosts = 50101,

#endregion

#region 2FA (60XXX)
Requires2FA = 60003,
#endregion

#region User Searches (80XXX)
NoUsersWithTag = 80004,
#endregion

#region Reactions (90XXX)
ReactionBlocked = 90001,
#endregion

#region API Status (130XXX)
APIOverloaded = 130000,
#endregion

#region Stage Errors (150XXX)
StageAlreadyOpened = 150006,
#endregion

#region Reply and Thread Errors (160XXX)
CannotReplyWithoutReadMessageHistory = 160002,
MessageAlreadyContainsThread = 160004,
ThreadIsLocked = 160005,
MaximumActiveThreadsReached = 160006,
MaximumAnnouncementThreadsReached = 160007,
#endregion

#region Sticker Uploads (170XXX)
InvalidJSONLottie = 170001,
LottieCantContainRasters = 170002,
StickerMaximumFramerateExceeded = 170003,
StickerMaximumFrameCountExceeded = 170004,
LottieMaximumDimentionsExceeded = 170005,
StickerFramerateBoundsExceeed = 170006,
StickerAnimationDurationTooLong = 170007,
#endregion

#region Guild Scheduled Events
CannotUpdateFinishedEvent = 180000,
FailedStageCreation = 180002,
#endregion
}
}

+ 53
- 0
src/Discord.Net.Core/DiscordJsonError.cs View File

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

namespace Discord
{
/// <summary>
/// Represents a generic parsed json error received from discord after performing a rest request.
/// </summary>
public struct DiscordJsonError
{
/// <summary>
/// Gets the json path of the error.
/// </summary>
public string Path { get; }

/// <summary>
/// Gets a collection of errors associated with the specific property at the path.
/// </summary>
public IReadOnlyCollection<DiscordError> Errors { get; }

internal DiscordJsonError(string path, DiscordError[] errors)
{
Path = path;
Errors = errors.ToImmutableArray();
}
}

/// <summary>
/// Represents an error with a property.
/// </summary>
public struct DiscordError
{
/// <summary>
/// Gets the code of the error.
/// </summary>
public string Code { get; }

/// <summary>
/// Gets the message describing what went wrong.
/// </summary>
public string Message { get; }

internal DiscordError(string code, string message)
{
Code = code;
Message = message;
}
}
}

+ 13
- 1
src/Discord.Net.Core/Entities/Activities/ActivityProperties.cs View File

@@ -33,6 +33,18 @@ namespace Discord
/// <summary>
/// Indicates that a user can play this song.
/// </summary>
Play = 0b100000
Play = 0b100000,
/// <summary>
/// Indicates that a user is playing an activity in a voice channel with friends.
/// </summary>
PartyPrivacyFriends = 0b1000000,
/// <summary>
/// Indicates that a user is playing an activity in a voice channel.
/// </summary>
PartyPrivacyVoiceChannel = 0b10000000,
/// <summary>
/// Indicates that a user is playing an activity in a voice channel.
/// </summary>
Embedded = 0b10000000
}
}

+ 4
- 0
src/Discord.Net.Core/Entities/Activities/ActivityType.cs View File

@@ -25,5 +25,9 @@ namespace Discord
/// The user has set a custom status.
/// </summary>
CustomStatus = 4,
/// <summary>
/// The user is competing in a game.
/// </summary>
Competing = 5,
}
}

+ 86
- 0
src/Discord.Net.Core/Entities/Activities/DefaultApplications.cs View File

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

namespace Discord
{
public enum DefaultApplications : ulong
{
/// <summary>
/// Watch youtube together.
/// </summary>
Youtube = 880218394199220334,

/// <summary>
/// Youtube development application.
/// </summary>
YoutubeDev = 880218832743055411,

/// <summary>
/// Poker!
/// </summary>
Poker = 755827207812677713,

/// <summary>
/// Betrayal: A Party Adventure. Betrayal is a social deduction game inspired by Werewolf, Town of Salem, and Among Us.
/// </summary>
Betrayal = 773336526917861400,

/// <summary>
/// Sit back, relax, and do some fishing!
/// </summary>
Fishing = 814288819477020702,

/// <summary>
/// The queens gambit.
/// </summary>
Chess = 832012774040141894,

/// <summary>
/// Development version of chess.
/// </summary>
ChessDev = 832012586023256104,

/// <summary>
/// LetterTile is a version of scrabble.
/// </summary>
LetterTile = 879863686565621790,

/// <summary>
/// Find words in a jumble of letters in coffee.
/// </summary>
WordSnack = 879863976006127627,

/// <summary>
/// It's like skribbl.io.
/// </summary>
DoodleCrew = 878067389634314250,

/// <summary>
/// It's like cards against humanity.
/// </summary>
Awkword = 879863881349087252,

/// <summary>
/// A word-search like game where you unscramble words and score points in a scrabble fashion.
/// </summary>
SpellCast = 852509694341283871,

/// <summary>
/// Classic checkers
/// </summary>
Checkers = 832013003968348200,

/// <summary>
/// The development version of poker.
/// </summary>
PokerDev = 763133495793942528,

/// <summary>
/// SketchyArtist.
/// </summary>
SketchyArtist = 879864070101172255
}
}

+ 23
- 0
src/Discord.Net.Core/Entities/ApplicationFlags.cs View File

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

namespace Discord
{
/// <summary>
/// Represents public flags for an application.
/// </summary>
public enum ApplicationFlags
{
GatewayPresence = 1 << 12,
GatewayPresenceLimited = 1 << 13,
GatewayGuildMembers = 1 << 14,
GatewayGuildMembersLimited = 1 << 15,
VerificationPendingGuildLimit = 1 << 16,
Embedded = 1 << 17,
GatewayMessageContent = 1 << 18,
GatewayMessageContentLimited = 1 << 19
}
}

+ 31
- 0
src/Discord.Net.Core/Entities/ApplicationInstallParams.cs View File

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

namespace Discord
{
/// <summary>
/// Represents install parameters for an application.
/// </summary>
public class ApplicationInstallParams
{
/// <summary>
/// Gets the scopes to install this application.
/// </summary>
public IReadOnlyCollection<string> Scopes { get; }

/// <summary>
/// Gets the default permissions to install this application
/// </summary>
public GuildPermission? Permission { get; }

internal ApplicationInstallParams(string[] scopes, GuildPermission? permission)
{
Scopes = scopes.ToImmutableArray();
Permission = permission;
}
}
}

+ 50
- 0
src/Discord.Net.Core/Entities/AuditLogs/ActionType.cs View File

@@ -142,5 +142,55 @@ namespace Discord
/// A message was unpinned from this guild.
/// </summary>
MessageUnpinned = 75,

/// <summary>
/// A integration was created
/// </summary>
IntegrationCreated = 80,
/// <summary>
/// A integration was updated
/// </summary>
IntegrationUpdated = 81,
/// <summary>
/// An integration was deleted
/// </summary>
IntegrationDeleted = 82,
/// <summary>
/// A stage instance was created.
/// </summary>
StageInstanceCreated = 83,
/// <summary>
/// A stage instance was updated.
/// </summary>
StageInstanceUpdated = 84,
/// <summary>
/// A stage instance was deleted.
/// </summary>
StageInstanceDeleted = 85,

/// <summary>
/// A sticker was created.
/// </summary>
StickerCreated = 90,
/// <summary>
/// A sticker was updated.
/// </summary>
StickerUpdated = 91,
/// <summary>
/// A sticker was deleted.
/// </summary>
StickerDeleted = 92,
/// <summary>
/// A thread was created.
/// </summary>
ThreadCreate = 110,
/// <summary>
/// A thread was updated.
/// </summary>
ThreadUpdate = 111,
/// <summary>
/// A thread was deleted.
/// </summary>
ThreadDelete = 112
}
}

+ 13
- 1
src/Discord.Net.Core/Entities/Channels/ChannelType.cs View File

@@ -14,6 +14,18 @@ namespace Discord
/// <summary> The channel is a category channel. </summary>
Category = 4,
/// <summary> The channel is a news channel. </summary>
News = 5
News = 5,
/// <summary> The channel is a store channel. </summary>
Store = 6,
/// <summary> The channel is a temporary thread channel under a news channel. </summary>
NewsThread = 10,
/// <summary> The channel is a temporary thread channel under a text channel. </summary>
PublicThread = 11,
/// <summary> The channel is a private temporary thread channel under a text channel. </summary>
PrivateThread = 12,
/// <summary> The channel is a stage voice channel. </summary>
Stage = 13,
/// <summary> The channel is a guild directory used in hub servers. (Unreleased)</summary>
GuildDirectory = 14
}
}

+ 70
- 3
src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs View File

@@ -28,11 +28,14 @@ namespace Discord
/// If <c>null</c>, all mentioned roles and users will be notified.
/// </param>
/// <param name="messageReference">The message references to be included. Used to reply to specific messages.</param>
/// <param name="component">The message components to be included with this message. Used for interactions.</param>
/// <param name="stickers">A collection of stickers to send with the message.</param>
/// <param name="embeds">A array of <see cref="Embed"/>s to send with this response. Max 10.</param>
/// <returns>
/// A task that represents an asynchronous send operation for delivering the message. The task result
/// contains the sent message.
/// </returns>
Task<IUserMessage> SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null);
Task<IUserMessage> SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null, ISticker[] stickers = null, Embed[] embeds = null);
/// <summary>
/// Sends a file to this message channel with an optional caption.
/// </summary>
@@ -65,11 +68,14 @@ namespace Discord
/// If <c>null</c>, all mentioned roles and users will be notified.
/// </param>
/// <param name="messageReference">The message references to be included. Used to reply to specific messages.</param>
/// <param name="component">The message components to be included with this message. Used for interactions.</param>
/// <param name="stickers">A collection of stickers to send with the file.</param>
/// <param name="embeds">A array of <see cref="Embed"/>s to send with this response. Max 10.</param>
/// <returns>
/// A task that represents an asynchronous send operation for delivering the message. The task result
/// contains the sent message.
/// </returns>
Task<IUserMessage> SendFileAsync(string filePath, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null);
Task<IUserMessage> SendFileAsync(string filePath, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null, ISticker[] stickers = null, Embed[] embeds = null);
/// <summary>
/// Sends a file to this message channel with an optional caption.
/// </summary>
@@ -99,11 +105,72 @@ namespace Discord
/// If <c>null</c>, all mentioned roles and users will be notified.
/// </param>
/// <param name="messageReference">The message references to be included. Used to reply to specific messages.</param>
/// <param name="component">The message components to be included with this message. Used for interactions.</param>
/// <param name="stickers">A collection of stickers to send with the file.</param>
/// <param name="embeds">A array of <see cref="Embed"/>s to send with this response. Max 10.</param>
/// <returns>
/// A task that represents an asynchronous send operation for delivering the message. The task result
/// contains the sent message.
/// </returns>
Task<IUserMessage> SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null);
Task<IUserMessage> SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null, ISticker[] stickers = null, Embed[] embeds = null);
/// <summary>
/// Sends a file to this message channel with an optional caption.
/// </summary>
/// <remarks>
/// This method sends a file as if you are uploading an attachment directly from your Discord client.
/// <note>
/// If you wish to upload an image and have it embedded in a <see cref="Discord.EmbedType.Rich"/> embed,
/// you may upload the file and refer to the file with "attachment://filename.ext" in the
/// <see cref="Discord.EmbedBuilder.ImageUrl"/>. See the example section for its usage.
/// </note>
/// </remarks>
/// <param name="attachment">The attachment containing the file and description.</param>
/// <param name="text">The message to be sent.</param>
/// <param name="isTTS">Whether the message should be read aloud by Discord or not.</param>
/// <param name="embed">The <see cref="Discord.EmbedType.Rich"/> <see cref="Embed"/> to be sent.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <param name="allowedMentions">
/// Specifies if notifications are sent for mentioned users and roles in the message <paramref name="text"/>.
/// If <c>null</c>, all mentioned roles and users will be notified.
/// </param>
/// <param name="messageReference">The message references to be included. Used to reply to specific messages.</param>
/// <param name="component">The message components to be included with this message. Used for interactions.</param>
/// <param name="stickers">A collection of stickers to send with the file.</param>
/// <param name="embeds">A array of <see cref="Embed"/>s to send with this response. Max 10.</param>
/// <returns>
/// A task that represents an asynchronous send operation for delivering the message. The task result
/// contains the sent message.
/// </returns>
Task<IUserMessage> SendFileAsync(FileAttachment attachment, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null, ISticker[] stickers = null, Embed[] embeds = null);
/// <summary>
/// Sends a collection of files to this message channel.
/// </summary>
/// <remarks>
/// This method sends files as if you are uploading attachments directly from your Discord client.
/// <note>
/// If you wish to upload an image and have it embedded in a <see cref="Discord.EmbedType.Rich"/> embed,
/// you may upload the file and refer to the file with "attachment://filename.ext" in the
/// <see cref="Discord.EmbedBuilder.ImageUrl"/>. See the example section for its usage.
/// </note>
/// </remarks>
/// <param name="attachments">A collection of attachments to upload.</param>
/// <param name="text">The message to be sent.</param>
/// <param name="isTTS">Whether the message should be read aloud by Discord or not.</param>
/// <param name="embed">The <see cref="Discord.EmbedType.Rich"/> <see cref="Embed"/> to be sent.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <param name="allowedMentions">
/// Specifies if notifications are sent for mentioned users and roles in the message <paramref name="text"/>.
/// If <c>null</c>, all mentioned roles and users will be notified.
/// </param>
/// <param name="messageReference">The message references to be included. Used to reply to specific messages.</param>
/// <param name="component">The message components to be included with this message. Used for interactions.</param>
/// <param name="stickers">A collection of stickers to send with the file.</param>
/// <param name="embeds">A array of <see cref="Embed"/>s to send with this response. Max 10.</param>
/// <returns>
/// A task that represents an asynchronous send operation for delivering the message. The task result
/// contains the sent message.
/// </returns>
Task<IUserMessage> SendFilesAsync(IEnumerable<FileAttachment> attachments, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null, ISticker[] stickers = null, Embed[] embeds = null);

/// <summary>
/// Gets a message from this message channel.


+ 17
- 9
src/Discord.Net.Core/Entities/Channels/INestedChannel.cs View File

@@ -60,14 +60,7 @@ namespace Discord
/// <summary>
/// Creates a new invite to this channel.
/// </summary>
/// <example>
/// <para>The following example creates a new invite to this channel; the invite lasts for 12 hours and can only
/// be used 3 times throughout its lifespan.</para>
/// <code language="cs">
/// await guildChannel.CreateInviteAsync(maxAge: 43200, maxUses: 3);
/// </code>
/// </example>
/// <param name="applicationId">The id of the embedded application to open for this invite</param>
/// <param name="applicationId">The id of the embedded application to open for this invite.</param>
/// <param name="maxAge">The time (in seconds) until the invite expires. Set to <c>null</c> to never expire.</param>
/// <param name="maxUses">The max amount of times this invite may be used. Set to <c>null</c> to have unlimited uses.</param>
/// <param name="isTemporary">If <c>true</c>, the user accepting this invite will be kicked from the guild after closing their client.</param>
@@ -79,6 +72,21 @@ namespace Discord
/// </returns>
Task<IInviteMetadata> CreateInviteToApplicationAsync(ulong applicationId, int? maxAge = 86400, int? maxUses = default(int?), bool isTemporary = false, bool isUnique = false, RequestOptions options = null);

/// <summary>
/// Creates a new invite to this channel.
/// </summary>
/// <param name="application">The application to open for this invite.</param>
/// <param name="maxAge">The time (in seconds) until the invite expires. Set to <c>null</c> to never expire.</param>
/// <param name="maxUses">The max amount of times this invite may be used. Set to <c>null</c> to have unlimited uses.</param>
/// <param name="isTemporary">If <c>true</c>, the user accepting this invite will be kicked from the guild after closing their client.</param>
/// <param name="isUnique">If <c>true</c>, don't try to reuse a similar invite (useful for creating many unique one time use invites).</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous invite creation operation. The task result contains an invite
/// metadata object containing information for the created invite.
/// </returns>
Task<IInviteMetadata> CreateInviteToApplicationAsync(DefaultApplications application, int? maxAge = 86400, int? maxUses = default(int?), bool isTemporary = false, bool isUnique = false, RequestOptions options = null);

/// <summary>
/// Creates a new invite to this channel.
/// </summary>
@@ -89,7 +97,7 @@ namespace Discord
/// await guildChannel.CreateInviteAsync(maxAge: 43200, maxUses: 3);
/// </code>
/// </example>
/// <param name="user">The id of the user whose stream to display for this invite</param>
/// <param name="user">The id of the user whose stream to display for this invite.</param>
/// <param name="maxAge">The time (in seconds) until the invite expires. Set to <c>null</c> to never expire.</param>
/// <param name="maxUses">The max amount of times this invite may be used. Set to <c>null</c> to have unlimited uses.</param>
/// <param name="isTemporary">If <c>true</c>, the user accepting this invite will be kicked from the guild after closing their client.</param>


+ 114
- 0
src/Discord.Net.Core/Entities/Channels/IStageChannel.cs View File

@@ -0,0 +1,114 @@
using System;
using System.Threading.Tasks;

namespace Discord
{
/// <summary>
/// Represents a generic Stage Channel.
/// </summary>
public interface IStageChannel : IVoiceChannel
{
/// <summary>
/// Gets the topic of the Stage instance.
/// </summary>
/// <remarks>
/// If the stage isn't live then this property will be set to <see langword="null"/>.
/// </remarks>
string Topic { get; }

/// <summary>
/// Gets the <see cref="StagePrivacyLevel"/> of the current stage.
/// </summary>
/// <remarks>
/// If the stage isn't live then this property will be set to <see langword="null"/>.
/// </remarks>
StagePrivacyLevel? PrivacyLevel { get; }

/// <summary>
/// Gets whether or not stage discovery is disabled.
/// </summary>
bool? IsDiscoverableDisabled { get; }

/// <summary>
/// Gets whether or not the stage is live.
/// </summary>
bool IsLive { get; }

/// <summary>
/// Starts the stage, creating a stage instance.
/// </summary>
/// <param name="topic">The topic for the stage/</param>
/// <param name="privacyLevel">The privacy level of the stage.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous start operation.
/// </returns>
Task StartStageAsync(string topic, StagePrivacyLevel privacyLevel = StagePrivacyLevel.GuildOnly, RequestOptions options = null);

/// <summary>
/// Modifies the current stage instance.
/// </summary>
/// <param name="func">The properties to modify the stage instance with.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous modify operation.
/// </returns>
Task ModifyInstanceAsync(Action<StageInstanceProperties> func, RequestOptions options = null);

/// <summary>
/// Stops the stage, deleting the stage instance.
/// </summary>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous stop operation.
/// </returns>
Task StopStageAsync(RequestOptions options = null);

/// <summary>
/// Indicates that the bot would like to speak within a stage channel.
/// </summary>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous request to speak operation.
/// </returns>
Task RequestToSpeakAsync(RequestOptions options = null);

/// <summary>
/// Makes the current user become a speaker within a stage.
/// </summary>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous speaker modify operation.
/// </returns>
Task BecomeSpeakerAsync(RequestOptions options = null);

/// <summary>
/// Makes the current user a listener.
/// </summary>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous stop operation.
/// </returns>
Task StopSpeakingAsync(RequestOptions options = null);

/// <summary>
/// Makes a user a speaker within a stage.
/// </summary>
/// <param name="user">The user to make the speaker.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous move operation.
/// </returns>
Task MoveToSpeakerAsync(IGuildUser user, RequestOptions options = null);

/// <summary>
/// Removes a user from speaking.
/// </summary>
/// <param name="user">The user to remove from speaking.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous remove operation.
/// </returns>
Task RemoveFromSpeakerAsync(IGuildUser user, RequestOptions options = null);
}
}

+ 35
- 0
src/Discord.Net.Core/Entities/Channels/ITextChannel.cs View File

@@ -114,5 +114,40 @@ namespace Discord
/// of webhooks that is available in this channel.
/// </returns>
Task<IReadOnlyCollection<IWebhook>> GetWebhooksAsync(RequestOptions options = null);

/// <summary>
/// Creates a thread within this <see cref="ITextChannel"/>.
/// </summary>
/// <remarks>
/// When <paramref name="message"/> is <see langword="null"/> the thread type will be based off of the
/// channel its created in. When called on a <see cref="ITextChannel"/>, it creates a <see cref="ThreadType.PublicThread"/>.
/// When called on a <see cref="INewsChannel"/>, it creates a <see cref="ThreadType.NewsThread"/>. The id of the created
/// thread will be the same as the id of the message, and as such a message can only have a
/// single thread created from it.
/// </remarks>
/// <param name="name">The name of the thread.</param>
/// <param name="type">
/// The type of the thread.
/// <para>
/// <b>Note: </b>This parameter is not used if the <paramref name="message"/> parameter is not specified.
/// </para>
/// </param>
/// <param name="autoArchiveDuration">
/// The duration on which this thread archives after.
/// <para>
/// <b>Note: </b> Options <see cref="ThreadArchiveDuration.OneWeek"/> and <see cref="ThreadArchiveDuration.ThreeDays"/>
/// are only available for guilds that are boosted. You can check in the <see cref="IGuild.Features"/> to see if the
/// guild has the <b>THREE_DAY_THREAD_ARCHIVE</b> and <b>SEVEN_DAY_THREAD_ARCHIVE</b>.
/// </para>
/// </param>
/// <param name="message">The message which to start the thread from.</param>
/// <param name="invitable">Whether non-moderators can add other non-moderators to a thread; only available when creating a private thread</param>
/// <param name="slowmode">The amount of seconds a user has to wait before sending another message (0-21600)</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous create operation. The task result contains a <see cref="IThreadChannel"/>
/// </returns>
Task<IThreadChannel> CreateThreadAsync(string name, ThreadType type = ThreadType.PublicThread, ThreadArchiveDuration autoArchiveDuration = ThreadArchiveDuration.OneDay,
IMessage message = null, bool? invitable = null, int? slowmode = null, RequestOptions options = null);
}
}

+ 89
- 0
src/Discord.Net.Core/Entities/Channels/IThreadChannel.cs View File

@@ -0,0 +1,89 @@
using System;
using System.Threading.Tasks;

namespace Discord
{
/// <summary>
/// Represents a thread channel inside of a guild.
/// </summary>
public interface IThreadChannel : ITextChannel
{
/// <summary>
/// Gets the type of the current thread channel.
/// </summary>
ThreadType Type { get; }

/// <summary>
/// Gets whether or not the current user has joined this thread.
/// </summary>
bool HasJoined { get; }

/// <summary>
/// Gets whether or not the current thread is archived.
/// </summary>
bool IsArchived { get; }

/// <summary>
/// Gets the duration of time before the thread is automatically archived after no activity.
/// </summary>
ThreadArchiveDuration AutoArchiveDuration { get; }

/// <summary>
/// Gets the timestamp when the thread's archive status was last changed, used for calculating recent activity.
/// </summary>
DateTimeOffset ArchiveTimestamp { get; }

/// <summary>
/// Gets whether or not the current thread is locked.
/// </summary>
bool IsLocked { get; }

/// <summary>
/// Gets an approximate count of users in a thread, stops counting after 50.
/// </summary>
int MemberCount { get; }

/// <summary>
/// Gets an approximate count of messages in a thread, stops counting after 50.
/// </summary>
int MessageCount { get; }

/// <summary>
/// Joins the current thread.
/// </summary>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous join operation.
/// </returns>
Task JoinAsync(RequestOptions options = null);

/// <summary>
/// Leaves the current thread.
/// </summary>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous leave operation.
/// </returns>
Task LeaveAsync(RequestOptions options = null);

/// <summary>
/// Adds a user to this thread.
/// </summary>
/// <param name="user">The <see cref="IGuildUser"/> to add.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous operation of adding a member to a thread.
/// </returns>
Task AddUserAsync(IGuildUser user, RequestOptions options = null);

/// <summary>
/// Removes a user from this thread.
/// </summary>
/// <param name="user">The <see cref="IGuildUser"/> to remove from this thread.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous operation of removing a user from this thread.
/// </returns>
Task RemoveUserAsync(IGuildUser user, RequestOptions options = null);
}
}

+ 1
- 1
src/Discord.Net.Core/Entities/Channels/IVoiceChannel.cs View File

@@ -6,7 +6,7 @@ namespace Discord
/// <summary>
/// Represents a generic voice channel in a guild.
/// </summary>
public interface IVoiceChannel : INestedChannel, IAudioChannel
public interface IVoiceChannel : INestedChannel, IAudioChannel, IMentionable
{
/// <summary>
/// Gets the bit-rate that the clients in this voice channel are requested to use.


+ 18
- 0
src/Discord.Net.Core/Entities/Channels/StageInstanceProperties.cs View File

@@ -0,0 +1,18 @@
namespace Discord
{
/// <summary>
/// Represents properties to use when modifying a stage instance.
/// </summary>
public class StageInstanceProperties
{
/// <summary>
/// Gets or sets the topic of the stage.
/// </summary>
public Optional<string> Topic { get; set; }

/// <summary>
/// Gets or sets the privacy level of the stage.
/// </summary>
public Optional<StagePrivacyLevel> PrivacyLevel { get; set; }
}
}

+ 17
- 0
src/Discord.Net.Core/Entities/Channels/StagePrivacyLevel.cs View File

@@ -0,0 +1,17 @@
namespace Discord
{
/// <summary>
/// Represents the privacy level of a stage.
/// </summary>
public enum StagePrivacyLevel
{
/// <summary>
/// The Stage instance is visible publicly, such as on Stage Discovery.
/// </summary>
Public = 1,
/// <summary>
/// The Stage instance is visible to only guild members.
/// </summary>
GuildOnly = 2
}
}

+ 16
- 0
src/Discord.Net.Core/Entities/Channels/TextChannelProperties.cs View File

@@ -38,5 +38,21 @@ namespace Discord
/// </remarks>
/// <exception cref="ArgumentOutOfRangeException">Thrown if the value does not fall within [0, 21600].</exception>
public Optional<int> SlowModeInterval { get; set; }

/// <summary>
/// Gets or sets whether or not the thread is archived.
/// </summary>
public Optional<bool> Archived { get; set; }

/// <summary>
/// Gets or sets whether or not the thread is locked.
/// </summary>
public Optional<bool> Locked { get; set; }

/// <summary>
/// Gets or sets the auto archive duration.
/// </summary>
public Optional<ThreadArchiveDuration> AutoArchiveDuration { get; set; }
}
}

+ 34
- 0
src/Discord.Net.Core/Entities/Channels/ThreadArchiveDuration.cs View File

@@ -0,0 +1,34 @@
namespace Discord
{
/// <summary>
/// Represents the thread auto archive duration.
/// </summary>
public enum ThreadArchiveDuration
{
/// <summary>
/// One hour (60 minutes).
/// </summary>
OneHour = 60,

/// <summary>
/// One day (1440 minutes).
/// </summary>
OneDay = 1440,

/// <summary>
/// Three days (4320 minutes).
/// <remarks>
/// This option is explicitly available to nitro users.
/// </remarks>
/// </summary>
ThreeDays = 4320,

/// <summary>
/// One week (10080 minutes).
/// <remarks>
/// This option is explicitly available to nitro users.
/// </remarks>
/// </summary>
OneWeek = 10080
}
}

+ 23
- 0
src/Discord.Net.Core/Entities/Channels/ThreadType.cs View File

@@ -0,0 +1,23 @@
namespace Discord
{
/// <summary>
/// Represents types of threads.
/// </summary>
public enum ThreadType
{
/// <summary>
/// Represents a temporary sub-channel within a GUILD_NEWS channel.
/// </summary>
NewsThread = 10,

/// <summary>
/// Represents a temporary sub-channel within a GUILD_TEXT channel.
/// </summary>
PublicThread = 11,

/// <summary>
/// Represents a temporary sub-channel within a GUILD_TEXT channel that is only viewable by those invited and those with the MANAGE_THREADS permission
/// </summary>
PrivateThread = 12
}
}

+ 4
- 0
src/Discord.Net.Core/Entities/Channels/VoiceChannelProperties.cs View File

@@ -13,5 +13,9 @@ namespace Discord
/// Gets or sets the maximum number of users that can be present in a channel, or <c>null</c> if none.
/// </summary>
public Optional<int?> UserLimit { get; set; }
/// <summary>
/// Gets or sets the channel voice region id, automatic when set to <see langword="null"/>.
/// </summary>
public Optional<string> RTCRegion { get; set; }
}
}

+ 5937
- 8
src/Discord.Net.Core/Entities/Emotes/Emoji.cs
File diff suppressed because it is too large
View File


+ 6
- 0
src/Discord.Net.Core/Entities/Emotes/Emote.cs View File

@@ -74,6 +74,10 @@ namespace Discord
public static bool TryParse(string text, out Emote result)
{
result = null;

if (text == null)
return false;

if (text.Length >= 4 && text[0] == '<' && (text[1] == ':' || (text[1] == 'a' && text[2] == ':')) && text[text.Length - 1] == '>')
{
bool animated = text[1] == 'a';
@@ -102,5 +106,7 @@ namespace Discord
/// A string representing the raw presentation of the emote (e.g. <c>&lt;:thonkang:282745590985523200&gt;</c>).
/// </returns>
public override string ToString() => $"<{(Animated ? "a" : "")}:{Name}:{Id}>";

public static implicit operator Emote(string s) => Parse(s);
}
}

+ 105
- 0
src/Discord.Net.Core/Entities/Guilds/GuildFeature.cs View File

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

namespace Discord
{
[Flags]
public enum GuildFeature
{
/// <summary>
/// The guild has no features.
/// </summary>
None = 0,
/// <summary>
/// The guild has access to set an animated guild icon.
/// </summary>
AnimatedIcon = 1 << 0,
/// <summary>
/// The guild has access to set a guild banner image.
/// </summary>
Banner = 1 << 1,
/// <summary>
/// The guild has access to use commerce features (i.e. create store channels).
/// </summary>
Commerce = 1 << 2,
/// <summary>
/// The guild can enable welcome screen, Membership Screening, stage channels and discovery, and receives community updates.
/// </summary>
Community = 1 << 3,
/// <summary>
/// The guild is able to be discovered in the directory.
/// </summary>
Discoverable = 1 << 4,
/// <summary>
/// The guild is able to be featured in the directory.
/// </summary>
Featureable = 1 << 5,
/// <summary>
/// The guild has access to set an invite splash background.
/// </summary>
InviteSplash = 1 << 6,
/// <summary>
/// The guild has enabled <seealso href="https://discord.com/developers/docs/resources/guild#membership-screening-object">Membership Screening</seealso>.
/// </summary>
MemberVerificationGateEnabled = 1 << 7,
/// <summary>
/// The guild has enabled monetization.
/// </summary>
MonetizationEnabled = 1 << 8,
/// <summary>
/// The guild has increased custom sticker slots.
/// </summary>
MoreStickers = 1 << 9,
/// <summary>
/// The guild has access to create news channels.
/// </summary>
News = 1 << 10,
/// <summary>
/// The guild is partnered.
/// </summary>
Partnered = 1 << 11,
/// <summary>
/// The guild can be previewed before joining via Membership Screening or the directory.
/// </summary>
PreviewEnabled = 1 << 12,
/// <summary>
/// The guild has access to create private threads.
/// </summary>
PrivateThreads = 1 << 13,
/// <summary>
/// The guild is able to set role icons.
/// </summary>
RoleIcons = 1 << 14,
/// <summary>
/// The guild has access to the seven day archive time for threads.
/// </summary>
SevenDayThreadArchive = 1 << 15,
/// <summary>
/// The guild has access to the three day archive time for threads.
/// </summary>
ThreeDayThreadArchive = 1 << 16,
/// <summary>
/// The guild has enabled ticketed events.
/// </summary>
TicketedEventsEnabled = 1 << 17,
/// <summary>
/// The guild has access to set a vanity URL.
/// </summary>
VanityUrl = 1 << 18,
/// <summary>
/// The guild is verified.
/// </summary>
Verified = 1 << 19,
/// <summary>
/// The guild has access to set 384kbps bitrate in voice (previously VIP voice servers).
/// </summary>
VIPRegions = 1 << 20,
/// <summary>
/// The guild has enabled the welcome screen.
/// </summary>
WelcomeScreenEnabled = 1 << 21,
}
}

+ 46
- 0
src/Discord.Net.Core/Entities/Guilds/GuildFeatures.cs View File

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

namespace Discord
{
public class GuildFeatures
{
/// <summary>
/// Gets the flags of recognized features for this guild.
/// </summary>
public GuildFeature Value { get; }

/// <summary>
/// Gets a collection of experimental features for this guild.
/// </summary>
public IReadOnlyCollection<string> Experimental { get; }


internal GuildFeatures(GuildFeature value, string[] experimental)
{
Value = value;
Experimental = experimental.ToImmutableArray();
}

public bool HasFeature(GuildFeature feature)
=> Value.HasFlag(feature);
public bool HasFeature(string feature)
=> Experimental.Contains(feature);

internal void EnsureFeature(GuildFeature feature)
{
if (!HasFeature(feature))
{
var vals = Enum.GetValues(typeof(GuildFeature)).Cast<GuildFeature>();

var missingValues = vals.Where(x => feature.HasFlag(x) && !Value.HasFlag(x));

throw new InvalidOperationException($"Missing required guild feature{(missingValues.Count() > 1 ? "s" : "")} {string.Join(", ", missingValues.Select(x => x.ToString()))} in order to execute this operation.");
}
}
}
}

+ 7
- 2
src/Discord.Net.Core/Entities/Guilds/GuildProperties.cs View File

@@ -85,8 +85,9 @@ namespace Discord
/// given that the <see cref="SystemChannelId"/> has also been set.
/// A value of <see cref="SystemChannelMessageDeny.GuildBoost"/> will deny guild boost messages from being sent, and allow all
/// other types of messages.
/// Refer to the extension methods <see cref="GuildExtensions.GetGuildBoostMessagesEnabled(IGuild)"/> and
/// <see cref="GuildExtensions.GetWelcomeMessagesEnabled(IGuild)"/> to check if these system channel message types
/// Refer to the extension methods <see cref="GuildExtensions.GetGuildBoostMessagesEnabled(IGuild)"/>,
/// <see cref="GuildExtensions.GetWelcomeMessagesEnabled(IGuild)"/>, <see cref="GuildExtensions.GetGuildSetupTipMessagesEnabled(IGuild)"/>,
/// and <see cref="GuildExtensions.GetGuildWelcomeMessageReplyEnabled(IGuild)"/> to check if these system channel message types
/// are enabled, without the need to manipulate the logic of the flag.
/// </remarks>
public Optional<SystemChannelMessageDeny> SystemChannelFlags { get; set; }
@@ -108,5 +109,9 @@ namespace Discord
/// the value of <see cref="PreferredCulture"/> will be unused.
/// </remarks>
public Optional<CultureInfo> PreferredCulture { get; set; }
/// <summary>
/// Gets or sets if the boost progress bar is enabled.
/// </summary>
public Optional<bool> IsBoostProgressBarEnabled { get; set; }
}
}

+ 25
- 0
src/Discord.Net.Core/Entities/Guilds/GuildScheduledEventPrivacyLevel.cs View File

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

namespace Discord
{
/// <summary>
/// Represents the privacy level of a guild scheduled event.
/// </summary>
public enum GuildScheduledEventPrivacyLevel
{
/// <summary>
/// The scheduled event is public and available in discovery.
/// </summary>
[Obsolete("This event type isn't supported yet! check back later.", true)]
Public = 1,

/// <summary>
/// The scheduled event is only accessible to guild members.
/// </summary>
Private = 2,
}
}

+ 34
- 0
src/Discord.Net.Core/Entities/Guilds/GuildScheduledEventStatus.cs View File

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

namespace Discord
{
/// <summary>
/// Represents the status of a guild event.
/// </summary>
public enum GuildScheduledEventStatus
{
/// <summary>
/// The event is scheduled for a set time.
/// </summary>
Scheduled = 1,

/// <summary>
/// The event has started.
/// </summary>
Active = 2,

/// <summary>
/// The event was completed.
/// </summary>
Completed = 3,

/// <summary>
/// The event was canceled.
/// </summary>
Cancelled = 4,
}
}

+ 34
- 0
src/Discord.Net.Core/Entities/Guilds/GuildScheduledEventType.cs View File

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

namespace Discord
{
/// <summary>
/// Represents the type of a guild scheduled event.
/// </summary>
public enum GuildScheduledEventType
{
/// <summary>
/// The event doesn't have a set type.
/// </summary>
None = 0,

/// <summary>
/// The event is set in a stage channel.
/// </summary>
Stage = 1,

/// <summary>
/// The event is set in a voice channel.
/// </summary>
Voice = 2,

/// <summary>
/// The event is set for somewhere externally from discord.
/// </summary>
External = 3,
}
}

+ 58
- 0
src/Discord.Net.Core/Entities/Guilds/GuildScheduledEventsProperties.cs View File

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

namespace Discord
{
/// <summary>
/// Provides properties that are used to modify an <see cref="IGuildScheduledEvent" /> with the specified changes.
/// </summary>
public class GuildScheduledEventsProperties
{
/// <summary>
/// Gets or sets the channel id of the event.
/// </summary>
public Optional<ulong?> ChannelId { get; set; }

/// <summary>
/// Gets or sets the location of this event.
/// </summary>
public Optional<string> Location { get; set; }

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

/// <summary>
/// Gets or sets the privacy level of the event.
/// </summary>
public Optional<GuildScheduledEventPrivacyLevel> PrivacyLevel { get; set; }

/// <summary>
/// Gets or sets the start time of the event.
/// </summary>
public Optional<DateTimeOffset> StartTime { get; set; }
/// <summary>
/// Gets or sets the end time of the event.
/// </summary>
public Optional<DateTimeOffset> EndTime { get; set; }

/// <summary>
/// Gets or sets the description of the event.
/// </summary>
public Optional<string> Description { get; set; }

/// <summary>
/// Gets or sets the type of the event.
/// </summary>
public Optional<GuildScheduledEventType> Type { get; set; }

/// <summary>
/// Gets or sets the status of the event.
/// </summary>
public Optional<GuildScheduledEventStatus> Status { get; set; }
}
}

+ 272
- 6
src/Discord.Net.Core/Entities/Guilds/IGuild.cs View File

@@ -2,6 +2,7 @@ using Discord.Audio;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Threading.Tasks;

namespace Discord
@@ -199,12 +200,19 @@ namespace Discord
/// </returns>
IReadOnlyCollection<GuildEmote> Emotes { get; }
/// <summary>
/// Gets a collection of all extra features added to this guild.
/// Gets a collection of all custom stickers for this guild.
/// </summary>
/// <returns>
/// A read-only collection of enabled features in this guild.
/// A read-only collection of all custom stickers for this guild.
/// </returns>
IReadOnlyCollection<string> Features { get; }
IReadOnlyCollection<ICustomSticker> Stickers { get; }
/// <summary>
/// Gets the features for this guild.
/// </summary>
/// <returns>
/// A flags enum containing all the features for the guild.
/// </returns>
GuildFeatures Features { get; }
/// <summary>
/// Gets a collection of all roles in this guild.
/// </summary>
@@ -305,6 +313,13 @@ namespace Discord
/// The approximate number of non-offline members in this guild.
/// </returns>
int? ApproximatePresenceCount { get; }
/// <summary>
/// Gets the max bitrate for voice channels in this guild.
/// </summary>
/// <returns>
/// A <see cref="int"/> representing the maximum bitrate value allowed by Discord in this guild.
/// </returns>
int MaxBitrate { get; }

/// <summary>
/// Gets the preferred locale of this guild in IETF BCP 47
@@ -316,6 +331,14 @@ namespace Discord
/// </returns>
string PreferredLocale { get; }

/// <summary>
/// Gets the NSFW level of this guild.
/// </summary>
/// <returns>
/// The NSFW level of this guild.
/// </returns>
NsfwLevel NsfwLevel { get; }

/// <summary>
/// Gets the preferred culture of this guild.
/// </summary>
@@ -323,6 +346,13 @@ namespace Discord
/// The preferred culture information of this guild.
/// </returns>
CultureInfo PreferredCulture { get; }
/// <summary>
/// Gets whether the guild has the boost progress bar enabled.
/// </summary>
/// <returns>
/// <see langword="true"/> if the boost progress bar is enabled; otherwise <see langword="false"/>.
/// </returns>
bool IsBoostProgressBarEnabled { get; }

/// <summary>
/// Modifies this guild.
@@ -522,6 +552,27 @@ namespace Discord
/// </returns>
Task<IVoiceChannel> GetVoiceChannelAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null);
/// <summary>
/// Gets a stage channel in this guild.
/// </summary>
/// <param name="id">The snowflake identifier for the stage channel.</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 task that represents the asynchronous get operation. The task result contains the stage channel associated
/// with the specified <paramref name="id"/>; <see langword="null" /> if none is found.
/// </returns>
Task<IStageChannel> GetStageChannelAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null);
/// <summary>
/// Gets a collection of all stage channels in this guild.
/// </summary>
/// <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 task that represents the asynchronous get operation. The task result contains a read-only collection of
/// stage channels found within this guild.
/// </returns>
Task<IReadOnlyCollection<IStageChannel>> GetStageChannelsAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null);
/// <summary>
/// Gets the AFK voice channel in this guild.
/// </summary>
/// <param name="mode">The <see cref="CacheMode"/> that determines whether the object should be fetched from cache.</param>
@@ -572,15 +623,35 @@ namespace Discord
/// </returns>
Task<ITextChannel> GetRulesChannelAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null);
/// <summary>
/// Gets the text channel channel where admins and moderators of Community guilds receive notices from Discord.
/// Gets the text channel where admins and moderators of Community guilds receive notices from Discord.
/// </summary>
/// <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 task that represents the asynchronous get operation. The task result contains the text channel channel where
/// A task that represents the asynchronous get operation. The task result contains the text channel where
/// admins and moderators of Community guilds receive notices from Discord; <see langword="null" /> if none is set.
/// </returns>
Task<ITextChannel> GetPublicUpdatesChannelAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null);
/// <summary>
/// Gets a thread channel within this guild.
/// </summary>
/// <param name="id">The id of the thread channel.</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 task that represents the asynchronous get operation. The task result contains the thread channel.
/// </returns>
Task<IThreadChannel> GetThreadChannelAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null);
/// <summary>
/// Gets a collection of all thread channels in this guild.
/// </summary>
/// <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 task that represents the asynchronous get operation. The task result contains a read-only collection of
/// thread channels found within this guild.
/// </returns>
Task<IReadOnlyCollection<IThreadChannel>> GetThreadChannelsAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null);

/// <summary>
/// Creates a new text channel in this guild.
@@ -610,6 +681,17 @@ namespace Discord
/// </returns>
Task<IVoiceChannel> CreateVoiceChannelAsync(string name, Action<VoiceChannelProperties> func = null, RequestOptions options = null);
/// <summary>
/// Creates a new stage channel in this guild.
/// </summary>
/// <param name="name">The new name for the stage channel.</param>
/// <param name="func">The delegate containing the properties to be applied to the channel upon its creation.</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 newly created
/// stage channel.
/// </returns>
Task<IStageChannel> CreateStageChannelAsync(string name, Action<VoiceChannelProperties> func = null, RequestOptions options = null);
/// <summary>
/// Creates a new channel category in this guild.
/// </summary>
/// <param name="name">The new name for the category.</param>
@@ -703,6 +785,12 @@ namespace Discord
/// <returns>A guild user associated with the specified <paramref name="userId" />; <see langword="null" /> if the user is already in the guild.</returns>
Task<IGuildUser> AddGuildUserAsync(ulong userId, string accessToken, Action<AddGuildUserProperties> func = null, RequestOptions options = null);
/// <summary>
/// Disconnects the user from its current voice channel.
/// </summary>
/// <param name="user">The user to disconnect.</param>
/// <returns>A task that represents the asynchronous operation for disconnecting a user.</returns>
Task DisconnectAsync(IGuildUser user);
/// <summary>
/// Gets a collection of all users in this guild.
/// </summary>
/// <remarks>
@@ -760,7 +848,7 @@ namespace Discord
/// Downloads all users for this guild if the current list is incomplete.
/// </summary>
/// <remarks>
/// This method downloads all users found within this guild throught the Gateway and caches them.
/// This method downloads all users found within this guild through the Gateway and caches them.
/// </remarks>
/// <returns>
/// A task that represents the asynchronous download operation.
@@ -883,6 +971,15 @@ namespace Discord
/// emote.
/// </returns>
Task<GuildEmote> ModifyEmoteAsync(GuildEmote emote, Action<EmoteProperties> func, RequestOptions options = null);

/// <summary>
/// Moves the user to the voice channel.
/// </summary>
/// <param name="user">The user to move.</param>
/// <param name="targetChannel">the channel where the user gets moved to.</param>
/// <returns>A task that represents the asynchronous operation for moving a user.</returns>
Task MoveAsync(IGuildUser user, IVoiceChannel targetChannel);

/// <summary>
/// Deletes an existing <see cref="GuildEmote"/> from this guild.
/// </summary>
@@ -892,5 +989,174 @@ namespace Discord
/// A task that represents the asynchronous removal operation.
/// </returns>
Task DeleteEmoteAsync(GuildEmote emote, RequestOptions options = null);

/// <summary>
/// Creates a new sticker in this guild.
/// </summary>
/// <param name="name">The name of the sticker.</param>
/// <param name="description">The description of the sticker.</param>
/// <param name="tags">The tags of the sticker.</param>
/// <param name="image">The image of the new emote.</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 sticker.
/// </returns>
Task<ICustomSticker> CreateStickerAsync(string name, string description, IEnumerable<string> tags, Image image, RequestOptions options = null);

/// <summary>
/// Creates a new sticker in this guild.
/// </summary>
/// <param name="name">The name of the sticker.</param>
/// <param name="description">The description of the sticker.</param>
/// <param name="tags">The tags of the sticker.</param>
/// <param name="path">The path of the file to upload.</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 sticker.
/// </returns>
Task<ICustomSticker> CreateStickerAsync(string name, string description, IEnumerable<string> tags, string path, RequestOptions options = null);

/// <summary>
/// Creates a new sticker in this guild.
/// </summary>
/// <param name="name">The name of the sticker.</param>
/// <param name="description">The description of the sticker.</param>
/// <param name="tags">The tags of the sticker.</param>
/// <param name="stream">The stream containing the file data.</param>
/// <param name="filename">The name of the file <b>with</b> the extension, ex: image.png.</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 sticker.
/// </returns>
Task<ICustomSticker> CreateStickerAsync(string name, string description, IEnumerable<string> tags, Stream stream, string filename, RequestOptions options = null);

/// <summary>
/// Gets a specific sticker within this guild.
/// </summary>
/// <param name="id">The id of the sticker 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 task that represents the asynchronous get operation. The task result contains the sticker found with the
/// specified <paramref name="id"/>; <see langword="null" /> if none is found.
/// </returns>
Task<ICustomSticker> GetStickerAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null);

/// <summary>
/// Gets a collection of all stickers within this guild.
/// </summary>
/// <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 task that represents the asynchronous get operation. The task result contains a read-only collection
/// of stickers found within the guild.
/// </returns>
Task<IReadOnlyCollection<ICustomSticker>> GetStickersAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null);

/// <summary>
/// Deletes a sticker within this guild.
/// </summary>
/// <param name="sticker">The sticker to delete.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous removal operation.
/// </returns>
Task DeleteStickerAsync(ICustomSticker sticker, RequestOptions options = null);

/// <summary>
/// Gets a event within this guild.
/// </summary>
/// <param name="id">The id of the event.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous get operation.
/// </returns>
Task<IGuildScheduledEvent> GetEventAsync(ulong id, RequestOptions options = null);

/// <summary>
/// Gets a collection of events within this guild.
/// </summary>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous get operation.
/// </returns>
Task<IReadOnlyCollection<IGuildScheduledEvent>> GetEventsAsync(RequestOptions options = null);

/// <summary>
/// Creates an event within this guild.
/// </summary>
/// <param name="name">The name of the event.</param>
/// <param name="privacyLevel">The privacy level of the event.</param>
/// <param name="startTime">The start time of the event.</param>
/// <param name="type">The type of the event.</param>
/// <param name="description">The description of the event.</param>
/// <param name="endTime">The end time of the event.</param>
/// <param name="channelId">
/// The channel id of the event.
/// <remarks>
/// The event must have a type of <see cref="GuildScheduledEventType.Stage"/> or <see cref="GuildScheduledEventType.Voice"/>
/// in order to use this property.
/// </remarks>
/// </param>
/// <param name="speakers">A collection of speakers for the event.</param>
/// <param name="location">The location of the event; links are supported</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous create operation.
/// </returns>
Task<IGuildScheduledEvent> CreateEventAsync(
string name,
DateTimeOffset startTime,
GuildScheduledEventType type,
GuildScheduledEventPrivacyLevel privacyLevel = GuildScheduledEventPrivacyLevel.Private,
string description = null,
DateTimeOffset? endTime = null,
ulong? channelId = null,
string location = null,
RequestOptions options = null);

/// <summary>
/// Gets this guilds application 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>
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);
}
}

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save