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..023400c80 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,28 @@
# Changelog
+## [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
### Added
diff --git a/Discord.Net.targets b/Discord.Net.targets
index e50e6eceb..adb0a338c 100644
--- a/Discord.Net.targets
+++ b/Discord.Net.targets
@@ -1,6 +1,6 @@
- 3.5.0
+ 3.6.1
latest
Discord.Net Contributors
discord;discordapp
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..105aa0493 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.6.1",
"_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_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/other_libs/samples/ModifyLogMethod.cs b/docs/guides/other_libs/samples/ModifyLogMethod.cs
index b4870cfd1..0f7c11daf 100644
--- a/docs/guides/other_libs/samples/ModifyLogMethod.cs
+++ b/docs/guides/other_libs/samples/ModifyLogMethod.cs
@@ -6,8 +6,8 @@ private static async Task LogAsync(LogMessage message)
LogSeverity.Error => LogEventLevel.Error,
LogSeverity.Warning => LogEventLevel.Warning,
LogSeverity.Info => LogEventLevel.Information,
- LogSeverity.Verbose => LogEventLevel.Debug,
- LogSeverity.Debug => LogEventLevel.Verbose,
+ LogSeverity.Verbose => LogEventLevel.Verbose,
+ LogSeverity.Debug => LogEventLevel.Debug,
_ => LogEventLevel.Information
};
Log.Write(severity, message.Exception, "[{Source}] {Message}", message.Source, message.Message);
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/Modules/ExampleModule.cs b/samples/InteractionFramework/Modules/ExampleModule.cs
index 1c0a6c8a2..21064bbe3 100644
--- a/samples/InteractionFramework/Modules/ExampleModule.cs
+++ b/samples/InteractionFramework/Modules/ExampleModule.cs
@@ -14,7 +14,7 @@ namespace InteractionFramework.Modules
private InteractionHandler _handler;
- // Constructor injection is also a valid way to access the dependecies
+ // Constructor injection is also a valid way to access the dependencies
public ExampleModule(InteractionHandler handler)
{
_handler = handler;
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/DiscordErrorCode.cs b/src/Discord.Net.Core/DiscordErrorCode.cs
index e9ed63e58..a6861c10c 100644
--- a/src/Discord.Net.Core/DiscordErrorCode.cs
+++ b/src/Discord.Net.Core/DiscordErrorCode.cs
@@ -58,6 +58,7 @@ namespace Discord
#endregion
#region General Actions (20XXX)
+ UnknownTag = 10087,
BotsCannotUse = 20001,
OnlyBotsCanUse = 20002,
CannotSendExplicitContent = 20009,
@@ -98,6 +99,8 @@ namespace Discord
#region General Request Errors (40XXX)
MaximumNumberOfEditsReached = 30046,
+ MaximumNumberOfPinnedThreadsInAForumChannelReached = 30047,
+ MaximumNumberOfTagsInAForumChannelReached = 30048,
TokenUnauthorized = 40001,
InvalidVerification = 40002,
OpeningDMTooFast = 40003,
@@ -112,6 +115,7 @@ namespace Discord
#region Action Preconditions/Checks (50XXX)
InteractionHasAlreadyBeenAcknowledged = 40060,
+ TagNamesMustBeUnique = 40061,
MissingPermissions = 50001,
InvalidAccountType = 50002,
CannotExecuteForDM = 50003,
@@ -148,6 +152,7 @@ namespace Discord
InvalidMessageType = 50068,
PaymentSourceRequiredForGift = 50070,
CannotDeleteRequiredCommunityChannel = 50074,
+ CannotEditStickersWithinAMessage = 50080,
InvalidSticker = 50081,
CannotExecuteOnArchivedThread = 50083,
InvalidThreadNotificationSettings = 50084,
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/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..9017d310f 100644
--- a/src/Discord.Net.Core/Entities/Interactions/IDiscordInteraction.cs
+++ b/src/Discord.Net.Core/Entities/Interactions/IDiscordInteraction.cs
@@ -55,7 +55,7 @@ namespace Discord
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.
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..bf74a160c 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;
@@ -236,6 +263,9 @@ namespace Discord
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;
}
@@ -379,7 +409,7 @@ namespace Discord
MinValue = MinValue,
MaxValue = MaxValue
};
- }
+ }
///
/// Adds an option to the current slash command.
@@ -400,21 +430,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,6 +471,7 @@ 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;
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
public Optional GuildId { get; internal set; }
+ ///
+ /// Gets whether to error if the referenced message doesn't exist instead of sending as a normal (non-reply) message
+ /// Defaults to true.
+ ///
+ public Optional FailIfNotExists { get; internal set; }
+
///
/// Initializes a new instance of the class.
///
@@ -39,16 +45,21 @@ namespace Discord
///
/// The ID of the guild that will be referenced. It will be validated if sent.
///
- public MessageReference(ulong? messageId = null, ulong? channelId = null, ulong? guildId = null)
+ ///
+ /// Whether to error if the referenced message doesn't exist instead of sending as a normal (non-reply) message. Defaults to true.
+ ///
+ public MessageReference(ulong? messageId = null, ulong? channelId = null, ulong? guildId = null, bool? failIfNotExists = null)
{
MessageId = messageId ?? Optional.Create();
InternalChannelId = channelId ?? Optional.Create();
GuildId = guildId ?? Optional.Create();
+ FailIfNotExists = failIfNotExists ?? Optional.Create();
}
private string DebuggerDisplay
=> $"Channel ID: ({ChannelId}){(GuildId.IsSpecified ? $", Guild ID: ({GuildId.Value})" : "")}" +
- $"{(MessageId.IsSpecified ? $", Message ID: ({MessageId.Value})" : "")}";
+ $"{(MessageId.IsSpecified ? $", Message ID: ({MessageId.Value})" : "")}" +
+ $"{(FailIfNotExists.IsSpecified ? $", FailIfNotExists: ({FailIfNotExists.Value})" : "")}";
public override string ToString()
=> DebuggerDisplay;
diff --git a/src/Discord.Net.Core/Entities/Users/GuildUserProperties.cs b/src/Discord.Net.Core/Entities/Users/GuildUserProperties.cs
index 935b956c3..5411f5ebf 100644
--- a/src/Discord.Net.Core/Entities/Users/GuildUserProperties.cs
+++ b/src/Discord.Net.Core/Entities/Users/GuildUserProperties.cs
@@ -79,7 +79,7 @@ namespace Discord
/// Sets a timestamp how long a user should be timed out for.
///
///
- /// or a time in the past to clear a currently existing timeout.
+ /// or a time in the past to clear a currently existing timeout.
///
public Optional TimedOutUntil { get; set; }
}
diff --git a/src/Discord.Net.Core/Entities/Users/IGuildUser.cs b/src/Discord.Net.Core/Entities/Users/IGuildUser.cs
index 96de06ed8..9703eafe7 100644
--- a/src/Discord.Net.Core/Entities/Users/IGuildUser.cs
+++ b/src/Discord.Net.Core/Entities/Users/IGuildUser.cs
@@ -104,7 +104,7 @@ namespace Discord
/// Gets the date and time that indicates if and for how long a user has been timed out.
///
///
- /// or a timestamp in the past if the user is not timed out.
+ /// or a timestamp in the past if the user is not timed out.
///
///
/// A indicating how long the user will be timed out for.
@@ -116,7 +116,7 @@ namespace Discord
///
///
/// The following example checks if the current user has the ability to send a message with attachment in
- /// this channel; if so, uploads a file via .
+ /// this channel; if so, uploads a file via .
///
/// if (currentUser?.GetPermissions(targetChannel)?.AttachFiles)
/// await targetChannel.SendFileAsync("fortnite.png");
@@ -151,7 +151,7 @@ namespace Discord
/// If the user does not have a guild avatar, this will be the user's regular avatar.
///
/// 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..f57c75a31 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,7 +423,7 @@ 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.
/// Commands to be registered to Discord.
@@ -517,7 +519,7 @@ 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.
///
/// Commands to be registered to Discord.
///
@@ -775,6 +777,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 +824,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 +967,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 +980,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 +993,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 +1006,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.
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 f5d57a942..d9d7d469c 100644
--- a/src/Discord.Net.Rest/API/Common/Channel.cs
+++ b/src/Discord.Net.Rest/API/Common/Channel.cs
@@ -70,7 +70,8 @@ namespace Discord.API
//ForumChannel
[JsonProperty("available_tags")]
public Optional ForumTags { get; set; }
+
[JsonProperty("default_auto_archive_duration")]
- public Optional DefaultAutoArchiveDuration { get; set; }
+ public Optional AutoArchiveDuration { 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/AssemblyInfo.cs b/src/Discord.Net.Rest/AssemblyInfo.cs
index 837fd1d04..59e1f0b4b 100644
--- a/src/Discord.Net.Rest/AssemblyInfo.cs
+++ b/src/Discord.Net.Rest/AssemblyInfo.cs
@@ -6,6 +6,7 @@ using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Discord.Net.Commands")]
[assembly: InternalsVisibleTo("Discord.Net.Tests")]
[assembly: InternalsVisibleTo("Discord.Net.Tests.Unit")]
+[assembly: InternalsVisibleTo("Discord.Net.Tests.Integration")]
[assembly: InternalsVisibleTo("Discord.Net.Interactions")]
[assembly: TypeForwardedTo(typeof(Discord.Embed))]
diff --git a/src/Discord.Net.Rest/Discord.Net.Rest.csproj b/src/Discord.Net.Rest/Discord.Net.Rest.csproj
index 98692998f..bec2396ef 100644
--- a/src/Discord.Net.Rest/Discord.Net.Rest.csproj
+++ b/src/Discord.Net.Rest/Discord.Net.Rest.csproj
@@ -7,6 +7,8 @@
A core Discord.Net library containing the REST client and models.
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.Rest/DiscordRestApiClient.cs b/src/Discord.Net.Rest/DiscordRestApiClient.cs
index a6fdfc399..e179675ba 100644
--- a/src/Discord.Net.Rest/DiscordRestApiClient.cs
+++ b/src/Discord.Net.Rest/DiscordRestApiClient.cs
@@ -173,10 +173,12 @@ namespace Discord.API
private async Task LogoutInternalAsync()
{
//An exception here will lock the client into the unusable LoggingOut state, but that's probably fine since our client is in an undefined state too.
- if (LoginState == LoginState.LoggedOut) return;
+ if (LoginState == LoginState.LoggedOut)
+ return;
LoginState = LoginState.LoggingOut;
- try { _loginCancelToken?.Cancel(false); }
+ try
+ { _loginCancelToken?.Cancel(false); }
catch { }
await DisconnectInternalAsync(null).ConfigureAwait(false);
@@ -398,7 +400,7 @@ namespace Discord.API
Preconditions.AtLeast(args.Position, 0, nameof(args.Position));
Preconditions.NotNullOrWhitespace(args.Name, nameof(args.Name));
- if(args.Name.IsSpecified)
+ if (args.Name.IsSpecified)
Preconditions.AtMost(args.Name.Value.Length, 100, nameof(args.Name));
options = RequestOptions.CreateOrClone(options);
@@ -414,9 +416,9 @@ namespace Discord.API
Preconditions.AtLeast(args.Position, 0, nameof(args.Position));
Preconditions.NotNullOrWhitespace(args.Name, nameof(args.Name));
- if(args.Name.IsSpecified)
+ if (args.Name.IsSpecified)
Preconditions.AtMost(args.Name.Value.Length, 100, nameof(args.Name));
- if(args.Topic.IsSpecified)
+ if (args.Topic.IsSpecified)
Preconditions.AtMost(args.Topic.Value.Length, 1024, nameof(args.Name));
Preconditions.AtLeast(args.SlowModeInterval, 0, nameof(args.SlowModeInterval));
@@ -689,9 +691,11 @@ namespace Discord.API
options = RequestOptions.CreateOrClone(options);
+ var bucket = new BucketIds(channelId: channelId);
+
try
{
- await SendAsync("DELETE", $"stage-instances/{channelId}", options: options).ConfigureAwait(false);
+ await SendAsync("DELETE", () => $"stage-instances/{channelId}", bucket, options: options).ConfigureAwait(false);
}
catch (HttpException httpEx) when (httpEx.HttpCode == HttpStatusCode.NotFound) { }
}
@@ -816,9 +820,11 @@ namespace Discord.API
var ids = new BucketIds(channelId: channelId);
return await SendJsonAsync("POST", () => $"channels/{channelId}/messages", args, ids, clientBucket: ClientBucketType.SendEdit, options: options).ConfigureAwait(false);
}
+
+
/// Message content is too long, length must be less or equal to .
/// This operation may only be called with a token.
- public async Task CreateWebhookMessageAsync(ulong webhookId, CreateWebhookMessageParams args, RequestOptions options = null)
+ public async Task CreateWebhookMessageAsync(ulong webhookId, CreateWebhookMessageParams args, RequestOptions options = null, ulong? threadId = null)
{
if (AuthTokenType != TokenType.Webhook)
throw new InvalidOperationException($"This operation may only be called with a {nameof(TokenType.Webhook)} token.");
@@ -834,12 +840,12 @@ namespace Discord.API
options = RequestOptions.CreateOrClone(options);
var ids = new BucketIds(webhookId: webhookId);
- return await SendJsonAsync("POST", () => $"webhooks/{webhookId}/{AuthToken}?wait=true", args, ids, clientBucket: ClientBucketType.SendEdit, options: options).ConfigureAwait(false);
+ return await SendJsonAsync("POST", () => $"webhooks/{webhookId}/{AuthToken}?{WebhookQuery(true, threadId)}", args, ids, clientBucket: ClientBucketType.SendEdit, options: options).ConfigureAwait(false);
}
/// Message content is too long, length must be less or equal to .
/// This operation may only be called with a token.
- public async Task ModifyWebhookMessageAsync(ulong webhookId, ulong messageId, ModifyWebhookMessageParams args, RequestOptions options = null)
+ public async Task ModifyWebhookMessageAsync(ulong webhookId, ulong messageId, ModifyWebhookMessageParams args, RequestOptions options = null, ulong? threadId = null)
{
if (AuthTokenType != TokenType.Webhook)
throw new InvalidOperationException($"This operation may only be called with a {nameof(TokenType.Webhook)} token.");
@@ -855,11 +861,11 @@ namespace Discord.API
options = RequestOptions.CreateOrClone(options);
var ids = new BucketIds(webhookId: webhookId);
- await SendJsonAsync("PATCH", () => $"webhooks/{webhookId}/{AuthToken}/messages/{messageId}", args, ids, clientBucket: ClientBucketType.SendEdit, options: options).ConfigureAwait(false);
+ await SendJsonAsync("PATCH", () => $"webhooks/{webhookId}/{AuthToken}/messages/{messageId}${WebhookQuery(false, threadId)}", args, ids, clientBucket: ClientBucketType.SendEdit, options: options).ConfigureAwait(false);
}
/// This operation may only be called with a token.
- public async Task DeleteWebhookMessageAsync(ulong webhookId, ulong messageId, RequestOptions options = null)
+ public async Task DeleteWebhookMessageAsync(ulong webhookId, ulong messageId, RequestOptions options = null, ulong? threadId = null)
{
if (AuthTokenType != TokenType.Webhook)
throw new InvalidOperationException($"This operation may only be called with a {nameof(TokenType.Webhook)} token.");
@@ -870,7 +876,7 @@ namespace Discord.API
options = RequestOptions.CreateOrClone(options);
var ids = new BucketIds(webhookId: webhookId);
- await SendAsync("DELETE", () => $"webhooks/{webhookId}/{AuthToken}/messages/{messageId}", ids, options: options).ConfigureAwait(false);
+ await SendAsync("DELETE", () => $"webhooks/{webhookId}/{AuthToken}/messages/{messageId}?{WebhookQuery(false, threadId)}", ids, options: options).ConfigureAwait(false);
}
/// Message content is too long, length must be less or equal to .
@@ -891,7 +897,7 @@ namespace Discord.API
/// Message content is too long, length must be less or equal to .
/// This operation may only be called with a token.
- public async Task UploadWebhookFileAsync(ulong webhookId, UploadWebhookFileParams args, RequestOptions options = null)
+ public async Task UploadWebhookFileAsync(ulong webhookId, UploadWebhookFileParams args, RequestOptions options = null, ulong? threadId = null)
{
if (AuthTokenType != TokenType.Webhook)
throw new InvalidOperationException($"This operation may only be called with a {nameof(TokenType.Webhook)} token.");
@@ -911,7 +917,7 @@ namespace Discord.API
}
var ids = new BucketIds(webhookId: webhookId);
- return await SendMultipartAsync("POST", () => $"webhooks/{webhookId}/{AuthToken}?wait=true", args.ToDictionary(), ids, clientBucket: ClientBucketType.SendEdit, options: options).ConfigureAwait(false);
+ return await SendMultipartAsync("POST", () => $"webhooks/{webhookId}/{AuthToken}?{WebhookQuery(true, threadId)}", args.ToDictionary(), ids, clientBucket: ClientBucketType.SendEdit, options: options).ConfigureAwait(false);
}
public async Task DeleteMessageAsync(ulong channelId, ulong messageId, RequestOptions options = null)
{
@@ -1398,7 +1404,7 @@ namespace Discord.API
if ((!args.Embeds.IsSpecified || args.Embeds.Value == null || args.Embeds.Value.Length == 0) && !args.File.IsSpecified)
Preconditions.NotNullOrEmpty(args.Content, nameof(args.Content));
- if(args.Content.IsSpecified && args.Content.Value?.Length > DiscordConfig.MaxMessageSize)
+ if (args.Content.IsSpecified && args.Content.Value?.Length > DiscordConfig.MaxMessageSize)
throw new ArgumentException(message: $"Message content is too long, length must be less or equal to {DiscordConfig.MaxMessageSize}.", paramName: nameof(args.Content));
options = RequestOptions.CreateOrClone(options);
@@ -1418,7 +1424,7 @@ namespace Discord.API
throw new ArgumentException(message: $"Message content is too long, length must be less or equal to {DiscordConfig.MaxMessageSize}.", paramName: nameof(args.Content));
options = RequestOptions.CreateOrClone(options);
-
+
var ids = new BucketIds();
return await SendMultipartAsync("POST", () => $"webhooks/{CurrentApplicationId}/{token}?wait=true", args.ToDictionary(), ids, clientBucket: ClientBucketType.SendEdit, options: options).ConfigureAwait(false);
}
@@ -1747,8 +1753,10 @@ namespace Discord.API
if (args.TargetType.IsSpecified)
{
Preconditions.NotEqual((int)args.TargetType.Value, (int)TargetUserType.Undefined, nameof(args.TargetType));
- if (args.TargetType.Value == TargetUserType.Stream) Preconditions.GreaterThan(args.TargetUserId, 0, nameof(args.TargetUserId));
- if (args.TargetType.Value == TargetUserType.EmbeddedApplication) Preconditions.GreaterThan(args.TargetApplicationId, 0, nameof(args.TargetUserId));
+ if (args.TargetType.Value == TargetUserType.Stream)
+ Preconditions.GreaterThan(args.TargetUserId, 0, nameof(args.TargetUserId));
+ if (args.TargetType.Value == TargetUserType.EmbeddedApplication)
+ Preconditions.GreaterThan(args.TargetApplicationId, 0, nameof(args.TargetUserId));
}
options = RequestOptions.CreateOrClone(options);
@@ -2432,6 +2440,18 @@ namespace Discord.API
return (expr as MemberExpression).Member.Name;
}
+
+ private static string WebhookQuery(bool wait = false, ulong? threadId = null)
+ {
+ List querys = new List() { };
+ if (wait)
+ querys.Add("wait=true");
+ if (threadId.HasValue)
+ querys.Add($"thread_id={threadId}");
+
+ return $"{string.Join("&", querys)}";
+ }
+
#endregion
}
}
diff --git a/src/Discord.Net.Rest/DiscordRestClient.cs b/src/Discord.Net.Rest/DiscordRestClient.cs
index b1948f80a..7cb15bed1 100644
--- a/src/Discord.Net.Rest/DiscordRestClient.cs
+++ b/src/Discord.Net.Rest/DiscordRestClient.cs
@@ -32,9 +32,15 @@ namespace Discord.Rest
/// Initializes a new with the provided configuration.
///
/// 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 != null ? doApiCallOnCreation(model.Type) : _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/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/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 fb44b101a..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)
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..59adc0347 100644
--- a/src/Discord.Net.Rest/Entities/Interactions/RestInteraction.cs
+++ b/src/Discord.Net.Rest/Entities/Interactions/RestInteraction.cs
@@ -16,6 +16,10 @@ namespace Discord.Rest
///
public abstract class RestInteraction : RestEntity, IDiscordInteraction
{
+ // Added so channel & guild methods don't need a client reference
+ private Func> _getChannel = null;
+ private Func> _getGuild = null;
+
///
public InteractionType Type { get; private set; }
@@ -31,6 +35,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; }
///
@@ -48,14 +56,38 @@ namespace Discord.Rest
public bool IsValidToken
=> InteractionHelper.CanRespondOrFollowup(this);
+ ///
+ /// Gets the ID of the channel this interaction was executed in.
+ ///
+ ///
+ /// if the interaction was not executed in a guild.
+ ///
+ public ulong? ChannelId { get; private set; } = null;
+
///
/// Gets the channel that this interaction was executed in.
///
+ ///
+ /// if is set to false.
+ /// Call to set this property and get the interaction channel.
+ ///
public IRestMessageChannel Channel { get; private set; }
///
- /// Gets the guild this interaction was executed in.
+ /// Gets the ID of the guild this interaction was executed in if applicable.
///
+ ///
+ /// if the interaction was not executed in a guild.
+ ///
+ public ulong? GuildId { get; private set; } = null;
+
+ ///
+ /// 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; }
///
@@ -72,11 +104,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,26 +122,26 @@ 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;
@@ -120,16 +152,23 @@ namespace Discord.Rest
Version = model.Version;
Type = model.Type;
- if(Guild == null && model.GuildId.IsSpecified)
+ if (Guild == null && model.GuildId.IsSpecified)
{
- Guild = await discord.GetGuildAsync(model.GuildId.Value);
+ GuildId = model.GuildId.Value;
+ if (doApiCall)
+ Guild = await discord.GetGuildAsync(model.GuildId.Value);
+ else
+ {
+ Guild = null;
+ _getGuild = new(async (opt, ul) => await discord.GetGuildAsync(ul, opt));
+ }
}
if (User == null)
{
if (model.Member.IsSpecified && model.GuildId.IsSpecified)
{
- User = RestGuildUser.Create(Discord, Guild, model.Member.Value);
+ User = RestGuildUser.Create(Discord, Guild, model.Member.Value, (Guild is null) ? model.GuildId.Value : null);
}
else
{
@@ -137,18 +176,33 @@ namespace Discord.Rest
}
}
- if(Channel == null && model.ChannelId.IsSpecified)
+ if (Channel == null && model.ChannelId.IsSpecified)
{
try
{
- Channel = (IRestMessageChannel)await discord.GetChannelAsync(model.ChannelId.Value);
+ ChannelId = model.ChannelId.Value;
+ if (doApiCall)
+ Channel = (IRestMessageChannel)await discord.GetChannelAsync(model.ChannelId.Value);
+ else
+ {
+ _getChannel = new(async (opt, ul) =>
+ {
+ if (Guild is null)
+ return (IRestMessageChannel)await discord.GetChannelAsync(ul, opt);
+ else // get a guild channel if the guild is set.
+ return (IRestMessageChannel)await Guild.GetChannelAsync(ul, opt);
+ });
+
+ Channel = null;
+ }
}
- 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 +218,59 @@ 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 succesfully 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 (IsDMInteraction && Channel is null)
+ {
+ var channel = await User.CreateDMChannelAsync(options);
+ Channel = channel;
+ }
+
+ else if (Channel is null)
+ {
+ var channel = await _getChannel(options, ChannelId.Value);
+
+ if (channel is null)
+ throw new InvalidOperationException("The interaction channel was not able to be retrieved.");
+ Channel = channel;
+
+ _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 succesfully 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 (IsDMInteraction)
+ return null;
+
+ if (Guild is 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 +440,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/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..57d58a8b1 100644
--- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs
+++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs
@@ -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/SocketGuildChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs
index c538bc7fe..16ed7b32d 100644
--- a/src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs
+++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs
@@ -223,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..5fc99c3f1 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,21 @@ 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;
+ public virtual bool IsTextInVoice
+ => Guild.Features.HasTextInVoice;
///
- 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 +37,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 +54,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 +87,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 49d2cd3bd..e12f3d1ef 100644
--- a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs
+++ b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs
@@ -1291,7 +1291,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.
@@ -1781,7 +1780,7 @@ namespace Discord.WebSocket
///
ulong? IGuild.AFKChannelId => AFKChannelId;
///
- IAudioClient IGuild.AudioClient => null;
+ IAudioClient IGuild.AudioClient => AudioClient;
///
bool IGuild.Available => 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/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..3985536f4 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.6.1$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();
}
}