diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
index 84ee6e5a1..807381d31 100644
--- a/.github/FUNDING.yml
+++ b/.github/FUNDING.yml
@@ -1 +1,3 @@
+github: quinchs
open_collective: discordnet
+custom: https://paypal.me/quinchs
diff --git a/.github/ISSUE_TEMPLATE/bugreport.yml b/.github/ISSUE_TEMPLATE/bugreport.yml
index e2c154130..29759facf 100644
--- a/.github/ISSUE_TEMPLATE/bugreport.yml
+++ b/.github/ISSUE_TEMPLATE/bugreport.yml
@@ -38,7 +38,7 @@ body:
id: description
attributes:
label: Description
- description: A brief explination of the bug.
+ description: A brief explanation of the bug.
placeholder: When I start a DiscordSocketClient without stopping it, the gateway thread gets blocked.
validations:
required: true
@@ -62,7 +62,7 @@ body:
id: logs
attributes:
label: Logs
- description: Add applicable logs and/or a stacktrace here.
+ description: Add applicable logs and/or a stack trace here.
validations:
required: true
- type: textarea
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3e4de065c..a4022e1b6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,4 +1,65 @@
# Changelog
+## [3.7.2] - 2022-06-02
+### Added
+- #2328 Add method overloads to InteractionService (0fad3e8)
+- #2336 Add support for attachments on interaction response type 7 (35db22e)
+- #2338 AddOptions no longer has an uneeded restriction, added AddOptions to SlashCommandOptionBuilder (3a37f89)
+
+### Fixed
+- #2342 Disable TIV restrictions for rollout of TIV (7adf516)
+
+## [3.7.1] - 2022-05-27
+### Added
+- #2325 Add missing interaction properties (d3a693a)
+- #2330 Add better call control in ParseHttpInteraction (a890de9)
+
+### Fixed
+- #2329 Voice perms not retaining text perms. (712a4ae)
+- #2331 NRE with Cacheable.DownloadAsync() (e1f9b76)
+
+## [3.7.0] - 2022-05-24
+### Added
+- #2269 Text-In-Voice (23656e8)
+- #2281 Optional API calling to RestInteraction (a24dde4)
+- #2283 Support FailIfNotExists on MessageReference (0ec8938)
+- #2284 Add Parse & TryParse to EmbedBuilder & Add ToJsonString extension (cea59b5)
+- #2289 Add UpdateAsync to SocketModal (b333de2)
+- #2291 Webhook support for threads (b0a3b65)
+- #2295 Add DefaultArchiveDuration to ITextChannel (1f01881)
+- #2296 Add `.With` methods to ActionRowBuilder (13ccc7c)
+- #2307 Add Nullable ComponentTypeConverter and TypeReader (6fbd396)
+- #2316 Forum channels (7a07fd6)
+
+### Fixed
+- #2290 Possible NRE in Sanitize (20ffa64)
+- #2293 Application commands are disabled to everyone except admins by default (b465d60)
+- #2299 Close-stage bucketId being null (725d255)
+- #2313 Upload file size limit being incorrectly calculated (54a5af7)
+- #2319 Use `IDiscordClient.GetUserAsync` impl in `DiscordSocketClient` (f47f319)
+- #2320 NRE with bot scope and user parameters (88f6168)
+
+## [3.6.1] - 2022-04-30
+### Added
+- #2272 add 50080 Error code (503e720)
+
+### Fixed
+- #2267 Permissions v2 Invalid Operation Exception (a8f6075)
+- #2271 null user on interaction without bot scope (f2bb55e)
+- #2274 Implement fix for Custom Id Segments NRE (0d74c5c)
+
+### Misc
+- 3.6.0 (27226f0)
+
+
+## [3.6.0] - 2022-04-28
+### Added
+- #2136 Passing CustomId matches into contexts (4ce1801)
+- #2222 V2 Permissions (d98b3cc)
+
+### Fixed
+- #2260 Guarding against empty descriptions in `SlashCommandBuilder`/`SlashCommandOptionBuilder` (0554ac2)
+- #2248 Fix SocketGuild not returning the AudioClient (daba58c)
+- #2254 Fix browser property (275b833)
## [3.5.0] - 2022-04-05
diff --git a/Discord.Net.targets b/Discord.Net.targets
index e50e6eceb..8cedb40e7 100644
--- a/Discord.Net.targets
+++ b/Discord.Net.targets
@@ -1,12 +1,12 @@
- 3.5.0
+ 3.7.2
latest
Discord.Net Contributors
discord;discordapp
https://github.com/Discord-Net/Discord.Net
- http://opensource.org/licenses/MIT
- https://github.com/Discord-Net/Discord.Net/raw/dev/docs/marketing/logo/PackageLogo.png
+ MIT
+ PackageLogo.png
git
git://github.com/Discord-Net/Discord.Net
@@ -23,4 +23,7 @@
true
true
+
+
+
diff --git a/README.md b/README.md
index 541948f4b..e85216dbf 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,3 @@
-# Discord.Net
@@ -18,7 +17,7 @@
-Discord NET is an unofficial .NET API Wrapper for the Discord client (https://discord.com).
+Discord.Net is an unofficial .NET API Wrapper for the Discord client (https://discord.com).
## Documentation
diff --git a/docs/docfx.json b/docs/docfx.json
index 2a4ee2867..5dd1e640d 100644
--- a/docs/docfx.json
+++ b/docs/docfx.json
@@ -60,7 +60,7 @@
"overwrite": "_overwrites/**/**.md",
"globalMetadata": {
"_appTitle": "Discord.Net Documentation",
- "_appFooter": "Discord.Net (c) 2015-2022 3.5.0",
+ "_appFooter": "Discord.Net (c) 2015-2022 3.7.2",
"_enableSearch": true,
"_appLogoPath": "marketing/logo/SVG/Logomark Purple.svg",
"_appFaviconPath": "favicon.ico"
diff --git a/docs/guides/getting_started/first-bot.md b/docs/guides/getting_started/first-bot.md
index e1af20d30..a5b0dbbd4 100644
--- a/docs/guides/getting_started/first-bot.md
+++ b/docs/guides/getting_started/first-bot.md
@@ -202,7 +202,7 @@ online in Discord.
To create commands for your bot, you may choose from a variety of
command processors available. Throughout the guides, we will be using
-the one that Discord.Net ships with. @Guides.Commands.Intro will
+the one that Discord.Net ships with. @Guides.TextCommands.Intro will
guide you through how to setup a program that is ready for
[CommandService].
diff --git a/docs/guides/int_basics/application-commands/slash-commands/parameters.md b/docs/guides/int_basics/application-commands/slash-commands/parameters.md
index 6afd83729..4f3cd2e8c 100644
--- a/docs/guides/int_basics/application-commands/slash-commands/parameters.md
+++ b/docs/guides/int_basics/application-commands/slash-commands/parameters.md
@@ -15,9 +15,10 @@ Slash commands can have a bunch of parameters, each their own type. Let's first
| Integer | A number. |
| Boolean | True or False. |
| User | A user |
-| Channel | A channel, this includes voice text and categories |
| Role | A role. |
+| Channel | A channel, this includes voice text and categories |
| Mentionable | A role or a user. |
+| File | A file |
Each one of the parameter types has its own DNET type in the `SocketSlashCommandDataOption`'s Value field:
| Name | C# Type |
@@ -31,6 +32,7 @@ Each one of the parameter types has its own DNET type in the `SocketSlashCommand
| Role | `SocketRole` |
| Channel | `SocketChannel` |
| Mentionable | `SocketUser`, `SocketGuildUser`, or `SocketRole` |
+| File | `IAttachment` |
Let's start by making a command that takes in a user and lists their roles.
diff --git a/docs/guides/int_basics/modals/intro.md b/docs/guides/int_basics/modals/intro.md
index 81f0da03c..3e738c6d8 100644
--- a/docs/guides/int_basics/modals/intro.md
+++ b/docs/guides/int_basics/modals/intro.md
@@ -99,7 +99,7 @@ When we run the command, our modal should pop up:
### Respond to modals
> [!WARNING]
-> Modals can not be sent when respoding to a modal.
+> Modals can not be sent when responding to a modal.
Once a user has submitted the modal, we need to let everyone know what
their favorite food is. We can start by hooking a task to the client's
diff --git a/docs/guides/int_framework/autocompletion.md b/docs/guides/int_framework/autocompletion.md
index 834db2b4f..27da54e36 100644
--- a/docs/guides/int_framework/autocompletion.md
+++ b/docs/guides/int_framework/autocompletion.md
@@ -18,6 +18,8 @@ AutocompleteHandlers raise the `AutocompleteHandlerExecuted` event on execution.
A valid AutocompleteHandlers must inherit [AutocompleteHandler] base type and implement all of its abstract methods.
+[!code-csharp[Autocomplete Command Example](samples/autocompletion/autocomplete-example.cs)]
+
### GenerateSuggestionsAsync()
The Interactions Service uses this method to generate a response of an Autocomplete Interaction.
diff --git a/docs/guides/int_framework/intro.md b/docs/guides/int_framework/intro.md
index abea2a735..54e9086a1 100644
--- a/docs/guides/int_framework/intro.md
+++ b/docs/guides/int_framework/intro.md
@@ -86,6 +86,7 @@ By default, your methods can feature the following parameter types:
- Implementations of [IChannel]
- Implementations of [IRole]
- Implementations of [IMentionable]
+- Implementations of [IAttachment]
- `string`
- `float`, `double`, `decimal`
- `bool`
@@ -158,6 +159,14 @@ Interaction service complex parameter constructors are prioritized in the follow
2. Constuctor tagged with `[ComplexParameterCtor]`.
3. Type's only public constuctor.
+#### DM Permissions
+
+You can use the [EnabledInDmAttribute] to configure whether a globally-scoped top level command should be enabled in Dms or not. Only works on top level commands.
+
+#### Default Member Permissions
+
+[DefaultMemberPermissionsAttribute] can be used when creating a command to set the permissions a user must have to use the command. Permission overwrites can be configured from the Integrations page of Guild Settings. [DefaultMemberPermissionsAttribute] cumulatively propagates down the class hierarchy until it reaches a top level command. This attribute can be only used on top level commands and will not work on commands that are nested in command groups.
+
## User Commands
A valid User Command must have the following structure:
@@ -282,6 +291,8 @@ By nesting commands inside a module that is tagged with [GroupAttribute] you can
> Although creating nested module stuctures are allowed,
> you are not permitted to use more than 2 [GroupAttribute]'s in module hierarchy.
+[!code-csharp[Command Group Example](samples/intro/groupmodule.cs)]
+
## Executing Commands
Any of the following socket events can be used to execute commands:
diff --git a/docs/guides/int_framework/permissions.md b/docs/guides/int_framework/permissions.md
new file mode 100644
index 000000000..e35bb162d
--- /dev/null
+++ b/docs/guides/int_framework/permissions.md
@@ -0,0 +1,59 @@
+---
+uid: Guides.IntFw.Perms
+title: How to handle permissions.
+---
+
+# Permissions
+
+This page covers everything to know about setting up permissions for Slash & context commands.
+
+Application command (Slash, User & Message) permissions are set up at creation.
+When you add your commands to a guild or globally, the permissions will be set up from the attributes you defined.
+
+Commands that are added will only show up for members that meet the required permissions.
+There is no further internal handling, as Discord deals with this on its own.
+
+> [!WARNING]
+> Permissions can only be configured at top level commands. Not in subcommands.
+
+## Disallowing commands in DM
+
+Commands can be blocked from being executed in DM if a guild is required to execute them in as followed:
+
+[!code-csharp[no-DM permission](samples/permissions/guild-only.cs)]
+
+> [!TIP]
+> This attribute only works on global-level commands. Commands that are registered in guilds alone do not have a need for it.
+
+## Server permissions
+
+As previously shown, a command like ban can be blocked from being executed inside DMs,
+as there are no members to ban inside of a DM. However, for a command like this,
+we'll also want to make block it from being used by members that do not have the [permissions].
+To do this, we can use the `DefaultMemberPermissions` attribute:
+
+[!code-csharp[Server permissions](samples/permissions/guild-perms.cs)]
+
+### Stacking permissions
+
+If you want a user to have multiple [permissions] in order to execute a command, you can use the `|` operator, just like with setting up intents:
+
+[!code-csharp[Permission stacking](samples/permissions/perm-stacking.cs)]
+
+### Nesting permissions
+
+Alternatively, permissions can also be nested.
+It will look for all uses of `DefaultMemberPermissions` up until the highest level class.
+The `EnabledInDm` attribute can be defined at top level as well,
+and will be set up for all of the commands & nested modules inside this class.
+
+[!code-csharp[Permission stacking](samples/permissions/perm-nesting.cs)]
+
+The amount of nesting you can do is realistically endless.
+
+> [!NOTE]
+> If the nested class is marked with `Group`, as required for setting up subcommands, this example will not work.
+> As mentioned before, subcommands cannot have seperate permissions from the top level command.
+
+[permissions]: xref:Discord.GuildPermission
+
diff --git a/docs/guides/int_framework/samples/autocompletion/autocomplete-example.cs b/docs/guides/int_framework/samples/autocompletion/autocomplete-example.cs
new file mode 100644
index 000000000..30c0697e1
--- /dev/null
+++ b/docs/guides/int_framework/samples/autocompletion/autocomplete-example.cs
@@ -0,0 +1,20 @@
+// you need to add `Autocomplete` attribute before parameter to add autocompletion to it
+[SlashCommand("command_name", "command_description")]
+public async Task ExampleCommand([Summary("parameter_name"), Autocomplete(typeof(ExampleAutocompleteHandler))] string parameterWithAutocompletion)
+ => await RespondAsync($"Your choice: {parameterWithAutocompletion}");
+
+public class ExampleAutocompleteHandler : AutocompleteHandler
+{
+ public override async Task GenerateSuggestionsAsync(IInteractionContext context, IAutocompleteInteraction autocompleteInteraction, IParameterInfo parameter, IServiceProvider services)
+ {
+ // Create a collection with suggestions for autocomplete
+ IEnumerable results = new[]
+ {
+ new AutocompleteResult("Name1", "value111"),
+ new AutocompleteResult("Name2", "value2")
+ };
+
+ // max - 25 suggestions at a time (API limit)
+ return AutocompletionResult.FromSuccess(results.Take(25));
+ }
+}
\ No newline at end of file
diff --git a/docs/guides/int_framework/samples/intro/autocomplete.cs b/docs/guides/int_framework/samples/intro/autocomplete.cs
index f93c56eaa..11de489f1 100644
--- a/docs/guides/int_framework/samples/intro/autocomplete.cs
+++ b/docs/guides/int_framework/samples/intro/autocomplete.cs
@@ -1,9 +1,21 @@
[AutocompleteCommand("parameter_name", "command_name")]
public async Task Autocomplete()
{
- IEnumerable results;
+ string userInput = (Context.Interaction as SocketAutocompleteInteraction).Data.Current.Value.ToString();
- ...
+ IEnumerable results = new[]
+ {
+ new AutocompleteResult("foo", "foo_value"),
+ new AutocompleteResult("bar", "bar_value"),
+ new AutocompleteResult("baz", "baz_value"),
+ }.Where(x => x.Name.StartsWith(userInput, StringComparison.InvariantCultureIgnoreCase)); // only send suggestions that starts with user's input; use case insensitive matching
- await (Context.Interaction as SocketAutocompleteInteraction).RespondAsync(results);
+
+ // max - 25 suggestions at a time
+ await (Context.Interaction as SocketAutocompleteInteraction).RespondAsync(results.Take(25));
}
+
+// you need to add `Autocomplete` attribute before parameter to add autocompletion to it
+[SlashCommand("command_name", "command_description")]
+public async Task ExampleCommand([Summary("parameter_name"), Autocomplete] string parameterWithAutocompletion)
+ => await RespondAsync($"Your choice: {parameterWithAutocompletion}");
\ No newline at end of file
diff --git a/docs/guides/int_framework/samples/intro/groupmodule.cs b/docs/guides/int_framework/samples/intro/groupmodule.cs
new file mode 100644
index 000000000..f0d992aff
--- /dev/null
+++ b/docs/guides/int_framework/samples/intro/groupmodule.cs
@@ -0,0 +1,21 @@
+// You can put commands in groups
+[Group("group-name", "Group description")]
+public class CommandGroupModule : InteractionModuleBase
+{
+ // This command will look like
+ // group-name ping
+ [SlashCommand("ping", "Get a pong")]
+ public async Task PongSubcommand()
+ => await RespondAsync("Pong!");
+
+ // And even in sub-command groups
+ [Group("subcommand-group-name", "Subcommand group description")]
+ public class SubСommandGroupModule : InteractionModuleBase
+ {
+ // This command will look like
+ // group-name subcommand-group-name echo
+ [SlashCommand("echo", "Echo an input")]
+ public async Task EchoSubcommand(string input)
+ => await RespondAsync(input);
+ }
+}
\ No newline at end of file
diff --git a/docs/guides/int_framework/samples/permissions/guild-only.cs b/docs/guides/int_framework/samples/permissions/guild-only.cs
new file mode 100644
index 000000000..2e907e2d3
--- /dev/null
+++ b/docs/guides/int_framework/samples/permissions/guild-only.cs
@@ -0,0 +1,6 @@
+[EnabledInDm(false)]
+[SlashCommand("ban", "Bans a user in this guild")]
+public async Task BanAsync(...)
+{
+ ...
+}
diff --git a/docs/guides/int_framework/samples/permissions/guild-perms.cs b/docs/guides/int_framework/samples/permissions/guild-perms.cs
new file mode 100644
index 000000000..2853f23e7
--- /dev/null
+++ b/docs/guides/int_framework/samples/permissions/guild-perms.cs
@@ -0,0 +1,7 @@
+[EnabledInDm(false)]
+[DefaultMemberPermissions(GuildPermission.BanMembers)]
+[SlashCommand("ban", "Bans a user in this guild")]
+public async Task BanAsync(...)
+{
+ ...
+}
diff --git a/docs/guides/int_framework/samples/permissions/perm-nesting.cs b/docs/guides/int_framework/samples/permissions/perm-nesting.cs
new file mode 100644
index 000000000..8913b1ac1
--- /dev/null
+++ b/docs/guides/int_framework/samples/permissions/perm-nesting.cs
@@ -0,0 +1,16 @@
+[EnabledInDm(true)]
+[DefaultMemberPermissions(GuildPermission.ViewChannels)]
+public class Module : InteractionModuleBase
+{
+ [DefaultMemberPermissions(GuildPermission.SendMessages)]
+ public class NestedModule : InteractionModuleBase
+ {
+ // While looking for more permissions, it has found 'ViewChannels' and 'SendMessages'. The result of this lookup will be:
+ // ViewChannels + SendMessages + ManageMessages.
+ // If these together are not found for target user, the command will not show up for them.
+ [DefaultMemberPermissions(GuildPermission.ManageMessages)]
+ [SlashCommand("ping", "Pong!")]
+ public async Task Ping()
+ => await RespondAsync("pong");
+ }
+}
diff --git a/docs/guides/int_framework/samples/permissions/perm-stacking.cs b/docs/guides/int_framework/samples/permissions/perm-stacking.cs
new file mode 100644
index 000000000..92cc51477
--- /dev/null
+++ b/docs/guides/int_framework/samples/permissions/perm-stacking.cs
@@ -0,0 +1,4 @@
+[DefaultMemberPermissions(GuildPermission.SendMessages | GuildPermission.ViewChannels)]
+[SlashCommand("ping", "Pong!")]
+public async Task Ping()
+ => await RespondAsync("pong");
diff --git a/docs/guides/toc.yml b/docs/guides/toc.yml
index af0a8e2b4..f122ea6ba 100644
--- a/docs/guides/toc.yml
+++ b/docs/guides/toc.yml
@@ -57,6 +57,8 @@
topicUid: Guides.IntFw.DI
- name: Post-execution Handling
topicUid: Guides.IntFw.PostExecution
+ - name: Permissions
+ topicUid: Guides.IntFw.Perms
- name: Slash Command Basics
items:
- name: Introduction
diff --git a/samples/BasicBot/_BasicBot.csproj b/samples/BasicBot/_BasicBot.csproj
index 6e1a6365f..e6245d340 100644
--- a/samples/BasicBot/_BasicBot.csproj
+++ b/samples/BasicBot/_BasicBot.csproj
@@ -1,12 +1,12 @@
-
+
Exe
- net5.0
+ net6.0
-
+
diff --git a/samples/InteractionFramework/_InteractionFramework.csproj b/samples/InteractionFramework/_InteractionFramework.csproj
index f11c2bd3d..8892a65b7 100644
--- a/samples/InteractionFramework/_InteractionFramework.csproj
+++ b/samples/InteractionFramework/_InteractionFramework.csproj
@@ -2,7 +2,7 @@
Exe
- net5.0
+ net6.0
InteractionFramework
@@ -13,13 +13,7 @@
-
-
-
-
-
-
-
+
diff --git a/samples/MediatRSample/MediatRSample.sln b/samples/MediatRSample/MediatRSample.sln
deleted file mode 100644
index d0599ae26..000000000
--- a/samples/MediatRSample/MediatRSample.sln
+++ /dev/null
@@ -1,16 +0,0 @@
-
-Microsoft Visual Studio Solution File, Format Version 12.00
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediatRSample", "MediatRSample\MediatRSample.csproj", "{CE066EE5-7ED1-42A0-8DB2-862D44F40EA7}"
-EndProject
-Global
- GlobalSection(SolutionConfigurationPlatforms) = preSolution
- Debug|Any CPU = Debug|Any CPU
- Release|Any CPU = Release|Any CPU
- EndGlobalSection
- GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {CE066EE5-7ED1-42A0-8DB2-862D44F40EA7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {CE066EE5-7ED1-42A0-8DB2-862D44F40EA7}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {CE066EE5-7ED1-42A0-8DB2-862D44F40EA7}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {CE066EE5-7ED1-42A0-8DB2-862D44F40EA7}.Release|Any CPU.Build.0 = Release|Any CPU
- EndGlobalSection
-EndGlobal
diff --git a/samples/ShardedClient/_ShardedClient.csproj b/samples/ShardedClient/_ShardedClient.csproj
index 69576ea27..68a43c7cd 100644
--- a/samples/ShardedClient/_ShardedClient.csproj
+++ b/samples/ShardedClient/_ShardedClient.csproj
@@ -2,18 +2,13 @@
Exe
- net5.0
+ net6.0
ShardedClient
-
-
-
-
-
-
+
diff --git a/samples/TextCommandFramework/_TextCommandFramework.csproj b/samples/TextCommandFramework/_TextCommandFramework.csproj
index ee64205f5..6e00625e8 100644
--- a/samples/TextCommandFramework/_TextCommandFramework.csproj
+++ b/samples/TextCommandFramework/_TextCommandFramework.csproj
@@ -2,17 +2,14 @@
Exe
- net5.0
+ net6.0
TextCommandFramework
-
-
-
-
-
+
+
diff --git a/samples/WebhookClient/_WebhookClient.csproj b/samples/WebhookClient/_WebhookClient.csproj
index 91131894d..515fcf3a4 100644
--- a/samples/WebhookClient/_WebhookClient.csproj
+++ b/samples/WebhookClient/_WebhookClient.csproj
@@ -2,12 +2,12 @@
Exe
- net5.0
+ net6.0
WebHookClient
-
+
diff --git a/src/Discord.Net.Commands/Discord.Net.Commands.csproj b/src/Discord.Net.Commands/Discord.Net.Commands.csproj
index fea719016..4fdecd254 100644
--- a/src/Discord.Net.Commands/Discord.Net.Commands.csproj
+++ b/src/Discord.Net.Commands/Discord.Net.Commands.csproj
@@ -7,6 +7,8 @@
A Discord.Net extension adding support for bot commands.
net6.0;net5.0;net461;netstandard2.0;netstandard2.1
net6.0;net5.0;netstandard2.0;netstandard2.1
+ 5
+ True
diff --git a/src/Discord.Net.Commands/Results/MatchResult.cs b/src/Discord.Net.Commands/Results/MatchResult.cs
index fb266efa6..5b9bfe72b 100644
--- a/src/Discord.Net.Commands/Results/MatchResult.cs
+++ b/src/Discord.Net.Commands/Results/MatchResult.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
namespace Discord.Commands
{
@@ -12,7 +12,7 @@ namespace Discord.Commands
///
/// Gets on which pipeline stage the command may have matched or failed.
///
- public IResult? Pipeline { get; }
+ public IResult Pipeline { get; }
///
public CommandError? Error { get; }
@@ -21,7 +21,7 @@ namespace Discord.Commands
///
public bool IsSuccess => !Error.HasValue;
- private MatchResult(CommandMatch? match, IResult? pipeline, CommandError? error, string errorReason)
+ private MatchResult(CommandMatch? match, IResult pipeline, CommandError? error, string errorReason)
{
Match = match;
Error = error;
diff --git a/src/Discord.Net.Core/Discord.Net.Core.csproj b/src/Discord.Net.Core/Discord.Net.Core.csproj
index 783565e04..41d83bbc8 100644
--- a/src/Discord.Net.Core/Discord.Net.Core.csproj
+++ b/src/Discord.Net.Core/Discord.Net.Core.csproj
@@ -7,6 +7,8 @@
The core components for the Discord.Net library.
net6.0;net5.0;net461;netstandard2.0;netstandard2.1
net6.0;net5.0;netstandard2.0;netstandard2.1
+ 5
+ True
diff --git a/src/Discord.Net.Core/DiscordConfig.cs b/src/Discord.Net.Core/DiscordConfig.cs
index 067c55225..2db802f1e 100644
--- a/src/Discord.Net.Core/DiscordConfig.cs
+++ b/src/Discord.Net.Core/DiscordConfig.cs
@@ -132,6 +132,16 @@ namespace Discord
///
public const int MaxAuditLogEntriesPerBatch = 100;
+ ///
+ /// Returns the max number of stickers that can be sent with a message.
+ ///
+ public const int MaxStickersPerMessage = 3;
+
+ ///
+ /// Returns the max number of embeds that can be sent with a message.
+ ///
+ public const int MaxEmbedsPerMessage = 10;
+
///
/// Gets or sets how a request should act in the case of an error, by default.
///
diff --git a/src/Discord.Net.Core/DiscordErrorCode.cs b/src/Discord.Net.Core/DiscordErrorCode.cs
index 51fd736f6..b444614e4 100644
--- a/src/Discord.Net.Core/DiscordErrorCode.cs
+++ b/src/Discord.Net.Core/DiscordErrorCode.cs
@@ -152,6 +152,7 @@ namespace Discord
InvalidMessageType = 50068,
PaymentSourceRequiredForGift = 50070,
CannotDeleteRequiredCommunityChannel = 50074,
+ CannotEditStickersWithinAMessage = 50080,
InvalidSticker = 50081,
CannotExecuteOnArchivedThread = 50083,
InvalidThreadNotificationSettings = 50084,
@@ -164,6 +165,7 @@ namespace Discord
#endregion
#region 2FA (60XXX)
+ MissingPermissionToSendThisSticker = 50600,
Requires2FA = 60003,
#endregion
diff --git a/src/Discord.Net.Core/Entities/Channels/ChannelType.cs b/src/Discord.Net.Core/Entities/Channels/ChannelType.cs
index e60bd5031..15965abc3 100644
--- a/src/Discord.Net.Core/Entities/Channels/ChannelType.cs
+++ b/src/Discord.Net.Core/Entities/Channels/ChannelType.cs
@@ -26,6 +26,8 @@ namespace Discord
/// The channel is a stage voice channel.
Stage = 13,
/// The channel is a guild directory used in hub servers. (Unreleased)
- GuildDirectory = 14
+ GuildDirectory = 14,
+ /// The channel is a forum channel containing multiple threads.
+ Forum = 15
}
}
diff --git a/src/Discord.Net.Core/Entities/Channels/IForumChannel.cs b/src/Discord.Net.Core/Entities/Channels/IForumChannel.cs
new file mode 100644
index 000000000..f4c6da2e2
--- /dev/null
+++ b/src/Discord.Net.Core/Entities/Channels/IForumChannel.cs
@@ -0,0 +1,216 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Discord
+{
+ public interface IForumChannel : IGuildChannel, IMentionable
+ {
+ ///
+ /// Gets a value that indicates whether the channel is NSFW.
+ ///
+ ///
+ /// true if the channel has the NSFW flag enabled; otherwise false.
+ ///
+ bool IsNsfw { get; }
+
+ ///
+ /// Gets the current topic for this text channel.
+ ///
+ ///
+ /// A string representing the topic set in the channel; null if none is set.
+ ///
+ string Topic { get; }
+
+ ///
+ /// Gets the default archive duration for a newly created post.
+ ///
+ ThreadArchiveDuration DefaultAutoArchiveDuration { get; }
+
+ ///
+ /// Gets a collection of tags inside of this forum channel.
+ ///
+ IReadOnlyCollection Tags { get; }
+
+ ///
+ /// Creates a new post (thread) within the forum.
+ ///
+ /// The title of the post.
+ /// The archive duration of the post.
+ /// The slowmode for the posts thread.
+ /// The message to be sent.
+ /// The to be sent.
+ /// The options to be used when sending the request.
+ ///
+ /// Specifies if notifications are sent for mentioned users and roles in the message .
+ /// If null, all mentioned roles and users will be notified.
+ ///
+ /// The message components to be included with this message. Used for interactions.
+ /// A collection of stickers to send with the message.
+ /// A array of s to send with this response. Max 10.
+ /// A message flag to be applied to the sent message, only is permitted.
+ ///
+ /// A task that represents the asynchronous creation operation.
+ ///
+ Task CreatePostAsync(string title, ThreadArchiveDuration archiveDuration = ThreadArchiveDuration.OneDay, int? slowmode = null,
+ string text = null, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null,
+ MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None);
+
+ ///
+ /// Creates a new post (thread) within the forum.
+ ///
+ /// The title of the post.
+ /// The archive duration of the post.
+ /// The slowmode for the posts thread.
+ /// The file path of the file.
+ /// The message to be sent.
+ /// The to be sent.
+ /// The options to be used when sending the request.
+ /// Whether the message attachment should be hidden as a spoiler.
+ ///
+ /// Specifies if notifications are sent for mentioned users and roles in the message .
+ /// If null, all mentioned roles and users will be notified.
+ ///
+ /// The message components to be included with this message. Used for interactions.
+ /// A collection of stickers to send with the file.
+ /// A array of s to send with this response. Max 10.
+ /// A message flag to be applied to the sent message, only is permitted.
+ ///
+ /// A task that represents the asynchronous creation operation.
+ ///
+ Task CreatePostWithFileAsync(string title, string filePath, ThreadArchiveDuration archiveDuration = ThreadArchiveDuration.OneDay,
+ int? slowmode = null, string text = null, Embed embed = null, RequestOptions options = null, bool isSpoiler = false,
+ AllowedMentions allowedMentions = null, MessageComponent components = null,
+ ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None);
+
+ ///
+ /// Creates a new post (thread) within the forum.
+ ///
+ /// The title of the post.
+ /// The of the file to be sent.
+ /// The name of the attachment.
+ /// The archive duration of the post.
+ /// The slowmode for the posts thread.
+ /// The message to be sent.
+ /// The to be sent.
+ /// The options to be used when sending the request.
+ /// Whether the message attachment should be hidden as a spoiler.
+ ///
+ /// Specifies if notifications are sent for mentioned users and roles in the message .
+ /// If null, all mentioned roles and users will be notified.
+ ///
+ /// The message components to be included with this message. Used for interactions.
+ /// A collection of stickers to send with the file.
+ /// A array of s to send with this response. Max 10.
+ /// A message flag to be applied to the sent message, only is permitted.
+ ///
+ /// A task that represents the asynchronous creation operation.
+ ///
+ public Task CreatePostWithFileAsync(string title, Stream stream, string filename, ThreadArchiveDuration archiveDuration = ThreadArchiveDuration.OneDay,
+ int? slowmode = null, string text = null, Embed embed = null, RequestOptions options = null, bool isSpoiler = false,
+ AllowedMentions allowedMentions = null, MessageComponent components = null,
+ ISticker[] stickers = null, Embed[] embeds = null,MessageFlags flags = MessageFlags.None);
+
+ ///
+ /// Creates a new post (thread) within the forum.
+ ///
+ /// The title of the post.
+ /// The attachment containing the file and description.
+ /// The archive duration of the post.
+ /// The slowmode for the posts thread.
+ /// The message to be sent.
+ /// The to be sent.
+ /// The options to be used when sending the request.
+ ///
+ /// Specifies if notifications are sent for mentioned users and roles in the message .
+ /// If null, all mentioned roles and users will be notified.
+ ///
+ /// The message components to be included with this message. Used for interactions.
+ /// A collection of stickers to send with the file.
+ /// A array of s to send with this response. Max 10.
+ /// A message flag to be applied to the sent message, only is permitted.
+ ///
+ /// A task that represents the asynchronous creation operation.
+ ///
+ public Task CreatePostWithFileAsync(string title, FileAttachment attachment, ThreadArchiveDuration archiveDuration = ThreadArchiveDuration.OneDay,
+ int? slowmode = null, string text = null, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null,
+ MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None);
+
+ ///
+ /// Creates a new post (thread) within the forum.
+ ///
+ /// The title of the post.
+ /// A collection of attachments to upload.
+ /// The archive duration of the post.
+ /// The slowmode for the posts thread.
+ /// The message to be sent.
+ /// The to be sent.
+ /// The options to be used when sending the request.
+ ///
+ /// Specifies if notifications are sent for mentioned users and roles in the message .
+ /// If null, all mentioned roles and users will be notified.
+ ///
+ /// The message components to be included with this message. Used for interactions.
+ /// A collection of stickers to send with the file.
+ /// A array of s to send with this response. Max 10.
+ /// A message flag to be applied to the sent message, only is permitted.
+ ///
+ /// A task that represents the asynchronous creation operation.
+ ///
+ public Task CreatePostWithFilesAsync(string title, IEnumerable attachments, ThreadArchiveDuration archiveDuration = ThreadArchiveDuration.OneDay,
+ int? slowmode = null, string text = null, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null,
+ MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None);
+
+ ///
+ /// Gets a collection of active threads within this forum channel.
+ ///
+ /// The options to be used when sending the request.
+ ///
+ /// A task that represents an asynchronous get operation for retrieving the threads. The task result contains
+ /// a collection of active threads.
+ ///
+ Task> GetActiveThreadsAsync(RequestOptions options = null);
+
+ ///
+ /// Gets a collection of publicly archived threads within this forum channel.
+ ///
+ /// The optional limit of how many to get.
+ /// The optional date to return threads created before this timestamp.
+ /// The options to be used when sending the request.
+ ///
+ /// A task that represents an asynchronous get operation for retrieving the threads. The task result contains
+ /// a collection of publicly archived threads.
+ ///
+ Task> GetPublicArchivedThreadsAsync(int? limit = null, DateTimeOffset? before = null, RequestOptions options = null);
+
+ ///
+ /// Gets a collection of privately archived threads within this forum channel.
+ ///
+ ///
+ /// The bot requires the permission in order to execute this request.
+ ///
+ /// The optional limit of how many to get.
+ /// The optional date to return threads created before this timestamp.
+ /// The options to be used when sending the request.
+ ///
+ /// A task that represents an asynchronous get operation for retrieving the threads. The task result contains
+ /// a collection of privately archived threads.
+ ///
+ Task> GetPrivateArchivedThreadsAsync(int? limit = null, DateTimeOffset? before = null, RequestOptions options = null);
+
+ ///
+ /// Gets a collection of privately archived threads that the current bot has joined within this forum channel.
+ ///
+ /// The optional limit of how many to get.
+ /// The optional date to return threads created before this timestamp.
+ /// The options to be used when sending the request.
+ ///
+ /// A task that represents an asynchronous get operation for retrieving the threads. The task result contains
+ /// a collection of privately archived threads.
+ ///
+ Task> GetJoinedPrivateArchivedThreadsAsync(int? limit = null, DateTimeOffset? before = null, RequestOptions options = null);
+ }
+}
diff --git a/src/Discord.Net.Core/Entities/Channels/ITextChannel.cs b/src/Discord.Net.Core/Entities/Channels/ITextChannel.cs
index ae0fe674b..af4e5ec6a 100644
--- a/src/Discord.Net.Core/Entities/Channels/ITextChannel.cs
+++ b/src/Discord.Net.Core/Entities/Channels/ITextChannel.cs
@@ -35,6 +35,17 @@ namespace Discord
///
int SlowModeInterval { get; }
+ ///
+ /// Gets the default auto-archive duration for client-created threads in this channel.
+ ///
+ ///
+ /// The value of this property does not affect API thread creation, it will not respect this value.
+ ///
+ ///
+ /// The default auto-archive duration for thread creation in this channel.
+ ///
+ ThreadArchiveDuration DefaultArchiveDuration { get; }
+
///
/// Bulk-deletes multiple messages.
///
diff --git a/src/Discord.Net.Core/Entities/Channels/IVoiceChannel.cs b/src/Discord.Net.Core/Entities/Channels/IVoiceChannel.cs
index 1d36a41b9..d921a2474 100644
--- a/src/Discord.Net.Core/Entities/Channels/IVoiceChannel.cs
+++ b/src/Discord.Net.Core/Entities/Channels/IVoiceChannel.cs
@@ -6,7 +6,7 @@ namespace Discord
///
/// Represents a generic voice channel in a guild.
///
- public interface IVoiceChannel : INestedChannel, IAudioChannel, IMentionable
+ public interface IVoiceChannel : IMessageChannel, INestedChannel, IAudioChannel, IMentionable
{
///
/// Gets the bit-rate that the clients in this voice channel are requested to use.
diff --git a/src/Discord.Net.Core/Entities/ForumTag.cs b/src/Discord.Net.Core/Entities/ForumTag.cs
new file mode 100644
index 000000000..26ae4301e
--- /dev/null
+++ b/src/Discord.Net.Core/Entities/ForumTag.cs
@@ -0,0 +1,42 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Discord
+{
+ ///
+ /// A struct representing a forum channel tag.
+ ///
+ public struct ForumTag
+ {
+ ///
+ /// Gets the Id of the tag.
+ ///
+ public ulong Id { get; }
+
+ ///
+ /// Gets the name of the tag.
+ ///
+ public string Name { get; }
+
+ ///
+ /// Gets the emoji of the tag or if none is set.
+ ///
+ public IEmote Emoji { get; }
+
+ internal ForumTag(ulong id, string name, ulong? emojiId, string emojiName)
+ {
+ if (emojiId.HasValue && emojiId.Value != 0)
+ Emoji = new Emote(emojiId.Value, emojiName, false);
+ else if (emojiName != null)
+ Emoji = new Emoji(name);
+ else
+ Emoji = null;
+
+ Id = id;
+ Name = name;
+ }
+ }
+}
diff --git a/src/Discord.Net.Core/Entities/Guilds/IGuild.cs b/src/Discord.Net.Core/Entities/Guilds/IGuild.cs
index 4706b629e..775ff9e65 100644
--- a/src/Discord.Net.Core/Entities/Guilds/IGuild.cs
+++ b/src/Discord.Net.Core/Entities/Guilds/IGuild.cs
@@ -1173,7 +1173,6 @@ namespace Discord
/// in order to use this property.
///
///
- /// A collection of speakers for the event.
/// The location of the event; links are supported
/// The optional banner image for the event.
/// The options to be used when sending the request.
diff --git a/src/Discord.Net.Core/Entities/Guilds/IGuildScheduledEvent.cs b/src/Discord.Net.Core/Entities/Guilds/IGuildScheduledEvent.cs
index 4b2fa3bee..7219682b7 100644
--- a/src/Discord.Net.Core/Entities/Guilds/IGuildScheduledEvent.cs
+++ b/src/Discord.Net.Core/Entities/Guilds/IGuildScheduledEvent.cs
@@ -89,7 +89,7 @@ namespace Discord
/// Gets this events banner image url.
///
/// The format to return.
- /// The size of the image to return in. This can be any power of two between 16 and 2048.
+ /// The size of the image to return in. This can be any power of two between 16 and 2048.
/// The cover images url.
string GetCoverImageUrl(ImageFormat format = ImageFormat.Auto, ushort size = 1024);
diff --git a/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandOptionType.cs b/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandOptionType.cs
index 5bb00797b..4506b66d9 100644
--- a/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandOptionType.cs
+++ b/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandOptionType.cs
@@ -56,7 +56,7 @@ namespace Discord
Number = 10,
///
- /// A .
+ /// A .
///
Attachment = 11
}
diff --git a/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandProperties.cs b/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandProperties.cs
index 501a0e905..9b3ac8453 100644
--- a/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandProperties.cs
+++ b/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandProperties.cs
@@ -17,6 +17,16 @@ namespace Discord
///
public Optional IsDefaultPermission { get; set; }
+ ///
+ /// Gets or sets whether or not this command can be used in DMs.
+ ///
+ public Optional IsDMEnabled { get; set; }
+
+ ///
+ /// Gets or sets the default permissions required by a user to execute this application command.
+ ///
+ public Optional DefaultMemberPermissions { get; set; }
+
internal ApplicationCommandProperties() { }
}
}
diff --git a/src/Discord.Net.Core/Entities/Interactions/ContextMenus/MessageCommandBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/ContextMenus/MessageCommandBuilder.cs
index c7a7cf741..59040dd4e 100644
--- a/src/Discord.Net.Core/Entities/Interactions/ContextMenus/MessageCommandBuilder.cs
+++ b/src/Discord.Net.Core/Entities/Interactions/ContextMenus/MessageCommandBuilder.cs
@@ -31,6 +31,16 @@ namespace Discord
///
public bool IsDefaultPermission { get; set; } = true;
+ ///
+ /// Gets or sets whether or not this command can be used in DMs.
+ ///
+ public bool IsDMEnabled { get; set; } = true;
+
+ ///
+ /// Gets or sets the default permission required to use this slash command.
+ ///
+ public GuildPermission? DefaultMemberPermissions { get; set; }
+
private string _name;
///
@@ -44,7 +54,9 @@ namespace Discord
var props = new MessageCommandProperties
{
Name = Name,
- IsDefaultPermission = IsDefaultPermission
+ IsDefaultPermission = IsDefaultPermission,
+ IsDMEnabled = IsDMEnabled,
+ DefaultMemberPermissions = DefaultMemberPermissions ?? Optional.Unspecified
};
return props;
@@ -73,5 +85,27 @@ namespace Discord
IsDefaultPermission = isDefaultPermission;
return this;
}
+
+ ///
+ /// Sets whether or not this command can be used in dms
+ ///
+ /// if the command is available in dms, otherwise .
+ /// The current builder.
+ public MessageCommandBuilder WithDMPermission(bool permission)
+ {
+ IsDMEnabled = permission;
+ return this;
+ }
+
+ ///
+ /// Sets the default member permissions required to use this application command.
+ ///
+ /// The permissions required to use this command.
+ /// The current builder.
+ public MessageCommandBuilder WithDefaultMemberPermissions(GuildPermission? permissions)
+ {
+ DefaultMemberPermissions = permissions;
+ return this;
+ }
}
}
diff --git a/src/Discord.Net.Core/Entities/Interactions/ContextMenus/UserCommandBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/ContextMenus/UserCommandBuilder.cs
index bd1078be3..7c82dce55 100644
--- a/src/Discord.Net.Core/Entities/Interactions/ContextMenus/UserCommandBuilder.cs
+++ b/src/Discord.Net.Core/Entities/Interactions/ContextMenus/UserCommandBuilder.cs
@@ -31,6 +31,16 @@ namespace Discord
///
public bool IsDefaultPermission { get; set; } = true;
+ ///
+ /// Gets or sets whether or not this command can be used in DMs.
+ ///
+ public bool IsDMEnabled { get; set; } = true;
+
+ ///
+ /// Gets or sets the default permission required to use this slash command.
+ ///
+ public GuildPermission? DefaultMemberPermissions { get; set; }
+
private string _name;
///
@@ -42,7 +52,9 @@ namespace Discord
var props = new UserCommandProperties
{
Name = Name,
- IsDefaultPermission = IsDefaultPermission
+ IsDefaultPermission = IsDefaultPermission,
+ IsDMEnabled = IsDMEnabled,
+ DefaultMemberPermissions = DefaultMemberPermissions ?? Optional.Unspecified
};
return props;
@@ -71,5 +83,27 @@ namespace Discord
IsDefaultPermission = isDefaultPermission;
return this;
}
+
+ ///
+ /// Sets whether or not this command can be used in dms
+ ///
+ /// if the command is available in dms, otherwise .
+ /// The current builder.
+ public UserCommandBuilder WithDMPermission(bool permission)
+ {
+ IsDMEnabled = permission;
+ return this;
+ }
+
+ ///
+ /// Sets the default member permissions required to use this application command.
+ ///
+ /// The permissions required to use this command.
+ /// The current builder.
+ public UserCommandBuilder WithDefaultMemberPermissions(GuildPermission? permissions)
+ {
+ DefaultMemberPermissions = permissions;
+ return this;
+ }
}
}
diff --git a/src/Discord.Net.Core/Entities/Interactions/IApplicationCommand.cs b/src/Discord.Net.Core/Entities/Interactions/IApplicationCommand.cs
index 72045a52a..58a002649 100644
--- a/src/Discord.Net.Core/Entities/Interactions/IApplicationCommand.cs
+++ b/src/Discord.Net.Core/Entities/Interactions/IApplicationCommand.cs
@@ -34,6 +34,19 @@ namespace Discord
///
bool IsDefaultPermission { get; }
+ ///
+ /// Indicates whether the command is available in DMs with the app.
+ ///
+ ///
+ /// Only for globally-scoped commands.
+ ///
+ bool IsEnabledInDm { get; }
+
+ ///
+ /// Set of default required to invoke the command.
+ ///
+ GuildPermissions DefaultMemberPermissions { get; }
+
///
/// Gets a collection of options for this application command.
///
diff --git a/src/Discord.Net.Core/Entities/Interactions/IDiscordInteraction.cs b/src/Discord.Net.Core/Entities/Interactions/IDiscordInteraction.cs
index 8f6bef995..a2dbe0e5f 100644
--- a/src/Discord.Net.Core/Entities/Interactions/IDiscordInteraction.cs
+++ b/src/Discord.Net.Core/Entities/Interactions/IDiscordInteraction.cs
@@ -52,10 +52,13 @@ namespace Discord
///
/// Gets the preferred locale of the invoking User.
///
+ ///
+ /// This property returns if the interaction is a REST ping interaction.
+ ///
string UserLocale { get; }
///
- /// Gets the preferred locale of the guild this interaction was executed in. if not executed in a guild.
+ /// Gets the preferred locale of the guild this interaction was executed in. if not executed in a guild.
///
///
/// Non-community guilds (With no locale setting available) will have en-US as the default value sent by Discord.
@@ -67,6 +70,27 @@ namespace Discord
///
bool IsDMInteraction { get; }
+ ///
+ /// Gets the ID of the channel this interaction was executed in.
+ ///
+ ///
+ /// This property returns if the interaction is a REST ping interaction.
+ ///
+ ulong? ChannelId { get; }
+
+ ///
+ /// Gets the ID of the guild this interaction was executed in.
+ ///
+ ///
+ /// This property returns if the interaction was not executed in a guild.
+ ///
+ ulong? GuildId { get; }
+
+ ///
+ /// Gets the ID of the application this interaction is for.
+ ///
+ ulong ApplicationId { get; }
+
///
/// Responds to an Interaction with type .
///
diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ComponentBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ComponentBuilder.cs
index 7becca0e0..37342b039 100644
--- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ComponentBuilder.cs
+++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ComponentBuilder.cs
@@ -195,7 +195,7 @@ namespace Discord
///
/// The button to add.
/// The row to add the button.
- /// There is no more row to add a menu.
+ /// There is no more row to add a button.
/// must be less than .
/// The current builder.
public ComponentBuilder WithButton(ButtonBuilder button, int row = 0)
@@ -348,6 +348,100 @@ namespace Discord
return this;
}
+ ///
+ /// Adds a to the .
+ ///
+ /// The custom id of the menu.
+ /// The options of the menu.
+ /// The placeholder of the menu.
+ /// The min values of the placeholder.
+ /// The max values of the placeholder.
+ /// Whether or not the menu is disabled.
+ /// The current builder.
+ public ActionRowBuilder WithSelectMenu(string customId, List options,
+ string placeholder = null, int minValues = 1, int maxValues = 1, bool disabled = false)
+ {
+ return WithSelectMenu(new SelectMenuBuilder()
+ .WithCustomId(customId)
+ .WithOptions(options)
+ .WithPlaceholder(placeholder)
+ .WithMaxValues(maxValues)
+ .WithMinValues(minValues)
+ .WithDisabled(disabled));
+ }
+
+ ///
+ /// Adds a to the .
+ ///
+ /// The menu to add.
+ /// A Select Menu cannot exist in a pre-occupied ActionRow.
+ /// The current builder.
+ public ActionRowBuilder WithSelectMenu(SelectMenuBuilder menu)
+ {
+ if (menu.Options.Distinct().Count() != menu.Options.Count)
+ throw new InvalidOperationException("Please make sure that there is no duplicates values.");
+
+ var builtMenu = menu.Build();
+
+ if (Components.Count != 0)
+ throw new InvalidOperationException($"A Select Menu cannot exist in a pre-occupied ActionRow.");
+
+ AddComponent(builtMenu);
+
+ return this;
+ }
+
+ ///
+ /// Adds a with specified parameters to the .
+ ///
+ /// The label text for the newly added button.
+ /// The style of this newly added button.
+ /// A to be used with this button.
+ /// The custom id of the newly added button.
+ /// A URL to be used only if the is a Link.
+ /// Whether or not the newly created button is disabled.
+ /// The current builder.
+ public ActionRowBuilder WithButton(
+ string label = null,
+ string customId = null,
+ ButtonStyle style = ButtonStyle.Primary,
+ IEmote emote = null,
+ string url = null,
+ bool disabled = false)
+ {
+ var button = new ButtonBuilder()
+ .WithLabel(label)
+ .WithStyle(style)
+ .WithEmote(emote)
+ .WithCustomId(customId)
+ .WithUrl(url)
+ .WithDisabled(disabled);
+
+ return WithButton(button);
+ }
+
+ ///
+ /// Adds a to the .
+ ///
+ /// The button to add.
+ /// Components count reached .
+ /// A button cannot be added to a row with a SelectMenu.
+ /// The current builder.
+ public ActionRowBuilder WithButton(ButtonBuilder button)
+ {
+ var builtButton = button.Build();
+
+ if(Components.Count >= 5)
+ throw new InvalidOperationException($"Components count reached {MaxChildCount}");
+
+ if (Components.Any(x => x.Type == ComponentType.SelectMenu))
+ throw new InvalidOperationException($"A button cannot be added to a row with a SelectMenu");
+
+ AddComponent(builtButton);
+
+ return this;
+ }
+
///
/// Builds the current builder to a that can be used within a
///
@@ -1194,9 +1288,9 @@ namespace Discord
///
/// Gets or sets the default value of the text input.
///
- /// is less than 0.
+ /// .Length is less than 0.
///
- /// is greater than or .
+ /// .Length is greater than or .
///
public string Value
{
@@ -1227,7 +1321,7 @@ namespace Discord
/// The text input's minimum length.
/// The text input's maximum length.
/// The text input's required value.
- public TextInputBuilder (string label, string customId, TextInputStyle style = TextInputStyle.Short, string placeholder = null,
+ public TextInputBuilder(string label, string customId, TextInputStyle style = TextInputStyle.Short, string placeholder = null,
int? minLength = null, int? maxLength = null, bool? required = null, string value = null)
{
Label = label;
@@ -1291,7 +1385,7 @@ namespace Discord
Placeholder = placeholder;
return this;
}
-
+
///
/// Sets the value of the current builder.
///
@@ -1306,18 +1400,18 @@ namespace Discord
///
/// Sets the minimum length of the current builder.
///
- /// The value to set.
+ /// The value to set.
/// The current builder.
public TextInputBuilder WithMinLength(int minLength)
{
MinLength = minLength;
return this;
}
-
+
///
/// Sets the maximum length of the current builder.
///
- /// The value to set.
+ /// The value to set.
/// The current builder.
public TextInputBuilder WithMaxLength(int maxLength)
{
diff --git a/src/Discord.Net.Core/Entities/Interactions/Modals/ModalBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/Modals/ModalBuilder.cs
index 3a3e3cc49..817f69415 100644
--- a/src/Discord.Net.Core/Entities/Interactions/Modals/ModalBuilder.cs
+++ b/src/Discord.Net.Core/Entities/Interactions/Modals/ModalBuilder.cs
@@ -64,18 +64,18 @@ namespace Discord
///
/// Sets the custom id of the current modal.
///
- /// The value to set the custom id to.
+ /// The value to set the custom id to.
/// The current builder.
public ModalBuilder WithCustomId(string customId)
{
CustomId = customId;
return this;
}
-
+
///
/// Adds a component to the current builder.
///
- /// The component to add.
+ /// The component to add.
/// The current builder.
public ModalBuilder AddTextInput(TextInputBuilder component)
{
@@ -213,7 +213,7 @@ namespace Discord
/// Adds a to the at the specific row.
/// If the row cannot accept the component then it will add it to a row that can.
///
- /// The to add.
+ /// The to add.
/// The row to add the text input.
/// There are no more rows to add a text input to.
/// must be less than .
diff --git a/src/Discord.Net.Core/Entities/Interactions/SlashCommands/SlashCommandBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/SlashCommands/SlashCommandBuilder.cs
index ccfb2da0a..d7d086762 100644
--- a/src/Discord.Net.Core/Entities/Interactions/SlashCommands/SlashCommandBuilder.cs
+++ b/src/Discord.Net.Core/Entities/Interactions/SlashCommands/SlashCommandBuilder.cs
@@ -81,6 +81,16 @@ namespace Discord
///
public bool IsDefaultPermission { get; set; } = true;
+ ///
+ /// Gets or sets whether or not this command can be used in DMs.
+ ///
+ public bool IsDMEnabled { get; set; } = true;
+
+ ///
+ /// Gets or sets the default permission required to use this slash command.
+ ///
+ public GuildPermission? DefaultMemberPermissions { get; set; }
+
private string _name;
private string _description;
private List _options;
@@ -96,6 +106,8 @@ namespace Discord
Name = Name,
Description = Description,
IsDefaultPermission = IsDefaultPermission,
+ IsDMEnabled = IsDMEnabled,
+ DefaultMemberPermissions = DefaultMemberPermissions ?? Optional.Unspecified
};
if (Options != null && Options.Any())
@@ -145,6 +157,28 @@ namespace Discord
return this;
}
+ ///
+ /// Sets whether or not this command can be used in dms
+ ///
+ /// if the command is available in dms, otherwise .
+ /// The current builder.
+ public SlashCommandBuilder WithDMPermission(bool permission)
+ {
+ IsDMEnabled = permission;
+ return this;
+ }
+
+ ///
+ /// Sets the default member permissions required to use this application command.
+ ///
+ /// The permissions required to use this command.
+ /// The current builder.
+ public SlashCommandBuilder WithDefaultMemberPermissions(GuildPermission? permissions)
+ {
+ DefaultMemberPermissions = permissions;
+ return this;
+ }
+
///
/// Adds an option to the current slash command.
///
@@ -164,21 +198,13 @@ namespace Discord
string description, bool? isRequired = null, bool? isDefault = null, bool isAutocomplete = false, double? minValue = null, double? maxValue = null,
List options = null, List channelTypes = null, params ApplicationCommandOptionChoiceProperties[] choices)
{
- // Make sure the name matches the requirements from discord
- Preconditions.NotNullOrEmpty(name, nameof(name));
- Preconditions.AtLeast(name.Length, 1, nameof(name));
- Preconditions.AtMost(name.Length, MaxNameLength, nameof(name));
+ Preconditions.Options(name, description);
// Discord updated the docs, this regex prevents special characters like @!$%( and s p a c e s.. etc,
// https://discord.com/developers/docs/interactions/slash-commands#applicationcommand
if (!Regex.IsMatch(name, @"^[\w-]{1,32}$"))
throw new ArgumentException("Command name cannot contain any special characters or whitespaces!", nameof(name));
- // same with description
- Preconditions.NotNullOrEmpty(description, nameof(description));
- Preconditions.AtLeast(description.Length, 1, nameof(description));
- Preconditions.AtMost(description.Length, MaxDescriptionLength, nameof(description));
-
// make sure theres only one option with default set to true
if (isDefault == true && Options?.Any(x => x.IsDefault == true) == true)
throw new ArgumentException("There can only be one command option with default set to true!", nameof(isDefault));
@@ -214,6 +240,7 @@ namespace Discord
throw new InvalidOperationException($"Cannot have more than {MaxOptionsCount} options!");
Preconditions.NotNull(option, nameof(option));
+ Preconditions.Options(option.Name, option.Description); // this is a double-check when this method is called via AddOption(string name... )
Options.Add(option);
return this;
@@ -228,14 +255,14 @@ namespace Discord
if (options == null)
throw new ArgumentNullException(nameof(options), "Options cannot be null!");
- if (options.Length == 0)
- throw new ArgumentException("Options cannot be empty!", nameof(options));
-
Options ??= new List();
if (Options.Count + options.Length > MaxOptionsCount)
throw new ArgumentOutOfRangeException(nameof(options), $"Cannot have more than {MaxOptionsCount} options!");
+ foreach (var option in options)
+ Preconditions.Options(option.Name, option.Description);
+
Options.AddRange(options);
return this;
}
@@ -400,21 +427,13 @@ namespace Discord
string description, bool? isRequired = null, bool isDefault = false, bool isAutocomplete = false, double? minValue = null, double? maxValue = null,
List options = null, List channelTypes = null, params ApplicationCommandOptionChoiceProperties[] choices)
{
- // Make sure the name matches the requirements from discord
- Preconditions.NotNullOrEmpty(name, nameof(name));
- Preconditions.AtLeast(name.Length, 1, nameof(name));
- Preconditions.AtMost(name.Length, SlashCommandBuilder.MaxNameLength, nameof(name));
+ Preconditions.Options(name, description);
// Discord updated the docs, this regex prevents special characters like @!$%( and s p a c e s.. etc,
// https://discord.com/developers/docs/interactions/slash-commands#applicationcommand
if (!Regex.IsMatch(name, @"^[\w-]{1,32}$"))
throw new ArgumentException("Command name cannot contain any special characters or whitespaces!", nameof(name));
- // same with description
- Preconditions.NotNullOrEmpty(description, nameof(description));
- Preconditions.AtLeast(description.Length, 1, nameof(description));
- Preconditions.AtMost(description.Length, SlashCommandBuilder.MaxDescriptionLength, nameof(description));
-
// make sure theres only one option with default set to true
if (isDefault && Options?.Any(x => x.IsDefault == true) == true)
throw new ArgumentException("There can only be one command option with default set to true!", nameof(isDefault));
@@ -449,11 +468,32 @@ namespace Discord
throw new InvalidOperationException($"There can only be {SlashCommandBuilder.MaxOptionsCount} options per sub command group!");
Preconditions.NotNull(option, nameof(option));
+ Preconditions.Options(option.Name, option.Description); // double check again
Options.Add(option);
return this;
}
+ ///
+ /// Adds a collection of options to the current option.
+ ///
+ /// The collection of options to add.
+ /// The current builder.
+ public SlashCommandOptionBuilder AddOptions(params SlashCommandOptionBuilder[] options)
+ {
+ if (options == null)
+ throw new ArgumentNullException(nameof(options), "Options cannot be null!");
+
+ if ((Options?.Count ?? 0) + options.Length > SlashCommandBuilder.MaxOptionsCount)
+ throw new ArgumentOutOfRangeException(nameof(options), $"There can only be {SlashCommandBuilder.MaxOptionsCount} options per sub command group!");
+
+ foreach (var option in options)
+ Preconditions.Options(option.Name, option.Description);
+
+ Options.AddRange(options);
+ return this;
+ }
+
///
/// Adds a choice to the current option.
///
@@ -617,7 +657,7 @@ namespace Discord
MinValue = value;
return this;
}
-
+
///
/// Sets the current builders max value field.
///
diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs b/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs
index 0304120f5..1e2a7b0d7 100644
--- a/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs
+++ b/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Discord.Utils;
+using Newtonsoft.Json;
namespace Discord
{
@@ -155,6 +156,55 @@ namespace Discord
}
}
+ ///
+ /// Tries to parse a string into an .
+ ///
+ /// The json string to parse.
+ /// The with populated values. An empty instance if method returns .
+ /// if was succesfully parsed. if not.
+ public static bool TryParse(string json, out EmbedBuilder builder)
+ {
+ builder = new EmbedBuilder();
+ try
+ {
+ var model = JsonConvert.DeserializeObject
/// The format to return.
- /// The size of the image to return in. This can be any power of two between 16 and 2048.
+ /// The size of the image to return in. This can be any power of two between 16 and 2048.
///
/// A string representing the URL of the displayed avatar for this user. if the user does not have an avatar in place.
///
diff --git a/src/Discord.Net.Core/Format.cs b/src/Discord.Net.Core/Format.cs
index dc2a06540..d9ad43f0d 100644
--- a/src/Discord.Net.Core/Format.cs
+++ b/src/Discord.Net.Core/Format.cs
@@ -37,8 +37,9 @@ namespace Discord
/// Sanitizes the string, safely escaping any Markdown sequences.
public static string Sanitize(string text)
{
- foreach (string unsafeChar in SensitiveCharacters)
- text = text.Replace(unsafeChar, $"\\{unsafeChar}");
+ if (text != null)
+ foreach (string unsafeChar in SensitiveCharacters)
+ text = text.Replace(unsafeChar, $"\\{unsafeChar}");
return text;
}
diff --git a/src/Discord.Net.Core/Interactions/IRouteMatchContainer.cs b/src/Discord.Net.Core/Interactions/IRouteMatchContainer.cs
new file mode 100644
index 000000000..f9a3a3183
--- /dev/null
+++ b/src/Discord.Net.Core/Interactions/IRouteMatchContainer.cs
@@ -0,0 +1,24 @@
+using System.Collections.Generic;
+
+namespace Discord
+{
+ ///
+ /// Represents a container for temporarily storing CustomId wild card matches of a component.
+ ///
+ public interface IRouteMatchContainer
+ {
+ ///
+ /// Gets the collection of captured route segments in this container.
+ ///
+ ///
+ /// A collection of captured route segments.
+ ///
+ IEnumerable SegmentMatches { get; }
+
+ ///
+ /// Sets the property of this container.
+ ///
+ /// The collection of captured route segments.
+ void SetSegmentMatches(IEnumerable segmentMatches);
+ }
+}
diff --git a/src/Discord.Net.Core/Interactions/IRouteSegmentMatch.cs b/src/Discord.Net.Core/Interactions/IRouteSegmentMatch.cs
new file mode 100644
index 000000000..675bd6754
--- /dev/null
+++ b/src/Discord.Net.Core/Interactions/IRouteSegmentMatch.cs
@@ -0,0 +1,16 @@
+namespace Discord
+{
+ ///
+ /// Represents an object for storing a CustomId wild card match.
+ ///
+ public interface IRouteSegmentMatch
+ {
+ ///
+ /// Gets the captured value of this wild card match.
+ ///
+ ///
+ /// The value of this wild card.
+ ///
+ string Value { get; }
+ }
+}
diff --git a/src/Discord.Net.Core/Interactions/RouteSegmentMatch.cs b/src/Discord.Net.Core/Interactions/RouteSegmentMatch.cs
new file mode 100644
index 000000000..f1d80cfea
--- /dev/null
+++ b/src/Discord.Net.Core/Interactions/RouteSegmentMatch.cs
@@ -0,0 +1,16 @@
+namespace Discord
+{
+ ///
+ /// Represents an object for storing a CustomId wild card match.
+ ///
+ internal record RouteSegmentMatch : IRouteSegmentMatch
+ {
+ ///
+ public string Value { get; }
+
+ public RouteSegmentMatch(string value)
+ {
+ Value = value;
+ }
+ }
+}
diff --git a/src/Discord.Net.Core/Utils/Preconditions.cs b/src/Discord.Net.Core/Utils/Preconditions.cs
index ff8eb7c0d..2f24e660d 100644
--- a/src/Discord.Net.Core/Utils/Preconditions.cs
+++ b/src/Discord.Net.Core/Utils/Preconditions.cs
@@ -297,5 +297,22 @@ namespace Discord
}
}
#endregion
+
+ #region SlashCommandOptions
+
+ /// or is null.
+ /// or are either empty or their length exceed limits.
+ public static void Options(string name, string description)
+ {
+ // Make sure the name matches the requirements from discord
+ NotNullOrEmpty(name, nameof(name));
+ NotNullOrEmpty(description, nameof(description));
+ AtLeast(name.Length, 1, nameof(name));
+ AtMost(name.Length, SlashCommandBuilder.MaxNameLength, nameof(name));
+ AtLeast(description.Length, 1, nameof(description));
+ AtMost(description.Length, SlashCommandBuilder.MaxDescriptionLength, nameof(description));
+ }
+
+ #endregion
}
}
diff --git a/src/Discord.Net.Core/Utils/UrlValidation.cs b/src/Discord.Net.Core/Utils/UrlValidation.cs
index 8e877bd4e..55ae3bdf7 100644
--- a/src/Discord.Net.Core/Utils/UrlValidation.cs
+++ b/src/Discord.Net.Core/Utils/UrlValidation.cs
@@ -23,7 +23,7 @@ namespace Discord.Utils
///
/// Not full URL validation right now. Just Ensures the protocol is either http, https, or discord
- /// should be used everything other than url buttons.
+ /// should be used everything other than url buttons.
///
/// The URL to validate before sending to discord.
/// A URL must include a protocol (either http, https, or discord).
diff --git a/src/Discord.Net.Examples/Discord.Net.Examples.csproj b/src/Discord.Net.Examples/Discord.Net.Examples.csproj
index b4a336f9f..1bdca7992 100644
--- a/src/Discord.Net.Examples/Discord.Net.Examples.csproj
+++ b/src/Discord.Net.Examples/Discord.Net.Examples.csproj
@@ -1,7 +1,7 @@
- net5.0
+ net6.0
diff --git a/src/Discord.Net.Interactions/Attributes/AutocompleteAttribute.cs b/src/Discord.Net.Interactions/Attributes/AutocompleteAttribute.cs
index e17c9ff14..c8a3428db 100644
--- a/src/Discord.Net.Interactions/Attributes/AutocompleteAttribute.cs
+++ b/src/Discord.Net.Interactions/Attributes/AutocompleteAttribute.cs
@@ -3,7 +3,7 @@ using System;
namespace Discord.Interactions
{
///
- /// Set the to .
+ /// Set the to .
///
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)]
public class AutocompleteAttribute : Attribute
@@ -14,7 +14,7 @@ namespace Discord.Interactions
public Type AutocompleteHandlerType { get; }
///
- /// Set the to and define a to handle
+ /// Set the to and define a to handle
/// Autocomplete interactions targeting the parameter this is applied to.
///
///
@@ -29,7 +29,7 @@ namespace Discord.Interactions
}
///
- /// Set the to without specifying a .
+ /// Set the to without specifying a .
///
public AutocompleteAttribute() { }
}
diff --git a/src/Discord.Net.Interactions/Attributes/DefaultMemberPermissionAttribute.cs b/src/Discord.Net.Interactions/Attributes/DefaultMemberPermissionAttribute.cs
new file mode 100644
index 000000000..ec79da1e3
--- /dev/null
+++ b/src/Discord.Net.Interactions/Attributes/DefaultMemberPermissionAttribute.cs
@@ -0,0 +1,25 @@
+using System;
+
+namespace Discord.Interactions
+{
+ ///
+ /// Sets the of an application command or module.
+ ///
+ [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
+ public class DefaultMemberPermissionsAttribute : Attribute
+ {
+ ///
+ /// Gets the default permission required to use this command.
+ ///
+ public GuildPermission Permissions { get; }
+
+ ///
+ /// Sets the of an application command or module.
+ ///
+ /// The default permission required to use this command.
+ public DefaultMemberPermissionsAttribute(GuildPermission permissions)
+ {
+ Permissions = permissions;
+ }
+ }
+}
diff --git a/src/Discord.Net.Interactions/Attributes/DefaultPermissionAttribute.cs b/src/Discord.Net.Interactions/Attributes/DefaultPermissionAttribute.cs
index ed0a532be..2e03dfac6 100644
--- a/src/Discord.Net.Interactions/Attributes/DefaultPermissionAttribute.cs
+++ b/src/Discord.Net.Interactions/Attributes/DefaultPermissionAttribute.cs
@@ -6,6 +6,7 @@ namespace Discord.Interactions
/// Set the "Default Permission" property of an Application Command.
///
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
+ [Obsolete($"Soon to be deprecated, use Permissions-v2 attributes like {nameof(EnabledInDmAttribute)} and {nameof(DefaultMemberPermissionsAttribute)}")]
public class DefaultPermissionAttribute : Attribute
{
///
diff --git a/src/Discord.Net.Interactions/Attributes/EnabledInDmAttribute.cs b/src/Discord.Net.Interactions/Attributes/EnabledInDmAttribute.cs
new file mode 100644
index 000000000..a97f85a25
--- /dev/null
+++ b/src/Discord.Net.Interactions/Attributes/EnabledInDmAttribute.cs
@@ -0,0 +1,25 @@
+using System;
+
+namespace Discord.Interactions
+{
+ ///
+ /// Sets the property of an application command or module.
+ ///
+ [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
+ public class EnabledInDmAttribute : Attribute
+ {
+ ///
+ /// Gets whether or not this command can be used in DMs.
+ ///
+ public bool IsEnabled { get; }
+
+ ///
+ /// Sets the property of an application command or module.
+ ///
+ /// Whether or not this command can be used in DMs.
+ public EnabledInDmAttribute(bool isEnabled)
+ {
+ IsEnabled = isEnabled;
+ }
+ }
+}
diff --git a/src/Discord.Net.Interactions/Attributes/Modals/ModalInputAttribute.cs b/src/Discord.Net.Interactions/Attributes/Modals/ModalInputAttribute.cs
index d611b574d..e9b877268 100644
--- a/src/Discord.Net.Interactions/Attributes/Modals/ModalInputAttribute.cs
+++ b/src/Discord.Net.Interactions/Attributes/Modals/ModalInputAttribute.cs
@@ -21,9 +21,7 @@ namespace Discord.Interactions
///
/// Create a new .
///
- /// The label of the input.
/// The custom id of the input.
- /// Whether the user is required to input a value.>
protected ModalInputAttribute(string customId)
{
CustomId = customId;
diff --git a/src/Discord.Net.Interactions/Attributes/Modals/ModalTextInputAttribute.cs b/src/Discord.Net.Interactions/Attributes/Modals/ModalTextInputAttribute.cs
index 35121cd6b..4439e1d84 100644
--- a/src/Discord.Net.Interactions/Attributes/Modals/ModalTextInputAttribute.cs
+++ b/src/Discord.Net.Interactions/Attributes/Modals/ModalTextInputAttribute.cs
@@ -36,7 +36,7 @@ namespace Discord.Interactions
///
/// Create a new .
///
- ///
+ /// The custom id of the text input.>
/// The style of the text input.
/// The placeholder of the text input.
/// The minimum length of the text input's content.
diff --git a/src/Discord.Net.Interactions/Attributes/Preconditions/RequireUserPermissionAttribute.cs b/src/Discord.Net.Interactions/Attributes/Preconditions/RequireUserPermissionAttribute.cs
index 77d6e8f25..0f6ecfc66 100644
--- a/src/Discord.Net.Interactions/Attributes/Preconditions/RequireUserPermissionAttribute.cs
+++ b/src/Discord.Net.Interactions/Attributes/Preconditions/RequireUserPermissionAttribute.cs
@@ -29,7 +29,7 @@ namespace Discord.Interactions
///
/// This precondition will always fail if the command is being invoked in a .
///
- ///
+ ///
/// The that the user must have. Multiple permissions can be
/// specified by ORing the permissions together.
///
@@ -41,7 +41,7 @@ namespace Discord.Interactions
///
/// Requires that the user invoking the command to have a specific .
///
- ///
+ ///
/// The that the user must have. Multiple permissions can be
/// specified by ORing the permissions together.
///
diff --git a/src/Discord.Net.Interactions/Builders/Commands/ContextCommandBuilder.cs b/src/Discord.Net.Interactions/Builders/Commands/ContextCommandBuilder.cs
index d40547b3c..be0e5eb70 100644
--- a/src/Discord.Net.Interactions/Builders/Commands/ContextCommandBuilder.cs
+++ b/src/Discord.Net.Interactions/Builders/Commands/ContextCommandBuilder.cs
@@ -17,8 +17,19 @@ namespace Discord.Interactions.Builders
///
/// Gets the default permission of this command.
///
+ [Obsolete($"To be deprecated soon, use {nameof(IsEnabledInDm)} and {nameof(DefaultMemberPermissions)} instead.")]
public bool DefaultPermission { get; set; } = true;
+ ///
+ /// Gets whether this command can be used in DMs.
+ ///
+ public bool IsEnabledInDm { get; set; } = true;
+
+ ///
+ /// Gets the default permissions needed for executing this command.
+ ///
+ public GuildPermission? DefaultMemberPermissions { get; set; } = null;
+
internal ContextCommandBuilder (ModuleBuilder module) : base(module) { }
///
@@ -49,6 +60,7 @@ namespace Discord.Interactions.Builders
///
/// The builder instance.
///
+ [Obsolete($"To be deprecated soon, use {nameof(SetEnabledInDm)} and {nameof(WithDefaultMemberPermissions)} instead.")]
public ContextCommandBuilder SetDefaultPermission (bool defaultPermision)
{
DefaultPermission = defaultPermision;
@@ -70,6 +82,32 @@ namespace Discord.Interactions.Builders
return this;
}
+ ///
+ /// Sets .
+ ///
+ /// New value of the .
+ ///
+ /// The builder instance.
+ ///
+ public ContextCommandBuilder SetEnabledInDm(bool isEnabled)
+ {
+ IsEnabledInDm = isEnabled;
+ return this;
+ }
+
+ ///
+ /// Sets .
+ ///
+ /// New value of the .
+ ///
+ /// The builder instance.
+ ///
+ public ContextCommandBuilder WithDefaultMemberPermissions(GuildPermission permissions)
+ {
+ DefaultMemberPermissions = permissions;
+ return this;
+ }
+
internal override ContextCommandInfo Build (ModuleInfo module, InteractionService commandService) =>
ContextCommandInfo.Create(this, module, commandService);
}
diff --git a/src/Discord.Net.Interactions/Builders/Commands/SlashCommandBuilder.cs b/src/Discord.Net.Interactions/Builders/Commands/SlashCommandBuilder.cs
index d8e9b0658..c21fd5ae8 100644
--- a/src/Discord.Net.Interactions/Builders/Commands/SlashCommandBuilder.cs
+++ b/src/Discord.Net.Interactions/Builders/Commands/SlashCommandBuilder.cs
@@ -17,8 +17,19 @@ namespace Discord.Interactions.Builders
///
/// Gets and sets the default permission of this command.
///
+ [Obsolete($"To be deprecated soon, use {nameof(IsEnabledInDm)} and {nameof(DefaultMemberPermissions)} instead.")]
public bool DefaultPermission { get; set; } = true;
+ ///
+ /// Gets whether this command can be used in DMs.
+ ///
+ public bool IsEnabledInDm { get; set; } = true;
+
+ ///
+ /// Gets the default permissions needed for executing this command.
+ ///
+ public GuildPermission? DefaultMemberPermissions { get; set; } = null;
+
internal SlashCommandBuilder (ModuleBuilder module) : base(module) { }
///
@@ -45,10 +56,11 @@ namespace Discord.Interactions.Builders
///
/// Sets .
///
- /// New value of the .
+ /// New value of the .
///
/// The builder instance.
///
+ [Obsolete($"To be deprecated soon, use {nameof(SetEnabledInDm)} and {nameof(WithDefaultMemberPermissions)} instead.")]
public SlashCommandBuilder WithDefaultPermission (bool permission)
{
DefaultPermission = permission;
@@ -70,6 +82,32 @@ namespace Discord.Interactions.Builders
return this;
}
+ ///
+ /// Sets .
+ ///
+ /// New value of the .
+ ///
+ /// The builder instance.
+ ///
+ public SlashCommandBuilder SetEnabledInDm(bool isEnabled)
+ {
+ IsEnabledInDm = isEnabled;
+ return this;
+ }
+
+ ///
+ /// Sets .
+ ///
+ /// New value of the .
+ ///
+ /// The builder instance.
+ ///
+ public SlashCommandBuilder WithDefaultMemberPermissions(GuildPermission permissions)
+ {
+ DefaultMemberPermissions = permissions;
+ return this;
+ }
+
internal override SlashCommandInfo Build (ModuleInfo module, InteractionService commandService) =>
new SlashCommandInfo(this, module, commandService);
}
diff --git a/src/Discord.Net.Interactions/Builders/Modals/Inputs/TextInputComponentBuilder.cs b/src/Discord.Net.Interactions/Builders/Modals/Inputs/TextInputComponentBuilder.cs
index 340119ddd..8dd2c4004 100644
--- a/src/Discord.Net.Interactions/Builders/Modals/Inputs/TextInputComponentBuilder.cs
+++ b/src/Discord.Net.Interactions/Builders/Modals/Inputs/TextInputComponentBuilder.cs
@@ -41,7 +41,7 @@ namespace Discord.Interactions.Builders
///
/// Sets .
///
- /// New value of the .
+ /// New value of the .
///
/// The builder instance.
///
diff --git a/src/Discord.Net.Interactions/Builders/Modals/ModalBuilder.cs b/src/Discord.Net.Interactions/Builders/Modals/ModalBuilder.cs
index fc1dbdc0e..c13ff40de 100644
--- a/src/Discord.Net.Interactions/Builders/Modals/ModalBuilder.cs
+++ b/src/Discord.Net.Interactions/Builders/Modals/ModalBuilder.cs
@@ -64,7 +64,7 @@ namespace Discord.Interactions.Builders
}
///
- /// Adds text components to .
+ /// Adds text components to .
///
/// Text Component builder factory.
///
diff --git a/src/Discord.Net.Interactions/Builders/ModuleBuilder.cs b/src/Discord.Net.Interactions/Builders/ModuleBuilder.cs
index 40c263643..0eb91ee6a 100644
--- a/src/Discord.Net.Interactions/Builders/ModuleBuilder.cs
+++ b/src/Discord.Net.Interactions/Builders/ModuleBuilder.cs
@@ -51,8 +51,19 @@ namespace Discord.Interactions.Builders
///
/// Gets and sets the default permission of this module.
///
+ [Obsolete($"To be deprecated soon, use {nameof(IsEnabledInDm)} and {nameof(DefaultMemberPermissions)} instead.")]
public bool DefaultPermission { get; set; } = true;
+ ///
+ /// Gets whether this command can be used in DMs.
+ ///
+ public bool IsEnabledInDm { get; set; } = true;
+
+ ///
+ /// Gets the default permissions needed for executing this command.
+ ///
+ public GuildPermission? DefaultMemberPermissions { get; set; } = null;
+
///
/// Gets and sets whether this has a .
///
@@ -159,12 +170,39 @@ namespace Discord.Interactions.Builders
///
/// The builder instance.
///
+ [Obsolete($"To be deprecated soon, use {nameof(SetEnabledInDm)} and {nameof(WithDefaultMemberPermissions)} instead.")]
public ModuleBuilder WithDefaultPermission (bool permission)
{
DefaultPermission = permission;
return this;
}
+ ///
+ /// Sets .
+ ///
+ /// New value of the .
+ ///
+ /// The builder instance.
+ ///
+ public ModuleBuilder SetEnabledInDm(bool isEnabled)
+ {
+ IsEnabledInDm = isEnabled;
+ return this;
+ }
+
+ ///
+ /// Sets .
+ ///
+ /// New value of the .
+ ///
+ /// The builder instance.
+ ///
+ public ModuleBuilder WithDefaultMemberPermissions(GuildPermission permissions)
+ {
+ DefaultMemberPermissions = permissions;
+ return this;
+ }
+
///
/// Adds attributes to .
///
@@ -319,7 +357,8 @@ namespace Discord.Interactions.Builders
return this;
}
-
+
+ ///
/// Adds a modal command builder to .
///
/// factory.
diff --git a/src/Discord.Net.Interactions/Builders/ModuleClassBuilder.cs b/src/Discord.Net.Interactions/Builders/ModuleClassBuilder.cs
index b2317d1f3..1bbdfcc4a 100644
--- a/src/Discord.Net.Interactions/Builders/ModuleClassBuilder.cs
+++ b/src/Discord.Net.Interactions/Builders/ModuleClassBuilder.cs
@@ -85,6 +85,16 @@ namespace Discord.Interactions.Builders
builder.DefaultPermission = defPermission.IsDefaultPermission;
}
break;
+ case EnabledInDmAttribute enabledInDm:
+ {
+ builder.IsEnabledInDm = enabledInDm.IsEnabled;
+ }
+ break;
+ case DefaultMemberPermissionsAttribute memberPermission:
+ {
+ builder.DefaultMemberPermissions = memberPermission.Permissions;
+ }
+ break;
case PreconditionAttribute precondition:
builder.AddPreconditions(precondition);
break;
@@ -169,6 +179,16 @@ namespace Discord.Interactions.Builders
builder.DefaultPermission = defaultPermission.IsDefaultPermission;
}
break;
+ case EnabledInDmAttribute enabledInDm:
+ {
+ builder.IsEnabledInDm = enabledInDm.IsEnabled;
+ }
+ break;
+ case DefaultMemberPermissionsAttribute memberPermission:
+ {
+ builder.DefaultMemberPermissions = memberPermission.Permissions;
+ }
+ break;
case PreconditionAttribute precondition:
builder.WithPreconditions(precondition);
break;
@@ -211,6 +231,16 @@ namespace Discord.Interactions.Builders
builder.DefaultPermission = defaultPermission.IsDefaultPermission;
}
break;
+ case EnabledInDmAttribute enabledInDm:
+ {
+ builder.IsEnabledInDm = enabledInDm.IsEnabled;
+ }
+ break;
+ case DefaultMemberPermissionsAttribute memberPermission:
+ {
+ builder.DefaultMemberPermissions = memberPermission.Permissions;
+ }
+ break;
case PreconditionAttribute precondition:
builder.WithPreconditions(precondition);
break;
diff --git a/src/Discord.Net.Interactions/Builders/Parameters/ParameterBuilder.cs b/src/Discord.Net.Interactions/Builders/Parameters/ParameterBuilder.cs
index 78d007d44..fec1a6ce9 100644
--- a/src/Discord.Net.Interactions/Builders/Parameters/ParameterBuilder.cs
+++ b/src/Discord.Net.Interactions/Builders/Parameters/ParameterBuilder.cs
@@ -122,7 +122,7 @@ namespace Discord.Interactions.Builders
///
/// Adds preconditions to
///
- /// New attributes to be added to .
+ /// New attributes to be added to .
///
/// The builder instance.
///
diff --git a/src/Discord.Net.Interactions/Discord.Net.Interactions.csproj b/src/Discord.Net.Interactions/Discord.Net.Interactions.csproj
index c617eff61..a3ac3d508 100644
--- a/src/Discord.Net.Interactions/Discord.Net.Interactions.csproj
+++ b/src/Discord.Net.Interactions/Discord.Net.Interactions.csproj
@@ -7,8 +7,10 @@
Discord.Interactions
Discord.Net.Interactions
A Discord.Net extension adding support for Application Commands.
+ 5
+ True
-
+
diff --git a/src/Discord.Net.Interactions/Info/Commands/ContextCommands/ContextCommandInfo.cs b/src/Discord.Net.Interactions/Info/Commands/ContextCommands/ContextCommandInfo.cs
index 4c2e7af7d..2d6d748d4 100644
--- a/src/Discord.Net.Interactions/Info/Commands/ContextCommands/ContextCommandInfo.cs
+++ b/src/Discord.Net.Interactions/Info/Commands/ContextCommands/ContextCommandInfo.cs
@@ -17,6 +17,12 @@ namespace Discord.Interactions
///
public bool DefaultPermission { get; }
+ ///
+ public bool IsEnabledInDm { get; }
+
+ ///
+ public GuildPermission? DefaultMemberPermissions { get; }
+
///
public override IReadOnlyCollection Parameters { get; }
@@ -31,6 +37,8 @@ namespace Discord.Interactions
{
CommandType = builder.CommandType;
DefaultPermission = builder.DefaultPermission;
+ IsEnabledInDm = builder.IsEnabledInDm;
+ DefaultMemberPermissions = builder.DefaultMemberPermissions;
Parameters = builder.Parameters.Select(x => x.Build(this)).ToImmutableArray();
}
diff --git a/src/Discord.Net.Interactions/Info/Commands/SlashCommandInfo.cs b/src/Discord.Net.Interactions/Info/Commands/SlashCommandInfo.cs
index a123ac183..e428144c7 100644
--- a/src/Discord.Net.Interactions/Info/Commands/SlashCommandInfo.cs
+++ b/src/Discord.Net.Interactions/Info/Commands/SlashCommandInfo.cs
@@ -26,6 +26,12 @@ namespace Discord.Interactions
///
public bool DefaultPermission { get; }
+ ///
+ public bool IsEnabledInDm { get; }
+
+ ///
+ public GuildPermission? DefaultMemberPermissions { get; }
+
///
public override IReadOnlyCollection Parameters { get; }
@@ -41,6 +47,8 @@ namespace Discord.Interactions
{
Description = builder.Description;
DefaultPermission = builder.DefaultPermission;
+ IsEnabledInDm = builder.IsEnabledInDm;
+ DefaultMemberPermissions = builder.DefaultMemberPermissions;
Parameters = builder.Parameters.Select(x => x.Build(this)).ToImmutableArray();
FlattenedParameters = FlattenParameters(Parameters).ToImmutableArray();
diff --git a/src/Discord.Net.Interactions/Info/IApplicationCommandInfo.cs b/src/Discord.Net.Interactions/Info/IApplicationCommandInfo.cs
index 1e0d532b0..dd1b97899 100644
--- a/src/Discord.Net.Interactions/Info/IApplicationCommandInfo.cs
+++ b/src/Discord.Net.Interactions/Info/IApplicationCommandInfo.cs
@@ -1,3 +1,5 @@
+using System;
+
namespace Discord.Interactions
{
///
@@ -18,6 +20,17 @@ namespace Discord.Interactions
///
/// Gets the DefaultPermission of this command.
///
+ [Obsolete($"To be deprecated soon, use {nameof(IsEnabledInDm)} and {nameof(DefaultMemberPermissions)} instead.")]
bool DefaultPermission { get; }
+
+ ///
+ /// Gets whether this command can be used in DMs.
+ ///
+ public bool IsEnabledInDm { get; }
+
+ ///
+ /// Gets the default permissions needed for executing this command.
+ ///
+ public GuildPermission? DefaultMemberPermissions { get; }
}
}
diff --git a/src/Discord.Net.Interactions/Info/ModuleInfo.cs b/src/Discord.Net.Interactions/Info/ModuleInfo.cs
index 321e0bfa9..4f40f1607 100644
--- a/src/Discord.Net.Interactions/Info/ModuleInfo.cs
+++ b/src/Discord.Net.Interactions/Info/ModuleInfo.cs
@@ -41,8 +41,19 @@ namespace Discord.Interactions
///
/// Gets the default Permission of this module.
///
+ [Obsolete($"To be deprecated soon, use {nameof(IsEnabledInDm)} and {nameof(DefaultMemberPermissions)} instead.")]
public bool DefaultPermission { get; }
+ ///
+ /// Gets whether this command can be used in DMs.
+ ///
+ public bool IsEnabledInDm { get; }
+
+ ///
+ /// Gets the default permissions needed for executing this command.
+ ///
+ public GuildPermission? DefaultMemberPermissions { get; }
+
///
/// Gets the collection of Sub Modules of this module.
///
@@ -110,6 +121,8 @@ namespace Discord.Interactions
Description = builder.Description;
Parent = parent;
DefaultPermission = builder.DefaultPermission;
+ IsEnabledInDm = builder.IsEnabledInDm;
+ DefaultMemberPermissions = BuildDefaultMemberPermissions(builder);
SlashCommands = BuildSlashCommands(builder).ToImmutableArray();
ContextCommands = BuildContextCommands(builder).ToImmutableArray();
ComponentCommands = BuildComponentCommands(builder).ToImmutableArray();
@@ -226,5 +239,20 @@ namespace Discord.Interactions
}
return true;
}
+
+ private static GuildPermission? BuildDefaultMemberPermissions(ModuleBuilder builder)
+ {
+ var permissions = builder.DefaultMemberPermissions;
+
+ var parent = builder.Parent;
+
+ while (parent != null)
+ {
+ permissions = (permissions ?? 0) | (parent.DefaultMemberPermissions ?? 0).SanitizeGuildPermissions();
+ parent = parent.Parent;
+ }
+
+ return permissions;
+ }
}
}
diff --git a/src/Discord.Net.Interactions/InteractionContext.cs b/src/Discord.Net.Interactions/InteractionContext.cs
index 99a8d8736..b81cc5938 100644
--- a/src/Discord.Net.Interactions/InteractionContext.cs
+++ b/src/Discord.Net.Interactions/InteractionContext.cs
@@ -1,7 +1,10 @@
+using System.Collections.Generic;
+using System.Collections.Immutable;
+
namespace Discord.Interactions
{
///
- public class InteractionContext : IInteractionContext
+ public class InteractionContext : IInteractionContext, IRouteMatchContainer
{
///
public IDiscordClient Client { get; }
@@ -13,14 +16,15 @@ namespace Discord.Interactions
public IUser User { get; }
///
public IDiscordInteraction Interaction { get; }
+ ///
+ public IReadOnlyCollection SegmentMatches { get; private set; }
///
/// Initializes a new .
///
/// The underlying client.
/// The underlying interaction.
- /// who executed the command.
- /// the command originated from.
+ /// the command originated from.
public InteractionContext(IDiscordClient client, IDiscordInteraction interaction, IMessageChannel channel = null)
{
Client = client;
@@ -30,5 +34,12 @@ namespace Discord.Interactions
User = interaction.User;
Interaction = interaction;
}
+
+ ///
+ public void SetSegmentMatches(IEnumerable segmentMatches) => SegmentMatches = segmentMatches.ToImmutableArray();
+
+ //IRouteMatchContainer
+ ///
+ IEnumerable IRouteMatchContainer.SegmentMatches => SegmentMatches;
}
}
diff --git a/src/Discord.Net.Interactions/InteractionModuleBase.cs b/src/Discord.Net.Interactions/InteractionModuleBase.cs
index 873f4c173..a14779dbb 100644
--- a/src/Discord.Net.Interactions/InteractionModuleBase.cs
+++ b/src/Discord.Net.Interactions/InteractionModuleBase.cs
@@ -45,7 +45,7 @@ namespace Discord.Interactions
protected virtual async Task DeferAsync(bool ephemeral = false, RequestOptions options = null) =>
await Context.Interaction.DeferAsync(ephemeral, options).ConfigureAwait(false);
- ///
+ ///
protected virtual async Task RespondAsync (string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false,
AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent components = null, Embed embed = null) =>
await Context.Interaction.RespondAsync(text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options).ConfigureAwait(false);
@@ -70,7 +70,7 @@ namespace Discord.Interactions
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null)
=> Context.Interaction.RespondWithFilesAsync(attachments, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options);
- ///
+ ///
protected virtual async Task FollowupAsync (string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false,
AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent components = null, Embed embed = null) =>
await Context.Interaction.FollowupAsync(text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options).ConfigureAwait(false);
@@ -95,7 +95,7 @@ namespace Discord.Interactions
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null)
=> Context.Interaction.FollowupWithFilesAsync(attachments, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options);
- ///
+ ///
protected virtual async Task ReplyAsync (string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null,
AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null) =>
await Context.Channel.SendMessageAsync(text, false, embed, options, allowedMentions, messageReference, components).ConfigureAwait(false);
@@ -118,9 +118,9 @@ namespace Discord.Interactions
///
protected virtual async Task RespondWithModalAsync(Modal modal, RequestOptions options = null) => await Context.Interaction.RespondWithModalAsync(modal);
- ///
- protected virtual async Task RespondWithModalAsync(string customId, RequestOptions options = null) where T : class, IModal
- => await Context.Interaction.RespondWithModalAsync(customId, options);
+ ///
+ protected virtual async Task RespondWithModalAsync(string customId, RequestOptions options = null) where TModal : class, IModal
+ => await Context.Interaction.RespondWithModalAsync(customId, options);
//IInteractionModuleBase
diff --git a/src/Discord.Net.Interactions/InteractionService.cs b/src/Discord.Net.Interactions/InteractionService.cs
index 01fb8cc9d..793d89cdc 100644
--- a/src/Discord.Net.Interactions/InteractionService.cs
+++ b/src/Discord.Net.Interactions/InteractionService.cs
@@ -223,7 +223,8 @@ namespace Discord.Interactions
new ConcurrentDictionary
{
[typeof(Array)] = typeof(DefaultArrayComponentConverter<>),
- [typeof(IConvertible)] = typeof(DefaultValueComponentConverter<>)
+ [typeof(IConvertible)] = typeof(DefaultValueComponentConverter<>),
+ [typeof(Nullable<>)] = typeof(NullableComponentConverter<>)
});
_typeReaderMap = new TypeMap(this, new ConcurrentDictionary(),
@@ -234,7 +235,8 @@ namespace Discord.Interactions
[typeof(IUser)] = typeof(DefaultUserReader<>),
[typeof(IMessage)] = typeof(DefaultMessageReader<>),
[typeof(IConvertible)] = typeof(DefaultValueReader<>),
- [typeof(Enum)] = typeof(EnumReader<>)
+ [typeof(Enum)] = typeof(EnumReader<>),
+ [typeof(Nullable<>)] = typeof(NullableReader<>)
});
}
@@ -421,20 +423,39 @@ namespace Discord.Interactions
///
///
/// Commands will be registered as standalone commands, if you want the to take effect,
- /// use . Registering a commands without group names might cause the command traversal to fail.
+ /// use . Registering a commands without group names might cause the command traversal to fail.
///
/// The target guild.
+ /// If , this operation will not delete the commands that are missing from .
/// Commands to be registered to Discord.
///
/// A task representing the command registration process. The task result contains the active application commands of the target guild.
///
public async Task> AddCommandsToGuildAsync(IGuild guild, bool deleteMissing = false, params ICommandInfo[] commands)
{
- EnsureClientReady();
-
if (guild is null)
throw new ArgumentNullException(nameof(guild));
+ return await AddCommandsToGuildAsync(guild.Id, deleteMissing, commands).ConfigureAwait(false);
+ }
+
+ ///
+ /// Register Application Commands from to a guild.
+ ///
+ ///
+ /// Commands will be registered as standalone commands, if you want the to take effect,
+ /// use . Registering a commands without group names might cause the command traversal to fail.
+ ///
+ /// The target guild ID.
+ /// If , this operation will not delete the commands that are missing from .
+ /// Commands to be registered to Discord.
+ ///
+ /// A task representing the command registration process. The task result contains the active application commands of the target guild.
+ ///
+ public async Task> AddCommandsToGuildAsync(ulong guildId, bool deleteMissing = false, params ICommandInfo[] commands)
+ {
+ EnsureClientReady();
+
var props = new List();
foreach (var command in commands)
@@ -454,44 +475,60 @@ namespace Discord.Interactions
if (!deleteMissing)
{
- var existing = await RestClient.GetGuildApplicationCommands(guild.Id).ConfigureAwait(false);
+ var existing = await RestClient.GetGuildApplicationCommands(guildId).ConfigureAwait(false);
var missing = existing.Where(x => !props.Any(y => y.Name.IsSpecified && y.Name.Value == x.Name));
props.AddRange(missing.Select(x => x.ToApplicationCommandProps()));
}
- return await RestClient.BulkOverwriteGuildCommands(props.ToArray(), guild.Id).ConfigureAwait(false);
+ return await RestClient.BulkOverwriteGuildCommands(props.ToArray(), guildId).ConfigureAwait(false);
}
///
/// Register Application Commands from modules provided in to a guild.
///
/// The target guild.
+ /// If , this operation will not delete the commands that are missing from .
/// Modules to be registered to Discord.
///
/// A task representing the command registration process. The task result contains the active application commands of the target guild.
///
public async Task> AddModulesToGuildAsync(IGuild guild, bool deleteMissing = false, params ModuleInfo[] modules)
{
- EnsureClientReady();
-
if (guild is null)
throw new ArgumentNullException(nameof(guild));
+ return await AddModulesToGuildAsync(guild.Id, deleteMissing, modules).ConfigureAwait(false);
+ }
+
+ ///
+ /// Register Application Commands from modules provided in to a guild.
+ ///
+ /// The target guild ID.
+ /// If , this operation will not delete the commands that are missing from .
+ /// Modules to be registered to Discord.
+ ///
+ /// A task representing the command registration process. The task result contains the active application commands of the target guild.
+ ///
+ public async Task> AddModulesToGuildAsync(ulong guildId, bool deleteMissing = false, params ModuleInfo[] modules)
+ {
+ EnsureClientReady();
+
var props = modules.SelectMany(x => x.ToApplicationCommandProps(true)).ToList();
if (!deleteMissing)
{
- var existing = await RestClient.GetGuildApplicationCommands(guild.Id).ConfigureAwait(false);
+ var existing = await RestClient.GetGuildApplicationCommands(guildId).ConfigureAwait(false);
var missing = existing.Where(x => !props.Any(y => y.Name.IsSpecified && y.Name.Value == x.Name));
props.AddRange(missing.Select(x => x.ToApplicationCommandProps()));
}
- return await RestClient.BulkOverwriteGuildCommands(props.ToArray(), guild.Id).ConfigureAwait(false);
+ return await RestClient.BulkOverwriteGuildCommands(props.ToArray(), guildId).ConfigureAwait(false);
}
///
/// Register Application Commands from modules provided in as global commands.
///
+ /// If , this operation will not delete the commands that are missing from .
/// Modules to be registered to Discord.
///
/// A task representing the command registration process. The task result contains the active application commands of the target guild.
@@ -517,8 +554,9 @@ namespace Discord.Interactions
///
///
/// Commands will be registered as standalone commands, if you want the to take effect,
- /// use . Registering a commands without group names might cause the command traversal to fail.
+ /// use . Registering a commands without group names might cause the command traversal to fail.
///
+ /// If , this operation will not delete the commands that are missing from .
/// Commands to be registered to Discord.
///
/// A task representing the command registration process. The task result contains the active application commands of the target guild.
@@ -775,6 +813,9 @@ namespace Discord.Interactions
await _componentCommandExecutedEvent.InvokeAsync(null, context, result).ConfigureAwait(false);
return result;
}
+
+ SetMatchesIfApplicable(context, result);
+
return await result.Command.ExecuteAsync(context, services, result.RegexCaptureGroups).ConfigureAwait(false);
}
@@ -819,9 +860,30 @@ namespace Discord.Interactions
await _componentCommandExecutedEvent.InvokeAsync(null, context, result).ConfigureAwait(false);
return result;
}
+
+ SetMatchesIfApplicable(context, result);
+
return await result.Command.ExecuteAsync(context, services, result.RegexCaptureGroups).ConfigureAwait(false);
}
+ private static void SetMatchesIfApplicable(IInteractionContext context, SearchResult searchResult)
+ where T : class, ICommandInfo
+ {
+ if (!searchResult.Command.SupportsWildCards || context is not IRouteMatchContainer matchContainer)
+ return;
+
+ if (searchResult.RegexCaptureGroups?.Length > 0)
+ {
+ var matches = new RouteSegmentMatch[searchResult.RegexCaptureGroups.Length];
+ for (var i = 0; i < searchResult.RegexCaptureGroups.Length; i++)
+ matches[i] = new RouteSegmentMatch(searchResult.RegexCaptureGroups[i]);
+
+ matchContainer.SetSegmentMatches(matches);
+ }
+ else
+ matchContainer.SetSegmentMatches(Array.Empty());
+ }
+
internal TypeConverter GetTypeConverter(Type type, IServiceProvider services = null)
=> _typeConverterMap.Get(type, services);
@@ -941,7 +1003,7 @@ namespace Discord.Interactions
/// Removes a type reader for the given type.
///
///
- /// Removing a from the will not dereference the from the loaded module/command instances.
+ /// Removing a from the will not dereference the from the loaded module/command instances.
/// You need to reload the modules for the changes to take effect.
///
/// The type to remove the reader from.
@@ -954,7 +1016,7 @@ namespace Discord.Interactions
/// Removes a generic type reader from the type .
///
///
- /// Removing a from the will not dereference the from the loaded module/command instances.
+ /// Removing a from the will not dereference the from the loaded module/command instances.
/// You need to reload the modules for the changes to take effect.
///
/// The type to remove the readers from.
@@ -967,7 +1029,7 @@ namespace Discord.Interactions
/// Removes a generic type reader from the given type.
///
///
- /// Removing a from the will not dereference the from the loaded module/command instances.
+ /// Removing a from the will not dereference the from the loaded module/command instances.
/// You need to reload the modules for the changes to take effect.
///
/// The type to remove the reader from.
@@ -980,7 +1042,7 @@ namespace Discord.Interactions
/// Serialize an object using a into a to be placed in a Component CustomId.
///
///
- /// Removing a from the will not dereference the from the loaded module/command instances.
+ /// Removing a from the will not dereference the from the loaded module/command instances.
/// You need to reload the modules for the changes to take effect.
///
/// Type of the object to be serialized.
@@ -1060,19 +1122,40 @@ namespace Discord.Interactions
///
/// The active command permissions after the modification.
///
- public async Task ModifySlashCommandPermissionsAsync (ModuleInfo module, IGuild guild,
+ public async Task ModifySlashCommandPermissionsAsync(ModuleInfo module, IGuild guild,
params ApplicationCommandPermission[] permissions)
{
+ if (module is null)
+ throw new ArgumentNullException(nameof(module));
+
+ if (guild is null)
+ throw new ArgumentNullException(nameof(guild));
+
+ return await ModifySlashCommandPermissionsAsync(module, guild.Id, permissions).ConfigureAwait(false);
+ }
+
+ ///
+ /// Modify the command permissions of the matching Discord Slash Command.
+ ///
+ /// Module representing the top level Slash Command.
+ /// Target guild ID.
+ /// New permission values.
+ ///
+ /// The active command permissions after the modification.
+ ///
+ public async Task ModifySlashCommandPermissionsAsync(ModuleInfo module, ulong guildId,
+ params ApplicationCommandPermission[] permissions)
+ {
+ if (module is null)
+ throw new ArgumentNullException(nameof(module));
+
if (!module.IsSlashGroup)
throw new InvalidOperationException($"This module does not have a {nameof(GroupAttribute)} and does not represent an Application Command");
if (!module.IsTopLevelGroup)
throw new InvalidOperationException("This module is not a top level application command. You cannot change its permissions");
- if (guild is null)
- throw new ArgumentNullException("guild");
-
- var commands = await RestClient.GetGuildApplicationCommands(guild.Id).ConfigureAwait(false);
+ var commands = await RestClient.GetGuildApplicationCommands(guildId).ConfigureAwait(false);
var appCommand = commands.First(x => x.Name == module.SlashGroupName);
return await appCommand.ModifyCommandPermissions(permissions).ConfigureAwait(false);
@@ -1087,9 +1170,29 @@ namespace Discord.Interactions
///
/// The active command permissions after the modification.
///
- public async Task ModifySlashCommandPermissionsAsync (SlashCommandInfo command, IGuild guild,
- params ApplicationCommandPermission[] permissions) =>
- await ModifyApplicationCommandPermissionsAsync(command, guild, permissions).ConfigureAwait(false);
+ public async Task ModifySlashCommandPermissionsAsync(SlashCommandInfo command, IGuild guild,
+ params ApplicationCommandPermission[] permissions)
+ {
+ if (command is null)
+ throw new ArgumentNullException(nameof(command));
+
+ if (guild is null)
+ throw new ArgumentNullException(nameof(guild));
+
+ return await ModifyApplicationCommandPermissionsAsync(command, guild.Id, permissions).ConfigureAwait(false);
+ }
+
+ ///
+ /// Modify the command permissions of the matching Discord Slash Command.
+ ///
+ /// The Slash Command.
+ /// Target guild ID.
+ /// New permission values.
+ ///
+ /// The active command permissions after the modification.
+ ///
+ public async Task ModifySlashCommandPermissionsAsync(SlashCommandInfo command, ulong guildId,
+ params ApplicationCommandPermission[] permissions) => await ModifyApplicationCommandPermissionsAsync(command, guildId, permissions).ConfigureAwait(false);
///
/// Modify the command permissions of the matching Discord Slash Command.
@@ -1100,20 +1203,40 @@ namespace Discord.Interactions
///
/// The active command permissions after the modification.
///
- public async Task ModifyContextCommandPermissionsAsync (ContextCommandInfo command, IGuild guild,
- params ApplicationCommandPermission[] permissions) =>
- await ModifyApplicationCommandPermissionsAsync(command, guild, permissions).ConfigureAwait(false);
+ public async Task ModifyContextCommandPermissionsAsync(ContextCommandInfo command, IGuild guild,
+ params ApplicationCommandPermission[] permissions)
+ {
+ if (command is null)
+ throw new ArgumentNullException(nameof(command));
- private async Task ModifyApplicationCommandPermissionsAsync (T command, IGuild guild,
+ if (guild is null)
+ throw new ArgumentNullException(nameof(guild));
+
+ return await ModifyApplicationCommandPermissionsAsync(command, guild.Id, permissions).ConfigureAwait(false);
+ }
+
+ ///
+ /// Modify the command permissions of the matching Discord Slash Command.
+ ///
+ /// The Context Command.
+ /// Target guild ID.
+ /// New permission values.
+ ///
+ /// The active command permissions after the modification.
+ ///
+ public async Task ModifyContextCommandPermissionsAsync(ContextCommandInfo command, ulong guildId,
+ params ApplicationCommandPermission[] permissions) => await ModifyApplicationCommandPermissionsAsync(command, guildId, permissions).ConfigureAwait(false);
+
+ private async Task ModifyApplicationCommandPermissionsAsync (T command, ulong guildId,
params ApplicationCommandPermission[] permissions) where T : class, IApplicationCommandInfo, ICommandInfo
{
+ if (command is null)
+ throw new ArgumentNullException(nameof(command));
+
if (!command.IsTopLevelCommand)
throw new InvalidOperationException("This command is not a top level application command. You cannot change its permissions");
- if (guild is null)
- throw new ArgumentNullException("guild");
-
- var commands = await RestClient.GetGuildApplicationCommands(guild.Id).ConfigureAwait(false);
+ var commands = await RestClient.GetGuildApplicationCommands(guildId).ConfigureAwait(false);
var appCommand = commands.First(x => x.Name == ( command as IApplicationCommandInfo ).Name);
return await appCommand.ModifyCommandPermissions(permissions).ConfigureAwait(false);
diff --git a/src/Discord.Net.Interactions/RestInteractionModuleBase.cs b/src/Discord.Net.Interactions/RestInteractionModuleBase.cs
index e83c91fef..b570e6d84 100644
--- a/src/Discord.Net.Interactions/RestInteractionModuleBase.cs
+++ b/src/Discord.Net.Interactions/RestInteractionModuleBase.cs
@@ -87,12 +87,12 @@ namespace Discord.Interactions
await InteractionService._restResponseCallback(Context, payload).ConfigureAwait(false);
}
- protected override async Task RespondWithModalAsync(string customId, RequestOptions options = null)
+ protected override async Task RespondWithModalAsync(string customId, RequestOptions options = null)
{
if (Context.Interaction is not RestInteraction restInteraction)
throw new InvalidOperationException($"Invalid interaction type. Interaction must be a type of {nameof(RestInteraction)} in order to execute this method");
- var payload = restInteraction.RespondWithModal(customId, options);
+ var payload = restInteraction.RespondWithModal(customId, options);
if (Context is IRestInteractionContext restContext && restContext.InteractionResponseCallback != null)
await restContext.InteractionResponseCallback.Invoke(payload).ConfigureAwait(false);
diff --git a/src/Discord.Net.Interactions/Results/TypeConverterResult.cs b/src/Discord.Net.Interactions/Results/TypeConverterResult.cs
index bd89bf6b7..a9a12ee33 100644
--- a/src/Discord.Net.Interactions/Results/TypeConverterResult.cs
+++ b/src/Discord.Net.Interactions/Results/TypeConverterResult.cs
@@ -3,7 +3,7 @@ using System;
namespace Discord.Interactions
{
///
- /// Represents a result type for .
+ /// Represents a result type for .
///
public struct TypeConverterResult : IResult
{
diff --git a/src/Discord.Net.Interactions/TypeConverters/ComponentInteractions/NullableComponentConverter.cs b/src/Discord.Net.Interactions/TypeConverters/ComponentInteractions/NullableComponentConverter.cs
new file mode 100644
index 000000000..ba6568ad1
--- /dev/null
+++ b/src/Discord.Net.Interactions/TypeConverters/ComponentInteractions/NullableComponentConverter.cs
@@ -0,0 +1,23 @@
+using System;
+using System.Threading.Tasks;
+
+namespace Discord.Interactions
+{
+ internal class NullableComponentConverter : ComponentTypeConverter
+ {
+ private readonly ComponentTypeConverter _typeConverter;
+
+ public NullableComponentConverter(InteractionService interactionService, IServiceProvider services)
+ {
+ var type = Nullable.GetUnderlyingType(typeof(T));
+
+ if (type is null)
+ throw new ArgumentException($"No type {nameof(TypeConverter)} is defined for this {type.FullName}", "type");
+
+ _typeConverter = interactionService.GetComponentTypeConverter(type, services);
+ }
+
+ public override Task ReadAsync(IInteractionContext context, IComponentInteractionData option, IServiceProvider services)
+ => string.IsNullOrEmpty(option.Value) ? Task.FromResult(TypeConverterResult.FromSuccess(null)) : _typeConverter.ReadAsync(context, option, services);
+ }
+}
diff --git a/src/Discord.Net.Interactions/TypeReaders/NullableReader.cs b/src/Discord.Net.Interactions/TypeReaders/NullableReader.cs
new file mode 100644
index 000000000..ed88dc64a
--- /dev/null
+++ b/src/Discord.Net.Interactions/TypeReaders/NullableReader.cs
@@ -0,0 +1,23 @@
+using System;
+using System.Threading.Tasks;
+
+namespace Discord.Interactions
+{
+ internal class NullableReader : TypeReader
+ {
+ private readonly TypeReader _typeReader;
+
+ public NullableReader(InteractionService interactionService, IServiceProvider services)
+ {
+ var type = Nullable.GetUnderlyingType(typeof(T));
+
+ if (type is null)
+ throw new ArgumentException($"No type {nameof(TypeConverter)} is defined for this {type.FullName}", "type");
+
+ _typeReader = interactionService.GetTypeReader(type, services);
+ }
+
+ public override Task ReadAsync(IInteractionContext context, string option, IServiceProvider services)
+ => string.IsNullOrEmpty(option) ? Task.FromResult(TypeConverterResult.FromSuccess(null)) : _typeReader.ReadAsync(context, option, services);
+ }
+}
diff --git a/src/Discord.Net.Interactions/Utilities/ApplicationCommandRestUtil.cs b/src/Discord.Net.Interactions/Utilities/ApplicationCommandRestUtil.cs
index c2052b7c7..e4b6f893c 100644
--- a/src/Discord.Net.Interactions/Utilities/ApplicationCommandRestUtil.cs
+++ b/src/Discord.Net.Interactions/Utilities/ApplicationCommandRestUtil.cs
@@ -40,7 +40,8 @@ namespace Discord.Interactions
{
Name = commandInfo.Name,
Description = commandInfo.Description,
- IsDefaultPermission = commandInfo.DefaultPermission,
+ IsDMEnabled = commandInfo.IsEnabledInDm,
+ DefaultMemberPermissions = ((commandInfo.DefaultMemberPermissions ?? 0) | (commandInfo.Module.DefaultMemberPermissions ?? 0)).SanitizeGuildPermissions(),
}.Build();
if (commandInfo.Parameters.Count > SlashCommandBuilder.MaxOptionsCount)
@@ -64,8 +65,20 @@ namespace Discord.Interactions
public static ApplicationCommandProperties ToApplicationCommandProps(this ContextCommandInfo commandInfo)
=> commandInfo.CommandType switch
{
- ApplicationCommandType.Message => new MessageCommandBuilder { Name = commandInfo.Name, IsDefaultPermission = commandInfo.DefaultPermission}.Build(),
- ApplicationCommandType.User => new UserCommandBuilder { Name = commandInfo.Name, IsDefaultPermission=commandInfo.DefaultPermission}.Build(),
+ ApplicationCommandType.Message => new MessageCommandBuilder
+ {
+ Name = commandInfo.Name,
+ IsDefaultPermission = commandInfo.DefaultPermission,
+ DefaultMemberPermissions = ((commandInfo.DefaultMemberPermissions ?? 0) | (commandInfo.Module.DefaultMemberPermissions ?? 0)).SanitizeGuildPermissions(),
+ IsDMEnabled = commandInfo.IsEnabledInDm
+ }.Build(),
+ ApplicationCommandType.User => new UserCommandBuilder
+ {
+ Name = commandInfo.Name,
+ IsDefaultPermission = commandInfo.DefaultPermission,
+ DefaultMemberPermissions = ((commandInfo.DefaultMemberPermissions ?? 0) | (commandInfo.Module.DefaultMemberPermissions ?? 0)).SanitizeGuildPermissions(),
+ IsDMEnabled = commandInfo.IsEnabledInDm
+ }.Build(),
_ => throw new InvalidOperationException($"{commandInfo.CommandType} isn't a supported command type.")
};
#endregion
@@ -113,6 +126,8 @@ namespace Discord.Interactions
Name = moduleInfo.SlashGroupName,
Description = moduleInfo.Description,
IsDefaultPermission = moduleInfo.DefaultPermission,
+ IsDMEnabled = moduleInfo.IsEnabledInDm,
+ DefaultMemberPermissions = moduleInfo.DefaultMemberPermissions
}.Build();
if (options.Count > SlashCommandBuilder.MaxOptionsCount)
@@ -217,5 +232,8 @@ namespace Discord.Interactions
return builder.Build();
}
+
+ public static GuildPermission? SanitizeGuildPermissions(this GuildPermission permissions) =>
+ permissions == 0 ? null : permissions;
}
}
diff --git a/src/Discord.Net.Rest/API/Common/ApplicationCommand.cs b/src/Discord.Net.Rest/API/Common/ApplicationCommand.cs
index 81598b96e..8b84149dd 100644
--- a/src/Discord.Net.Rest/API/Common/ApplicationCommand.cs
+++ b/src/Discord.Net.Rest/API/Common/ApplicationCommand.cs
@@ -24,5 +24,12 @@ namespace Discord.API
[JsonProperty("default_permission")]
public Optional DefaultPermissions { get; set; }
+
+ // V2 Permissions
+ [JsonProperty("dm_permission")]
+ public Optional DmPermission { get; set; }
+
+ [JsonProperty("default_member_permissions")]
+ public Optional DefaultMemberPermission { get; set; }
}
}
diff --git a/src/Discord.Net.Rest/API/Common/Channel.cs b/src/Discord.Net.Rest/API/Common/Channel.cs
index d565b269a..d9d7d469c 100644
--- a/src/Discord.Net.Rest/API/Common/Channel.cs
+++ b/src/Discord.Net.Rest/API/Common/Channel.cs
@@ -66,5 +66,12 @@ namespace Discord.API
[JsonProperty("member_count")]
public Optional MemberCount { get; set; }
+
+ //ForumChannel
+ [JsonProperty("available_tags")]
+ public Optional ForumTags { get; set; }
+
+ [JsonProperty("default_auto_archive_duration")]
+ public Optional AutoArchiveDuration { get; set; }
}
}
diff --git a/src/Discord.Net.Rest/API/Common/ChannelThreads.cs b/src/Discord.Net.Rest/API/Common/ChannelThreads.cs
index 94b2396bf..9fa3e38ce 100644
--- a/src/Discord.Net.Rest/API/Common/ChannelThreads.cs
+++ b/src/Discord.Net.Rest/API/Common/ChannelThreads.cs
@@ -9,8 +9,5 @@ namespace Discord.API.Rest
[JsonProperty("members")]
public ThreadMember[] Members { get; set; }
-
- [JsonProperty("has_more")]
- public bool HasMore { get; set; }
}
}
diff --git a/src/Discord.Net.Rest/API/Common/ForumTags.cs b/src/Discord.Net.Rest/API/Common/ForumTags.cs
new file mode 100644
index 000000000..18354e7b2
--- /dev/null
+++ b/src/Discord.Net.Rest/API/Common/ForumTags.cs
@@ -0,0 +1,21 @@
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Discord.API
+{
+ internal class ForumTags
+ {
+ [JsonProperty("id")]
+ public ulong Id { get; set; }
+ [JsonProperty("name")]
+ public string Name { get; set; }
+ [JsonProperty("emoji_id")]
+ public Optional EmojiId { get; set; }
+ [JsonProperty("emoji_name")]
+ public Optional EmojiName { get; set; }
+ }
+}
diff --git a/src/Discord.Net.Rest/API/Common/ForumThreadMessage.cs b/src/Discord.Net.Rest/API/Common/ForumThreadMessage.cs
new file mode 100644
index 000000000..132e38e5f
--- /dev/null
+++ b/src/Discord.Net.Rest/API/Common/ForumThreadMessage.cs
@@ -0,0 +1,33 @@
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Discord.API
+{
+ internal class ForumThreadMessage
+ {
+ [JsonProperty("content")]
+ public Optional Content { get; set; }
+
+ [JsonProperty("nonce")]
+ public Optional Nonce { get; set; }
+
+ [JsonProperty("embeds")]
+ public Optional Embeds { get; set; }
+
+ [JsonProperty("allowed_mentions")]
+ public Optional AllowedMentions { get; set; }
+
+ [JsonProperty("components")]
+ public Optional Components { get; set; }
+
+ [JsonProperty("sticker_ids")]
+ public Optional Stickers { get; set; }
+
+ [JsonProperty("flags")]
+ public Optional Flags { get; set; }
+ }
+}
diff --git a/src/Discord.Net.Rest/API/Common/MessageReference.cs b/src/Discord.Net.Rest/API/Common/MessageReference.cs
index 6cc7603e0..70ef4e678 100644
--- a/src/Discord.Net.Rest/API/Common/MessageReference.cs
+++ b/src/Discord.Net.Rest/API/Common/MessageReference.cs
@@ -12,5 +12,8 @@ namespace Discord.API
[JsonProperty("guild_id")]
public Optional GuildId { get; set; }
+
+ [JsonProperty("fail_if_not_exists")]
+ public Optional FailIfNotExists { get; set; }
}
}
diff --git a/src/Discord.Net.Rest/API/Rest/CreateApplicationCommandParams.cs b/src/Discord.Net.Rest/API/Rest/CreateApplicationCommandParams.cs
index 82f0befcd..7ae8718b6 100644
--- a/src/Discord.Net.Rest/API/Rest/CreateApplicationCommandParams.cs
+++ b/src/Discord.Net.Rest/API/Rest/CreateApplicationCommandParams.cs
@@ -19,6 +19,12 @@ namespace Discord.API.Rest
[JsonProperty("default_permission")]
public Optional DefaultPermission { get; set; }
+ [JsonProperty("dm_permission")]
+ public Optional DmPermission { get; set; }
+
+ [JsonProperty("default_member_permissions")]
+ public Optional DefaultMemberPermission { get; set; }
+
public CreateApplicationCommandParams() { }
public CreateApplicationCommandParams(string name, string description, ApplicationCommandType type, ApplicationCommandOption[] options = null)
{
diff --git a/src/Discord.Net.Rest/API/Rest/CreateMultipartPostAsync.cs b/src/Discord.Net.Rest/API/Rest/CreateMultipartPostAsync.cs
new file mode 100644
index 000000000..0c8bc5494
--- /dev/null
+++ b/src/Discord.Net.Rest/API/Rest/CreateMultipartPostAsync.cs
@@ -0,0 +1,96 @@
+using Discord.Net.Converters;
+using Discord.Net.Rest;
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Discord.API.Rest
+{
+ internal class CreateMultipartPostAsync
+ {
+ private static JsonSerializer _serializer = new JsonSerializer { ContractResolver = new DiscordContractResolver() };
+
+ public FileAttachment[] Files { get; }
+
+ public string Title { get; set; }
+ public ThreadArchiveDuration ArchiveDuration { get; set; }
+ public Optional Slowmode { get; set; }
+
+
+ public Optional Content { get; set; }
+ public Optional Embeds { get; set; }
+ public Optional AllowedMentions { get; set; }
+ public Optional MessageComponent { get; set; }
+ public Optional Flags { get; set; }
+ public Optional Stickers { get; set; }
+
+ public CreateMultipartPostAsync(params FileAttachment[] attachments)
+ {
+ Files = attachments;
+ }
+
+ public IReadOnlyDictionary ToDictionary()
+ {
+ var d = new Dictionary();
+
+ var payload = new Dictionary();
+ var message = new Dictionary();
+
+ payload["name"] = Title;
+ payload["auto_archive_duration"] = ArchiveDuration;
+
+ if (Slowmode.IsSpecified)
+ payload["rate_limit_per_user"] = Slowmode.Value;
+
+ // message
+ if (Content.IsSpecified)
+ message["content"] = Content.Value;
+ if (Embeds.IsSpecified)
+ message["embeds"] = Embeds.Value;
+ if (AllowedMentions.IsSpecified)
+ message["allowed_mentions"] = AllowedMentions.Value;
+ if (MessageComponent.IsSpecified)
+ message["components"] = MessageComponent.Value;
+ if (Stickers.IsSpecified)
+ message["sticker_ids"] = Stickers.Value;
+ if (Flags.IsSpecified)
+ message["flags"] = Flags.Value;
+
+ List
/// The configuration to be used with the client.
- public DiscordRestClient(DiscordRestConfig config) : base(config, CreateApiClient(config)) { }
+ public DiscordRestClient(DiscordRestConfig config) : base(config, CreateApiClient(config))
+ {
+ _apiOnCreation = config.APIOnRestInteractionCreation;
+ }
// used for socket client rest access
- internal DiscordRestClient(DiscordRestConfig config, API.DiscordRestApiClient api) : base(config, api) { }
+ internal DiscordRestClient(DiscordRestConfig config, API.DiscordRestApiClient api) : base(config, api)
+ {
+ _apiOnCreation = config.APIOnRestInteractionCreation;
+ }
private static API.DiscordRestApiClient CreateApiClient(DiscordRestConfig config)
=> new API.DiscordRestApiClient(config.RestClientProvider, DiscordRestConfig.UserAgent, serializer: Serializer, useSystemClock: config.UseSystemClock, defaultRatelimitCallback: config.DefaultRatelimitCallback);
@@ -82,6 +88,8 @@ namespace Discord.Rest
#region Rest interactions
+ private readonly bool _apiOnCreation;
+
public bool IsValidHttpInteraction(string publicKey, string signature, string timestamp, string body)
=> IsValidHttpInteraction(publicKey, signature, timestamp, Encoding.UTF8.GetBytes(body));
public bool IsValidHttpInteraction(string publicKey, string signature, string timestamp, byte[] body)
@@ -113,8 +121,8 @@ namespace Discord.Rest
/// A that represents the incoming http interaction.
///
/// Thrown when the signature doesn't match the public key.
- public Task ParseHttpInteractionAsync(string publicKey, string signature, string timestamp, string body)
- => ParseHttpInteractionAsync(publicKey, signature, timestamp, Encoding.UTF8.GetBytes(body));
+ public Task ParseHttpInteractionAsync(string publicKey, string signature, string timestamp, string body, Func doApiCallOnCreation = null)
+ => ParseHttpInteractionAsync(publicKey, signature, timestamp, Encoding.UTF8.GetBytes(body), doApiCallOnCreation);
///
/// Creates a from a http message.
@@ -127,7 +135,7 @@ namespace Discord.Rest
/// A that represents the incoming http interaction.
///
/// Thrown when the signature doesn't match the public key.
- public async Task ParseHttpInteractionAsync(string publicKey, string signature, string timestamp, byte[] body)
+ public async Task ParseHttpInteractionAsync(string publicKey, string signature, string timestamp, byte[] body, Func doApiCallOnCreation = null)
{
if (!IsValidHttpInteraction(publicKey, signature, timestamp, body))
{
@@ -138,12 +146,12 @@ namespace Discord.Rest
using (var jsonReader = new JsonTextReader(textReader))
{
var model = Serializer.Deserialize(jsonReader);
- return await RestInteraction.CreateAsync(this, model);
+ return await RestInteraction.CreateAsync(this, model, doApiCallOnCreation is not null ? doApiCallOnCreation(new InteractionProperties(model)) : _apiOnCreation);
}
}
#endregion
-
+
public async Task GetApplicationInfoAsync(RequestOptions options = null)
{
return _applicationInfo ??= await ClientHelper.GetApplicationInfoAsync(this, options).ConfigureAwait(false);
diff --git a/src/Discord.Net.Rest/DiscordRestConfig.cs b/src/Discord.Net.Rest/DiscordRestConfig.cs
index 7bf7440ce..a09d9ee98 100644
--- a/src/Discord.Net.Rest/DiscordRestConfig.cs
+++ b/src/Discord.Net.Rest/DiscordRestConfig.cs
@@ -9,5 +9,7 @@ namespace Discord.Rest
{
/// Gets or sets the provider used to generate new REST connections.
public RestClientProvider RestClientProvider { get; set; } = DefaultRestClientProvider.Instance;
+
+ public bool APIOnRestInteractionCreation { get; set; } = true;
}
}
diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/BanAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/BanAuditLogData.cs
index fc807cac0..7246ac197 100644
--- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/BanAuditLogData.cs
+++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/BanAuditLogData.cs
@@ -18,12 +18,15 @@ namespace Discord.Rest
internal static BanAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry)
{
var userInfo = log.Users.FirstOrDefault(x => x.Id == entry.TargetId);
- return new BanAuditLogData(RestUser.Create(discord, userInfo));
+ return new BanAuditLogData((userInfo != null) ? RestUser.Create(discord, userInfo) : null);
}
///
/// Gets the user that was banned.
///
+ ///
+ /// Will be if the user is a 'Deleted User#....' because Discord does send user data for deleted users.
+ ///
///
/// A user object representing the banned user.
///
diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/BotAddAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/BotAddAuditLogData.cs
index 0d12e4609..288cb9d0a 100644
--- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/BotAddAuditLogData.cs
+++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/BotAddAuditLogData.cs
@@ -18,12 +18,15 @@ namespace Discord.Rest
internal static BotAddAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry)
{
var userInfo = log.Users.FirstOrDefault(x => x.Id == entry.TargetId);
- return new BotAddAuditLogData(RestUser.Create(discord, userInfo));
+ return new BotAddAuditLogData((userInfo != null) ? RestUser.Create(discord, userInfo) : null);
}
///
/// Gets the bot that was added.
///
+ ///
+ /// Will be if the bot is a 'Deleted User#....' because Discord does send user data for deleted users.
+ ///
///
/// A user object representing the bot.
///
diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteCreateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteCreateAuditLogData.cs
index b177b2435..3560b9a27 100644
--- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteCreateAuditLogData.cs
+++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteCreateAuditLogData.cs
@@ -45,7 +45,7 @@ namespace Discord.Rest
{
var inviterId = inviterIdModel.NewValue.ToObject(discord.ApiClient.Serializer);
var inviterInfo = log.Users.FirstOrDefault(x => x.Id == inviterId);
- inviter = RestUser.Create(discord, inviterInfo);
+ inviter = (inviterInfo != null) ? RestUser.Create(discord, inviterInfo) : null;
}
return new InviteCreateAuditLogData(maxAge, code, temporary, inviter, channelId, uses, maxUses);
@@ -76,6 +76,9 @@ namespace Discord.Rest
///
/// Gets the user that created this invite if available.
///
+ ///
+ /// Will be if the user is a 'Deleted User#....' because Discord does send user data for deleted users.
+ ///
///
/// A user that created this invite or .
///
diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteDeleteAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteDeleteAuditLogData.cs
index 9d0aed12b..2dc2f22f6 100644
--- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteDeleteAuditLogData.cs
+++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteDeleteAuditLogData.cs
@@ -45,7 +45,7 @@ namespace Discord.Rest
{
var inviterId = inviterIdModel.OldValue.ToObject(discord.ApiClient.Serializer);
var inviterInfo = log.Users.FirstOrDefault(x => x.Id == inviterId);
- inviter = RestUser.Create(discord, inviterInfo);
+ inviter = (inviterInfo != null) ? RestUser.Create(discord, inviterInfo) : null;
}
return new InviteDeleteAuditLogData(maxAge, code, temporary, inviter, channelId, uses, maxUses);
@@ -76,6 +76,9 @@ namespace Discord.Rest
///
/// Gets the user that created this invite if available.
///
+ ///
+ /// Will be if the user is a 'Deleted User#....' because Discord does send user data for deleted users.
+ ///
///
/// A user that created this invite or .
///
diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/KickAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/KickAuditLogData.cs
index dceb73d0a..b533f0268 100644
--- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/KickAuditLogData.cs
+++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/KickAuditLogData.cs
@@ -1,4 +1,4 @@
-using System.Linq;
+using System.Linq;
using Model = Discord.API.AuditLog;
using EntryModel = Discord.API.AuditLogEntry;
@@ -18,12 +18,15 @@ namespace Discord.Rest
internal static KickAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry)
{
var userInfo = log.Users.FirstOrDefault(x => x.Id == entry.TargetId);
- return new KickAuditLogData(RestUser.Create(discord, userInfo));
+ return new KickAuditLogData((userInfo != null) ? RestUser.Create(discord, userInfo) : null);
}
///
/// Gets the user that was kicked.
///
+ ///
+ /// Will be if the user is a 'Deleted User#....' because Discord does send user data for deleted users.
+ ///
///
/// A user object representing the kicked user.
///
diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberRoleAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberRoleAuditLogData.cs
index 763c90c68..276604d03 100644
--- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberRoleAuditLogData.cs
+++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberRoleAuditLogData.cs
@@ -27,7 +27,7 @@ namespace Discord.Rest
.ToList();
var userInfo = log.Users.FirstOrDefault(x => x.Id == entry.TargetId);
- var user = RestUser.Create(discord, userInfo);
+ RestUser user = (userInfo != null) ? RestUser.Create(discord, userInfo) : null;
return new MemberRoleAuditLogData(roleInfos.ToReadOnlyCollection(), user);
}
diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberUpdateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberUpdateAuditLogData.cs
index f22b83e4c..f3437e621 100644
--- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberUpdateAuditLogData.cs
+++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberUpdateAuditLogData.cs
@@ -33,7 +33,7 @@ namespace Discord.Rest
newMute = muteModel?.NewValue?.ToObject(discord.ApiClient.Serializer);
var targetInfo = log.Users.FirstOrDefault(x => x.Id == entry.TargetId);
- var user = RestUser.Create(discord, targetInfo);
+ RestUser user = (targetInfo != null) ? RestUser.Create(discord, targetInfo) : null;
var before = new MemberInfo(oldNick, oldDeaf, oldMute);
var after = new MemberInfo(newNick, newDeaf, newMute);
@@ -44,6 +44,9 @@ namespace Discord.Rest
///
/// Gets the user that the changes were performed on.
///
+ ///
+ /// Will be if the user is a 'Deleted User#....' because Discord does send user data for deleted users.
+ ///
///
/// A user object representing the user who the changes were performed on.
///
diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MessageDeleteAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MessageDeleteAuditLogData.cs
index 66b3f7d83..746fc2ea6 100644
--- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MessageDeleteAuditLogData.cs
+++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MessageDeleteAuditLogData.cs
@@ -2,6 +2,7 @@ using System.Linq;
using Model = Discord.API.AuditLog;
using EntryModel = Discord.API.AuditLogEntry;
+using System;
namespace Discord.Rest
{
@@ -20,7 +21,7 @@ namespace Discord.Rest
internal static MessageDeleteAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry)
{
var userInfo = log.Users.FirstOrDefault(x => x.Id == entry.TargetId);
- return new MessageDeleteAuditLogData(entry.Options.ChannelId.Value, entry.Options.Count.Value, RestUser.Create(discord, userInfo));
+ return new MessageDeleteAuditLogData(entry.Options.ChannelId.Value, entry.Options.Count.Value, userInfo != null ? RestUser.Create(discord, userInfo) : null);
}
///
@@ -41,6 +42,9 @@ namespace Discord.Rest
///
/// Gets the user of the messages that were deleted.
///
+ ///
+ /// Will be if the user is a 'Deleted User#....' because Discord does send user data for deleted users.
+ ///
///
/// A user object representing the user that created the deleted messages.
///
diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MessagePinAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MessagePinAuditLogData.cs
index be66ac846..c33fd5f44 100644
--- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MessagePinAuditLogData.cs
+++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MessagePinAuditLogData.cs
@@ -23,7 +23,7 @@ namespace Discord.Rest
if (entry.TargetId.HasValue)
{
var userInfo = log.Users.FirstOrDefault(x => x.Id == entry.TargetId);
- user = RestUser.Create(discord, userInfo);
+ user = (userInfo != null) ? RestUser.Create(discord, userInfo) : null;
}
return new MessagePinAuditLogData(entry.Options.MessageId.Value, entry.Options.ChannelId.Value, user);
@@ -46,6 +46,9 @@ namespace Discord.Rest
///
/// Gets the user of the message that was pinned if available.
///
+ ///
+ /// Will be if the user is a 'Deleted User#....' because Discord does send user data for deleted users.
+ ///
///
/// A user object representing the user that created the pinned message or .
///
diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MessageUnpinAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MessageUnpinAuditLogData.cs
index b4fa389cc..f6fd31771 100644
--- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MessageUnpinAuditLogData.cs
+++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MessageUnpinAuditLogData.cs
@@ -23,7 +23,7 @@ namespace Discord.Rest
if (entry.TargetId.HasValue)
{
var userInfo = log.Users.FirstOrDefault(x => x.Id == entry.TargetId);
- user = RestUser.Create(discord, userInfo);
+ user = (userInfo != null) ? RestUser.Create(discord, userInfo) : null;
}
return new MessageUnpinAuditLogData(entry.Options.MessageId.Value, entry.Options.ChannelId.Value, user);
@@ -46,6 +46,9 @@ namespace Discord.Rest
///
/// Gets the user of the message that was unpinned if available.
///
+ ///
+ /// Will be if the user is a 'Deleted User#....' because Discord does send user data for deleted users.
+ ///
///
/// A user object representing the user that created the unpinned message or .
///
diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/UnbanAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/UnbanAuditLogData.cs
index bc7e7fd4f..f12d9a1af 100644
--- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/UnbanAuditLogData.cs
+++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/UnbanAuditLogData.cs
@@ -1,4 +1,4 @@
-using System.Linq;
+using System.Linq;
using Model = Discord.API.AuditLog;
using EntryModel = Discord.API.AuditLogEntry;
@@ -18,7 +18,7 @@ namespace Discord.Rest
internal static UnbanAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry)
{
var userInfo = log.Users.FirstOrDefault(x => x.Id == entry.TargetId);
- return new UnbanAuditLogData(RestUser.Create(discord, userInfo));
+ return new UnbanAuditLogData((userInfo != null) ? RestUser.Create(discord, userInfo) : null);
}
///
diff --git a/src/Discord.Net.Rest/Entities/Channels/RestForumChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestForumChannel.cs
new file mode 100644
index 000000000..aff8400aa
--- /dev/null
+++ b/src/Discord.Net.Rest/Entities/Channels/RestForumChannel.cs
@@ -0,0 +1,131 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Model = Discord.API.Channel;
+
+namespace Discord.Rest
+{
+ ///
+ /// Represents a REST-based forum channel in a guild.
+ ///
+ public class RestForumChannel : RestGuildChannel, IForumChannel
+ {
+ ///
+ public bool IsNsfw { get; private set; }
+
+ ///
+ public string Topic { get; private set; }
+
+ ///
+ public ThreadArchiveDuration DefaultAutoArchiveDuration { get; private set; }
+
+ ///
+ public IReadOnlyCollection Tags { get; private set; }
+
+ ///
+ public string Mention => MentionUtils.MentionChannel(Id);
+
+ internal RestForumChannel(BaseDiscordClient client, IGuild guild, ulong id)
+ : base(client, guild, id)
+ {
+
+ }
+
+ internal new static RestStageChannel Create(BaseDiscordClient discord, IGuild guild, Model model)
+ {
+ var entity = new RestStageChannel(discord, guild, model.Id);
+ entity.Update(model);
+ return entity;
+ }
+
+ internal override void Update(Model model)
+ {
+ base.Update(model);
+ IsNsfw = model.Nsfw.GetValueOrDefault(false);
+ Topic = model.Topic.GetValueOrDefault();
+ DefaultAutoArchiveDuration = model.AutoArchiveDuration.GetValueOrDefault(ThreadArchiveDuration.OneDay);
+
+ Tags = model.ForumTags.GetValueOrDefault(Array.Empty()).Select(
+ x => new ForumTag(x.Id, x.Name, x.EmojiId.GetValueOrDefault(null), x.EmojiName.GetValueOrDefault())
+ ).ToImmutableArray();
+ }
+
+ ///
+ public Task CreatePostAsync(string title, ThreadArchiveDuration archiveDuration = ThreadArchiveDuration.OneDay, int? slowmode = null, string text = null, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None)
+ => ThreadHelper.CreatePostAsync(this, Discord, title, archiveDuration, slowmode, text, embed, options, allowedMentions, components, stickers, embeds, flags);
+
+ ///
+ public async Task CreatePostWithFileAsync(string title, string filePath, ThreadArchiveDuration archiveDuration = ThreadArchiveDuration.OneDay,
+ int? slowmode = null, string text = null, Embed embed = null, RequestOptions options = null, bool isSpoiler = false,
+ AllowedMentions allowedMentions = null, MessageComponent components = null,
+ ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None)
+ {
+ using var file = new FileAttachment(filePath, isSpoiler: isSpoiler);
+ return await ThreadHelper.CreatePostAsync(this, Discord, title, new FileAttachment[] { file }, archiveDuration, slowmode, text, embed, options, allowedMentions, components, stickers, embeds, flags).ConfigureAwait(false);
+ }
+
+ ///
+ public async Task CreatePostWithFileAsync(string title, Stream stream, string filename, ThreadArchiveDuration archiveDuration = ThreadArchiveDuration.OneDay,
+ int? slowmode = null, string text = null, Embed embed = null, RequestOptions options = null, bool isSpoiler = false,
+ AllowedMentions allowedMentions = null, MessageComponent components = null,
+ ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None)
+ {
+ using var file = new FileAttachment(stream, filename, isSpoiler: isSpoiler);
+ return await ThreadHelper.CreatePostAsync(this, Discord, title, new FileAttachment[] { file }, archiveDuration, slowmode, text, embed, options, allowedMentions, components, stickers, embeds, flags).ConfigureAwait(false);
+ }
+
+ ///
+ public Task CreatePostWithFileAsync(string title, FileAttachment attachment, ThreadArchiveDuration archiveDuration = ThreadArchiveDuration.OneDay,
+ int? slowmode = null, string text = null, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null,
+ MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None)
+ => ThreadHelper.CreatePostAsync(this, Discord, title, new FileAttachment[] { attachment }, archiveDuration, slowmode, text, embed, options, allowedMentions, components, stickers, embeds, flags);
+
+ ///
+ public Task CreatePostWithFilesAsync(string title, IEnumerable attachments, ThreadArchiveDuration archiveDuration = ThreadArchiveDuration.OneDay,
+ int? slowmode = null, string text = null, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null,
+ MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None)
+ => ThreadHelper.CreatePostAsync(this, Discord, title, attachments, archiveDuration, slowmode, text, embed, options, allowedMentions, components, stickers, embeds, flags);
+
+ ///
+ public Task> GetActiveThreadsAsync(RequestOptions options = null)
+ => ThreadHelper.GetActiveThreadsAsync(Guild, Discord, options);
+
+ ///
+ public Task> GetJoinedPrivateArchivedThreadsAsync(int? limit = null, DateTimeOffset? before = null, RequestOptions options = null)
+ => ThreadHelper.GetJoinedPrivateArchivedThreadsAsync(this, Discord, limit, before, options);
+
+ ///
+ public Task> GetPrivateArchivedThreadsAsync(int? limit = null, DateTimeOffset? before = null, RequestOptions options = null)
+ => ThreadHelper.GetPrivateArchivedThreadsAsync(this, Discord, limit, before, options);
+
+ ///
+ public Task> GetPublicArchivedThreadsAsync(int? limit = null, DateTimeOffset? before = null, RequestOptions options = null)
+ => ThreadHelper.GetPublicArchivedThreadsAsync(this, Discord, limit, before, options);
+
+ #region IForumChannel
+ async Task> IForumChannel.GetActiveThreadsAsync(RequestOptions options)
+ => await GetActiveThreadsAsync(options).ConfigureAwait(false);
+ async Task> IForumChannel.GetPublicArchivedThreadsAsync(int? limit, DateTimeOffset? before, RequestOptions options)
+ => await GetPublicArchivedThreadsAsync(limit, before, options).ConfigureAwait(false);
+ async Task> IForumChannel.GetPrivateArchivedThreadsAsync(int? limit, DateTimeOffset? before, RequestOptions options)
+ => await GetPrivateArchivedThreadsAsync(limit, before, options).ConfigureAwait(false);
+ async Task> IForumChannel.GetJoinedPrivateArchivedThreadsAsync(int? limit, DateTimeOffset? before, RequestOptions options)
+ => await GetJoinedPrivateArchivedThreadsAsync(limit, before, options).ConfigureAwait(false);
+ async Task IForumChannel.CreatePostAsync(string title, ThreadArchiveDuration archiveDuration, int? slowmode, string text, Embed embed, RequestOptions options, AllowedMentions allowedMentions, MessageComponent components, ISticker[] stickers, Embed[] embeds, MessageFlags flags)
+ => await CreatePostAsync(title, archiveDuration, slowmode, text, embed, options, allowedMentions, components, stickers, embeds, flags).ConfigureAwait(false);
+ async Task IForumChannel.CreatePostWithFileAsync(string title, string filePath, ThreadArchiveDuration archiveDuration, int? slowmode, string text, Embed embed, RequestOptions options, bool isSpoiler, AllowedMentions allowedMentions, MessageComponent components, ISticker[] stickers, Embed[] embeds, MessageFlags flags)
+ => await CreatePostWithFileAsync(title, filePath, archiveDuration, slowmode, text, embed, options, isSpoiler, allowedMentions, components, stickers, embeds, flags).ConfigureAwait(false);
+ async Task IForumChannel.CreatePostWithFileAsync(string title, Stream stream, string filename, ThreadArchiveDuration archiveDuration, int? slowmode, string text, Embed embed, RequestOptions options, bool isSpoiler, AllowedMentions allowedMentions, MessageComponent components, ISticker[] stickers, Embed[] embeds, MessageFlags flags)
+ => await CreatePostWithFileAsync(title, stream, filename, archiveDuration, slowmode, text, embed, options, isSpoiler, allowedMentions, components, stickers, embeds, flags).ConfigureAwait(false);
+ async Task IForumChannel.CreatePostWithFileAsync(string title, FileAttachment attachment, ThreadArchiveDuration archiveDuration, int? slowmode, string text, Embed embed, RequestOptions options, AllowedMentions allowedMentions, MessageComponent components, ISticker[] stickers, Embed[] embeds, MessageFlags flags)
+ => await CreatePostWithFileAsync(title, attachment, archiveDuration, slowmode, text, embed, options, allowedMentions, components, stickers, embeds, flags).ConfigureAwait(false);
+ async Task IForumChannel.CreatePostWithFilesAsync(string title, IEnumerable attachments, ThreadArchiveDuration archiveDuration, int? slowmode, string text, Embed embed, RequestOptions options, AllowedMentions allowedMentions, MessageComponent components, ISticker[] stickers, Embed[] embeds, MessageFlags flags)
+ => await CreatePostWithFilesAsync(title, attachments, archiveDuration, slowmode, text, embed, options, allowedMentions, components, stickers, embeds, flags);
+
+ #endregion
+ }
+}
diff --git a/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs
index fa2362854..4f9af0335 100644
--- a/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs
+++ b/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs
@@ -39,6 +39,7 @@ namespace Discord.Rest
ChannelType.Text => RestTextChannel.Create(discord, guild, model),
ChannelType.Voice => RestVoiceChannel.Create(discord, guild, model),
ChannelType.Stage => RestStageChannel.Create(discord, guild, model),
+ ChannelType.Forum => RestForumChannel.Create(discord, guild, model),
ChannelType.Category => RestCategoryChannel.Create(discord, guild, model),
ChannelType.PublicThread or ChannelType.PrivateThread or ChannelType.NewsThread => RestThreadChannel.Create(discord, guild, model),
_ => new RestGuildChannel(discord, guild, model.Id),
diff --git a/src/Discord.Net.Rest/Entities/Channels/RestStageChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestStageChannel.cs
index c01df96fd..b34afd027 100644
--- a/src/Discord.Net.Rest/Entities/Channels/RestStageChannel.cs
+++ b/src/Discord.Net.Rest/Entities/Channels/RestStageChannel.cs
@@ -12,7 +12,11 @@ namespace Discord.Rest
public class RestStageChannel : RestVoiceChannel, IStageChannel
{
///
- public string Topic { get; private set; }
+ ///
+ /// This field is always false for stage channels.
+ ///
+ public override bool IsTextInVoice
+ => false;
///
public StagePrivacyLevel? PrivacyLevel { get; private set; }
@@ -37,13 +41,11 @@ namespace Discord.Rest
IsLive = isLive;
if(isLive)
{
- Topic = model.Topic;
PrivacyLevel = model.PrivacyLevel;
IsDiscoverableDisabled = model.DiscoverableDisabled;
}
else
{
- Topic = null;
PrivacyLevel = null;
IsDiscoverableDisabled = null;
}
diff --git a/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs
index 76c75ab6e..81f21bcd7 100644
--- a/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs
+++ b/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs
@@ -21,11 +21,12 @@ namespace Discord.Rest
public virtual int SlowModeInterval { get; private set; }
///
public ulong? CategoryId { get; private set; }
-
///
public string Mention => MentionUtils.MentionChannel(Id);
///
public bool IsNsfw { get; private set; }
+ ///
+ public ThreadArchiveDuration DefaultArchiveDuration { get; private set; }
internal RestTextChannel(BaseDiscordClient discord, IGuild guild, ulong id)
: base(discord, guild, id)
@@ -46,6 +47,12 @@ namespace Discord.Rest
if (model.SlowMode.IsSpecified)
SlowModeInterval = model.SlowMode.Value;
IsNsfw = model.Nsfw.GetValueOrDefault();
+
+ if (model.AutoArchiveDuration.IsSpecified)
+ DefaultArchiveDuration = model.AutoArchiveDuration.Value;
+ else
+ DefaultArchiveDuration = ThreadArchiveDuration.OneDay;
+ // basic value at channel creation. Shouldn't be called since guild text channels always have this property
}
///
@@ -86,25 +93,25 @@ namespace Discord.Rest
=> ChannelHelper.GetUsersAsync(this, Guild, Discord, null, null, options);
///
- public Task GetMessageAsync(ulong id, RequestOptions options = null)
+ public virtual Task GetMessageAsync(ulong id, RequestOptions options = null)
=> ChannelHelper.GetMessageAsync(this, Discord, id, options);
///
- public IAsyncEnumerable> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null)
+ public virtual IAsyncEnumerable> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null)
=> ChannelHelper.GetMessagesAsync(this, Discord, null, Direction.Before, limit, options);
///
- public IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null)
+ public virtual IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null)
=> ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit, options);
///
- public IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null)
+ public virtual IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null)
=> ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit, options);
///
- public Task> GetPinnedMessagesAsync(RequestOptions options = null)
+ public virtual Task> GetPinnedMessagesAsync(RequestOptions options = null)
=> ChannelHelper.GetPinnedMessagesAsync(this, Discord, options);
///
/// Message content is too long, length must be less or equal to .
/// The only valid are and .
- public Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null,
+ public virtual Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null,
RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null,
MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None)
=> ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, allowedMentions, messageReference,
@@ -136,7 +143,7 @@ namespace Discord.Rest
/// An I/O error occurred while opening the file.
/// Message content is too long, length must be less or equal to .
/// The only valid are and .
- public Task SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null,
+ public virtual Task SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null,
RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null,
MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null,
Embed[] embeds = null, MessageFlags flags = MessageFlags.None)
@@ -146,7 +153,7 @@ namespace Discord.Rest
///
/// Message content is too long, length must be less or equal to .
/// The only valid are and .
- public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false,
+ public virtual Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false,
Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null,
MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null,
Embed[] embeds = null, MessageFlags flags = MessageFlags.None)
@@ -156,7 +163,7 @@ namespace Discord.Rest
///
/// Message content is too long, length must be less or equal to .
/// The only valid are and .
- public Task SendFileAsync(FileAttachment attachment, string text, bool isTTS = false,
+ public virtual Task SendFileAsync(FileAttachment attachment, string text, bool isTTS = false,
Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null,
MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null,
Embed[] embeds = null, MessageFlags flags = MessageFlags.None)
@@ -166,35 +173,35 @@ namespace Discord.Rest
///
/// Message content is too long, length must be less or equal to .
/// The only valid are and .
- public Task SendFilesAsync(IEnumerable attachments, string text, bool isTTS = false,
+ public virtual Task SendFilesAsync(IEnumerable attachments, string text, bool isTTS = false,
Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null,
MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null,
Embed[] embeds = null, MessageFlags flags = MessageFlags.None)
=> ChannelHelper.SendFilesAsync(this, Discord, attachments, text, isTTS, embed, allowedMentions, messageReference, components, stickers, options, embeds, flags);
///
- public Task DeleteMessageAsync(ulong messageId, RequestOptions options = null)
+ public virtual Task DeleteMessageAsync(ulong messageId, RequestOptions options = null)
=> ChannelHelper.DeleteMessageAsync(this, messageId, Discord, options);
///
- public Task DeleteMessageAsync(IMessage message, RequestOptions options = null)
+ public virtual Task DeleteMessageAsync(IMessage message, RequestOptions options = null)
=> ChannelHelper.DeleteMessageAsync(this, message.Id, Discord, options);
///
- public Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null)
+ public virtual Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null)
=> ChannelHelper.DeleteMessagesAsync(this, Discord, messages.Select(x => x.Id), options);
///
- public Task DeleteMessagesAsync(IEnumerable messageIds, RequestOptions options = null)
+ public virtual Task DeleteMessagesAsync(IEnumerable messageIds, RequestOptions options = null)
=> ChannelHelper.DeleteMessagesAsync(this, Discord, messageIds, options);
///
- public async Task ModifyMessageAsync(ulong messageId, Action func, RequestOptions options = null)
+ public virtual async Task ModifyMessageAsync(ulong messageId, Action func, RequestOptions options = null)
=> await ChannelHelper.ModifyMessageAsync(this, messageId, func, Discord, options).ConfigureAwait(false);
///
- public Task TriggerTypingAsync(RequestOptions options = null)
+ public virtual Task TriggerTypingAsync(RequestOptions options = null)
=> ChannelHelper.TriggerTypingAsync(this, Discord, options);
///
- public IDisposable EnterTypingState(RequestOptions options = null)
+ public virtual IDisposable EnterTypingState(RequestOptions options = null)
=> ChannelHelper.EnterTypingState(this, Discord, options);
///
@@ -231,38 +238,6 @@ namespace Discord.Rest
public virtual Task> GetWebhooksAsync(RequestOptions options = null)
=> ChannelHelper.GetWebhooksAsync(this, Discord, options);
- ///
- /// Gets the parent (category) channel of this channel.
- ///
- /// The options to be used when sending the request.
- ///
- /// A task that represents the asynchronous get operation. The task result contains the category channel
- /// representing the parent of this channel; null if none is set.
- ///
- public virtual Task GetCategoryAsync(RequestOptions options = null)
- => ChannelHelper.GetCategoryAsync(this, Discord, options);
- ///
- public Task SyncPermissionsAsync(RequestOptions options = null)
- => ChannelHelper.SyncPermissionsAsync(this, Discord, options);
- #endregion
-
- #region Invites
- ///
- public virtual async Task CreateInviteAsync(int? maxAge = 86400, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null)
- => await ChannelHelper.CreateInviteAsync(this, Discord, maxAge, maxUses, isTemporary, isUnique, options).ConfigureAwait(false);
- public virtual async Task CreateInviteToApplicationAsync(ulong applicationId, int? maxAge = 86400, int? maxUses = default(int?), bool isTemporary = false, bool isUnique = false, RequestOptions options = null)
- => await ChannelHelper.CreateInviteToApplicationAsync(this, Discord, maxAge, maxUses, isTemporary, isUnique, applicationId, options);
- ///
- public virtual async Task CreateInviteToApplicationAsync(DefaultApplications application, int? maxAge = 86400, int? maxUses = default(int?), bool isTemporary = false, bool isUnique = false, RequestOptions options = null)
- => await ChannelHelper.CreateInviteToApplicationAsync(this, Discord, maxAge, maxUses, isTemporary, isUnique, (ulong)application, options);
- public virtual Task CreateInviteToStreamAsync(IUser user, int? maxAge, int? maxUses = default(int?), bool isTemporary = false, bool isUnique = false, RequestOptions options = null)
- => throw new NotImplementedException();
- ///
- public virtual async Task> GetInvitesAsync(RequestOptions options = null)
- => await ChannelHelper.GetInvitesAsync(this, Discord, options).ConfigureAwait(false);
-
- private string DebuggerDisplay => $"{Name} ({Id}, Text)";
-
///
/// Creates a thread within this .
///
@@ -299,6 +274,38 @@ namespace Discord.Rest
var model = await ThreadHelper.CreateThreadAsync(Discord, this, name, type, autoArchiveDuration, message, invitable, slowmode, options);
return RestThreadChannel.Create(Discord, Guild, model);
}
+
+ ///
+ /// Gets the parent (category) channel of this channel.
+ ///
+ /// The options to be used when sending the request.
+ ///
+ /// A task that represents the asynchronous get operation. The task result contains the category channel
+ /// representing the parent of this channel; null if none is set.
+ ///
+ public virtual Task GetCategoryAsync(RequestOptions options = null)
+ => ChannelHelper.GetCategoryAsync(this, Discord, options);
+ ///
+ public Task SyncPermissionsAsync(RequestOptions options = null)
+ => ChannelHelper.SyncPermissionsAsync(this, Discord, options);
+ #endregion
+
+ #region Invites
+ ///
+ public virtual async Task CreateInviteAsync(int? maxAge = 86400, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null)
+ => await ChannelHelper.CreateInviteAsync(this, Discord, maxAge, maxUses, isTemporary, isUnique, options).ConfigureAwait(false);
+ public virtual async Task CreateInviteToApplicationAsync(ulong applicationId, int? maxAge = 86400, int? maxUses = default(int?), bool isTemporary = false, bool isUnique = false, RequestOptions options = null)
+ => await ChannelHelper.CreateInviteToApplicationAsync(this, Discord, maxAge, maxUses, isTemporary, isUnique, applicationId, options);
+ ///
+ public virtual async Task CreateInviteToApplicationAsync(DefaultApplications application, int? maxAge = 86400, int? maxUses = default(int?), bool isTemporary = false, bool isUnique = false, RequestOptions options = null)
+ => await ChannelHelper.CreateInviteToApplicationAsync(this, Discord, maxAge, maxUses, isTemporary, isUnique, (ulong)application, options);
+ public virtual Task CreateInviteToStreamAsync(IUser user, int? maxAge, int? maxUses = default(int?), bool isTemporary = false, bool isUnique = false, RequestOptions options = null)
+ => throw new NotImplementedException();
+ ///
+ public virtual async Task> GetInvitesAsync(RequestOptions options = null)
+ => await ChannelHelper.GetInvitesAsync(this, Discord, options).ConfigureAwait(false);
+
+ private string DebuggerDisplay => $"{Name} ({Id}, Text)";
#endregion
#region ITextChannel
diff --git a/src/Discord.Net.Rest/Entities/Channels/RestVoiceChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestVoiceChannel.cs
index bcf03a5bc..31d313a48 100644
--- a/src/Discord.Net.Rest/Entities/Channels/RestVoiceChannel.cs
+++ b/src/Discord.Net.Rest/Entities/Channels/RestVoiceChannel.cs
@@ -2,6 +2,7 @@ using Discord.Audio;
using System;
using System.Collections.Generic;
using System.Diagnostics;
+using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Model = Discord.API.Channel;
@@ -12,21 +13,21 @@ namespace Discord.Rest
/// Represents a REST-based voice channel in a guild.
///
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
- public class RestVoiceChannel : RestGuildChannel, IVoiceChannel, IRestAudioChannel
+ public class RestVoiceChannel : RestTextChannel, IVoiceChannel, IRestAudioChannel
{
#region RestVoiceChannel
+ ///
+ /// Gets whether or not the guild has Text-In-Voice enabled and the voice channel is a TiV channel.
+ ///
+ public virtual bool IsTextInVoice
+ => Guild.Features.HasTextInVoice;
///
public int Bitrate { get; private set; }
///
public int? UserLimit { get; private set; }
- ///
- public ulong? CategoryId { get; private set; }
///
public string RTCRegion { get; private set; }
- ///
- public string Mention => MentionUtils.MentionChannel(Id);
-
internal RestVoiceChannel(BaseDiscordClient discord, IGuild guild, ulong id)
: base(discord, guild, id)
{
@@ -41,7 +42,6 @@ namespace Discord.Rest
internal override void Update(Model model)
{
base.Update(model);
- CategoryId = model.CategoryId;
if(model.Bitrate.IsSpecified)
Bitrate = model.Bitrate.Value;
@@ -59,41 +59,185 @@ namespace Discord.Rest
Update(model);
}
- ///
- /// Gets the parent (category) channel of this channel.
- ///
- /// The options to be used when sending the request.
- ///
- /// A task that represents the asynchronous get operation. The task result contains the category channel
- /// representing the parent of this channel; null if none is set.
- ///
- public Task GetCategoryAsync(RequestOptions options = null)
- => ChannelHelper.GetCategoryAsync(this, Discord, options);
- ///
- public Task SyncPermissionsAsync(RequestOptions options = null)
- => ChannelHelper.SyncPermissionsAsync(this, Discord, options);
- #endregion
+ ///
+ /// Cannot modify text channel properties of a voice channel.
+ public override Task ModifyAsync(Action func, RequestOptions options = null)
+ => throw new InvalidOperationException("Cannot modify text channel properties of a voice channel");
- #region Invites
- ///
- public async Task CreateInviteAsync(int? maxAge = 86400, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null)
- => await ChannelHelper.CreateInviteAsync(this, Discord, maxAge, maxUses, isTemporary, isUnique, options).ConfigureAwait(false);
- ///
- public async Task CreateInviteToApplicationAsync(ulong applicationId, int? maxAge, int? maxUses = default(int?), bool isTemporary = false, bool isUnique = false, RequestOptions options = null)
- => await ChannelHelper.CreateInviteToApplicationAsync(this, Discord, maxAge, maxUses, isTemporary, isUnique, applicationId, options).ConfigureAwait(false);
- ///
- public virtual async Task CreateInviteToApplicationAsync(DefaultApplications application, int? maxAge = 86400, int? maxUses = default(int?), bool isTemporary = false, bool isUnique = false, RequestOptions options = null)
- => await ChannelHelper.CreateInviteToApplicationAsync(this, Discord, maxAge, maxUses, isTemporary, isUnique, (ulong)application, options);
- ///
- public async Task CreateInviteToStreamAsync(IUser user, int? maxAge, int? maxUses = default(int?), bool isTemporary = false, bool isUnique = false, RequestOptions options = null)
- => await ChannelHelper.CreateInviteToStreamAsync(this, Discord, maxAge, maxUses, isTemporary, isUnique, user, options).ConfigureAwait(false);
- ///
- public async Task> GetInvitesAsync(RequestOptions options = null)
- => await ChannelHelper.GetInvitesAsync(this, Discord, options).ConfigureAwait(false);
+ ///
+ /// Cannot create a thread within a voice channel.
+ public override Task CreateThreadAsync(string name, ThreadType type = ThreadType.PublicThread, ThreadArchiveDuration autoArchiveDuration = ThreadArchiveDuration.OneDay, IMessage message = null, bool? invitable = null, int? slowmode = null, RequestOptions options = null)
+ => throw new InvalidOperationException("Cannot create a thread within a voice channel");
+
+ #endregion
private string DebuggerDisplay => $"{Name} ({Id}, Voice)";
+
+ #region TextOverrides
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override Task GetMessageAsync(ulong id, RequestOptions options = null)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.GetMessageAsync(id, options);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override Task DeleteMessageAsync(IMessage message, RequestOptions options = null)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.DeleteMessageAsync(message, options);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override Task DeleteMessageAsync(ulong messageId, RequestOptions options = null)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.DeleteMessageAsync(messageId, options);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.DeleteMessagesAsync(messages, options);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override Task DeleteMessagesAsync(IEnumerable messageIds, RequestOptions options = null)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.DeleteMessagesAsync(messageIds, options);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override IDisposable EnterTypingState(RequestOptions options = null)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.EnterTypingState(options);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = 100, RequestOptions options = null)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.GetMessagesAsync(fromMessage, dir, limit, options);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override IAsyncEnumerable> GetMessagesAsync(int limit = 100, RequestOptions options = null)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.GetMessagesAsync(limit, options);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = 100, RequestOptions options = null)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.GetMessagesAsync(fromMessageId, dir, limit, options);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override Task> GetPinnedMessagesAsync(RequestOptions options = null)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.GetPinnedMessagesAsync(options);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override Task GetWebhookAsync(ulong id, RequestOptions options = null)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.GetWebhookAsync(id, options);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override Task> GetWebhooksAsync(RequestOptions options = null)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.GetWebhooksAsync(options);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override Task CreateWebhookAsync(string name, Stream avatar = null, RequestOptions options = null)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.CreateWebhookAsync(name, avatar, options);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override Task ModifyMessageAsync(ulong messageId, Action func, RequestOptions options = null)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.ModifyMessageAsync(messageId, func, options);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override Task SendFileAsync(FileAttachment attachment, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.SendFileAsync(attachment, text, isTTS, embed, options, allowedMentions, messageReference, components, stickers, embeds, flags);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.SendFileAsync(stream, filename, text, isTTS, embed, options, isSpoiler, allowedMentions, messageReference, components, stickers, embeds, flags);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override Task SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.SendFileAsync(filePath, text, isTTS, embed, options, isSpoiler, allowedMentions, messageReference, components, stickers, embeds, flags);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override Task SendFilesAsync(IEnumerable attachments, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.SendFilesAsync(attachments, text, isTTS, embed, options, allowedMentions, messageReference, components, stickers, embeds, flags);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.SendMessageAsync(text, isTTS, embed, options, allowedMentions, messageReference, components, stickers, embeds, flags);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override Task TriggerTypingAsync(RequestOptions options = null)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.TriggerTypingAsync(options);
+ }
+
#endregion
+
#region IAudioChannel
///
/// Connecting to a REST-based channel is not supported.
diff --git a/src/Discord.Net.Rest/Entities/Channels/ThreadHelper.cs b/src/Discord.Net.Rest/Entities/Channels/ThreadHelper.cs
index e0074ecff..f5fce5a50 100644
--- a/src/Discord.Net.Rest/Entities/Channels/ThreadHelper.cs
+++ b/src/Discord.Net.Rest/Entities/Channels/ThreadHelper.cs
@@ -1,5 +1,7 @@
using Discord.API.Rest;
using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
using System.Linq;
using System.Threading.Tasks;
using Model = Discord.API.Channel;
@@ -60,6 +62,33 @@ namespace Discord.Rest
return await client.ApiClient.ModifyThreadAsync(channel.Id, apiArgs, options).ConfigureAwait(false);
}
+ public static async Task> GetActiveThreadsAsync(IGuild guild, BaseDiscordClient client, RequestOptions options)
+ {
+ var result = await client.ApiClient.GetActiveThreadsAsync(guild.Id, options).ConfigureAwait(false);
+ return result.Threads.Select(x => RestThreadChannel.Create(client, guild, x)).ToImmutableArray();
+ }
+
+ public static async Task> GetPublicArchivedThreadsAsync(IGuildChannel channel, BaseDiscordClient client, int? limit = null,
+ DateTimeOffset? before = null, RequestOptions options = null)
+ {
+ var result = await client.ApiClient.GetPublicArchivedThreadsAsync(channel.Id, before, limit, options);
+ return result.Threads.Select(x => RestThreadChannel.Create(client, channel.Guild, x)).ToImmutableArray();
+ }
+
+ public static async Task> GetPrivateArchivedThreadsAsync(IGuildChannel channel, BaseDiscordClient client, int? limit = null,
+ DateTimeOffset? before = null, RequestOptions options = null)
+ {
+ var result = await client.ApiClient.GetPrivateArchivedThreadsAsync(channel.Id, before, limit, options);
+ return result.Threads.Select(x => RestThreadChannel.Create(client, channel.Guild, x)).ToImmutableArray();
+ }
+
+ public static async Task> GetJoinedPrivateArchivedThreadsAsync(IGuildChannel channel, BaseDiscordClient client, int? limit = null,
+ DateTimeOffset? before = null, RequestOptions options = null)
+ {
+ var result = await client.ApiClient.GetJoinedPrivateArchivedThreadsAsync(channel.Id, before, limit, options);
+ return result.Threads.Select(x => RestThreadChannel.Create(client, channel.Guild, x)).ToImmutableArray();
+ }
+
public static async Task GetUsersAsync(IThreadChannel channel, BaseDiscordClient client, RequestOptions options = null)
{
var users = await client.ApiClient.ListThreadMembersAsync(channel.Id, options);
@@ -73,5 +102,114 @@ namespace Discord.Rest
return RestThreadUser.Create(client, channel.Guild, model, channel);
}
+
+ public static async Task CreatePostAsync(IForumChannel channel, BaseDiscordClient client, string title, ThreadArchiveDuration archiveDuration = ThreadArchiveDuration.OneDay, int? slowmode = null, string text = null, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None)
+ {
+ embeds ??= Array.Empty();
+ if (embed != null)
+ embeds = new[] { embed }.Concat(embeds).ToArray();
+
+ Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed.");
+ Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed.");
+ Preconditions.AtMost(embeds.Length, 10, nameof(embeds), "A max of 10 embeds are allowed.");
+
+ // check that user flag and user Id list are exclusive, same with role flag and role Id list
+ if (allowedMentions != null && allowedMentions.AllowedTypes.HasValue)
+ {
+ if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Users) &&
+ allowedMentions.UserIds != null && allowedMentions.UserIds.Count > 0)
+ {
+ throw new ArgumentException("The Users flag is mutually exclusive with the list of User Ids.", nameof(allowedMentions));
+ }
+
+ if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Roles) &&
+ allowedMentions.RoleIds != null && allowedMentions.RoleIds.Count > 0)
+ {
+ throw new ArgumentException("The Roles flag is mutually exclusive with the list of Role Ids.", nameof(allowedMentions));
+ }
+ }
+
+ if (stickers != null)
+ {
+ Preconditions.AtMost(stickers.Length, 3, nameof(stickers), "A max of 3 stickers are allowed.");
+ }
+
+
+ if (flags is not MessageFlags.None and not MessageFlags.SuppressEmbeds)
+ throw new ArgumentException("The only valid MessageFlags are SuppressEmbeds and none.", nameof(flags));
+
+ var args = new CreatePostParams()
+ {
+ Title = title,
+ ArchiveDuration = archiveDuration,
+ Slowmode = slowmode,
+ Message = new()
+ {
+ AllowedMentions = allowedMentions.ToModel(),
+ Content = text,
+ Embeds = embeds.Any() ? embeds.Select(x => x.ToModel()).ToArray() : Optional.Unspecified,
+ Flags = flags,
+ Components = components?.Components?.Any() ?? false ? components.Components.Select(x => new API.ActionRowComponent(x)).ToArray() : Optional.Unspecified,
+ Stickers = stickers?.Any() ?? false ? stickers.Select(x => x.Id).ToArray() : Optional.Unspecified,
+ }
+ };
+
+ var model = await client.ApiClient.CreatePostAsync(channel.Id, args, options).ConfigureAwait(false);
+
+ return RestThreadChannel.Create(client, channel.Guild, model);
+ }
+
+ public static async Task CreatePostAsync(IForumChannel channel, BaseDiscordClient client, string title, IEnumerable attachments, ThreadArchiveDuration archiveDuration, int? slowmode, string text, Embed embed, RequestOptions options, AllowedMentions allowedMentions, MessageComponent components, ISticker[] stickers, Embed[] embeds, MessageFlags flags)
+ {
+ embeds ??= Array.Empty();
+ if (embed != null)
+ embeds = new[] { embed }.Concat(embeds).ToArray();
+
+ Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed.");
+ Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed.");
+ Preconditions.AtMost(embeds.Length, 10, nameof(embeds), "A max of 10 embeds are allowed.");
+
+ // check that user flag and user Id list are exclusive, same with role flag and role Id list
+ if (allowedMentions != null && allowedMentions.AllowedTypes.HasValue)
+ {
+ if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Users) &&
+ allowedMentions.UserIds != null && allowedMentions.UserIds.Count > 0)
+ {
+ throw new ArgumentException("The Users flag is mutually exclusive with the list of User Ids.", nameof(allowedMentions));
+ }
+
+ if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Roles) &&
+ allowedMentions.RoleIds != null && allowedMentions.RoleIds.Count > 0)
+ {
+ throw new ArgumentException("The Roles flag is mutually exclusive with the list of Role Ids.", nameof(allowedMentions));
+ }
+ }
+
+ if (stickers != null)
+ {
+ Preconditions.AtMost(stickers.Length, 3, nameof(stickers), "A max of 3 stickers are allowed.");
+ }
+
+
+ if (flags is not MessageFlags.None and not MessageFlags.SuppressEmbeds)
+ throw new ArgumentException("The only valid MessageFlags are SuppressEmbeds and none.", nameof(flags));
+
+ var args = new CreateMultipartPostAsync(attachments.ToArray())
+ {
+ AllowedMentions = allowedMentions.ToModel(),
+ ArchiveDuration = archiveDuration,
+ Content = text,
+ Embeds = embeds.Any() ? embeds.Select(x => x.ToModel()).ToArray() : Optional.Unspecified,
+ Flags = flags,
+ MessageComponent = components?.Components?.Any() ?? false ? components.Components.Select(x => new API.ActionRowComponent(x)).ToArray() : Optional.Unspecified,
+ Slowmode = slowmode,
+ Stickers = stickers?.Any() ?? false ? stickers.Select(x => x.Id).ToArray() : Optional.Unspecified,
+ Title = title
+ };
+
+ var model = await client.ApiClient.CreatePostAsync(channel.Id, args, options);
+
+ return RestThreadChannel.Create(client, channel.Guild, model);
+ }
}
}
diff --git a/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs
index 469e93db4..8bab35937 100644
--- a/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs
+++ b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs
@@ -132,12 +132,15 @@ namespace Discord.Rest
}
public static ulong GetUploadLimit(IGuild guild)
{
- return guild.PremiumTier switch
+ var tierFactor = guild.PremiumTier switch
{
- PremiumTier.Tier2 => 50ul * 1000000,
- PremiumTier.Tier3 => 100ul * 1000000,
- _ => 8ul * 1000000
+ PremiumTier.Tier2 => 50,
+ PremiumTier.Tier3 => 100,
+ _ => 8
};
+
+ var mebibyte = Math.Pow(2, 20);
+ return (ulong) (tierFactor * mebibyte);
}
#endregion
@@ -151,7 +154,7 @@ namespace Discord.Rest
if (fromUserId.HasValue)
return GetBansAsync(guild, client, fromUserId.Value + 1, Direction.Before, around + 1, options)
.Concat(GetBansAsync(guild, client, fromUserId.Value, Direction.After, around, options));
- else
+ else
return GetBansAsync(guild, client, null, Direction.Before, around + 1, options);
}
@@ -908,7 +911,7 @@ namespace Discord.Rest
if (endTime != null && endTime <= startTime)
throw new ArgumentOutOfRangeException(nameof(endTime), $"{nameof(endTime)} cannot be before the start time");
-
+
var apiArgs = new CreateGuildScheduledEventParams()
{
ChannelId = channelId ?? Optional.Unspecified,
diff --git a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs
index 92d598466..974ea69ad 100644
--- a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs
+++ b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs
@@ -1161,7 +1161,6 @@ namespace Discord.Rest
/// in order to use this property.
///
///
- /// A collection of speakers for the event.
/// The location of the event; links are supported
/// The optional banner image for the event.
/// The options to be used when sending the request.
diff --git a/src/Discord.Net.Rest/Entities/Interactions/CommandBase/RestCommandBase.cs b/src/Discord.Net.Rest/Entities/Interactions/CommandBase/RestCommandBase.cs
index 196416f0e..22e56a733 100644
--- a/src/Discord.Net.Rest/Entities/Interactions/CommandBase/RestCommandBase.cs
+++ b/src/Discord.Net.Rest/Entities/Interactions/CommandBase/RestCommandBase.cs
@@ -39,16 +39,16 @@ namespace Discord.Rest
{
}
- internal new static async Task CreateAsync(DiscordRestClient client, Model model)
+ internal new static async Task CreateAsync(DiscordRestClient client, Model model, bool doApiCall)
{
var entity = new RestCommandBase(client, model);
- await entity.UpdateAsync(client, model).ConfigureAwait(false);
+ await entity.UpdateAsync(client, model, doApiCall).ConfigureAwait(false);
return entity;
}
- internal override async Task UpdateAsync(DiscordRestClient client, Model model)
+ internal override async Task UpdateAsync(DiscordRestClient client, Model model, bool doApiCall)
{
- await base.UpdateAsync(client, model).ConfigureAwait(false);
+ await base.UpdateAsync(client, model, doApiCall).ConfigureAwait(false);
}
///
diff --git a/src/Discord.Net.Rest/Entities/Interactions/CommandBase/RestCommandBaseData.cs b/src/Discord.Net.Rest/Entities/Interactions/CommandBase/RestCommandBaseData.cs
index 4227c802a..828299d22 100644
--- a/src/Discord.Net.Rest/Entities/Interactions/CommandBase/RestCommandBaseData.cs
+++ b/src/Discord.Net.Rest/Entities/Interactions/CommandBase/RestCommandBaseData.cs
@@ -27,20 +27,20 @@ namespace Discord.Rest
{
}
- internal static async Task CreateAsync(DiscordRestClient client, Model model, RestGuild guild, IRestMessageChannel channel)
+ internal static async Task CreateAsync(DiscordRestClient client, Model model, RestGuild guild, IRestMessageChannel channel, bool doApiCall)
{
var entity = new RestCommandBaseData(client, model);
- await entity.UpdateAsync(client, model, guild, channel).ConfigureAwait(false);
+ await entity.UpdateAsync(client, model, guild, channel, doApiCall).ConfigureAwait(false);
return entity;
}
- internal virtual async Task UpdateAsync(DiscordRestClient client, Model model, RestGuild guild, IRestMessageChannel channel)
+ internal virtual async Task UpdateAsync(DiscordRestClient client, Model model, RestGuild guild, IRestMessageChannel channel, bool doApiCall)
{
Name = model.Name;
if (model.Resolved.IsSpecified && ResolvableData == null)
{
ResolvableData = new RestResolvableData();
- await ResolvableData.PopulateAsync(client, guild, channel, model).ConfigureAwait(false);
+ await ResolvableData.PopulateAsync(client, guild, channel, model, doApiCall).ConfigureAwait(false);
}
}
diff --git a/src/Discord.Net.Rest/Entities/Interactions/CommandBase/RestResolvableData.cs b/src/Discord.Net.Rest/Entities/Interactions/CommandBase/RestResolvableData.cs
index 9353a8530..72b894729 100644
--- a/src/Discord.Net.Rest/Entities/Interactions/CommandBase/RestResolvableData.cs
+++ b/src/Discord.Net.Rest/Entities/Interactions/CommandBase/RestResolvableData.cs
@@ -22,7 +22,7 @@ namespace Discord.Rest
internal readonly Dictionary Attachments
= new Dictionary();
- internal async Task PopulateAsync(DiscordRestClient discord, RestGuild guild, IRestMessageChannel channel, T model)
+ internal async Task PopulateAsync(DiscordRestClient discord, RestGuild guild, IRestMessageChannel channel, T model, bool doApiCall)
{
var resolved = model.Resolved.Value;
@@ -38,15 +38,26 @@ namespace Discord.Rest
if (resolved.Channels.IsSpecified)
{
- var channels = await guild.GetChannelsAsync().ConfigureAwait(false);
+ var channels = doApiCall ? await guild.GetChannelsAsync().ConfigureAwait(false) : null;
foreach (var channelModel in resolved.Channels.Value)
{
- var restChannel = channels.FirstOrDefault(x => x.Id == channelModel.Value.Id);
+ if (channels != null)
+ {
+ var guildChannel = channels.FirstOrDefault(x => x.Id == channelModel.Value.Id);
- restChannel.Update(channelModel.Value);
+ guildChannel.Update(channelModel.Value);
- Channels.Add(ulong.Parse(channelModel.Key), restChannel);
+ Channels.Add(ulong.Parse(channelModel.Key), guildChannel);
+ }
+ else
+ {
+ var restChannel = RestChannel.Create(discord, channelModel.Value);
+
+ restChannel.Update(channelModel.Value);
+
+ Channels.Add(ulong.Parse(channelModel.Key), restChannel);
+ }
}
}
@@ -76,7 +87,10 @@ namespace Discord.Rest
{
foreach (var msg in resolved.Messages.Value)
{
- channel ??= (IRestMessageChannel)(Channels.FirstOrDefault(x => x.Key == msg.Value.ChannelId).Value ?? await discord.GetChannelAsync(msg.Value.ChannelId).ConfigureAwait(false));
+ channel ??= (IRestMessageChannel)(Channels.FirstOrDefault(x => x.Key == msg.Value.ChannelId).Value
+ ?? (doApiCall
+ ? await discord.GetChannelAsync(msg.Value.ChannelId).ConfigureAwait(false)
+ : null));
RestUser author;
diff --git a/src/Discord.Net.Rest/Entities/Interactions/ContextMenuCommands/MessageCommands/RestMessageCommand.cs b/src/Discord.Net.Rest/Entities/Interactions/ContextMenuCommands/MessageCommands/RestMessageCommand.cs
index 609fe0829..34c664b09 100644
--- a/src/Discord.Net.Rest/Entities/Interactions/ContextMenuCommands/MessageCommands/RestMessageCommand.cs
+++ b/src/Discord.Net.Rest/Entities/Interactions/ContextMenuCommands/MessageCommands/RestMessageCommand.cs
@@ -20,22 +20,22 @@ namespace Discord.Rest
}
- internal new static async Task CreateAsync(DiscordRestClient client, Model model)
+ internal new static async Task CreateAsync(DiscordRestClient client, Model model, bool doApiCall)
{
var entity = new RestMessageCommand(client, model);
- await entity.UpdateAsync(client, model).ConfigureAwait(false);
+ await entity.UpdateAsync(client, model, doApiCall).ConfigureAwait(false);
return entity;
}
- internal override async Task UpdateAsync(DiscordRestClient client, Model model)
+ internal override async Task UpdateAsync(DiscordRestClient client, Model model, bool doApiCall)
{
- await base.UpdateAsync(client, model).ConfigureAwait(false);
+ await base.UpdateAsync(client, model, doApiCall).ConfigureAwait(false);
var dataModel = model.Data.IsSpecified
? (DataModel)model.Data.Value
: null;
- Data = await RestMessageCommandData.CreateAsync(client, dataModel, Guild, Channel).ConfigureAwait(false);
+ Data = await RestMessageCommandData.CreateAsync(client, dataModel, Guild, Channel, doApiCall).ConfigureAwait(false);
}
//IMessageCommandInteraction
diff --git a/src/Discord.Net.Rest/Entities/Interactions/ContextMenuCommands/MessageCommands/RestMessageCommandData.cs b/src/Discord.Net.Rest/Entities/Interactions/ContextMenuCommands/MessageCommands/RestMessageCommandData.cs
index 127d539d9..d2968a38a 100644
--- a/src/Discord.Net.Rest/Entities/Interactions/ContextMenuCommands/MessageCommands/RestMessageCommandData.cs
+++ b/src/Discord.Net.Rest/Entities/Interactions/ContextMenuCommands/MessageCommands/RestMessageCommandData.cs
@@ -23,15 +23,15 @@ namespace Discord.Rest
/// Note Not implemented for
///
public override IReadOnlyCollection Options
- => throw new System.NotImplementedException();
+ => throw new NotImplementedException();
internal RestMessageCommandData(DiscordRestClient client, Model model)
: base(client, model) { }
- internal new static async Task CreateAsync(DiscordRestClient client, Model model, RestGuild guild, IRestMessageChannel channel)
+ internal new static async Task CreateAsync(DiscordRestClient client, Model model, RestGuild guild, IRestMessageChannel channel, bool doApiCall)
{
var entity = new RestMessageCommandData(client, model);
- await entity.UpdateAsync(client, model, guild, channel).ConfigureAwait(false);
+ await entity.UpdateAsync(client, model, guild, channel, doApiCall).ConfigureAwait(false);
return entity;
}
diff --git a/src/Discord.Net.Rest/Entities/Interactions/ContextMenuCommands/UserCommands/RestUserCommand.cs b/src/Discord.Net.Rest/Entities/Interactions/ContextMenuCommands/UserCommands/RestUserCommand.cs
index 7f55fd61b..91319a649 100644
--- a/src/Discord.Net.Rest/Entities/Interactions/ContextMenuCommands/UserCommands/RestUserCommand.cs
+++ b/src/Discord.Net.Rest/Entities/Interactions/ContextMenuCommands/UserCommands/RestUserCommand.cs
@@ -23,22 +23,22 @@ namespace Discord.Rest
{
}
- internal new static async Task CreateAsync(DiscordRestClient client, Model model)
+ internal new static async Task CreateAsync(DiscordRestClient client, Model model, bool doApiCall)
{
var entity = new RestUserCommand(client, model);
- await entity.UpdateAsync(client, model).ConfigureAwait(false);
+ await entity.UpdateAsync(client, model, doApiCall).ConfigureAwait(false);
return entity;
}
- internal override async Task UpdateAsync(DiscordRestClient client, Model model)
+ internal override async Task UpdateAsync(DiscordRestClient client, Model model, bool doApiCall)
{
- await base.UpdateAsync(client, model).ConfigureAwait(false);
+ await base.UpdateAsync(client, model, doApiCall).ConfigureAwait(false);
var dataModel = model.Data.IsSpecified
? (DataModel)model.Data.Value
: null;
- Data = await RestUserCommandData.CreateAsync(client, dataModel, Guild, Channel).ConfigureAwait(false);
+ Data = await RestUserCommandData.CreateAsync(client, dataModel, Guild, Channel, doApiCall).ConfigureAwait(false);
}
//IUserCommandInteractionData
diff --git a/src/Discord.Net.Rest/Entities/Interactions/ContextMenuCommands/UserCommands/RestUserCommandData.cs b/src/Discord.Net.Rest/Entities/Interactions/ContextMenuCommands/UserCommands/RestUserCommandData.cs
index e18499d42..61b291f7c 100644
--- a/src/Discord.Net.Rest/Entities/Interactions/ContextMenuCommands/UserCommands/RestUserCommandData.cs
+++ b/src/Discord.Net.Rest/Entities/Interactions/ContextMenuCommands/UserCommands/RestUserCommandData.cs
@@ -26,10 +26,10 @@ namespace Discord.Rest
internal RestUserCommandData(DiscordRestClient client, Model model)
: base(client, model) { }
- internal new static async Task CreateAsync(DiscordRestClient client, Model model, RestGuild guild, IRestMessageChannel channel)
+ internal new static async Task CreateAsync(DiscordRestClient client, Model model, RestGuild guild, IRestMessageChannel channel, bool doApiCall)
{
var entity = new RestUserCommandData(client, model);
- await entity.UpdateAsync(client, model, guild, channel).ConfigureAwait(false);
+ await entity.UpdateAsync(client, model, guild, channel, doApiCall).ConfigureAwait(false);
return entity;
}
diff --git a/src/Discord.Net.Rest/Entities/Interactions/InteractionHelper.cs b/src/Discord.Net.Rest/Entities/Interactions/InteractionHelper.cs
index e345bfa94..522c098e6 100644
--- a/src/Discord.Net.Rest/Entities/Interactions/InteractionHelper.cs
+++ b/src/Discord.Net.Rest/Entities/Interactions/InteractionHelper.cs
@@ -100,7 +100,12 @@ namespace Discord.Rest
Type = arg.Type,
DefaultPermission = arg.IsDefaultPermission.IsSpecified
? arg.IsDefaultPermission.Value
- : Optional.Unspecified
+ : Optional.Unspecified,
+
+ // TODO: better conversion to nullable optionals
+ DefaultMemberPermission = arg.DefaultMemberPermissions.ToNullable(),
+ DmPermission = arg.IsDMEnabled.ToNullable()
+
};
if (arg is SlashCommandProperties slashProps)
@@ -134,7 +139,11 @@ namespace Discord.Rest
Type = arg.Type,
DefaultPermission = arg.IsDefaultPermission.IsSpecified
? arg.IsDefaultPermission.Value
- : Optional.Unspecified
+ : Optional.Unspecified,
+
+ // TODO: better conversion to nullable optionals
+ DefaultMemberPermission = arg.DefaultMemberPermissions.ToNullable(),
+ DmPermission = arg.IsDMEnabled.ToNullable()
};
if (arg is SlashCommandProperties slashProps)
@@ -171,7 +180,11 @@ namespace Discord.Rest
Type = arg.Type,
DefaultPermission = arg.IsDefaultPermission.IsSpecified
? arg.IsDefaultPermission.Value
- : Optional.Unspecified
+ : Optional.Unspecified,
+
+ // TODO: better conversion to nullable optionals
+ DefaultMemberPermission = arg.DefaultMemberPermissions.ToNullable(),
+ DmPermission = arg.IsDMEnabled.ToNullable()
};
if (arg is SlashCommandProperties slashProps)
@@ -285,7 +298,11 @@ namespace Discord.Rest
Type = arg.Type,
DefaultPermission = arg.IsDefaultPermission.IsSpecified
? arg.IsDefaultPermission.Value
- : Optional.Unspecified
+ : Optional.Unspecified,
+
+ // TODO: better conversion to nullable optionals
+ DefaultMemberPermission = arg.DefaultMemberPermissions.ToNullable(),
+ DmPermission = arg.IsDMEnabled.ToNullable()
};
if (arg is SlashCommandProperties slashProps)
@@ -352,7 +369,7 @@ namespace Discord.Rest
#endregion
#region Responses
- public static async Task ModifyFollowupMessageAsync(BaseDiscordClient client, RestFollowupMessage message, Action func,
+ public static async Task ModifyFollowupMessageAsync(BaseDiscordClient client, RestFollowupMessage message, Action func,
RequestOptions options = null)
{
var args = new MessageProperties();
@@ -394,7 +411,7 @@ namespace Discord.Rest
}
public static async Task DeleteFollowupMessageAsync(BaseDiscordClient client, RestFollowupMessage message, RequestOptions options = null)
=> await client.ApiClient.DeleteInteractionFollowupMessageAsync(message.Id, message.Token, options);
- public static async Task ModifyInteractionResponseAsync(BaseDiscordClient client, string token, Action func,
+ public static async Task ModifyInteractionResponseAsync(BaseDiscordClient client, string token, Action func,
RequestOptions options = null)
{
var args = new MessageProperties();
diff --git a/src/Discord.Net.Rest/Entities/Interactions/InteractionProperties.cs b/src/Discord.Net.Rest/Entities/Interactions/InteractionProperties.cs
new file mode 100644
index 000000000..03750d7d9
--- /dev/null
+++ b/src/Discord.Net.Rest/Entities/Interactions/InteractionProperties.cs
@@ -0,0 +1,101 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Discord.Rest
+{
+ ///
+ /// Represents a class that contains data present in all interactions to evaluate against at rest-interaction creation.
+ ///
+ public readonly struct InteractionProperties
+ {
+ ///
+ /// The type of this interaction.
+ ///
+ public InteractionType Type { get; }
+
+ ///
+ /// Gets the type of application command this interaction represents.
+ ///
+ ///
+ /// This will be if the is not .
+ ///
+ public ApplicationCommandType? CommandType { get; }
+
+ ///
+ /// Gets the name of the interaction.
+ ///
+ ///
+ /// This will be if the is not .
+ ///
+ public string Name { get; } = string.Empty;
+
+ ///
+ /// Gets the custom ID of the interaction.
+ ///
+ ///
+ /// This will be if the is not or .
+ ///
+ public string CustomId { get; } = string.Empty;
+
+ ///
+ /// Gets the guild ID of the interaction.
+ ///
+ ///
+ /// This will be if this interaction was not executed in a guild.
+ ///
+ public ulong? GuildId { get; }
+
+ ///
+ /// Gets the channel ID of the interaction.
+ ///
+ ///
+ /// This will be if this interaction is .
+ ///
+ public ulong? ChannelId { get; }
+
+ internal InteractionProperties(API.Interaction model)
+ {
+ Type = model.Type;
+ CommandType = null;
+
+ if (model.GuildId.IsSpecified)
+ GuildId = model.GuildId.Value;
+ else
+ GuildId = null;
+
+ if (model.ChannelId.IsSpecified)
+ ChannelId = model.ChannelId.Value;
+ else
+ ChannelId = null;
+
+ switch (Type)
+ {
+ case InteractionType.ApplicationCommand:
+ {
+ var data = (API.ApplicationCommandInteractionData)model.Data;
+
+ CommandType = data.Type;
+ Name = data.Name;
+ }
+ break;
+ case InteractionType.MessageComponent:
+ {
+ var data = (API.MessageComponentInteractionData)model.Data;
+
+ CustomId = data.CustomId;
+ }
+ break;
+ case InteractionType.ModalSubmit:
+ {
+ var data = (API.ModalInteractionData)model.Data;
+
+ CustomId = data.CustomId;
+ }
+ break;
+ }
+ }
+ }
+}
diff --git a/src/Discord.Net.Rest/Entities/Interactions/MessageComponents/RestMessageComponent.cs b/src/Discord.Net.Rest/Entities/Interactions/MessageComponents/RestMessageComponent.cs
index 002510eac..e0eab6051 100644
--- a/src/Discord.Net.Rest/Entities/Interactions/MessageComponents/RestMessageComponent.cs
+++ b/src/Discord.Net.Rest/Entities/Interactions/MessageComponents/RestMessageComponent.cs
@@ -37,15 +37,15 @@ namespace Discord.Rest
Data = new RestMessageComponentData(dataModel);
}
- internal new static async Task CreateAsync(DiscordRestClient client, Model model)
+ internal new static async Task CreateAsync(DiscordRestClient client, Model model, bool doApiCall)
{
var entity = new RestMessageComponent(client, model);
- await entity.UpdateAsync(client, model).ConfigureAwait(false);
+ await entity.UpdateAsync(client, model, doApiCall).ConfigureAwait(false);
return entity;
}
- internal override async Task UpdateAsync(DiscordRestClient discord, Model model)
+ internal override async Task UpdateAsync(DiscordRestClient discord, Model model, bool doApiCall)
{
- await base.UpdateAsync(discord, model).ConfigureAwait(false);
+ await base.UpdateAsync(discord, model, doApiCall).ConfigureAwait(false);
if (model.Message.IsSpecified && model.ChannelId.IsSpecified)
{
diff --git a/src/Discord.Net.Rest/Entities/Interactions/Modals/RestModal.cs b/src/Discord.Net.Rest/Entities/Interactions/Modals/RestModal.cs
index 5f54fe051..9229b63b5 100644
--- a/src/Discord.Net.Rest/Entities/Interactions/Modals/RestModal.cs
+++ b/src/Discord.Net.Rest/Entities/Interactions/Modals/RestModal.cs
@@ -26,10 +26,10 @@ namespace Discord.Rest
Data = new RestModalData(dataModel);
}
- internal new static async Task CreateAsync(DiscordRestClient client, ModelBase model)
+ internal new static async Task CreateAsync(DiscordRestClient client, ModelBase model, bool doApiCall)
{
var entity = new RestModal(client, model);
- await entity.UpdateAsync(client, model);
+ await entity.UpdateAsync(client, model, doApiCall);
return entity;
}
diff --git a/src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommand.cs b/src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommand.cs
index ea8d5bc42..667609ef4 100644
--- a/src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommand.cs
+++ b/src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommand.cs
@@ -27,6 +27,12 @@ namespace Discord.Rest
///
public bool IsDefaultPermission { get; private set; }
+ ///
+ public bool IsEnabledInDm { get; private set; }
+
+ ///
+ public GuildPermissions DefaultMemberPermissions { get; private set; }
+
///
/// Gets a collection of options for this command.
///
@@ -57,6 +63,9 @@ namespace Discord.Rest
Options = model.Options.IsSpecified
? model.Options.Value.Select(RestApplicationCommandOption.Create).ToImmutableArray()
: ImmutableArray.Create();
+
+ IsEnabledInDm = model.DmPermission.GetValueOrDefault(true).GetValueOrDefault(true);
+ DefaultMemberPermissions = new GuildPermissions((ulong)model.DefaultMemberPermission.GetValueOrDefault(0).GetValueOrDefault(0));
}
///
diff --git a/src/Discord.Net.Rest/Entities/Interactions/RestInteraction.cs b/src/Discord.Net.Rest/Entities/Interactions/RestInteraction.cs
index 8a8921abe..43d13f521 100644
--- a/src/Discord.Net.Rest/Entities/Interactions/RestInteraction.cs
+++ b/src/Discord.Net.Rest/Entities/Interactions/RestInteraction.cs
@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
-using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Model = Discord.API.Interaction;
@@ -16,6 +15,10 @@ namespace Discord.Rest
///
public abstract class RestInteraction : RestEntity, IDiscordInteraction
{
+ // Added so channel & guild methods don't need a client reference
+ private Func> _getChannel;
+ private Func> _getGuild;
+
///
public InteractionType Type { get; private set; }
@@ -31,6 +34,10 @@ namespace Discord.Rest
///
/// Gets the user who invoked the interaction.
///
+ ///
+ /// If this user is an and is set to false,
+ /// will return
+ ///
public RestUser User { get; private set; }
///
@@ -51,19 +58,36 @@ namespace Discord.Rest
///
/// Gets the channel that this interaction was executed in.
///
+ ///
+ /// This property will be if is set to false.
+ /// Call to set this property and get the interaction channel.
+ ///
public IRestMessageChannel Channel { get; private set; }
+ ///
+ public ulong? ChannelId { get; private set; }
+
///
- /// Gets the guild this interaction was executed in.
+ /// Gets the guild this interaction was executed in if applicable.
///
+ ///
+ /// This property will be if is set to false
+ /// or if the interaction was not executed in a guild.
+ ///
public RestGuild Guild { get; private set; }
+ ///
+ public ulong? GuildId { get; private set; }
+
///
public bool HasResponded { get; protected set; }
///
public bool IsDMInteraction { get; private set; }
+ ///
+ public ulong ApplicationId { get; private set; }
+
internal RestInteraction(BaseDiscordClient discord, ulong id)
: base(discord, id)
{
@@ -72,11 +96,11 @@ namespace Discord.Rest
: DateTime.UtcNow;
}
- internal static async Task CreateAsync(DiscordRestClient client, Model model)
+ internal static async Task CreateAsync(DiscordRestClient client, Model model, bool doApiCall)
{
if(model.Type == InteractionType.Ping)
{
- return await RestPingInteraction.CreateAsync(client, model);
+ return await RestPingInteraction.CreateAsync(client, model, doApiCall);
}
if (model.Type == InteractionType.ApplicationCommand)
@@ -90,65 +114,97 @@ namespace Discord.Rest
return dataModel.Type switch
{
- ApplicationCommandType.Slash => await RestSlashCommand.CreateAsync(client, model).ConfigureAwait(false),
- ApplicationCommandType.Message => await RestMessageCommand.CreateAsync(client, model).ConfigureAwait(false),
- ApplicationCommandType.User => await RestUserCommand.CreateAsync(client, model).ConfigureAwait(false),
+ ApplicationCommandType.Slash => await RestSlashCommand.CreateAsync(client, model, doApiCall).ConfigureAwait(false),
+ ApplicationCommandType.Message => await RestMessageCommand.CreateAsync(client, model, doApiCall).ConfigureAwait(false),
+ ApplicationCommandType.User => await RestUserCommand.CreateAsync(client, model, doApiCall).ConfigureAwait(false),
_ => null
};
}
if (model.Type == InteractionType.MessageComponent)
- return await RestMessageComponent.CreateAsync(client, model).ConfigureAwait(false);
+ return await RestMessageComponent.CreateAsync(client, model, doApiCall).ConfigureAwait(false);
if (model.Type == InteractionType.ApplicationCommandAutocomplete)
- return await RestAutocompleteInteraction.CreateAsync(client, model).ConfigureAwait(false);
+ return await RestAutocompleteInteraction.CreateAsync(client, model, doApiCall).ConfigureAwait(false);
if (model.Type == InteractionType.ModalSubmit)
- return await RestModal.CreateAsync(client, model).ConfigureAwait(false);
+ return await RestModal.CreateAsync(client, model, doApiCall).ConfigureAwait(false);
return null;
}
- internal virtual async Task UpdateAsync(DiscordRestClient discord, Model model)
+ internal virtual async Task UpdateAsync(DiscordRestClient discord, Model model, bool doApiCall)
{
- IsDMInteraction = !model.GuildId.IsSpecified;
+ ChannelId = model.ChannelId.IsSpecified
+ ? model.ChannelId.Value
+ : null;
+
+ GuildId = model.GuildId.IsSpecified
+ ? model.GuildId.Value
+ : null;
+
+ IsDMInteraction = GuildId is null;
Data = model.Data.IsSpecified
? model.Data.Value
: null;
+
Token = model.Token;
Version = model.Version;
Type = model.Type;
+ ApplicationId = model.ApplicationId;
- if(Guild == null && model.GuildId.IsSpecified)
+ if (Guild is null && GuildId is not null)
{
- Guild = await discord.GetGuildAsync(model.GuildId.Value);
+ if (doApiCall)
+ Guild = await discord.GetGuildAsync(GuildId.Value);
+ else
+ {
+ Guild = null;
+ _getGuild = async (opt, ul) => await discord.GetGuildAsync(ul, opt);
+ }
}
- if (User == null)
+ if (User is null)
{
- if (model.Member.IsSpecified && model.GuildId.IsSpecified)
+ if (model.Member.IsSpecified && GuildId is not null)
{
- User = RestGuildUser.Create(Discord, Guild, model.Member.Value);
+ User = RestGuildUser.Create(Discord, Guild, model.Member.Value, GuildId);
}
else
{
User = RestUser.Create(Discord, model.User.Value);
}
}
+
- if(Channel == null && model.ChannelId.IsSpecified)
+ if (Channel is null && ChannelId is not null)
{
try
{
- Channel = (IRestMessageChannel)await discord.GetChannelAsync(model.ChannelId.Value);
+ if (doApiCall)
+ Channel = (IRestMessageChannel)await discord.GetChannelAsync(ChannelId.Value);
+ else
+ {
+ Channel = null;
+
+ _getChannel = async (opt, ul) =>
+ {
+ if (Guild is null)
+ return (IRestMessageChannel)await discord.GetChannelAsync(ul, opt);
+
+ // get a guild channel if the guild is set.
+ return (IRestMessageChannel)await Guild.GetChannelAsync(ul, opt);
+ };
+ }
}
- catch(HttpException x) when(x.DiscordCode == DiscordErrorCode.MissingPermissions) { } // ignore
+ catch (HttpException x) when (x.DiscordCode == DiscordErrorCode.MissingPermissions) { } // ignore
}
UserLocale = model.UserLocale.IsSpecified
- ? model.UserLocale.Value
- : null;
+ ? model.UserLocale.Value
+ : null;
+
GuildLocale = model.GuildLocale.IsSpecified
? model.GuildLocale.Value
: null;
@@ -164,6 +220,54 @@ namespace Discord.Rest
return json.ToString();
}
+ ///
+ /// Gets the channel this interaction was executed in. Will be a DM channel if the interaction was executed in DM.
+ ///
+ ///
+ /// Calling this method successfully will populate the property.
+ /// After this, further calls to this method will no longer call the API, and depend on the value set in .
+ ///
+ /// The request options for this request.
+ /// A Rest channel to send messages to.
+ /// Thrown if no channel can be received.
+ public async Task GetChannelAsync(RequestOptions options = null)
+ {
+ if (Channel is not null)
+ return Channel;
+
+ if (IsDMInteraction)
+ {
+ Channel = await User.CreateDMChannelAsync(options);
+ }
+ else if (ChannelId is not null)
+ {
+ Channel = await _getChannel(options, ChannelId.Value) ?? throw new InvalidOperationException("The interaction channel was not able to be retrieved.");
+ _getChannel = null; // get rid of it, we don't need it anymore.
+ }
+
+ return Channel;
+ }
+
+ ///
+ /// Gets the guild this interaction was executed in if applicable.
+ ///
+ ///
+ /// Calling this method successfully will populate the property.
+ /// After this, further calls to this method will no longer call the API, and depend on the value set in .
+ ///
+ /// The request options for this request.
+ /// The guild this interaction was executed in. if the interaction was executed inside DM.
+ public async Task GetGuildAsync(RequestOptions options)
+ {
+ if (GuildId is null)
+ return null;
+
+ Guild ??= await _getGuild(options, GuildId.Value);
+ _getGuild = null; // get rid of it, we don't need it anymore.
+
+ return Guild;
+ }
+
///
public abstract string Defer(bool ephemeral = false, RequestOptions options = null);
///
@@ -333,7 +437,6 @@ namespace Discord.Rest
=> await FollowupWithFilesAsync(attachments, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options).ConfigureAwait(false);
///
Task IDiscordInteraction.RespondWithFilesAsync(IEnumerable attachments, string text, Embed[] embeds, bool isTTS, bool ephemeral, AllowedMentions allowedMentions, MessageComponent components, Embed embed, RequestOptions options) => throw new NotSupportedException("REST-Based interactions don't support files.");
- ///
#if NETCOREAPP3_0_OR_GREATER != true
///
Task IDiscordInteraction.RespondWithFileAsync(Stream fileStream, string fileName, string text, Embed[] embeds, bool isTTS, bool ephemeral, AllowedMentions allowedMentions, MessageComponent components, Embed embed, RequestOptions options) => throw new NotSupportedException("REST-Based interactions don't support files.");
diff --git a/src/Discord.Net.Rest/Entities/Interactions/RestPingInteraction.cs b/src/Discord.Net.Rest/Entities/Interactions/RestPingInteraction.cs
index bd15bc2d3..47e1a3b0f 100644
--- a/src/Discord.Net.Rest/Entities/Interactions/RestPingInteraction.cs
+++ b/src/Discord.Net.Rest/Entities/Interactions/RestPingInteraction.cs
@@ -18,10 +18,10 @@ namespace Discord.Rest
{
}
- internal static new async Task CreateAsync(DiscordRestClient client, Model model)
+ internal static new async Task CreateAsync(DiscordRestClient client, Model model, bool doApiCall)
{
var entity = new RestPingInteraction(client, model.Id);
- await entity.UpdateAsync(client, model);
+ await entity.UpdateAsync(client, model, doApiCall);
return entity;
}
diff --git a/src/Discord.Net.Rest/Entities/Interactions/SlashCommands/RestAutocompleteInteraction.cs b/src/Discord.Net.Rest/Entities/Interactions/SlashCommands/RestAutocompleteInteraction.cs
index 24dbae37a..27c536240 100644
--- a/src/Discord.Net.Rest/Entities/Interactions/SlashCommands/RestAutocompleteInteraction.cs
+++ b/src/Discord.Net.Rest/Entities/Interactions/SlashCommands/RestAutocompleteInteraction.cs
@@ -32,10 +32,10 @@ namespace Discord.Rest
Data = new RestAutocompleteInteractionData(dataModel);
}
- internal new static async Task CreateAsync(DiscordRestClient client, Model model)
+ internal new static async Task CreateAsync(DiscordRestClient client, Model model, bool doApiCall)
{
var entity = new RestAutocompleteInteraction(client, model);
- await entity.UpdateAsync(client, model).ConfigureAwait(false);
+ await entity.UpdateAsync(client, model, doApiCall).ConfigureAwait(false);
return entity;
}
diff --git a/src/Discord.Net.Rest/Entities/Interactions/SlashCommands/RestSlashCommand.cs b/src/Discord.Net.Rest/Entities/Interactions/SlashCommands/RestSlashCommand.cs
index 21184fcf6..f955e7855 100644
--- a/src/Discord.Net.Rest/Entities/Interactions/SlashCommands/RestSlashCommand.cs
+++ b/src/Discord.Net.Rest/Entities/Interactions/SlashCommands/RestSlashCommand.cs
@@ -23,22 +23,22 @@ namespace Discord.Rest
{
}
- internal new static async Task CreateAsync(DiscordRestClient client, Model model)
+ internal new static async Task CreateAsync(DiscordRestClient client, Model model, bool doApiCall)
{
var entity = new RestSlashCommand(client, model);
- await entity.UpdateAsync(client, model).ConfigureAwait(false);
+ await entity.UpdateAsync(client, model, doApiCall).ConfigureAwait(false);
return entity;
}
- internal override async Task UpdateAsync(DiscordRestClient client, Model model)
+ internal override async Task UpdateAsync(DiscordRestClient client, Model model, bool doApiCall)
{
- await base.UpdateAsync(client, model).ConfigureAwait(false);
+ await base.UpdateAsync(client, model, doApiCall).ConfigureAwait(false);
var dataModel = model.Data.IsSpecified
? (DataModel)model.Data.Value
: null;
- Data = await RestSlashCommandData.CreateAsync(client, dataModel, Guild, Channel).ConfigureAwait(false);
+ Data = await RestSlashCommandData.CreateAsync(client, dataModel, Guild, Channel, doApiCall).ConfigureAwait(false);
}
//ISlashCommandInteraction
diff --git a/src/Discord.Net.Rest/Entities/Interactions/SlashCommands/RestSlashCommandData.cs b/src/Discord.Net.Rest/Entities/Interactions/SlashCommands/RestSlashCommandData.cs
index f967cc628..19a819ab4 100644
--- a/src/Discord.Net.Rest/Entities/Interactions/SlashCommands/RestSlashCommandData.cs
+++ b/src/Discord.Net.Rest/Entities/Interactions/SlashCommands/RestSlashCommandData.cs
@@ -14,15 +14,15 @@ namespace Discord.Rest
internal RestSlashCommandData(DiscordRestClient client, Model model)
: base(client, model) { }
- internal static new async Task CreateAsync(DiscordRestClient client, Model model, RestGuild guild, IRestMessageChannel channel)
+ internal static new async Task CreateAsync(DiscordRestClient client, Model model, RestGuild guild, IRestMessageChannel channel, bool doApiCall)
{
var entity = new RestSlashCommandData(client, model);
- await entity.UpdateAsync(client, model, guild, channel).ConfigureAwait(false);
+ await entity.UpdateAsync(client, model, guild, channel, doApiCall).ConfigureAwait(false);
return entity;
}
- internal override async Task UpdateAsync(DiscordRestClient client, Model model, RestGuild guild, IRestMessageChannel channel)
+ internal override async Task UpdateAsync(DiscordRestClient client, Model model, RestGuild guild, IRestMessageChannel channel, bool doApiCall)
{
- await base.UpdateAsync(client, model, guild, channel).ConfigureAwait(false);
+ await base.UpdateAsync(client, model, guild, channel, doApiCall).ConfigureAwait(false);
Options = model.Options.IsSpecified
? model.Options.Value.Select(x => new RestSlashCommandDataOption(this, x)).ToImmutableArray()
diff --git a/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs
index c48a60aac..69e038fd2 100644
--- a/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs
+++ b/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs
@@ -144,7 +144,8 @@ namespace Discord.Rest
{
GuildId = model.Reference.Value.GuildId,
InternalChannelId = model.Reference.Value.ChannelId,
- MessageId = model.Reference.Value.MessageId
+ MessageId = model.Reference.Value.MessageId,
+ FailIfNotExists = model.Reference.Value.FailIfNotExists
};
}
diff --git a/src/Discord.Net.Rest/Entities/Roles/RestRole.cs b/src/Discord.Net.Rest/Entities/Roles/RestRole.cs
index a2ad4fd77..df629bec7 100644
--- a/src/Discord.Net.Rest/Entities/Roles/RestRole.cs
+++ b/src/Discord.Net.Rest/Entities/Roles/RestRole.cs
@@ -25,7 +25,7 @@ namespace Discord.Rest
public string Name { get; private set; }
///
public string Icon { get; private set; }
- /// />
+ ///
public Emoji Emoji { get; private set; }
///
public GuildPermissions Permissions { get; private set; }
diff --git a/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs b/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs
index 0a4a33099..6c311b6b5 100644
--- a/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs
+++ b/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs
@@ -35,7 +35,7 @@ namespace Discord.Rest
///
public DateTimeOffset? PremiumSince => DateTimeUtils.FromTicks(_premiumSinceTicks);
///
- public ulong GuildId => Guild.Id;
+ public ulong GuildId { get; }
///
public bool? IsPending { get; private set; }
///
@@ -80,14 +80,16 @@ namespace Discord.Rest
///
public DateTimeOffset? JoinedAt => DateTimeUtils.FromTicks(_joinedAtTicks);
- internal RestGuildUser(BaseDiscordClient discord, IGuild guild, ulong id)
+ internal RestGuildUser(BaseDiscordClient discord, IGuild guild, ulong id, ulong? guildId = null)
: base(discord, id)
{
- Guild = guild;
+ if (guild is not null)
+ Guild = guild;
+ GuildId = guildId ?? Guild.Id;
}
- internal static RestGuildUser Create(BaseDiscordClient discord, IGuild guild, Model model)
+ internal static RestGuildUser Create(BaseDiscordClient discord, IGuild guild, Model model, ulong? guildId = null)
{
- var entity = new RestGuildUser(discord, guild, model.User.Id);
+ var entity = new RestGuildUser(discord, guild, model.User.Id, guildId);
entity.Update(model);
return entity;
}
@@ -116,7 +118,7 @@ namespace Discord.Rest
private void UpdateRoles(ulong[] roleIds)
{
var roles = ImmutableArray.CreateBuilder(roleIds.Length + 1);
- roles.Add(Guild.Id);
+ roles.Add(GuildId);
for (int i = 0; i < roleIds.Length; i++)
roles.Add(roleIds[i]);
_roleIds = roles.ToImmutable();
diff --git a/src/Discord.Net.Rest/Extensions/EntityExtensions.cs b/src/Discord.Net.Rest/Extensions/EntityExtensions.cs
index 4062cda3d..f5a88486b 100644
--- a/src/Discord.Net.Rest/Extensions/EntityExtensions.cs
+++ b/src/Discord.Net.Rest/Extensions/EntityExtensions.cs
@@ -87,6 +87,7 @@ namespace Discord.Rest
ChannelId = entity.InternalChannelId,
GuildId = entity.GuildId,
MessageId = entity.MessageId,
+ FailIfNotExists = entity.FailIfNotExists
};
}
public static IEnumerable EnumerateMentionTypes(this AllowedMentionTypes mentionTypes)
diff --git a/src/Discord.Net.Rest/Extensions/StringExtensions.cs b/src/Discord.Net.Rest/Extensions/StringExtensions.cs
new file mode 100644
index 000000000..4981a4298
--- /dev/null
+++ b/src/Discord.Net.Rest/Extensions/StringExtensions.cs
@@ -0,0 +1,47 @@
+using Discord.Net.Converters;
+using Newtonsoft.Json;
+using System.Linq;
+using System;
+
+namespace Discord.Rest
+{
+ ///
+ /// Responsible for formatting certain entities as Json , to reuse later on.
+ ///
+ public static class StringExtensions
+ {
+ private static Lazy _settings = new(() =>
+ {
+ var serializer = new JsonSerializerSettings()
+ {
+ ContractResolver = new DiscordContractResolver()
+ };
+ serializer.Converters.Add(new EmbedTypeConverter());
+ return serializer;
+ });
+
+ ///
+ /// Gets a Json formatted from an .
+ ///
+ ///
+ /// See to parse Json back into embed.
+ ///
+ /// The builder to format as Json .
+ /// The formatting in which the Json will be returned.
+ /// A Json containing the data from the .
+ public static string ToJsonString(this EmbedBuilder builder, Formatting formatting = Formatting.Indented)
+ => ToJsonString(builder.Build(), formatting);
+
+ ///
+ /// Gets a Json formatted from an .
+ ///
+ ///
+ /// See to parse Json back into embed.
+ ///
+ /// The embed to format as Json .
+ /// The formatting in which the Json will be returned.
+ /// A Json containing the data from the .
+ public static string ToJsonString(this Embed embed, Formatting formatting = Formatting.Indented)
+ => JsonConvert.SerializeObject(embed.ToModel(), formatting, _settings.Value);
+ }
+}
diff --git a/src/Discord.Net.Rest/Interactions/RestInteractionContext.cs b/src/Discord.Net.Rest/Interactions/RestInteractionContext.cs
index 196c6133b..d407f5103 100644
--- a/src/Discord.Net.Rest/Interactions/RestInteractionContext.cs
+++ b/src/Discord.Net.Rest/Interactions/RestInteractionContext.cs
@@ -1,4 +1,6 @@
using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
using System.Threading.Tasks;
namespace Discord.Rest
@@ -6,7 +8,7 @@ namespace Discord.Rest
///
/// Represents a Rest based context of an .
///
- public class RestInteractionContext : IRestInteractionContext
+ public class RestInteractionContext : IRestInteractionContext, IRouteMatchContainer
where TInteraction : RestInteraction
{
///
@@ -45,6 +47,9 @@ namespace Discord.Rest
///
public Func InteractionResponseCallback { get; set; }
+ ///
+ public IReadOnlyCollection SegmentMatches { get; private set; }
+
///
/// Initializes a new .
///
@@ -71,6 +76,13 @@ namespace Discord.Rest
InteractionResponseCallback = interactionResponseCallback;
}
+ ///
+ public void SetSegmentMatches(IEnumerable segmentMatches) => SegmentMatches = segmentMatches.ToImmutableArray();
+
+ //IRouteMatchContainer
+ ///
+ IEnumerable IRouteMatchContainer.SegmentMatches => SegmentMatches;
+
// IInterationContext
///
IDiscordClient IInteractionContext.Client => Client;
diff --git a/src/Discord.Net.Rest/Net/Converters/UInt64Converter.cs b/src/Discord.Net.Rest/Net/Converters/UInt64Converter.cs
index 27cbe9290..d7655a30a 100644
--- a/src/Discord.Net.Rest/Net/Converters/UInt64Converter.cs
+++ b/src/Discord.Net.Rest/Net/Converters/UInt64Converter.cs
@@ -1,4 +1,4 @@
-using Newtonsoft.Json;
+using Newtonsoft.Json;
using System;
using System.Globalization;
@@ -14,7 +14,7 @@ namespace Discord.Net.Converters
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
- return ulong.Parse((string)reader.Value, NumberStyles.None, CultureInfo.InvariantCulture);
+ return ulong.Parse(reader.Value?.ToString(), NumberStyles.None, CultureInfo.InvariantCulture);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
diff --git a/src/Discord.Net.Rest/Net/ED25519/CryptoBytes.cs b/src/Discord.Net.Rest/Net/ED25519/CryptoBytes.cs
index cfd64104d..43cd3f902 100644
--- a/src/Discord.Net.Rest/Net/ED25519/CryptoBytes.cs
+++ b/src/Discord.Net.Rest/Net/ED25519/CryptoBytes.cs
@@ -243,7 +243,7 @@ namespace Discord.Net.ED25519
///
/// // Decode a base58-encoded string into byte array
///
- /// Base58 data string
+ /// Base58 data string
/// Byte array
public static byte[] Base58Decode(string input)
{
diff --git a/src/Discord.Net.Rest/Net/Queue/RequestQueue.cs b/src/Discord.Net.Rest/Net/Queue/RequestQueue.cs
index 75e79eec2..4915a5c39 100644
--- a/src/Discord.Net.Rest/Net/Queue/RequestQueue.cs
+++ b/src/Discord.Net.Rest/Net/Queue/RequestQueue.cs
@@ -60,14 +60,9 @@ namespace Discord.Net.Queue
_clearToken?.Cancel();
_clearToken?.Dispose();
_clearToken = new CancellationTokenSource();
- if (_parentToken != null)
- {
- _requestCancelTokenSource?.Dispose();
- _requestCancelTokenSource = CancellationTokenSource.CreateLinkedTokenSource(_clearToken.Token, _parentToken);
- _requestCancelToken = _requestCancelTokenSource.Token;
- }
- else
- _requestCancelToken = _clearToken.Token;
+ _requestCancelTokenSource?.Dispose();
+ _requestCancelTokenSource = CancellationTokenSource.CreateLinkedTokenSource(_clearToken.Token, _parentToken);
+ _requestCancelToken = _requestCancelTokenSource.Token;
}
finally { _tokenLock.Release(); }
}
diff --git a/src/Discord.Net.WebSocket/Discord.Net.WebSocket.csproj b/src/Discord.Net.WebSocket/Discord.Net.WebSocket.csproj
index 2ce89be5b..a4355bc02 100644
--- a/src/Discord.Net.WebSocket/Discord.Net.WebSocket.csproj
+++ b/src/Discord.Net.WebSocket/Discord.Net.WebSocket.csproj
@@ -8,6 +8,8 @@
net6.0;net5.0;net461;netstandard2.0;netstandard2.1
net6.0;net5.0;netstandard2.0;netstandard2.1
true
+ 5
+ True
diff --git a/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs b/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs
index 21594fed7..cca2de203 100644
--- a/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs
+++ b/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs
@@ -274,7 +274,7 @@ namespace Discord.API
{
["$device"] = "Discord.Net",
["$os"] = Environment.OSVersion.Platform.ToString(),
- [$"browser"] = "Discord.Net"
+ ["$browser"] = "Discord.Net"
};
var msg = new IdentifyParams()
{
diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs
index aaef4656a..5743d9abd 100644
--- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs
+++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs
@@ -403,7 +403,7 @@ namespace Discord.WebSocket
/// the snowflake identifier; null if the user is not found.
///
public async ValueTask GetUserAsync(ulong id, RequestOptions options = null)
- => await ClientHelper.GetUserAsync(this, id, options).ConfigureAwait(false);
+ => await ((IDiscordClient)this).GetUserAsync(id, CacheMode.AllowDownload, options).ConfigureAwait(false);
///
/// Clears all cached channels from the client.
///
@@ -1305,13 +1305,13 @@ namespace Discord.WebSocket
user.Update(State, data);
- var cacheableBefore = new Cacheable(before, user.Id, true, () => null);
+ var cacheableBefore = new Cacheable(before, user.Id, true, () => Task.FromResult(null));
await TimedInvokeAsync(_guildMemberUpdatedEvent, nameof(GuildMemberUpdated), cacheableBefore, user).ConfigureAwait(false);
}
else
{
user = guild.AddOrUpdateUser(data);
- var cacheableBefore = new Cacheable(null, user.Id, false, () => null);
+ var cacheableBefore = new Cacheable(null, user.Id, false, () => Task.FromResult(null));
await TimedInvokeAsync(_guildMemberUpdatedEvent, nameof(GuildMemberUpdated), cacheableBefore, user).ConfigureAwait(false);
}
}
@@ -2331,7 +2331,9 @@ namespace Discord.WebSocket
SocketUser user = data.User.IsSpecified
? State.GetOrAddUser(data.User.Value.Id, (_) => SocketGlobalUser.Create(this, State, data.User.Value))
- : guild?.AddOrUpdateUser(data.Member.Value); // null if the bot scope isn't set, so the guild cannot be retrieved.
+ : guild != null
+ ? guild.AddOrUpdateUser(data.Member.Value) // null if the bot scope isn't set, so the guild cannot be retrieved.
+ : State.GetOrAddUser(data.Member.Value.User.Id, (_) => SocketGlobalUser.Create(this, State, data.Member.Value.User));
SocketChannel channel = null;
if(data.ChannelId.IsSpecified)
diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketForumChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketForumChannel.cs
new file mode 100644
index 000000000..bc6e28442
--- /dev/null
+++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketForumChannel.cs
@@ -0,0 +1,128 @@
+using Discord.Rest;
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Model = Discord.API.Channel;
+
+namespace Discord.WebSocket
+{
+ ///
+ /// Represents a forum channel in a guild.
+ ///
+ public class SocketForumChannel : SocketGuildChannel, IForumChannel
+ {
+ ///
+ public bool IsNsfw { get; private set; }
+
+ ///
+ public string Topic { get; private set; }
+
+ ///
+ public ThreadArchiveDuration DefaultAutoArchiveDuration { get; private set; }
+
+ ///
+ public IReadOnlyCollection Tags { get; private set; }
+
+ ///
+ public string Mention => MentionUtils.MentionChannel(Id);
+
+ internal SocketForumChannel(DiscordSocketClient discord, ulong id, SocketGuild guild) : base(discord, id, guild) { }
+
+ internal new static SocketForumChannel Create(SocketGuild guild, ClientState state, Model model)
+ {
+ var entity = new SocketForumChannel(guild.Discord, model.Id, guild);
+ entity.Update(state, model);
+ return entity;
+ }
+
+ internal override void Update(ClientState state, Model model)
+ {
+ base.Update(state, model);
+ IsNsfw = model.Nsfw.GetValueOrDefault(false);
+ Topic = model.Topic.GetValueOrDefault();
+ DefaultAutoArchiveDuration = model.AutoArchiveDuration.GetValueOrDefault(ThreadArchiveDuration.OneDay);
+
+ Tags = model.ForumTags.GetValueOrDefault(Array.Empty()).Select(
+ x => new ForumTag(x.Id, x.Name, x.EmojiId.GetValueOrDefault(null), x.EmojiName.GetValueOrDefault())
+ ).ToImmutableArray();
+ }
+
+ ///
+ public Task CreatePostAsync(string title, ThreadArchiveDuration archiveDuration = ThreadArchiveDuration.OneDay, int? slowmode = null, string text = null, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None)
+ => ThreadHelper.CreatePostAsync(this, Discord, title, archiveDuration, slowmode, text, embed, options, allowedMentions, components, stickers, embeds, flags);
+
+ ///
+ public async Task CreatePostWithFileAsync(string title, string filePath, ThreadArchiveDuration archiveDuration = ThreadArchiveDuration.OneDay,
+ int? slowmode = null, string text = null, Embed embed = null, RequestOptions options = null, bool isSpoiler = false,
+ AllowedMentions allowedMentions = null, MessageComponent components = null,
+ ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None)
+ {
+ using var file = new FileAttachment(filePath, isSpoiler: isSpoiler);
+ return await ThreadHelper.CreatePostAsync(this, Discord, title, new FileAttachment[] { file }, archiveDuration, slowmode, text, embed, options, allowedMentions, components, stickers, embeds, flags).ConfigureAwait(false);
+ }
+
+ ///
+ public async Task CreatePostWithFileAsync(string title, Stream stream, string filename, ThreadArchiveDuration archiveDuration = ThreadArchiveDuration.OneDay,
+ int? slowmode = null, string text = null, Embed embed = null, RequestOptions options = null, bool isSpoiler = false,
+ AllowedMentions allowedMentions = null, MessageComponent components = null,
+ ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None)
+ {
+ using var file = new FileAttachment(stream, filename, isSpoiler: isSpoiler);
+ return await ThreadHelper.CreatePostAsync(this, Discord, title, new FileAttachment[] { file }, archiveDuration, slowmode, text, embed, options, allowedMentions, components, stickers, embeds, flags).ConfigureAwait(false);
+ }
+
+ ///
+ public Task CreatePostWithFileAsync(string title, FileAttachment attachment, ThreadArchiveDuration archiveDuration = ThreadArchiveDuration.OneDay,
+ int? slowmode = null, string text = null, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null,
+ MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None)
+ => ThreadHelper.CreatePostAsync(this, Discord, title, new FileAttachment[] { attachment }, archiveDuration, slowmode, text, embed, options, allowedMentions, components, stickers, embeds, flags);
+
+ ///
+ public Task CreatePostWithFilesAsync(string title, IEnumerable attachments, ThreadArchiveDuration archiveDuration = ThreadArchiveDuration.OneDay,
+ int? slowmode = null, string text = null, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null,
+ MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None)
+ => ThreadHelper.CreatePostAsync(this, Discord, title, attachments, archiveDuration, slowmode, text, embed, options, allowedMentions, components, stickers, embeds, flags);
+
+ ///
+ public Task> GetActiveThreadsAsync(RequestOptions options = null)
+ => ThreadHelper.GetActiveThreadsAsync(Guild, Discord, options);
+
+ ///
+ public Task> GetJoinedPrivateArchivedThreadsAsync(int? limit = null, DateTimeOffset? before = null, RequestOptions options = null)
+ => ThreadHelper.GetJoinedPrivateArchivedThreadsAsync(this, Discord, limit, before, options);
+
+ ///
+ public Task> GetPrivateArchivedThreadsAsync(int? limit = null, DateTimeOffset? before = null, RequestOptions options = null)
+ => ThreadHelper.GetPrivateArchivedThreadsAsync(this, Discord, limit, before, options);
+
+ ///
+ public Task> GetPublicArchivedThreadsAsync(int? limit = null, DateTimeOffset? before = null, RequestOptions options = null)
+ => ThreadHelper.GetPublicArchivedThreadsAsync(this, Discord, limit, before, options);
+
+ #region IForumChannel
+ async Task> IForumChannel.GetActiveThreadsAsync(RequestOptions options)
+ => await GetActiveThreadsAsync(options).ConfigureAwait(false);
+ async Task> IForumChannel.GetPublicArchivedThreadsAsync(int? limit, DateTimeOffset? before, RequestOptions options)
+ => await GetPublicArchivedThreadsAsync(limit, before, options).ConfigureAwait(false);
+ async Task> IForumChannel.GetPrivateArchivedThreadsAsync(int? limit, DateTimeOffset? before, RequestOptions options)
+ => await GetPrivateArchivedThreadsAsync(limit, before, options).ConfigureAwait(false);
+ async Task> IForumChannel.GetJoinedPrivateArchivedThreadsAsync(int? limit, DateTimeOffset? before, RequestOptions options)
+ => await GetJoinedPrivateArchivedThreadsAsync(limit, before, options).ConfigureAwait(false);
+ async Task IForumChannel.CreatePostAsync(string title, ThreadArchiveDuration archiveDuration, int? slowmode, string text, Embed embed, RequestOptions options, AllowedMentions allowedMentions, MessageComponent components, ISticker[] stickers, Embed[] embeds, MessageFlags flags)
+ => await CreatePostAsync(title, archiveDuration, slowmode, text, embed, options, allowedMentions, components, stickers, embeds, flags).ConfigureAwait(false);
+ async Task IForumChannel.CreatePostWithFileAsync(string title, string filePath, ThreadArchiveDuration archiveDuration, int? slowmode, string text, Embed embed, RequestOptions options, bool isSpoiler, AllowedMentions allowedMentions, MessageComponent components, ISticker[] stickers, Embed[] embeds, MessageFlags flags)
+ => await CreatePostWithFileAsync(title, filePath, archiveDuration, slowmode, text, embed, options, isSpoiler, allowedMentions, components, stickers, embeds, flags).ConfigureAwait(false);
+ async Task IForumChannel.CreatePostWithFileAsync(string title, Stream stream, string filename, ThreadArchiveDuration archiveDuration, int? slowmode, string text, Embed embed, RequestOptions options, bool isSpoiler, AllowedMentions allowedMentions, MessageComponent components, ISticker[] stickers, Embed[] embeds, MessageFlags flags)
+ => await CreatePostWithFileAsync(title, stream, filename, archiveDuration, slowmode, text, embed, options, isSpoiler, allowedMentions, components, stickers, embeds, flags).ConfigureAwait(false);
+ async Task IForumChannel.CreatePostWithFileAsync(string title, FileAttachment attachment, ThreadArchiveDuration archiveDuration, int? slowmode, string text, Embed embed, RequestOptions options, AllowedMentions allowedMentions, MessageComponent components, ISticker[] stickers, Embed[] embeds, MessageFlags flags)
+ => await CreatePostWithFileAsync(title, attachment, archiveDuration, slowmode, text, embed, options, allowedMentions, components, stickers, embeds, flags).ConfigureAwait(false);
+ async Task IForumChannel.CreatePostWithFilesAsync(string title, IEnumerable attachments, ThreadArchiveDuration archiveDuration, int? slowmode, string text, Embed embed, RequestOptions options, AllowedMentions allowedMentions, MessageComponent components, ISticker[] stickers, Embed[] embeds, MessageFlags flags)
+ => await CreatePostWithFilesAsync(title, attachments, archiveDuration, slowmode, text, embed, options, allowedMentions, components, stickers, embeds, flags);
+
+ #endregion
+ }
+}
diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs
index 79f02fe1c..16ed7b32d 100644
--- a/src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs
+++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs
@@ -59,6 +59,7 @@ namespace Discord.WebSocket
ChannelType.Category => SocketCategoryChannel.Create(guild, state, model),
ChannelType.PrivateThread or ChannelType.PublicThread or ChannelType.NewsThread => SocketThreadChannel.Create(guild, state, model),
ChannelType.Stage => SocketStageChannel.Create(guild, state, model),
+ ChannelType.Forum => SocketForumChannel.Create(guild, state, model),
_ => new SocketGuildChannel(guild.Discord, model.Id, guild),
};
}
@@ -222,6 +223,8 @@ namespace Discord.WebSocket
#region IChannel
///
+ string IChannel.Name => Name;
+ ///
IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options)
=> ImmutableArray.Create>(Users).ToAsyncEnumerable(); //Overridden in Text/Voice
///
diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketStageChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketStageChannel.cs
index 91bca5054..56cd92185 100644
--- a/src/Discord.Net.WebSocket/Entities/Channels/SocketStageChannel.cs
+++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketStageChannel.cs
@@ -15,7 +15,11 @@ namespace Discord.WebSocket
public class SocketStageChannel : SocketVoiceChannel, IStageChannel
{
///
- public string Topic { get; private set; }
+ ///
+ /// This field is always false for stage channels.
+ ///
+ public override bool IsTextInVoice
+ => false;
///
public StagePrivacyLevel? PrivacyLevel { get; private set; }
@@ -49,19 +53,16 @@ namespace Discord.WebSocket
entity.Update(state, model);
return entity;
}
-
internal void Update(StageInstance model, bool isLive = false)
{
IsLive = isLive;
if (isLive)
{
- Topic = model.Topic;
PrivacyLevel = model.PrivacyLevel;
IsDiscoverableDisabled = model.DiscoverableDisabled;
}
else
{
- Topic = null;
PrivacyLevel = null;
IsDiscoverableDisabled = null;
}
diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs
index e4a299edc..6aece7d78 100644
--- a/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs
+++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs
@@ -40,7 +40,8 @@ namespace Discord.WebSocket
private bool _nsfw;
///
public bool IsNsfw => _nsfw;
-
+ ///
+ public ThreadArchiveDuration DefaultArchiveDuration { get; private set; }
///
public string Mention => MentionUtils.MentionChannel(Id);
///
@@ -76,6 +77,11 @@ namespace Discord.WebSocket
Topic = model.Topic.GetValueOrDefault();
SlowModeInterval = model.SlowMode.GetValueOrDefault(); // some guilds haven't been patched to include this yet?
_nsfw = model.Nsfw.GetValueOrDefault();
+ if (model.AutoArchiveDuration.IsSpecified)
+ DefaultArchiveDuration = model.AutoArchiveDuration.Value;
+ else
+ DefaultArchiveDuration = ThreadArchiveDuration.OneDay;
+ // basic value at channel creation. Shouldn't be called since guild text channels always have this property
}
///
@@ -128,7 +134,7 @@ namespace Discord.WebSocket
#region Messages
///
- public SocketMessage GetCachedMessage(ulong id)
+ public virtual SocketMessage GetCachedMessage(ulong id)
=> _messages?.Get(id);
///
/// Gets a message from this message channel.
@@ -143,7 +149,7 @@ namespace Discord.WebSocket
/// A task that represents an asynchronous get operation for retrieving the message. The task result contains
/// the retrieved message; null if no message is found with the specified identifier.
///
- public async Task GetMessageAsync(ulong id, RequestOptions options = null)
+ public virtual async Task GetMessageAsync(ulong id, RequestOptions options = null)
{
IMessage msg = _messages?.Get(id);
if (msg == null)
@@ -163,7 +169,7 @@ namespace Discord.WebSocket
///
/// Paged collection of messages.
///
- public IAsyncEnumerable> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null)
+ public virtual IAsyncEnumerable> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null)
=> SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, CacheMode.AllowDownload, options);
///
/// Gets a collection of messages in this channel.
@@ -179,7 +185,7 @@ namespace Discord.WebSocket
///
/// Paged collection of messages.
///
- public IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null)
+ public virtual IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null)
=> SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, CacheMode.AllowDownload, options);
///
/// Gets a collection of messages in this channel.
@@ -195,25 +201,25 @@ namespace Discord.WebSocket
///
/// Paged collection of messages.
///
- public IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null)
+ public virtual IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null)
=> SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, CacheMode.AllowDownload, options);
///
- public IReadOnlyCollection GetCachedMessages(int limit = DiscordConfig.MaxMessagesPerBatch)
+ public virtual IReadOnlyCollection GetCachedMessages(int limit = DiscordConfig.MaxMessagesPerBatch)
=> SocketChannelHelper.GetCachedMessages(this, Discord, _messages, null, Direction.Before, limit);
///
- public IReadOnlyCollection GetCachedMessages(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch)
+ public virtual IReadOnlyCollection GetCachedMessages(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch)
=> SocketChannelHelper.GetCachedMessages(this, Discord, _messages, fromMessageId, dir, limit);
///
- public IReadOnlyCollection GetCachedMessages(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch)
+ public virtual IReadOnlyCollection GetCachedMessages(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch)
=> SocketChannelHelper.GetCachedMessages(this, Discord, _messages, fromMessage.Id, dir, limit);
///
- public Task> GetPinnedMessagesAsync(RequestOptions options = null)
+ public virtual Task> GetPinnedMessagesAsync(RequestOptions options = null)
=> ChannelHelper.GetPinnedMessagesAsync(this, Discord, options);
///
/// Message content is too long, length must be less or equal to .
/// The only valid are and .
- public Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null,
+ public virtual Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null,
RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null,
MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None)
=> ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, allowedMentions, messageReference,
@@ -221,7 +227,7 @@ namespace Discord.WebSocket
///
/// The only valid are and .
- public Task SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null,
+ public virtual Task SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null,
RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null,
MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null,
Embed[] embeds = null, MessageFlags flags = MessageFlags.None)
@@ -230,7 +236,7 @@ namespace Discord.WebSocket
///
/// Message content is too long, length must be less or equal to .
/// The only valid are and .
- public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false,
+ public virtual Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false,
Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null,
MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null,
Embed[] embeds = null, MessageFlags flags = MessageFlags.None)
@@ -239,7 +245,7 @@ namespace Discord.WebSocket
///
/// Message content is too long, length must be less or equal to .
/// The only valid are and .
- public Task SendFileAsync(FileAttachment attachment, string text, bool isTTS = false,
+ public virtual Task SendFileAsync(FileAttachment attachment, string text, bool isTTS = false,
Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null,
MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null,
Embed[] embeds = null, MessageFlags flags = MessageFlags.None)
@@ -248,7 +254,7 @@ namespace Discord.WebSocket
///
/// Message content is too long, length must be less or equal to .
/// The only valid are and .
- public Task SendFilesAsync(IEnumerable attachments, string text, bool isTTS = false,
+ public virtual Task SendFilesAsync(IEnumerable attachments, string text, bool isTTS = false,
Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null,
MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null,
Embed[] embeds = null, MessageFlags flags = MessageFlags.None)
@@ -256,28 +262,28 @@ namespace Discord.WebSocket
messageReference, components, stickers, options, embeds, flags);
///
- public Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null)
+ public virtual Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null)
=> ChannelHelper.DeleteMessagesAsync(this, Discord, messages.Select(x => x.Id), options);
///
- public Task DeleteMessagesAsync(IEnumerable messageIds, RequestOptions options = null)
+ public virtual Task DeleteMessagesAsync(IEnumerable messageIds, RequestOptions options = null)
=> ChannelHelper.DeleteMessagesAsync(this, Discord, messageIds, options);
///
- public async Task ModifyMessageAsync(ulong messageId, Action func, RequestOptions options = null)
+ public virtual async Task ModifyMessageAsync(ulong messageId, Action func, RequestOptions options = null)
=> await ChannelHelper.ModifyMessageAsync(this, messageId, func, Discord, options).ConfigureAwait(false);
///
- public Task DeleteMessageAsync(ulong messageId, RequestOptions options = null)
+ public virtual Task DeleteMessageAsync(ulong messageId, RequestOptions options = null)
=> ChannelHelper.DeleteMessageAsync(this, messageId, Discord, options);
///
- public Task DeleteMessageAsync(IMessage message, RequestOptions options = null)
+ public virtual Task DeleteMessageAsync(IMessage message, RequestOptions options = null)
=> ChannelHelper.DeleteMessageAsync(this, message.Id, Discord, options);
///
- public Task TriggerTypingAsync(RequestOptions options = null)
+ public virtual Task TriggerTypingAsync(RequestOptions options = null)
=> ChannelHelper.TriggerTypingAsync(this, Discord, options);
///
- public IDisposable EnterTypingState(RequestOptions options = null)
+ public virtual IDisposable EnterTypingState(RequestOptions options = null)
=> ChannelHelper.EnterTypingState(this, Discord, options);
internal void AddMessage(SocketMessage msg)
diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketVoiceChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketVoiceChannel.cs
index 00003d4ed..7bf65d638 100644
--- a/src/Discord.Net.WebSocket/Entities/Channels/SocketVoiceChannel.cs
+++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketVoiceChannel.cs
@@ -4,6 +4,7 @@ using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
+using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Model = Discord.API.Channel;
@@ -14,33 +15,25 @@ namespace Discord.WebSocket
/// Represents a WebSocket-based voice channel in a guild.
///
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
- public class SocketVoiceChannel : SocketGuildChannel, IVoiceChannel, ISocketAudioChannel
+ public class SocketVoiceChannel : SocketTextChannel, IVoiceChannel, ISocketAudioChannel
{
#region SocketVoiceChannel
- ///
- public int Bitrate { get; private set; }
- ///
- public int? UserLimit { get; private set; }
- ///
- public string RTCRegion { get; private set; }
-
- ///
- public ulong? CategoryId { get; private set; }
///
- /// Gets the parent (category) channel of this channel.
+ /// Gets whether or not the guild has Text-In-Voice enabled and the voice channel is a TiV channel.
///
- ///
- /// A category channel representing the parent of this channel; null if none is set.
- ///
- public ICategoryChannel Category
- => CategoryId.HasValue ? Guild.GetChannel(CategoryId.Value) as ICategoryChannel : null;
+ ///
+ /// Discord currently doesn't have a way to disable Text-In-Voice yet so this field is always
+ /// on s and on
+ /// s.
+ ///
+ public virtual bool IsTextInVoice => true;
///
- public string Mention => MentionUtils.MentionChannel(Id);
-
+ public int Bitrate { get; private set; }
+ ///
+ public int? UserLimit { get; private set; }
///
- public Task SyncPermissionsAsync(RequestOptions options = null)
- => ChannelHelper.SyncPermissionsAsync(this, Discord, options);
+ public string RTCRegion { get; private set; }
///
/// Gets a collection of users that are currently connected to this voice channel.
@@ -48,7 +41,7 @@ namespace Discord.WebSocket
///
/// A read-only collection of users that are currently connected to this voice channel.
///
- public override IReadOnlyCollection Users
+ public IReadOnlyCollection ConnectedUsers
=> Guild.Users.Where(x => x.VoiceChannel?.Id == Id).ToImmutableArray();
internal SocketVoiceChannel(DiscordSocketClient discord, ulong id, SocketGuild guild)
@@ -65,7 +58,6 @@ namespace Discord.WebSocket
internal override void Update(ClientState state, Model model)
{
base.Update(state, model);
- CategoryId = model.CategoryId;
Bitrate = model.Bitrate.Value;
UserLimit = model.UserLimit.Value != 0 ? model.UserLimit.Value : (int?)null;
RTCRegion = model.RTCRegion.GetValueOrDefault(null);
@@ -99,28 +91,215 @@ namespace Discord.WebSocket
return user;
return null;
}
-#endregion
- #region Invites
- ///
- public async Task CreateInviteAsync(int? maxAge = 86400, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null)
- => await ChannelHelper.CreateInviteAsync(this, Discord, maxAge, maxUses, isTemporary, isUnique, options).ConfigureAwait(false);
- ///
- public async Task CreateInviteToApplicationAsync(ulong applicationId, int? maxAge, int? maxUses = default(int?), bool isTemporary = false, bool isUnique = false, RequestOptions options = null)
- => await ChannelHelper.CreateInviteToApplicationAsync(this, Discord, maxAge, maxUses, isTemporary, isUnique, applicationId, options).ConfigureAwait(false);
- ///
- public virtual async Task CreateInviteToApplicationAsync(DefaultApplications application, int? maxAge = 86400, int? maxUses = default(int?), bool isTemporary = false, bool isUnique = false, RequestOptions options = null)
- => await ChannelHelper.CreateInviteToApplicationAsync(this, Discord, maxAge, maxUses, isTemporary, isUnique, (ulong)application, options);
- ///
- public async Task CreateInviteToStreamAsync(IUser user, int? maxAge, int? maxUses = default(int?), bool isTemporary = false, bool isUnique = false, RequestOptions options = null)
- => await ChannelHelper.CreateInviteToStreamAsync(this, Discord, maxAge, maxUses, isTemporary, isUnique, user, options).ConfigureAwait(false);
- ///
- public async Task> GetInvitesAsync(RequestOptions options = null)
- => await ChannelHelper.GetInvitesAsync(this, Discord, options).ConfigureAwait(false);
+ /// Cannot create threads in voice channels.
+ public override Task CreateThreadAsync(string name, ThreadType type = ThreadType.PublicThread, ThreadArchiveDuration autoArchiveDuration = ThreadArchiveDuration.OneDay, IMessage message = null, bool? invitable = null, int? slowmode = null, RequestOptions options = null)
+ => throw new InvalidOperationException("Voice channels cannot contain threads.");
+
+ /// Cannot modify text channel properties for voice channels.
+ public override Task ModifyAsync(Action func, RequestOptions options = null)
+ => throw new InvalidOperationException("Cannot modify text channel properties for voice channels.");
+
+ #endregion
+
+ #region TextOverrides
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override Task GetMessageAsync(ulong id, RequestOptions options = null)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.GetMessageAsync(id, options);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override Task DeleteMessageAsync(IMessage message, RequestOptions options = null)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.DeleteMessageAsync(message, options);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override Task DeleteMessageAsync(ulong messageId, RequestOptions options = null)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.DeleteMessageAsync(messageId, options);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.DeleteMessagesAsync(messages, options);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override Task DeleteMessagesAsync(IEnumerable messageIds, RequestOptions options = null)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.DeleteMessagesAsync(messageIds, options);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override IDisposable EnterTypingState(RequestOptions options = null)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.EnterTypingState(options);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override SocketMessage GetCachedMessage(ulong id)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.GetCachedMessage(id);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override IReadOnlyCollection GetCachedMessages(IMessage fromMessage, Direction dir, int limit = 100)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.GetCachedMessages(fromMessage, dir, limit);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override IReadOnlyCollection GetCachedMessages(int limit = 100)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.GetCachedMessages(limit);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override IReadOnlyCollection GetCachedMessages(ulong fromMessageId, Direction dir, int limit = 100)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.GetCachedMessages(fromMessageId, dir, limit);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = 100, RequestOptions options = null)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.GetMessagesAsync(fromMessage, dir, limit, options);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override IAsyncEnumerable> GetMessagesAsync(int limit = 100, RequestOptions options = null)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.GetMessagesAsync(limit, options);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = 100, RequestOptions options = null)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.GetMessagesAsync(fromMessageId, dir, limit, options);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override Task> GetPinnedMessagesAsync(RequestOptions options = null)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.GetPinnedMessagesAsync(options);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override Task GetWebhookAsync(ulong id, RequestOptions options = null)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.GetWebhookAsync(id, options);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override Task> GetWebhooksAsync(RequestOptions options = null)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.GetWebhooksAsync(options);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override Task CreateWebhookAsync(string name, Stream avatar = null, RequestOptions options = null)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.CreateWebhookAsync(name, avatar, options);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override Task ModifyMessageAsync(ulong messageId, Action func, RequestOptions options = null)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.ModifyMessageAsync(messageId, func, options);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override Task SendFileAsync(FileAttachment attachment, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.SendFileAsync(attachment, text, isTTS, embed, options, allowedMentions, messageReference, components, stickers, embeds, flags);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.SendFileAsync(stream, filename, text, isTTS, embed, options, isSpoiler, allowedMentions, messageReference, components, stickers, embeds, flags);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override Task SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.SendFileAsync(filePath, text, isTTS, embed, options, isSpoiler, allowedMentions, messageReference, components, stickers, embeds, flags);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override Task SendFilesAsync(IEnumerable attachments, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.SendFilesAsync(attachments, text, isTTS, embed, options, allowedMentions, messageReference, components, stickers, embeds, flags);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.SendMessageAsync(text, isTTS, embed, options, allowedMentions, messageReference, components, stickers, embeds, flags);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override Task TriggerTypingAsync(RequestOptions options = null)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.TriggerTypingAsync(options);
+ }
+
+ #endregion
private string DebuggerDisplay => $"{Name} ({Id}, Voice)";
internal new SocketVoiceChannel Clone() => MemberwiseClone() as SocketVoiceChannel;
- #endregion
#region IGuildChannel
///
diff --git a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs
index 8b376b3ed..9ce2f507a 100644
--- a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs
+++ b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs
@@ -705,7 +705,15 @@ namespace Discord.WebSocket
///
public SocketThreadChannel GetThreadChannel(ulong id)
=> GetChannel(id) as SocketThreadChannel;
-
+ ///
+ /// Gets a forum channel in this guild.
+ ///
+ /// The snowflake identifier for the forum channel.
+ ///
+ /// A forum channel associated with the specified ; if none is found.
+ ///
+ public SocketForumChannel GetForumChannel(ulong id)
+ => GetChannel(id) as SocketForumChannel;
///
/// Gets a voice channel in this guild.
///
@@ -1291,7 +1299,6 @@ namespace Discord.WebSocket
/// in order to use this property.
///
///
- /// A collection of speakers for the event.
/// The location of the event; links are supported
/// The optional banner image for the event.
/// The options to be used when sending the request.
diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/MessageComponents/SocketMessageComponent.cs b/src/Discord.Net.WebSocket/Entities/Interaction/MessageComponents/SocketMessageComponent.cs
index aeff465bd..4f9a769c2 100644
--- a/src/Discord.Net.WebSocket/Entities/Interaction/MessageComponents/SocketMessageComponent.cs
+++ b/src/Discord.Net.WebSocket/Entities/Interaction/MessageComponents/SocketMessageComponent.cs
@@ -226,8 +226,12 @@ namespace Discord.WebSocket
bool hasText = args.Content.IsSpecified ? !string.IsNullOrEmpty(args.Content.Value) : !string.IsNullOrEmpty(Message.Content);
bool hasEmbeds = embed.IsSpecified && embed.Value != null || embeds.IsSpecified && embeds.Value?.Length > 0 || Message.Embeds.Any();
+ bool hasComponents = args.Components.IsSpecified && args.Components.Value != null;
+ bool hasAttachments = args.Attachments.IsSpecified;
+ bool hasFlags = args.Flags.IsSpecified;
- if (!hasText && !hasEmbeds)
+ // No content needed if modifying flags
+ if ((!hasComponents && !hasText && !hasEmbeds && !hasAttachments) && !hasFlags)
Preconditions.NotNullOrEmpty(args.Content.IsSpecified ? args.Content.Value : string.Empty, nameof(args.Content));
var apiEmbeds = embed.IsSpecified || embeds.IsSpecified ? new List() : null;
@@ -261,20 +265,41 @@ namespace Discord.WebSocket
}
}
- var response = new API.InteractionResponse
+ if (!args.Attachments.IsSpecified)
{
- Type = InteractionResponseType.UpdateMessage,
- Data = new API.InteractionCallbackData
+ var response = new API.InteractionResponse
{
+ Type = InteractionResponseType.UpdateMessage,
+ Data = new API.InteractionCallbackData
+ {
+ Content = args.Content,
+ AllowedMentions = args.AllowedMentions.IsSpecified ? args.AllowedMentions.Value?.ToModel() : Optional.Unspecified,
+ Embeds = apiEmbeds?.ToArray() ?? Optional.Unspecified,
+ Components = args.Components.IsSpecified
+ ? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Array.Empty()
+ : Optional.Unspecified,
+ Flags = args.Flags.IsSpecified ? args.Flags.Value ?? Optional.Unspecified : Optional.Unspecified
+ }
+ };
+
+ await InteractionHelper.SendInteractionResponseAsync(Discord, response, this, Channel, options).ConfigureAwait(false);
+ }
+ else
+ {
+ var response = new API.Rest.UploadInteractionFileParams(args.Attachments.Value.ToArray())
+ {
+ Type = InteractionResponseType.UpdateMessage,
Content = args.Content,
AllowedMentions = args.AllowedMentions.IsSpecified ? args.AllowedMentions.Value?.ToModel() : Optional.Unspecified,
Embeds = apiEmbeds?.ToArray() ?? Optional.Unspecified,
- Components = args.Components.IsSpecified
+ MessageComponents = args.Components.IsSpecified
? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Array.Empty()
: Optional.Unspecified,
Flags = args.Flags.IsSpecified ? args.Flags.Value ?? Optional.Unspecified : Optional.Unspecified
- }
- };
+ };
+
+ await InteractionHelper.SendInteractionResponseAsync(Discord, response, this, Channel, options).ConfigureAwait(false);
+ }
lock (_lock)
{
@@ -284,7 +309,6 @@ namespace Discord.WebSocket
}
}
- await InteractionHelper.SendInteractionResponseAsync(Discord, response, this, Channel, options).ConfigureAwait(false);
HasResponded = true;
}
diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/Modals/SocketModal.cs b/src/Discord.Net.WebSocket/Entities/Interaction/Modals/SocketModal.cs
index cfbd3096d..647544b48 100644
--- a/src/Discord.Net.WebSocket/Entities/Interaction/Modals/SocketModal.cs
+++ b/src/Discord.Net.WebSocket/Entities/Interaction/Modals/SocketModal.cs
@@ -174,6 +174,91 @@ namespace Discord.WebSocket
HasResponded = true;
}
+ public async Task UpdateAsync(Action func, RequestOptions options = null)
+ {
+ var args = new MessageProperties();
+ func(args);
+
+ if (!IsValidToken)
+ throw new InvalidOperationException("Interaction token is no longer valid");
+
+ if (!InteractionHelper.CanSendResponse(this))
+ throw new TimeoutException($"Cannot respond to an interaction after {InteractionHelper.ResponseTimeLimit} seconds!");
+
+ if (args.AllowedMentions.IsSpecified)
+ {
+ var allowedMentions = args.AllowedMentions.Value;
+ Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions), "A max of 100 role Ids are allowed.");
+ Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions), "A max of 100 user Ids are allowed.");
+ }
+
+ var embed = args.Embed;
+ var embeds = args.Embeds;
+
+ bool hasText = args.Content.IsSpecified ? !string.IsNullOrEmpty(args.Content.Value) : false;
+ bool hasEmbeds = embed.IsSpecified && embed.Value != null || embeds.IsSpecified && embeds.Value?.Length > 0;
+
+ if (!hasText && !hasEmbeds)
+ Preconditions.NotNullOrEmpty(args.Content.IsSpecified ? args.Content.Value : string.Empty, nameof(args.Content));
+
+ var apiEmbeds = embed.IsSpecified || embeds.IsSpecified ? new List() : null;
+
+ if (embed.IsSpecified && embed.Value != null)
+ {
+ apiEmbeds.Add(embed.Value.ToModel());
+ }
+
+ if (embeds.IsSpecified && embeds.Value != null)
+ {
+ apiEmbeds.AddRange(embeds.Value.Select(x => x.ToModel()));
+ }
+
+ Preconditions.AtMost(apiEmbeds?.Count ?? 0, 10, nameof(args.Embeds), "A max of 10 embeds are allowed.");
+
+ // check that user flag and user Id list are exclusive, same with role flag and role Id list
+ if (args.AllowedMentions.IsSpecified && args.AllowedMentions.Value != null && args.AllowedMentions.Value.AllowedTypes.HasValue)
+ {
+ var allowedMentions = args.AllowedMentions.Value;
+ if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Users)
+ && allowedMentions.UserIds != null && allowedMentions.UserIds.Count > 0)
+ {
+ throw new ArgumentException("The Users flag is mutually exclusive with the list of User Ids.", nameof(args.AllowedMentions));
+ }
+
+ if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Roles)
+ && allowedMentions.RoleIds != null && allowedMentions.RoleIds.Count > 0)
+ {
+ throw new ArgumentException("The Roles flag is mutually exclusive with the list of Role Ids.", nameof(args.AllowedMentions));
+ }
+ }
+
+ var response = new API.InteractionResponse
+ {
+ Type = InteractionResponseType.UpdateMessage,
+ Data = new API.InteractionCallbackData
+ {
+ Content = args.Content,
+ AllowedMentions = args.AllowedMentions.IsSpecified ? args.AllowedMentions.Value?.ToModel() : Optional.Unspecified,
+ Embeds = apiEmbeds?.ToArray() ?? Optional.Unspecified,
+ Components = args.Components.IsSpecified
+ ? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Array.Empty()
+ : Optional.Unspecified,
+ Flags = args.Flags.IsSpecified ? args.Flags.Value ?? Optional.Unspecified : Optional.Unspecified
+ }
+ };
+
+ lock (_lock)
+ {
+ if (HasResponded)
+ {
+ throw new InvalidOperationException("Cannot respond, update, or defer twice to the same interaction");
+ }
+ }
+
+ await InteractionHelper.SendInteractionResponseAsync(Discord, response, this, Channel, options).ConfigureAwait(false);
+ HasResponded = true;
+ }
+
///
public override async Task FollowupAsync(
string text = null,
diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketApplicationCommand.cs b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketApplicationCommand.cs
index 36eba0cd1..8f27b65f4 100644
--- a/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketApplicationCommand.cs
+++ b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketApplicationCommand.cs
@@ -36,6 +36,12 @@ namespace Discord.WebSocket
///
public bool IsDefaultPermission { get; private set; }
+ ///
+ public bool IsEnabledInDm { get; private set; }
+
+ ///
+ public GuildPermissions DefaultMemberPermissions { get; private set; }
+
///
/// Gets a collection of s for this command.
///
@@ -86,6 +92,9 @@ namespace Discord.WebSocket
Options = model.Options.IsSpecified
? model.Options.Value.Select(SocketApplicationCommandOption.Create).ToImmutableArray()
: ImmutableArray.Create();
+
+ IsEnabledInDm = model.DmPermission.GetValueOrDefault(true).GetValueOrDefault(true);
+ DefaultMemberPermissions = new GuildPermissions((ulong)model.DefaultMemberPermission.GetValueOrDefault(0).GetValueOrDefault(0));
}
///
diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketResolvableData.cs b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketResolvableData.cs
index d722c5a13..a629fd069 100644
--- a/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketResolvableData.cs
+++ b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketResolvableData.cs
@@ -59,7 +59,7 @@ namespace Discord.WebSocket
}
}
- if (resolved.Members.IsSpecified)
+ if (resolved.Members.IsSpecified && guild != null)
{
foreach (var member in resolved.Members.Value)
{
diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs b/src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs
index 5b2da04f5..f8eb6b12e 100644
--- a/src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs
+++ b/src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs
@@ -24,20 +24,11 @@ namespace Discord.WebSocket
///
public ISocketMessageChannel Channel { get; private set; }
- ///
- /// Gets the ID of the channel this interaction was used in.
- ///
- ///
- /// This property is exposed in cases where the bot scope is not provided, so the channel entity cannot be retrieved.
- ///
- /// To get the channel, you can call
- /// as this method makes a request for a if nothing was found in cache.
- ///
+ ///
public ulong? ChannelId { get; private set; }
///
/// Gets the who triggered this interaction.
- /// This property will be if the bot scope isn't used.
///
public SocketUser User { get; private set; }
@@ -74,6 +65,12 @@ namespace Discord.WebSocket
///
public bool IsDMInteraction { get; private set; }
+ ///
+ public ulong? GuildId { get; private set; }
+
+ ///
+ public ulong ApplicationId { get; private set; }
+
internal SocketInteraction(DiscordSocketClient client, ulong id, ISocketMessageChannel channel, SocketUser user)
: base(client, id)
{
@@ -119,13 +116,21 @@ namespace Discord.WebSocket
internal virtual void Update(Model model)
{
- IsDMInteraction = !model.GuildId.IsSpecified;
+ ChannelId = model.ChannelId.IsSpecified
+ ? model.ChannelId.Value
+ : null;
- ChannelId = model.ChannelId.ToNullable();
+ GuildId = model.GuildId.IsSpecified
+ ? model.GuildId.Value
+ : null;
+
+ IsDMInteraction = GuildId is null;
+ ApplicationId = model.ApplicationId;
Data = model.Data.IsSpecified
? model.Data.Value
: null;
+
Token = model.Token;
Version = model.Version;
Type = model.Type;
@@ -133,6 +138,7 @@ namespace Discord.WebSocket
UserLocale = model.UserLocale.IsSpecified
? model.UserLocale.Value
: null;
+
GuildLocale = model.GuildLocale.IsSpecified
? model.GuildLocale.Value
: null;
@@ -392,7 +398,7 @@ namespace Discord.WebSocket
/// The request options for this request.
/// A task that represents the asynchronous operation of responding to the interaction.
public abstract Task RespondWithModalAsync(Modal modal, RequestOptions options = null);
- #endregion
+#endregion
///
/// Attepts to get the channel this interaction was executed in.
@@ -416,7 +422,7 @@ namespace Discord.WebSocket
catch(HttpException ex) when (ex.DiscordCode == DiscordErrorCode.MissingPermissions) { return null; } // bot can't view that channel, return null instead of throwing.
}
- #region IDiscordInteraction
+#region IDiscordInteraction
///
IUser IDiscordInteraction.User => User;
@@ -446,6 +452,6 @@ namespace Discord.WebSocket
async Task IDiscordInteraction.FollowupWithFileAsync(FileAttachment attachment, string text, Embed[] embeds, bool isTTS, bool ephemeral, AllowedMentions allowedMentions, MessageComponent components, Embed embed, RequestOptions options)
=> await FollowupWithFileAsync(attachment, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options).ConfigureAwait(false);
#endif
- #endregion
+#endregion
}
}
diff --git a/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs
index 6668426e1..3cd67beb5 100644
--- a/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs
+++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs
@@ -182,7 +182,8 @@ namespace Discord.WebSocket
{
GuildId = model.Reference.Value.GuildId,
InternalChannelId = model.Reference.Value.ChannelId,
- MessageId = model.Reference.Value.MessageId
+ MessageId = model.Reference.Value.MessageId,
+ FailIfNotExists = model.Reference.Value.FailIfNotExists
};
}
diff --git a/src/Discord.Net.WebSocket/Interactions/SocketInteractionContext.cs b/src/Discord.Net.WebSocket/Interactions/SocketInteractionContext.cs
index 4cd9ef264..a2a101839 100644
--- a/src/Discord.Net.WebSocket/Interactions/SocketInteractionContext.cs
+++ b/src/Discord.Net.WebSocket/Interactions/SocketInteractionContext.cs
@@ -1,11 +1,13 @@
using Discord.WebSocket;
+using System.Collections.Generic;
+using System.Collections.Immutable;
namespace Discord.Interactions
{
///
/// Represents a Web-Socket based context of an .
///
- public class SocketInteractionContext : IInteractionContext
+ public class SocketInteractionContext : IInteractionContext, IRouteMatchContainer
where TInteraction : SocketInteraction
{
///
@@ -36,6 +38,9 @@ namespace Discord.Interactions
///
public TInteraction Interaction { get; }
+ ///
+ public IReadOnlyCollection SegmentMatches { get; private set; }
+
///
/// Initializes a new .
///
@@ -50,6 +55,13 @@ namespace Discord.Interactions
Interaction = interaction;
}
+ ///
+ public void SetSegmentMatches(IEnumerable segmentMatches) => SegmentMatches = segmentMatches.ToImmutableArray();
+
+ //IRouteMatchContainer
+ ///
+ IEnumerable IRouteMatchContainer.SegmentMatches => SegmentMatches;
+
// IInteractionContext
///
IDiscordClient IInteractionContext.Client => Client;
diff --git a/src/Discord.Net.Webhook/Discord.Net.Webhook.csproj b/src/Discord.Net.Webhook/Discord.Net.Webhook.csproj
index df920b7dc..1e3c3f7f8 100644
--- a/src/Discord.Net.Webhook/Discord.Net.Webhook.csproj
+++ b/src/Discord.Net.Webhook/Discord.Net.Webhook.csproj
@@ -6,6 +6,8 @@
Discord.Webhook
A core Discord.Net library containing the Webhook client and models.
net6.0;net5.0;netstandard2.0;netstandard2.1
+ 5
+ True
diff --git a/src/Discord.Net.Webhook/DiscordWebhookClient.cs b/src/Discord.Net.Webhook/DiscordWebhookClient.cs
index 405100f89..556338956 100644
--- a/src/Discord.Net.Webhook/DiscordWebhookClient.cs
+++ b/src/Discord.Net.Webhook/DiscordWebhookClient.cs
@@ -88,8 +88,8 @@ namespace Discord.Webhook
/// Returns the ID of the created message.
public Task SendMessageAsync(string text = null, bool isTTS = false, IEnumerable embeds = null,
string username = null, string avatarUrl = null, RequestOptions options = null, AllowedMentions allowedMentions = null,
- MessageComponent components = null, MessageFlags flags = MessageFlags.None)
- => WebhookClientHelper.SendMessageAsync(this, text, isTTS, embeds, username, avatarUrl, allowedMentions, options, components, flags);
+ MessageComponent components = null, MessageFlags flags = MessageFlags.None, ulong? threadId = null)
+ => WebhookClientHelper.SendMessageAsync(this, text, isTTS, embeds, username, avatarUrl, allowedMentions, options, components, flags, threadId);
///
/// Modifies a message posted using this webhook.
@@ -103,8 +103,8 @@ namespace Discord.Webhook
///
/// A task that represents the asynchronous modification operation.
///
- public Task ModifyMessageAsync(ulong messageId, Action func, RequestOptions options = null)
- => WebhookClientHelper.ModifyMessageAsync(this, messageId, func, options);
+ public Task ModifyMessageAsync(ulong messageId, Action func, RequestOptions options = null, ulong? threadId = null)
+ => WebhookClientHelper.ModifyMessageAsync(this, messageId, func, options, threadId);
///
/// Deletes a message posted using this webhook.
@@ -117,43 +117,43 @@ namespace Discord.Webhook
///
/// A task that represents the asynchronous deletion operation.
///
- public Task DeleteMessageAsync(ulong messageId, RequestOptions options = null)
- => WebhookClientHelper.DeleteMessageAsync(this, messageId, options);
+ public Task DeleteMessageAsync(ulong messageId, RequestOptions options = null, ulong ? threadId = null)
+ => WebhookClientHelper.DeleteMessageAsync(this, messageId, options, threadId);
/// Sends a message to the channel for this webhook with an attachment.
/// Returns the ID of the created message.
public Task SendFileAsync(string filePath, string text, bool isTTS = false,
IEnumerable embeds = null, string username = null, string avatarUrl = null,
RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null,
- MessageComponent components = null, MessageFlags flags = MessageFlags.None)
+ MessageComponent components = null, MessageFlags flags = MessageFlags.None, ulong? threadId = null)
=> WebhookClientHelper.SendFileAsync(this, filePath, text, isTTS, embeds, username, avatarUrl,
- allowedMentions, options, isSpoiler, components, flags);
+ allowedMentions, options, isSpoiler, components, flags, threadId);
/// Sends a message to the channel for this webhook with an attachment.
/// Returns the ID of the created message.
public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false,
IEnumerable embeds = null, string username = null, string avatarUrl = null,
RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null,
- MessageComponent components = null, MessageFlags flags = MessageFlags.None)
+ MessageComponent components = null, MessageFlags flags = MessageFlags.None, ulong? threadId = null)
=> WebhookClientHelper.SendFileAsync(this, stream, filename, text, isTTS, embeds, username,
- avatarUrl, allowedMentions, options, isSpoiler, components, flags);
+ avatarUrl, allowedMentions, options, isSpoiler, components, flags, threadId);
/// Sends a message to the channel for this webhook with an attachment.
/// Returns the ID of the created message.
public Task SendFileAsync(FileAttachment attachment, string text, bool isTTS = false,
IEnumerable embeds = null, string username = null, string avatarUrl = null,
RequestOptions options = null, AllowedMentions allowedMentions = null, MessageComponent components = null,
- MessageFlags flags = MessageFlags.None)
+ MessageFlags flags = MessageFlags.None, ulong? threadId = null)
=> WebhookClientHelper.SendFileAsync(this, attachment, text, isTTS, embeds, username,
- avatarUrl, allowedMentions, components, options, flags);
+ avatarUrl, allowedMentions, components, options, flags, threadId);
/// Sends a message to the channel for this webhook with an attachment.
/// Returns the ID of the created message.
public Task SendFilesAsync(IEnumerable attachments, string text, bool isTTS = false,
IEnumerable embeds = null, string username = null, string avatarUrl = null,
RequestOptions options = null, AllowedMentions allowedMentions = null, MessageComponent components = null,
- MessageFlags flags = MessageFlags.None)
+ MessageFlags flags = MessageFlags.None, ulong? threadId = null)
=> WebhookClientHelper.SendFilesAsync(this, attachments, text, isTTS, embeds, username, avatarUrl,
- allowedMentions, components, options, flags);
+ allowedMentions, components, options, flags, threadId);
/// Modifies the properties of this webhook.
diff --git a/src/Discord.Net.Webhook/WebhookClientHelper.cs b/src/Discord.Net.Webhook/WebhookClientHelper.cs
index 0a974a9d9..8ad74e7e7 100644
--- a/src/Discord.Net.Webhook/WebhookClientHelper.cs
+++ b/src/Discord.Net.Webhook/WebhookClientHelper.cs
@@ -21,8 +21,8 @@ namespace Discord.Webhook
return RestInternalWebhook.Create(client, model);
}
public static async Task SendMessageAsync(DiscordWebhookClient client,
- string text, bool isTTS, IEnumerable embeds, string username, string avatarUrl,
- AllowedMentions allowedMentions, RequestOptions options, MessageComponent components, MessageFlags flags)
+ string text, bool isTTS, IEnumerable embeds, string username, string avatarUrl,
+ AllowedMentions allowedMentions, RequestOptions options, MessageComponent components, MessageFlags flags, ulong? threadId = null)
{
var args = new CreateWebhookMessageParams
{
@@ -44,12 +44,13 @@ namespace Discord.Webhook
if (flags is not MessageFlags.None and not MessageFlags.SuppressEmbeds)
throw new ArgumentException("The only valid MessageFlags are SuppressEmbeds and none.", nameof(flags));
-
- var model = await client.ApiClient.CreateWebhookMessageAsync(client.Webhook.Id, args, options: options).ConfigureAwait(false);
+
+ var model = await client.ApiClient.CreateWebhookMessageAsync(client.Webhook.Id, args, options: options, threadId: threadId).ConfigureAwait(false);
return model.Id;
}
+
public static async Task ModifyMessageAsync(DiscordWebhookClient client, ulong messageId,
- Action func, RequestOptions options)
+ Action func, RequestOptions options, ulong? threadId)
{
var args = new WebhookMessageProperties();
func(args);
@@ -94,35 +95,35 @@ namespace Discord.Webhook
Components = args.Components.IsSpecified ? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() : Optional.Unspecified,
};
- await client.ApiClient.ModifyWebhookMessageAsync(client.Webhook.Id, messageId, apiArgs, options)
+ await client.ApiClient.ModifyWebhookMessageAsync(client.Webhook.Id, messageId, apiArgs, options, threadId)
.ConfigureAwait(false);
}
- public static async Task DeleteMessageAsync(DiscordWebhookClient client, ulong messageId, RequestOptions options)
+ public static async Task DeleteMessageAsync(DiscordWebhookClient client, ulong messageId, RequestOptions options, ulong? threadId)
{
- await client.ApiClient.DeleteWebhookMessageAsync(client.Webhook.Id, messageId, options).ConfigureAwait(false);
+ await client.ApiClient.DeleteWebhookMessageAsync(client.Webhook.Id, messageId, options, threadId).ConfigureAwait(false);
}
public static async Task SendFileAsync(DiscordWebhookClient client, string filePath, string text, bool isTTS,
IEnumerable embeds, string username, string avatarUrl, AllowedMentions allowedMentions, RequestOptions options,
- bool isSpoiler, MessageComponent components, MessageFlags flags = MessageFlags.None)
+ bool isSpoiler, MessageComponent components, MessageFlags flags = MessageFlags.None, ulong? threadId = null)
{
string filename = Path.GetFileName(filePath);
using (var file = File.OpenRead(filePath))
- return await SendFileAsync(client, file, filename, text, isTTS, embeds, username, avatarUrl, allowedMentions, options, isSpoiler, components, flags).ConfigureAwait(false);
+ return await SendFileAsync(client, file, filename, text, isTTS, embeds, username, avatarUrl, allowedMentions, options, isSpoiler, components, flags, threadId).ConfigureAwait(false);
}
public static Task SendFileAsync(DiscordWebhookClient client, Stream stream, string filename, string text, bool isTTS,
IEnumerable embeds, string username, string avatarUrl, AllowedMentions allowedMentions, RequestOptions options, bool isSpoiler,
- MessageComponent components, MessageFlags flags)
- => SendFileAsync(client, new FileAttachment(stream, filename, isSpoiler: isSpoiler), text, isTTS, embeds, username, avatarUrl, allowedMentions, components, options, flags);
+ MessageComponent components, MessageFlags flags, ulong? threadId)
+ => SendFileAsync(client, new FileAttachment(stream, filename, isSpoiler: isSpoiler), text, isTTS, embeds, username, avatarUrl, allowedMentions, components, options, flags, threadId);
public static Task SendFileAsync(DiscordWebhookClient client, FileAttachment attachment, string text, bool isTTS,
IEnumerable embeds, string username, string avatarUrl, AllowedMentions allowedMentions,
- MessageComponent components, RequestOptions options, MessageFlags flags)
- => SendFilesAsync(client, new FileAttachment[] { attachment }, text, isTTS, embeds, username, avatarUrl, allowedMentions, components, options, flags);
+ MessageComponent components, RequestOptions options, MessageFlags flags, ulong? threadId)
+ => SendFilesAsync(client, new FileAttachment[] { attachment }, text, isTTS, embeds, username, avatarUrl, allowedMentions, components, options, flags, threadId);
public static async Task SendFilesAsync(DiscordWebhookClient client,
IEnumerable attachments, string text, bool isTTS, IEnumerable embeds, string username,
string avatarUrl, AllowedMentions allowedMentions, MessageComponent components, RequestOptions options,
- MessageFlags flags)
+ MessageFlags flags, ulong? threadId)
{
embeds ??= Array.Empty();
@@ -164,7 +165,7 @@ namespace Discord.Webhook
MessageComponents = components?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional.Unspecified,
Flags = flags
};
- var msg = await client.ApiClient.UploadWebhookFileAsync(client.Webhook.Id, args, options).ConfigureAwait(false);
+ var msg = await client.ApiClient.UploadWebhookFileAsync(client.Webhook.Id, args, options, threadId).ConfigureAwait(false);
return msg.Id;
}
diff --git a/src/Discord.Net/Discord.Net.nuspec b/src/Discord.Net/Discord.Net.nuspec
index d79e9a24a..1a61ff97a 100644
--- a/src/Discord.Net/Discord.Net.nuspec
+++ b/src/Discord.Net/Discord.Net.nuspec
@@ -2,57 +2,57 @@
Discord.Net
- 3.5.0$suffix$
+ 3.7.2$suffix$
Discord.Net
Discord.Net Contributors
foxbot
An asynchronous API wrapper for Discord. This metapackage includes all of the optional Discord.Net components.
discord;discordapp
- https://github.com/RogueException/Discord.Net
+ https://github.com/discord-net/Discord.Net
http://opensource.org/licenses/MIT
false
- https://github.com/RogueException/Discord.Net/raw/dev/docs/marketing/logo/PackageLogo.png
+ https://github.com/discord-net/Discord.Net/raw/dev/docs/marketing/logo/PackageLogo.png
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
\ No newline at end of file
+
diff --git a/test/Discord.Net.Tests.Integration/Discord.Net.Tests.Integration.csproj b/test/Discord.Net.Tests.Integration/Discord.Net.Tests.Integration.csproj
index 0f399ab68..7b8257bfb 100644
--- a/test/Discord.Net.Tests.Integration/Discord.Net.Tests.Integration.csproj
+++ b/test/Discord.Net.Tests.Integration/Discord.Net.Tests.Integration.csproj
@@ -14,6 +14,7 @@
+
diff --git a/test/Discord.Net.Tests.Integration/DiscordRestApiClientTests.cs b/test/Discord.Net.Tests.Integration/DiscordRestApiClientTests.cs
new file mode 100644
index 000000000..96b33b141
--- /dev/null
+++ b/test/Discord.Net.Tests.Integration/DiscordRestApiClientTests.cs
@@ -0,0 +1,53 @@
+using Discord.API;
+using Discord.API.Rest;
+using Discord.Net;
+using Discord.Rest;
+using FluentAssertions;
+using System;
+using System.IO;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace Discord;
+
+[CollectionDefinition(nameof(DiscordRestApiClientTests), DisableParallelization = true)]
+public class DiscordRestApiClientTests : IClassFixture, IAsyncDisposable
+{
+ private readonly DiscordRestApiClient _apiClient;
+ private readonly IGuild _guild;
+ private readonly ITextChannel _channel;
+
+ public DiscordRestApiClientTests(RestGuildFixture guildFixture)
+ {
+ _guild = guildFixture.Guild;
+ _apiClient = guildFixture.Client.ApiClient;
+ _channel = _guild.CreateTextChannelAsync("testChannel").Result;
+ }
+
+ public async ValueTask DisposeAsync()
+ {
+ await _channel.DeleteAsync();
+ }
+
+ [Fact]
+ public async Task UploadFile_WithMaximumSize_DontThrowsException()
+ {
+ var fileSize = GuildHelper.GetUploadLimit(_guild);
+ using var stream = new MemoryStream(new byte[fileSize]);
+
+ await _apiClient.UploadFileAsync(_channel.Id, new UploadFileParams(new FileAttachment(stream, "filename")));
+ }
+
+ [Fact]
+ public async Task UploadFile_WithOverSize_ThrowsException()
+ {
+ var fileSize = GuildHelper.GetUploadLimit(_guild) + 1;
+ using var stream = new MemoryStream(new byte[fileSize]);
+
+ Func upload = async () =>
+ await _apiClient.UploadFileAsync(_channel.Id, new UploadFileParams(new FileAttachment(stream, "filename")));
+
+ await upload.Should().ThrowExactlyAsync()
+ .Where(e => e.DiscordCode == DiscordErrorCode.RequestEntityTooLarge);
+ }
+}
diff --git a/test/Discord.Net.Tests.Unit/Discord.Net.Tests.Unit.csproj b/test/Discord.Net.Tests.Unit/Discord.Net.Tests.Unit.csproj
index ec06c3c3d..087a64d83 100644
--- a/test/Discord.Net.Tests.Unit/Discord.Net.Tests.Unit.csproj
+++ b/test/Discord.Net.Tests.Unit/Discord.Net.Tests.Unit.csproj
@@ -12,7 +12,9 @@
+
+
all
diff --git a/test/Discord.Net.Tests.Unit/GuildHelperTests.cs b/test/Discord.Net.Tests.Unit/GuildHelperTests.cs
new file mode 100644
index 000000000..c68f415fe
--- /dev/null
+++ b/test/Discord.Net.Tests.Unit/GuildHelperTests.cs
@@ -0,0 +1,25 @@
+using Discord.Rest;
+using FluentAssertions;
+using Moq;
+using System;
+using Xunit;
+
+namespace Discord;
+
+public class GuildHelperTests
+{
+ [Theory]
+ [InlineData(PremiumTier.None, 8)]
+ [InlineData(PremiumTier.Tier1, 8)]
+ [InlineData(PremiumTier.Tier2, 50)]
+ [InlineData(PremiumTier.Tier3, 100)]
+ public void GetUploadLimit(PremiumTier tier, ulong factor)
+ {
+ var guild = Mock.Of(g => g.PremiumTier == tier);
+ var expected = factor * (ulong)Math.Pow(2, 20);
+
+ var actual = GuildHelper.GetUploadLimit(guild);
+
+ actual.Should().Be(expected);
+ }
+}
diff --git a/test/Discord.Net.Tests.Unit/MockedEntities/MockedTextChannel.cs b/test/Discord.Net.Tests.Unit/MockedEntities/MockedTextChannel.cs
index 0dfcab7a5..ab1d3e534 100644
--- a/test/Discord.Net.Tests.Unit/MockedEntities/MockedTextChannel.cs
+++ b/test/Discord.Net.Tests.Unit/MockedEntities/MockedTextChannel.cs
@@ -10,6 +10,8 @@ namespace Discord
{
public bool IsNsfw => throw new NotImplementedException();
+ public ThreadArchiveDuration DefaultArchiveDuration => throw new NotImplementedException();
+
public string Topic => throw new NotImplementedException();
public int SlowModeInterval => throw new NotImplementedException();
diff --git a/test/Discord.Net.Tests.Unit/MockedEntities/MockedVoiceChannel.cs b/test/Discord.Net.Tests.Unit/MockedEntities/MockedVoiceChannel.cs
index 533b1b1b5..fdbdeda5e 100644
--- a/test/Discord.Net.Tests.Unit/MockedEntities/MockedVoiceChannel.cs
+++ b/test/Discord.Net.Tests.Unit/MockedEntities/MockedVoiceChannel.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.IO;
using System.Text;
using System.Threading.Tasks;
using Discord.Audio;
@@ -12,8 +13,6 @@ namespace Discord
public int? UserLimit => throw new NotImplementedException();
- public string Mention => throw new NotImplementedException();
-
public ulong? CategoryId => throw new NotImplementedException();
public int Position => throw new NotImplementedException();
@@ -24,116 +23,53 @@ namespace Discord
public IReadOnlyCollection PermissionOverwrites => throw new NotImplementedException();
+ public string RTCRegion => throw new NotImplementedException();
+
public string Name => throw new NotImplementedException();
public DateTimeOffset CreatedAt => throw new NotImplementedException();
- public ulong Id => throw new NotImplementedException();
-
- public string RTCRegion => throw new NotImplementedException();
- public Task AddPermissionOverwriteAsync(IRole role, OverwritePermissions permissions, RequestOptions options = null)
- {
- throw new NotImplementedException();
- }
-
- public Task AddPermissionOverwriteAsync(IUser user, OverwritePermissions permissions, RequestOptions options = null)
- {
- throw new NotImplementedException();
- }
+ public ulong Id => throw new NotImplementedException();
- public Task ConnectAsync(bool selfDeaf = false, bool selfMute = false, bool external = false)
- {
- throw new NotImplementedException();
- }
+ public string Mention => throw new NotImplementedException();
- public Task CreateInviteAsync(int? maxAge = 86400, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null)
- {
- throw new NotImplementedException();
- }
- public Task CreateInviteToApplicationAsync(ulong applicationId, int? maxAge, int? maxUses = default(int?), bool isTemporary = false, bool isUnique = false, RequestOptions options = null)
- => throw new NotImplementedException();
+ public Task AddPermissionOverwriteAsync(IRole role, OverwritePermissions permissions, RequestOptions options = null) => throw new NotImplementedException();
+ public Task AddPermissionOverwriteAsync(IUser user, OverwritePermissions permissions, RequestOptions options = null) => throw new NotImplementedException();
+ public Task ConnectAsync(bool selfDeaf = false, bool selfMute = false, bool external = false) => throw new NotImplementedException();
+ public Task CreateInviteAsync(int? maxAge = 86400, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null) => throw new NotImplementedException();
+ public Task CreateInviteToApplicationAsync(ulong applicationId, int? maxAge = 86400, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null) => throw new NotImplementedException();
public Task CreateInviteToApplicationAsync(DefaultApplications application, int? maxAge = 86400, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null) => throw new NotImplementedException();
- public Task CreateInviteToStreamAsync(IUser user, int? maxAge, int? maxUses = default(int?), bool isTemporary = false, bool isUnique = false, RequestOptions options = null)
- => throw new NotImplementedException();
-
- public Task DeleteAsync(RequestOptions options = null)
- {
- throw new NotImplementedException();
- }
-
- public Task DisconnectAsync()
- {
- throw new NotImplementedException();
- }
-
- public Task ModifyAsync(Action func, RequestOptions options)
- {
- throw new NotImplementedException();
- }
-
- public Task GetCategoryAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null)
- {
- throw new NotImplementedException();
- }
-
- public Task> GetInvitesAsync(RequestOptions options = null)
- {
- throw new NotImplementedException();
- }
-
- public OverwritePermissions? GetPermissionOverwrite(IRole role)
- {
- throw new NotImplementedException();
- }
-
- public OverwritePermissions? GetPermissionOverwrite(IUser user)
- {
- throw new NotImplementedException();
- }
-
- public Task GetUserAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null)
- {
- throw new NotImplementedException();
- }
-
- public IAsyncEnumerable> GetUsersAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null)
- {
- throw new NotImplementedException();
- }
-
- public Task ModifyAsync(Action func, RequestOptions options = null)
- {
- throw new NotImplementedException();
- }
-
- public Task ModifyAsync(Action func, RequestOptions options = null)
- {
- throw new NotImplementedException();
- }
-
- public Task RemovePermissionOverwriteAsync(IRole role, RequestOptions options = null)
- {
- throw new NotImplementedException();
- }
-
- public Task RemovePermissionOverwriteAsync(IUser user, RequestOptions options = null)
- {
- throw new NotImplementedException();
- }
-
- public Task SyncPermissionsAsync(RequestOptions options = null)
- {
- throw new NotImplementedException();
- }
-
- Task IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options)
- {
- throw new NotImplementedException();
- }
-
- IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options)
- {
- throw new NotImplementedException();
- }
+ public Task CreateInviteToStreamAsync(IUser user, int? maxAge = 86400, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null) => throw new NotImplementedException();
+ public Task DeleteAsync(RequestOptions options = null) => throw new NotImplementedException();
+ public Task DeleteMessageAsync(ulong messageId, RequestOptions options = null) => throw new NotImplementedException();
+ public Task DeleteMessageAsync(IMessage message, RequestOptions options = null) => throw new NotImplementedException();
+ public Task DisconnectAsync() => throw new NotImplementedException();
+ public IDisposable EnterTypingState(RequestOptions options = null) => throw new NotImplementedException();
+ public Task GetCategoryAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null) => throw new NotImplementedException();
+ public Task> GetInvitesAsync(RequestOptions options = null) => throw new NotImplementedException();
+ public Task GetMessageAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null) => throw new NotImplementedException();
+ public IAsyncEnumerable> GetMessagesAsync(int limit = 100, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null) => throw new NotImplementedException();
+ public IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = 100, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null) => throw new NotImplementedException();
+ public IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = 100, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null) => throw new NotImplementedException();
+ public OverwritePermissions? GetPermissionOverwrite(IRole role) => throw new NotImplementedException();
+ public OverwritePermissions? GetPermissionOverwrite(IUser user) => throw new NotImplementedException();
+ public Task> GetPinnedMessagesAsync(RequestOptions options = null) => throw new NotImplementedException();
+ public Task GetUserAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null) => throw new NotImplementedException();
+ public IAsyncEnumerable> GetUsersAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null) => throw new NotImplementedException();
+ public Task ModifyAsync(Action func, RequestOptions options = null) => throw new NotImplementedException();
+ public Task ModifyAsync(Action func, RequestOptions options = null) => throw new NotImplementedException();
+ public Task ModifyAsync(Action func, RequestOptions options = null) => throw new NotImplementedException();
+ public Task ModifyMessageAsync(ulong messageId, Action func, RequestOptions options = null) => throw new NotImplementedException();
+ public Task RemovePermissionOverwriteAsync(IRole role, RequestOptions options = null) => throw new NotImplementedException();
+ public Task RemovePermissionOverwriteAsync(IUser user, RequestOptions options = null) => throw new NotImplementedException();
+ public Task 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 components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None) => throw new NotImplementedException();
+ public Task 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 components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None) => throw new NotImplementedException();
+ public Task SendFileAsync(FileAttachment attachment, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None) => throw new NotImplementedException();
+ public Task SendFilesAsync(IEnumerable attachments, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None) => throw new NotImplementedException();
+ public Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None) => throw new NotImplementedException();
+ public Task SyncPermissionsAsync(RequestOptions options = null) => throw new NotImplementedException();
+ public Task TriggerTypingAsync(RequestOptions options = null) => throw new NotImplementedException();
+ Task IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) => throw new NotImplementedException();
+ IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) => throw new NotImplementedException();
}
}