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 ac5547568..a4022e1b6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,4 +1,55 @@
# Changelog
+## [3.7.2] - 2022-06-02
+### Added
+- #2328 Add method overloads to InteractionService (0fad3e8)
+- #2336 Add support for attachments on interaction response type 7 (35db22e)
+- #2338 AddOptions no longer has an uneeded restriction, added AddOptions to SlashCommandOptionBuilder (3a37f89)
+
+### Fixed
+- #2342 Disable TIV restrictions for rollout of TIV (7adf516)
+
+## [3.7.1] - 2022-05-27
+### Added
+- #2325 Add missing interaction properties (d3a693a)
+- #2330 Add better call control in ParseHttpInteraction (a890de9)
+
+### Fixed
+- #2329 Voice perms not retaining text perms. (712a4ae)
+- #2331 NRE with Cacheable.DownloadAsync() (e1f9b76)
+
+## [3.7.0] - 2022-05-24
+### Added
+- #2269 Text-In-Voice (23656e8)
+- #2281 Optional API calling to RestInteraction (a24dde4)
+- #2283 Support FailIfNotExists on MessageReference (0ec8938)
+- #2284 Add Parse & TryParse to EmbedBuilder & Add ToJsonString extension (cea59b5)
+- #2289 Add UpdateAsync to SocketModal (b333de2)
+- #2291 Webhook support for threads (b0a3b65)
+- #2295 Add DefaultArchiveDuration to ITextChannel (1f01881)
+- #2296 Add `.With` methods to ActionRowBuilder (13ccc7c)
+- #2307 Add Nullable ComponentTypeConverter and TypeReader (6fbd396)
+- #2316 Forum channels (7a07fd6)
+
+### Fixed
+- #2290 Possible NRE in Sanitize (20ffa64)
+- #2293 Application commands are disabled to everyone except admins by default (b465d60)
+- #2299 Close-stage bucketId being null (725d255)
+- #2313 Upload file size limit being incorrectly calculated (54a5af7)
+- #2319 Use `IDiscordClient.GetUserAsync` impl in `DiscordSocketClient` (f47f319)
+- #2320 NRE with bot scope and user parameters (88f6168)
+
+## [3.6.1] - 2022-04-30
+### Added
+- #2272 add 50080 Error code (503e720)
+
+### Fixed
+- #2267 Permissions v2 Invalid Operation Exception (a8f6075)
+- #2271 null user on interaction without bot scope (f2bb55e)
+- #2274 Implement fix for Custom Id Segments NRE (0d74c5c)
+
+### Misc
+- 3.6.0 (27226f0)
+
## [3.6.0] - 2022-04-28
### Added
diff --git a/Discord.Net.targets b/Discord.Net.targets
index e17f6de98..8cedb40e7 100644
--- a/Discord.Net.targets
+++ b/Discord.Net.targets
@@ -1,12 +1,12 @@
- 3.6.0
+ 3.7.2
latest
Discord.Net Contributors
discord;discordapp
https://github.com/Discord-Net/Discord.Net
- http://opensource.org/licenses/MIT
- https://github.com/Discord-Net/Discord.Net/raw/dev/docs/marketing/logo/PackageLogo.png
+ MIT
+ PackageLogo.png
git
git://github.com/Discord-Net/Discord.Net
@@ -23,4 +23,7 @@
true
true
+
+
+
diff --git a/README.md b/README.md
index bb8437432..e85216dbf 100644
--- a/README.md
+++ b/README.md
@@ -17,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 585d4dbec..5dd1e640d 100644
--- a/docs/docfx.json
+++ b/docs/docfx.json
@@ -60,7 +60,7 @@
"overwrite": "_overwrites/**/**.md",
"globalMetadata": {
"_appTitle": "Discord.Net Documentation",
- "_appFooter": "Discord.Net (c) 2015-2022 3.6.0",
+ "_appFooter": "Discord.Net (c) 2015-2022 3.7.2",
"_enableSearch": true,
"_appLogoPath": "marketing/logo/SVG/Logomark Purple.svg",
"_appFaviconPath": "favicon.ico"
diff --git a/docs/guides/int_basics/modals/intro.md b/docs/guides/int_basics/modals/intro.md
index 81f0da03c..3e738c6d8 100644
--- a/docs/guides/int_basics/modals/intro.md
+++ b/docs/guides/int_basics/modals/intro.md
@@ -99,7 +99,7 @@ When we run the command, our modal should pop up:
### Respond to modals
> [!WARNING]
-> Modals can not be sent when respoding to a modal.
+> Modals can not be sent when responding to a modal.
Once a user has submitted the modal, we need to let everyone know what
their favorite food is. We can start by hooking a task to the client's
diff --git a/docs/guides/int_framework/autocompletion.md b/docs/guides/int_framework/autocompletion.md
index 834db2b4f..27da54e36 100644
--- a/docs/guides/int_framework/autocompletion.md
+++ b/docs/guides/int_framework/autocompletion.md
@@ -18,6 +18,8 @@ AutocompleteHandlers raise the `AutocompleteHandlerExecuted` event on execution.
A valid AutocompleteHandlers must inherit [AutocompleteHandler] base type and implement all of its abstract methods.
+[!code-csharp[Autocomplete Command Example](samples/autocompletion/autocomplete-example.cs)]
+
### GenerateSuggestionsAsync()
The Interactions Service uses this method to generate a response of an Autocomplete Interaction.
diff --git a/docs/guides/int_framework/intro.md b/docs/guides/int_framework/intro.md
index c019b1424..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`
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/samples/BasicBot/_BasicBot.csproj b/samples/BasicBot/_BasicBot.csproj
index 6e1a6365f..e6245d340 100644
--- a/samples/BasicBot/_BasicBot.csproj
+++ b/samples/BasicBot/_BasicBot.csproj
@@ -1,12 +1,12 @@
-
+
Exe
- net5.0
+ net6.0
-
+
diff --git a/samples/InteractionFramework/_InteractionFramework.csproj b/samples/InteractionFramework/_InteractionFramework.csproj
index f11c2bd3d..8892a65b7 100644
--- a/samples/InteractionFramework/_InteractionFramework.csproj
+++ b/samples/InteractionFramework/_InteractionFramework.csproj
@@ -2,7 +2,7 @@
Exe
- net5.0
+ net6.0
InteractionFramework
@@ -13,13 +13,7 @@
-
-
-
-
-
-
-
+
diff --git a/samples/ShardedClient/_ShardedClient.csproj b/samples/ShardedClient/_ShardedClient.csproj
index 69576ea27..68a43c7cd 100644
--- a/samples/ShardedClient/_ShardedClient.csproj
+++ b/samples/ShardedClient/_ShardedClient.csproj
@@ -2,18 +2,13 @@
Exe
- net5.0
+ net6.0
ShardedClient
-
-
-
-
-
-
+
diff --git a/samples/TextCommandFramework/_TextCommandFramework.csproj b/samples/TextCommandFramework/_TextCommandFramework.csproj
index ee64205f5..6e00625e8 100644
--- a/samples/TextCommandFramework/_TextCommandFramework.csproj
+++ b/samples/TextCommandFramework/_TextCommandFramework.csproj
@@ -2,17 +2,14 @@
Exe
- net5.0
+ net6.0
TextCommandFramework
-
-
-
-
-
+
+
diff --git a/samples/WebhookClient/_WebhookClient.csproj b/samples/WebhookClient/_WebhookClient.csproj
index 91131894d..515fcf3a4 100644
--- a/samples/WebhookClient/_WebhookClient.csproj
+++ b/samples/WebhookClient/_WebhookClient.csproj
@@ -2,12 +2,12 @@
Exe
- net5.0
+ net6.0
WebHookClient
-
+
diff --git a/src/Discord.Net.Commands/Discord.Net.Commands.csproj b/src/Discord.Net.Commands/Discord.Net.Commands.csproj
index fea719016..4fdecd254 100644
--- a/src/Discord.Net.Commands/Discord.Net.Commands.csproj
+++ b/src/Discord.Net.Commands/Discord.Net.Commands.csproj
@@ -7,6 +7,8 @@
A Discord.Net extension adding support for bot commands.
net6.0;net5.0;net461;netstandard2.0;netstandard2.1
net6.0;net5.0;netstandard2.0;netstandard2.1
+ 5
+ True
diff --git a/src/Discord.Net.Commands/Results/MatchResult.cs b/src/Discord.Net.Commands/Results/MatchResult.cs
index fb266efa6..5b9bfe72b 100644
--- a/src/Discord.Net.Commands/Results/MatchResult.cs
+++ b/src/Discord.Net.Commands/Results/MatchResult.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
namespace Discord.Commands
{
@@ -12,7 +12,7 @@ namespace Discord.Commands
///
/// Gets on which pipeline stage the command may have matched or failed.
///
- public IResult? Pipeline { get; }
+ public IResult Pipeline { get; }
///
public CommandError? Error { get; }
@@ -21,7 +21,7 @@ namespace Discord.Commands
///
public bool IsSuccess => !Error.HasValue;
- private MatchResult(CommandMatch? match, IResult? pipeline, CommandError? error, string errorReason)
+ private MatchResult(CommandMatch? match, IResult pipeline, CommandError? error, string errorReason)
{
Match = match;
Error = error;
diff --git a/src/Discord.Net.Core/Discord.Net.Core.csproj b/src/Discord.Net.Core/Discord.Net.Core.csproj
index 783565e04..41d83bbc8 100644
--- a/src/Discord.Net.Core/Discord.Net.Core.csproj
+++ b/src/Discord.Net.Core/Discord.Net.Core.csproj
@@ -7,6 +7,8 @@
The core components for the Discord.Net library.
net6.0;net5.0;net461;netstandard2.0;netstandard2.1
net6.0;net5.0;netstandard2.0;netstandard2.1
+ 5
+ True
diff --git a/src/Discord.Net.Core/DiscordConfig.cs b/src/Discord.Net.Core/DiscordConfig.cs
index 067c55225..2db802f1e 100644
--- a/src/Discord.Net.Core/DiscordConfig.cs
+++ b/src/Discord.Net.Core/DiscordConfig.cs
@@ -132,6 +132,16 @@ namespace Discord
///
public const int MaxAuditLogEntriesPerBatch = 100;
+ ///
+ /// Returns the max number of stickers that can be sent with a message.
+ ///
+ public const int MaxStickersPerMessage = 3;
+
+ ///
+ /// Returns the max number of embeds that can be sent with a message.
+ ///
+ public const int MaxEmbedsPerMessage = 10;
+
///
/// Gets or sets how a request should act in the case of an error, by default.
///
diff --git a/src/Discord.Net.Core/DiscordErrorCode.cs b/src/Discord.Net.Core/DiscordErrorCode.cs
index 51fd736f6..b444614e4 100644
--- a/src/Discord.Net.Core/DiscordErrorCode.cs
+++ b/src/Discord.Net.Core/DiscordErrorCode.cs
@@ -152,6 +152,7 @@ namespace Discord
InvalidMessageType = 50068,
PaymentSourceRequiredForGift = 50070,
CannotDeleteRequiredCommunityChannel = 50074,
+ CannotEditStickersWithinAMessage = 50080,
InvalidSticker = 50081,
CannotExecuteOnArchivedThread = 50083,
InvalidThreadNotificationSettings = 50084,
@@ -164,6 +165,7 @@ namespace Discord
#endregion
#region 2FA (60XXX)
+ MissingPermissionToSendThisSticker = 50600,
Requires2FA = 60003,
#endregion
diff --git a/src/Discord.Net.Core/Entities/Channels/ChannelType.cs b/src/Discord.Net.Core/Entities/Channels/ChannelType.cs
index e60bd5031..15965abc3 100644
--- a/src/Discord.Net.Core/Entities/Channels/ChannelType.cs
+++ b/src/Discord.Net.Core/Entities/Channels/ChannelType.cs
@@ -26,6 +26,8 @@ namespace Discord
/// The channel is a stage voice channel.
Stage = 13,
/// The channel is a guild directory used in hub servers. (Unreleased)
- GuildDirectory = 14
+ GuildDirectory = 14,
+ /// The channel is a forum channel containing multiple threads.
+ Forum = 15
}
}
diff --git a/src/Discord.Net.Core/Entities/Channels/IForumChannel.cs b/src/Discord.Net.Core/Entities/Channels/IForumChannel.cs
new file mode 100644
index 000000000..f4c6da2e2
--- /dev/null
+++ b/src/Discord.Net.Core/Entities/Channels/IForumChannel.cs
@@ -0,0 +1,216 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Discord
+{
+ public interface IForumChannel : IGuildChannel, IMentionable
+ {
+ ///
+ /// Gets a value that indicates whether the channel is NSFW.
+ ///
+ ///
+ /// true if the channel has the NSFW flag enabled; otherwise false.
+ ///
+ bool IsNsfw { get; }
+
+ ///
+ /// Gets the current topic for this text channel.
+ ///
+ ///
+ /// A string representing the topic set in the channel; null if none is set.
+ ///
+ string Topic { get; }
+
+ ///
+ /// Gets the default archive duration for a newly created post.
+ ///
+ ThreadArchiveDuration DefaultAutoArchiveDuration { get; }
+
+ ///
+ /// Gets a collection of tags inside of this forum channel.
+ ///
+ IReadOnlyCollection Tags { get; }
+
+ ///
+ /// Creates a new post (thread) within the forum.
+ ///
+ /// The title of the post.
+ /// The archive duration of the post.
+ /// The slowmode for the posts thread.
+ /// The message to be sent.
+ /// The to be sent.
+ /// The options to be used when sending the request.
+ ///
+ /// Specifies if notifications are sent for mentioned users and roles in the message .
+ /// If null, all mentioned roles and users will be notified.
+ ///
+ /// The message components to be included with this message. Used for interactions.
+ /// A collection of stickers to send with the message.
+ /// A array of s to send with this response. Max 10.
+ /// A message flag to be applied to the sent message, only is permitted.
+ ///
+ /// A task that represents the asynchronous creation operation.
+ ///
+ Task CreatePostAsync(string title, ThreadArchiveDuration archiveDuration = ThreadArchiveDuration.OneDay, int? slowmode = null,
+ string text = null, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null,
+ MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None);
+
+ ///
+ /// Creates a new post (thread) within the forum.
+ ///
+ /// The title of the post.
+ /// The archive duration of the post.
+ /// The slowmode for the posts thread.
+ /// The file path of the file.
+ /// The message to be sent.
+ /// The to be sent.
+ /// The options to be used when sending the request.
+ /// Whether the message attachment should be hidden as a spoiler.
+ ///
+ /// Specifies if notifications are sent for mentioned users and roles in the message .
+ /// If null, all mentioned roles and users will be notified.
+ ///
+ /// The message components to be included with this message. Used for interactions.
+ /// A collection of stickers to send with the file.
+ /// A array of s to send with this response. Max 10.
+ /// A message flag to be applied to the sent message, only is permitted.
+ ///
+ /// A task that represents the asynchronous creation operation.
+ ///
+ Task CreatePostWithFileAsync(string title, string filePath, ThreadArchiveDuration archiveDuration = ThreadArchiveDuration.OneDay,
+ int? slowmode = null, string text = null, Embed embed = null, RequestOptions options = null, bool isSpoiler = false,
+ AllowedMentions allowedMentions = null, MessageComponent components = null,
+ ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None);
+
+ ///
+ /// Creates a new post (thread) within the forum.
+ ///
+ /// The title of the post.
+ /// The of the file to be sent.
+ /// The name of the attachment.
+ /// The archive duration of the post.
+ /// The slowmode for the posts thread.
+ /// The message to be sent.
+ /// The to be sent.
+ /// The options to be used when sending the request.
+ /// Whether the message attachment should be hidden as a spoiler.
+ ///
+ /// Specifies if notifications are sent for mentioned users and roles in the message .
+ /// If null, all mentioned roles and users will be notified.
+ ///
+ /// The message components to be included with this message. Used for interactions.
+ /// A collection of stickers to send with the file.
+ /// A array of s to send with this response. Max 10.
+ /// A message flag to be applied to the sent message, only is permitted.
+ ///
+ /// A task that represents the asynchronous creation operation.
+ ///
+ public Task CreatePostWithFileAsync(string title, Stream stream, string filename, ThreadArchiveDuration archiveDuration = ThreadArchiveDuration.OneDay,
+ int? slowmode = null, string text = null, Embed embed = null, RequestOptions options = null, bool isSpoiler = false,
+ AllowedMentions allowedMentions = null, MessageComponent components = null,
+ ISticker[] stickers = null, Embed[] embeds = null,MessageFlags flags = MessageFlags.None);
+
+ ///
+ /// Creates a new post (thread) within the forum.
+ ///
+ /// The title of the post.
+ /// The attachment containing the file and description.
+ /// The archive duration of the post.
+ /// The slowmode for the posts thread.
+ /// The message to be sent.
+ /// The to be sent.
+ /// The options to be used when sending the request.
+ ///
+ /// Specifies if notifications are sent for mentioned users and roles in the message .
+ /// If null, all mentioned roles and users will be notified.
+ ///
+ /// The message components to be included with this message. Used for interactions.
+ /// A collection of stickers to send with the file.
+ /// A array of s to send with this response. Max 10.
+ /// A message flag to be applied to the sent message, only is permitted.
+ ///
+ /// A task that represents the asynchronous creation operation.
+ ///
+ public Task CreatePostWithFileAsync(string title, FileAttachment attachment, ThreadArchiveDuration archiveDuration = ThreadArchiveDuration.OneDay,
+ int? slowmode = null, string text = null, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null,
+ MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None);
+
+ ///
+ /// Creates a new post (thread) within the forum.
+ ///
+ /// The title of the post.
+ /// A collection of attachments to upload.
+ /// The archive duration of the post.
+ /// The slowmode for the posts thread.
+ /// The message to be sent.
+ /// The to be sent.
+ /// The options to be used when sending the request.
+ ///
+ /// Specifies if notifications are sent for mentioned users and roles in the message .
+ /// If null, all mentioned roles and users will be notified.
+ ///
+ /// The message components to be included with this message. Used for interactions.
+ /// A collection of stickers to send with the file.
+ /// A array of s to send with this response. Max 10.
+ /// A message flag to be applied to the sent message, only is permitted.
+ ///
+ /// A task that represents the asynchronous creation operation.
+ ///
+ public Task CreatePostWithFilesAsync(string title, IEnumerable attachments, ThreadArchiveDuration archiveDuration = ThreadArchiveDuration.OneDay,
+ int? slowmode = null, string text = null, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null,
+ MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None);
+
+ ///
+ /// Gets a collection of active threads within this forum channel.
+ ///
+ /// The options to be used when sending the request.
+ ///
+ /// A task that represents an asynchronous get operation for retrieving the threads. The task result contains
+ /// a collection of active threads.
+ ///
+ Task> GetActiveThreadsAsync(RequestOptions options = null);
+
+ ///
+ /// Gets a collection of publicly archived threads within this forum channel.
+ ///
+ /// The optional limit of how many to get.
+ /// The optional date to return threads created before this timestamp.
+ /// The options to be used when sending the request.
+ ///
+ /// A task that represents an asynchronous get operation for retrieving the threads. The task result contains
+ /// a collection of publicly archived threads.
+ ///
+ Task> GetPublicArchivedThreadsAsync(int? limit = null, DateTimeOffset? before = null, RequestOptions options = null);
+
+ ///
+ /// Gets a collection of privately archived threads within this forum channel.
+ ///
+ ///
+ /// The bot requires the permission in order to execute this request.
+ ///
+ /// The optional limit of how many to get.
+ /// The optional date to return threads created before this timestamp.
+ /// The options to be used when sending the request.
+ ///
+ /// A task that represents an asynchronous get operation for retrieving the threads. The task result contains
+ /// a collection of privately archived threads.
+ ///
+ Task> GetPrivateArchivedThreadsAsync(int? limit = null, DateTimeOffset? before = null, RequestOptions options = null);
+
+ ///
+ /// Gets a collection of privately archived threads that the current bot has joined within this forum channel.
+ ///
+ /// The optional limit of how many to get.
+ /// The optional date to return threads created before this timestamp.
+ /// The options to be used when sending the request.
+ ///
+ /// A task that represents an asynchronous get operation for retrieving the threads. The task result contains
+ /// a collection of privately archived threads.
+ ///
+ Task> GetJoinedPrivateArchivedThreadsAsync(int? limit = null, DateTimeOffset? before = null, RequestOptions options = null);
+ }
+}
diff --git a/src/Discord.Net.Core/Entities/Channels/ITextChannel.cs b/src/Discord.Net.Core/Entities/Channels/ITextChannel.cs
index ae0fe674b..af4e5ec6a 100644
--- a/src/Discord.Net.Core/Entities/Channels/ITextChannel.cs
+++ b/src/Discord.Net.Core/Entities/Channels/ITextChannel.cs
@@ -35,6 +35,17 @@ namespace Discord
///
int SlowModeInterval { get; }
+ ///
+ /// Gets the default auto-archive duration for client-created threads in this channel.
+ ///
+ ///
+ /// The value of this property does not affect API thread creation, it will not respect this value.
+ ///
+ ///
+ /// The default auto-archive duration for thread creation in this channel.
+ ///
+ ThreadArchiveDuration DefaultArchiveDuration { get; }
+
///
/// Bulk-deletes multiple messages.
///
diff --git a/src/Discord.Net.Core/Entities/Channels/IVoiceChannel.cs b/src/Discord.Net.Core/Entities/Channels/IVoiceChannel.cs
index 1d36a41b9..d921a2474 100644
--- a/src/Discord.Net.Core/Entities/Channels/IVoiceChannel.cs
+++ b/src/Discord.Net.Core/Entities/Channels/IVoiceChannel.cs
@@ -6,7 +6,7 @@ namespace Discord
///
/// Represents a generic voice channel in a guild.
///
- public interface IVoiceChannel : INestedChannel, IAudioChannel, IMentionable
+ public interface IVoiceChannel : IMessageChannel, INestedChannel, IAudioChannel, IMentionable
{
///
/// Gets the bit-rate that the clients in this voice channel are requested to use.
diff --git a/src/Discord.Net.Core/Entities/ForumTag.cs b/src/Discord.Net.Core/Entities/ForumTag.cs
new file mode 100644
index 000000000..26ae4301e
--- /dev/null
+++ b/src/Discord.Net.Core/Entities/ForumTag.cs
@@ -0,0 +1,42 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Discord
+{
+ ///
+ /// A struct representing a forum channel tag.
+ ///
+ public struct ForumTag
+ {
+ ///
+ /// Gets the Id of the tag.
+ ///
+ public ulong Id { get; }
+
+ ///
+ /// Gets the name of the tag.
+ ///
+ public string Name { get; }
+
+ ///
+ /// Gets the emoji of the tag or if none is set.
+ ///
+ public IEmote Emoji { get; }
+
+ internal ForumTag(ulong id, string name, ulong? emojiId, string emojiName)
+ {
+ if (emojiId.HasValue && emojiId.Value != 0)
+ Emoji = new Emote(emojiId.Value, emojiName, false);
+ else if (emojiName != null)
+ Emoji = new Emoji(name);
+ else
+ Emoji = null;
+
+ Id = id;
+ Name = name;
+ }
+ }
+}
diff --git a/src/Discord.Net.Core/Entities/Guilds/IGuild.cs b/src/Discord.Net.Core/Entities/Guilds/IGuild.cs
index 4706b629e..775ff9e65 100644
--- a/src/Discord.Net.Core/Entities/Guilds/IGuild.cs
+++ b/src/Discord.Net.Core/Entities/Guilds/IGuild.cs
@@ -1173,7 +1173,6 @@ namespace Discord
/// in order to use this property.
///
///
- /// A collection of speakers for the event.
/// The location of the event; links are supported
/// The optional banner image for the event.
/// The options to be used when sending the request.
diff --git a/src/Discord.Net.Core/Entities/Guilds/IGuildScheduledEvent.cs b/src/Discord.Net.Core/Entities/Guilds/IGuildScheduledEvent.cs
index 4b2fa3bee..7219682b7 100644
--- a/src/Discord.Net.Core/Entities/Guilds/IGuildScheduledEvent.cs
+++ b/src/Discord.Net.Core/Entities/Guilds/IGuildScheduledEvent.cs
@@ -89,7 +89,7 @@ namespace Discord
/// Gets this events banner image url.
///
/// The format to return.
- /// The size of the image to return in. This can be any power of two between 16 and 2048.
+ /// The size of the image to return in. This can be any power of two between 16 and 2048.
/// The cover images url.
string GetCoverImageUrl(ImageFormat format = ImageFormat.Auto, ushort size = 1024);
diff --git a/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandOptionType.cs b/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandOptionType.cs
index 5bb00797b..4506b66d9 100644
--- a/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandOptionType.cs
+++ b/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandOptionType.cs
@@ -56,7 +56,7 @@ namespace Discord
Number = 10,
///
- /// A .
+ /// A .
///
Attachment = 11
}
diff --git a/src/Discord.Net.Core/Entities/Interactions/IDiscordInteraction.cs b/src/Discord.Net.Core/Entities/Interactions/IDiscordInteraction.cs
index 8f6bef995..a2dbe0e5f 100644
--- a/src/Discord.Net.Core/Entities/Interactions/IDiscordInteraction.cs
+++ b/src/Discord.Net.Core/Entities/Interactions/IDiscordInteraction.cs
@@ -52,10 +52,13 @@ namespace Discord
///
/// Gets the preferred locale of the invoking User.
///
+ ///
+ /// This property returns if the interaction is a REST ping interaction.
+ ///
string UserLocale { get; }
///
- /// Gets the preferred locale of the guild this interaction was executed in. if not executed in a guild.
+ /// Gets the preferred locale of the guild this interaction was executed in. if not executed in a guild.
///
///
/// Non-community guilds (With no locale setting available) will have en-US as the default value sent by Discord.
@@ -67,6 +70,27 @@ namespace Discord
///
bool IsDMInteraction { get; }
+ ///
+ /// Gets the ID of the channel this interaction was executed in.
+ ///
+ ///
+ /// This property returns if the interaction is a REST ping interaction.
+ ///
+ ulong? ChannelId { get; }
+
+ ///
+ /// Gets the ID of the guild this interaction was executed in.
+ ///
+ ///
+ /// This property returns if the interaction was not executed in a guild.
+ ///
+ ulong? GuildId { get; }
+
+ ///
+ /// Gets the ID of the application this interaction is for.
+ ///
+ ulong ApplicationId { get; }
+
///
/// Responds to an Interaction with type .
///
diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ComponentBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ComponentBuilder.cs
index 7becca0e0..37342b039 100644
--- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ComponentBuilder.cs
+++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ComponentBuilder.cs
@@ -195,7 +195,7 @@ namespace Discord
///
/// The button to add.
/// The row to add the button.
- /// There is no more row to add a menu.
+ /// There is no more row to add a button.
/// must be less than .
/// The current builder.
public ComponentBuilder WithButton(ButtonBuilder button, int row = 0)
@@ -348,6 +348,100 @@ namespace Discord
return this;
}
+ ///
+ /// Adds a to the .
+ ///
+ /// The custom id of the menu.
+ /// The options of the menu.
+ /// The placeholder of the menu.
+ /// The min values of the placeholder.
+ /// The max values of the placeholder.
+ /// Whether or not the menu is disabled.
+ /// The current builder.
+ public ActionRowBuilder WithSelectMenu(string customId, List options,
+ string placeholder = null, int minValues = 1, int maxValues = 1, bool disabled = false)
+ {
+ return WithSelectMenu(new SelectMenuBuilder()
+ .WithCustomId(customId)
+ .WithOptions(options)
+ .WithPlaceholder(placeholder)
+ .WithMaxValues(maxValues)
+ .WithMinValues(minValues)
+ .WithDisabled(disabled));
+ }
+
+ ///
+ /// Adds a to the .
+ ///
+ /// The menu to add.
+ /// A Select Menu cannot exist in a pre-occupied ActionRow.
+ /// The current builder.
+ public ActionRowBuilder WithSelectMenu(SelectMenuBuilder menu)
+ {
+ if (menu.Options.Distinct().Count() != menu.Options.Count)
+ throw new InvalidOperationException("Please make sure that there is no duplicates values.");
+
+ var builtMenu = menu.Build();
+
+ if (Components.Count != 0)
+ throw new InvalidOperationException($"A Select Menu cannot exist in a pre-occupied ActionRow.");
+
+ AddComponent(builtMenu);
+
+ return this;
+ }
+
+ ///
+ /// Adds a with specified parameters to the .
+ ///
+ /// The label text for the newly added button.
+ /// The style of this newly added button.
+ /// A to be used with this button.
+ /// The custom id of the newly added button.
+ /// A URL to be used only if the is a Link.
+ /// Whether or not the newly created button is disabled.
+ /// The current builder.
+ public ActionRowBuilder WithButton(
+ string label = null,
+ string customId = null,
+ ButtonStyle style = ButtonStyle.Primary,
+ IEmote emote = null,
+ string url = null,
+ bool disabled = false)
+ {
+ var button = new ButtonBuilder()
+ .WithLabel(label)
+ .WithStyle(style)
+ .WithEmote(emote)
+ .WithCustomId(customId)
+ .WithUrl(url)
+ .WithDisabled(disabled);
+
+ return WithButton(button);
+ }
+
+ ///
+ /// Adds a to the .
+ ///
+ /// The button to add.
+ /// Components count reached .
+ /// A button cannot be added to a row with a SelectMenu.
+ /// The current builder.
+ public ActionRowBuilder WithButton(ButtonBuilder button)
+ {
+ var builtButton = button.Build();
+
+ if(Components.Count >= 5)
+ throw new InvalidOperationException($"Components count reached {MaxChildCount}");
+
+ if (Components.Any(x => x.Type == ComponentType.SelectMenu))
+ throw new InvalidOperationException($"A button cannot be added to a row with a SelectMenu");
+
+ AddComponent(builtButton);
+
+ return this;
+ }
+
///
/// Builds the current builder to a that can be used within a
///
@@ -1194,9 +1288,9 @@ namespace Discord
///
/// Gets or sets the default value of the text input.
///
- /// is less than 0.
+ /// .Length is less than 0.
///
- /// is greater than or .
+ /// .Length is greater than or .
///
public string Value
{
@@ -1227,7 +1321,7 @@ namespace Discord
/// The text input's minimum length.
/// The text input's maximum length.
/// The text input's required value.
- public TextInputBuilder (string label, string customId, TextInputStyle style = TextInputStyle.Short, string placeholder = null,
+ public TextInputBuilder(string label, string customId, TextInputStyle style = TextInputStyle.Short, string placeholder = null,
int? minLength = null, int? maxLength = null, bool? required = null, string value = null)
{
Label = label;
@@ -1291,7 +1385,7 @@ namespace Discord
Placeholder = placeholder;
return this;
}
-
+
///
/// Sets the value of the current builder.
///
@@ -1306,18 +1400,18 @@ namespace Discord
///
/// Sets the minimum length of the current builder.
///
- /// The value to set.
+ /// The value to set.
/// The current builder.
public TextInputBuilder WithMinLength(int minLength)
{
MinLength = minLength;
return this;
}
-
+
///
/// Sets the maximum length of the current builder.
///
- /// The value to set.
+ /// The value to set.
/// The current builder.
public TextInputBuilder WithMaxLength(int maxLength)
{
diff --git a/src/Discord.Net.Core/Entities/Interactions/Modals/ModalBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/Modals/ModalBuilder.cs
index 3a3e3cc49..817f69415 100644
--- a/src/Discord.Net.Core/Entities/Interactions/Modals/ModalBuilder.cs
+++ b/src/Discord.Net.Core/Entities/Interactions/Modals/ModalBuilder.cs
@@ -64,18 +64,18 @@ namespace Discord
///
/// Sets the custom id of the current modal.
///
- /// The value to set the custom id to.
+ /// The value to set the custom id to.
/// The current builder.
public ModalBuilder WithCustomId(string customId)
{
CustomId = customId;
return this;
}
-
+
///
/// Adds a component to the current builder.
///
- /// The component to add.
+ /// The component to add.
/// The current builder.
public ModalBuilder AddTextInput(TextInputBuilder component)
{
@@ -213,7 +213,7 @@ namespace Discord
/// Adds a to the at the specific row.
/// If the row cannot accept the component then it will add it to a row that can.
///
- /// The to add.
+ /// The to add.
/// The row to add the text input.
/// There are no more rows to add a text input to.
/// must be less than .
diff --git a/src/Discord.Net.Core/Entities/Interactions/SlashCommands/SlashCommandBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/SlashCommands/SlashCommandBuilder.cs
index bf74a160c..d7d086762 100644
--- a/src/Discord.Net.Core/Entities/Interactions/SlashCommands/SlashCommandBuilder.cs
+++ b/src/Discord.Net.Core/Entities/Interactions/SlashCommands/SlashCommandBuilder.cs
@@ -255,9 +255,6 @@ namespace Discord
if (options == null)
throw new ArgumentNullException(nameof(options), "Options cannot be null!");
- if (options.Length == 0)
- throw new ArgumentException("Options cannot be empty!", nameof(options));
-
Options ??= new List();
if (Options.Count + options.Length > MaxOptionsCount)
@@ -409,7 +406,7 @@ namespace Discord
MinValue = MinValue,
MaxValue = MaxValue
};
- }
+ }
///
/// Adds an option to the current slash command.
@@ -477,6 +474,26 @@ namespace Discord
return this;
}
+ ///
+ /// Adds a collection of options to the current option.
+ ///
+ /// The collection of options to add.
+ /// The current builder.
+ public SlashCommandOptionBuilder AddOptions(params SlashCommandOptionBuilder[] options)
+ {
+ if (options == null)
+ throw new ArgumentNullException(nameof(options), "Options cannot be null!");
+
+ if ((Options?.Count ?? 0) + options.Length > SlashCommandBuilder.MaxOptionsCount)
+ throw new ArgumentOutOfRangeException(nameof(options), $"There can only be {SlashCommandBuilder.MaxOptionsCount} options per sub command group!");
+
+ foreach (var option in options)
+ Preconditions.Options(option.Name, option.Description);
+
+ Options.AddRange(options);
+ return this;
+ }
+
///
/// Adds a choice to the current option.
///
@@ -640,7 +657,7 @@ namespace Discord
MinValue = value;
return this;
}
-
+
///
/// Sets the current builders max value field.
///
diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs b/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs
index 0304120f5..1e2a7b0d7 100644
--- a/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs
+++ b/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Discord.Utils;
+using Newtonsoft.Json;
namespace Discord
{
@@ -155,6 +156,55 @@ namespace Discord
}
}
+ ///
+ /// Tries to parse a string into an .
+ ///
+ /// The json string to parse.
+ /// The with populated values. An empty instance if method returns .
+ /// if was succesfully parsed. if not.
+ public static bool TryParse(string json, out EmbedBuilder builder)
+ {
+ builder = new EmbedBuilder();
+ try
+ {
+ var model = JsonConvert.DeserializeObject
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/Permissions/ChannelPermissions.cs b/src/Discord.Net.Core/Entities/Permissions/ChannelPermissions.cs
index ee5c9984a..3c6a804c5 100644
--- a/src/Discord.Net.Core/Entities/Permissions/ChannelPermissions.cs
+++ b/src/Discord.Net.Core/Entities/Permissions/ChannelPermissions.cs
@@ -7,30 +7,55 @@ namespace Discord
[DebuggerDisplay("{DebuggerDisplay,nq}")]
public struct ChannelPermissions
{
- /// Gets a blank that grants no permissions.
- /// A structure that does not contain any set permissions.
- public static readonly ChannelPermissions None = new ChannelPermissions();
- /// Gets a that grants all permissions for text channels.
- public static readonly ChannelPermissions Text = new ChannelPermissions(0b0_11111_0101100_0000000_1111111110001_010001);
- /// Gets a that grants all permissions for voice channels.
- public static readonly ChannelPermissions Voice = new ChannelPermissions(0b1_00000_0000100_1111110_0000000011100_010001);
- /// Gets a that grants all permissions for stage channels.
- public static readonly ChannelPermissions Stage = new ChannelPermissions(0b0_00000_1000100_0111010_0000000010000_010001);
- /// Gets a that grants all permissions for category channels.
- public static readonly ChannelPermissions Category = new ChannelPermissions(0b01100_1111110_1111111110001_010001);
- /// Gets a that grants all permissions for direct message channels.
- public static readonly ChannelPermissions DM = new ChannelPermissions(0b00000_1000110_1011100110001_000000);
- /// Gets a that grants all permissions for group channels.
- public static readonly ChannelPermissions Group = new ChannelPermissions(0b00000_1000110_0001101100000_000000);
- /// Gets a that grants all permissions for a given channel type.
+ ///
+ /// Gets a blank that grants no permissions.
+ ///
+ ///
+ /// A structure that does not contain any set permissions.
+ ///
+ public static readonly ChannelPermissions None = new();
+
+ ///
+ /// Gets a that grants all permissions for text channels.
+ ///
+ public static readonly ChannelPermissions Text = new(0b0_11111_0101100_0000000_1111111110001_010001);
+
+ ///
+ /// Gets a that grants all permissions for voice channels.
+ ///
+ public static readonly ChannelPermissions Voice = new(0b1_11111_0101100_1111110_1111111111101_010001); // (0b1_00000_0000100_1111110_0000000011100_010001 (<- voice only perms) |= Text)
+
+ ///
+ /// Gets a that grants all permissions for stage channels.
+ ///
+ public static readonly ChannelPermissions Stage = new(0b0_00000_1000100_0111010_0000000010000_010001);
+
+ ///
+ /// Gets a that grants all permissions for category channels.
+ ///
+ public static readonly ChannelPermissions Category = new(0b01100_1111110_1111111110001_010001);
+
+ ///
+ /// Gets a that grants all permissions for direct message channels.
+ ///
+ public static readonly ChannelPermissions DM = new(0b00000_1000110_1011100110001_000000);
+
+ ///
+ /// Gets a that grants all permissions for group channels.
+ ///
+ public static readonly ChannelPermissions Group = new(0b00000_1000110_0001101100000_000000);
+
+ ///
+ /// Gets a that grants all permissions for a given channel type.
+ ///
/// Unknown channel type.
public static ChannelPermissions All(IChannel channel)
{
return channel switch
{
- ITextChannel _ => Text,
IStageChannel _ => Stage,
IVoiceChannel _ => Voice,
+ ITextChannel _ => Text,
ICategoryChannel _ => Category,
IDMChannel _ => DM,
IGroupChannel _ => Group,
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/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/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/SlashCommandBuilder.cs b/src/Discord.Net.Interactions/Builders/Commands/SlashCommandBuilder.cs
index cd9bdfc24..c21fd5ae8 100644
--- a/src/Discord.Net.Interactions/Builders/Commands/SlashCommandBuilder.cs
+++ b/src/Discord.Net.Interactions/Builders/Commands/SlashCommandBuilder.cs
@@ -56,7 +56,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/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 b7f00025f..0eb91ee6a 100644
--- a/src/Discord.Net.Interactions/Builders/ModuleBuilder.cs
+++ b/src/Discord.Net.Interactions/Builders/ModuleBuilder.cs
@@ -357,7 +357,8 @@ namespace Discord.Interactions.Builders
return this;
}
-
+
+ ///
/// Adds a modal command builder to .
///
/// factory.
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/ModuleInfo.cs b/src/Discord.Net.Interactions/Info/ModuleInfo.cs
index 904d67410..4f40f1607 100644
--- a/src/Discord.Net.Interactions/Info/ModuleInfo.cs
+++ b/src/Discord.Net.Interactions/Info/ModuleInfo.cs
@@ -248,7 +248,7 @@ namespace Discord.Interactions
while (parent != null)
{
- permissions = (permissions ?? 0) | (parent.DefaultMemberPermissions ?? 0);
+ permissions = (permissions ?? 0) | (parent.DefaultMemberPermissions ?? 0).SanitizeGuildPermissions();
parent = parent.Parent;
}
diff --git a/src/Discord.Net.Interactions/InteractionContext.cs b/src/Discord.Net.Interactions/InteractionContext.cs
index 024ab5ef8..b81cc5938 100644
--- a/src/Discord.Net.Interactions/InteractionContext.cs
+++ b/src/Discord.Net.Interactions/InteractionContext.cs
@@ -24,8 +24,7 @@ namespace Discord.Interactions
///
/// 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;
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 8eb5799d6..793d89cdc 100644
--- a/src/Discord.Net.Interactions/InteractionService.cs
+++ b/src/Discord.Net.Interactions/InteractionService.cs
@@ -223,7 +223,8 @@ namespace Discord.Interactions
new ConcurrentDictionary
{
[typeof(Array)] = typeof(DefaultArrayComponentConverter<>),
- [typeof(IConvertible)] = typeof(DefaultValueComponentConverter<>)
+ [typeof(IConvertible)] = typeof(DefaultValueComponentConverter<>),
+ [typeof(Nullable<>)] = typeof(NullableComponentConverter<>)
});
_typeReaderMap = new TypeMap(this, new ConcurrentDictionary(),
@@ -234,7 +235,8 @@ namespace Discord.Interactions
[typeof(IUser)] = typeof(DefaultUserReader<>),
[typeof(IMessage)] = typeof(DefaultMessageReader<>),
[typeof(IConvertible)] = typeof(DefaultValueReader<>),
- [typeof(Enum)] = typeof(EnumReader<>)
+ [typeof(Enum)] = typeof(EnumReader<>),
+ [typeof(Nullable<>)] = typeof(NullableReader<>)
});
}
@@ -421,20 +423,39 @@ namespace Discord.Interactions
///
///
/// Commands will be registered as standalone commands, if you want the to take effect,
- /// use . Registering a commands without group names might cause the command traversal to fail.
+ /// use . Registering a commands without group names might cause the command traversal to fail.
///
/// The target guild.
+ /// If , this operation will not delete the commands that are missing from .
/// Commands to be registered to Discord.
///
/// A task representing the command registration process. The task result contains the active application commands of the target guild.
///
public async Task> AddCommandsToGuildAsync(IGuild guild, bool deleteMissing = false, params ICommandInfo[] commands)
{
- EnsureClientReady();
-
if (guild is null)
throw new ArgumentNullException(nameof(guild));
+ return await AddCommandsToGuildAsync(guild.Id, deleteMissing, commands).ConfigureAwait(false);
+ }
+
+ ///
+ /// Register Application Commands from to a guild.
+ ///
+ ///
+ /// Commands will be registered as standalone commands, if you want the to take effect,
+ /// use . Registering a commands without group names might cause the command traversal to fail.
+ ///
+ /// The target guild ID.
+ /// If , this operation will not delete the commands that are missing from .
+ /// Commands to be registered to Discord.
+ ///
+ /// A task representing the command registration process. The task result contains the active application commands of the target guild.
+ ///
+ public async Task> AddCommandsToGuildAsync(ulong guildId, bool deleteMissing = false, params ICommandInfo[] commands)
+ {
+ EnsureClientReady();
+
var props = new List();
foreach (var command in commands)
@@ -454,44 +475,60 @@ namespace Discord.Interactions
if (!deleteMissing)
{
- var existing = await RestClient.GetGuildApplicationCommands(guild.Id).ConfigureAwait(false);
+ var existing = await RestClient.GetGuildApplicationCommands(guildId).ConfigureAwait(false);
var missing = existing.Where(x => !props.Any(y => y.Name.IsSpecified && y.Name.Value == x.Name));
props.AddRange(missing.Select(x => x.ToApplicationCommandProps()));
}
- return await RestClient.BulkOverwriteGuildCommands(props.ToArray(), guild.Id).ConfigureAwait(false);
+ return await RestClient.BulkOverwriteGuildCommands(props.ToArray(), guildId).ConfigureAwait(false);
}
///
/// Register Application Commands from modules provided in to a guild.
///
/// The target guild.
+ /// If , this operation will not delete the commands that are missing from .
/// Modules to be registered to Discord.
///
/// A task representing the command registration process. The task result contains the active application commands of the target guild.
///
public async Task> AddModulesToGuildAsync(IGuild guild, bool deleteMissing = false, params ModuleInfo[] modules)
{
- EnsureClientReady();
-
if (guild is null)
throw new ArgumentNullException(nameof(guild));
+ return await AddModulesToGuildAsync(guild.Id, deleteMissing, modules).ConfigureAwait(false);
+ }
+
+ ///
+ /// Register Application Commands from modules provided in to a guild.
+ ///
+ /// The target guild ID.
+ /// If , this operation will not delete the commands that are missing from .
+ /// Modules to be registered to Discord.
+ ///
+ /// A task representing the command registration process. The task result contains the active application commands of the target guild.
+ ///
+ public async Task> AddModulesToGuildAsync(ulong guildId, bool deleteMissing = false, params ModuleInfo[] modules)
+ {
+ EnsureClientReady();
+
var props = modules.SelectMany(x => x.ToApplicationCommandProps(true)).ToList();
if (!deleteMissing)
{
- var existing = await RestClient.GetGuildApplicationCommands(guild.Id).ConfigureAwait(false);
+ var existing = await RestClient.GetGuildApplicationCommands(guildId).ConfigureAwait(false);
var missing = existing.Where(x => !props.Any(y => y.Name.IsSpecified && y.Name.Value == x.Name));
props.AddRange(missing.Select(x => x.ToApplicationCommandProps()));
}
- return await RestClient.BulkOverwriteGuildCommands(props.ToArray(), guild.Id).ConfigureAwait(false);
+ return await RestClient.BulkOverwriteGuildCommands(props.ToArray(), guildId).ConfigureAwait(false);
}
///
/// Register Application Commands from modules provided in as global commands.
///
+ /// If , this operation will not delete the commands that are missing from .
/// Modules to be registered to Discord.
///
/// A task representing the command registration process. The task result contains the active application commands of the target guild.
@@ -517,8 +554,9 @@ namespace Discord.Interactions
///
///
/// Commands will be registered as standalone commands, if you want the to take effect,
- /// use . Registering a commands without group names might cause the command traversal to fail.
+ /// use . Registering a commands without group names might cause the command traversal to fail.
///
+ /// If , this operation will not delete the commands that are missing from .
/// Commands to be registered to Discord.
///
/// A task representing the command registration process. The task result contains the active application commands of the target guild.
@@ -834,11 +872,16 @@ namespace Discord.Interactions
if (!searchResult.Command.SupportsWildCards || context is not IRouteMatchContainer matchContainer)
return;
- var matches = new RouteSegmentMatch[searchResult.RegexCaptureGroups.Length];
- for (var i = 0; i < searchResult.RegexCaptureGroups.Length; i++)
- matches[i] = new RouteSegmentMatch(searchResult.RegexCaptureGroups[i]);
+ 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);
+ matchContainer.SetSegmentMatches(matches);
+ }
+ else
+ matchContainer.SetSegmentMatches(Array.Empty());
}
internal TypeConverter GetTypeConverter(Type type, IServiceProvider services = null)
@@ -960,7 +1003,7 @@ namespace Discord.Interactions
/// Removes a type reader for the given type.
///
///
- /// Removing a from the will not dereference the from the loaded module/command instances.
+ /// Removing a from the will not dereference the from the loaded module/command instances.
/// You need to reload the modules for the changes to take effect.
///
/// The type to remove the reader from.
@@ -973,7 +1016,7 @@ namespace Discord.Interactions
/// Removes a generic type reader from the type .
///
///
- /// Removing a from the will not dereference the from the loaded module/command instances.
+ /// Removing a from the will not dereference the from the loaded module/command instances.
/// You need to reload the modules for the changes to take effect.
///
/// The type to remove the readers from.
@@ -986,7 +1029,7 @@ namespace Discord.Interactions
/// Removes a generic type reader from the given type.
///
///
- /// Removing a from the will not dereference the from the loaded module/command instances.
+ /// Removing a from the will not dereference the from the loaded module/command instances.
/// You need to reload the modules for the changes to take effect.
///
/// The type to remove the reader from.
@@ -999,7 +1042,7 @@ namespace Discord.Interactions
/// Serialize an object using a into a to be placed in a Component CustomId.
///
///
- /// Removing a from the will not dereference the from the loaded module/command instances.
+ /// Removing a from the will not dereference the from the loaded module/command instances.
/// You need to reload the modules for the changes to take effect.
///
/// Type of the object to be serialized.
@@ -1079,19 +1122,40 @@ namespace Discord.Interactions
///
/// The active command permissions after the modification.
///
- public async Task ModifySlashCommandPermissionsAsync (ModuleInfo module, IGuild guild,
+ public async Task ModifySlashCommandPermissionsAsync(ModuleInfo module, IGuild guild,
+ params ApplicationCommandPermission[] permissions)
+ {
+ if (module is null)
+ throw new ArgumentNullException(nameof(module));
+
+ if (guild is null)
+ throw new ArgumentNullException(nameof(guild));
+
+ return await ModifySlashCommandPermissionsAsync(module, guild.Id, permissions).ConfigureAwait(false);
+ }
+
+ ///
+ /// Modify the command permissions of the matching Discord Slash Command.
+ ///
+ /// Module representing the top level Slash Command.
+ /// Target guild ID.
+ /// New permission values.
+ ///
+ /// The active command permissions after the modification.
+ ///
+ public async Task ModifySlashCommandPermissionsAsync(ModuleInfo module, ulong guildId,
params ApplicationCommandPermission[] permissions)
{
+ if (module is null)
+ throw new ArgumentNullException(nameof(module));
+
if (!module.IsSlashGroup)
throw new InvalidOperationException($"This module does not have a {nameof(GroupAttribute)} and does not represent an Application Command");
if (!module.IsTopLevelGroup)
throw new InvalidOperationException("This module is not a top level application command. You cannot change its permissions");
- if (guild is null)
- throw new ArgumentNullException("guild");
-
- var commands = await RestClient.GetGuildApplicationCommands(guild.Id).ConfigureAwait(false);
+ var commands = await RestClient.GetGuildApplicationCommands(guildId).ConfigureAwait(false);
var appCommand = commands.First(x => x.Name == module.SlashGroupName);
return await appCommand.ModifyCommandPermissions(permissions).ConfigureAwait(false);
@@ -1106,9 +1170,29 @@ namespace Discord.Interactions
///
/// The active command permissions after the modification.
///
- public async Task ModifySlashCommandPermissionsAsync (SlashCommandInfo command, IGuild guild,
- params ApplicationCommandPermission[] permissions) =>
- await ModifyApplicationCommandPermissionsAsync(command, guild, permissions).ConfigureAwait(false);
+ public async Task ModifySlashCommandPermissionsAsync(SlashCommandInfo command, IGuild guild,
+ params ApplicationCommandPermission[] permissions)
+ {
+ if (command is null)
+ throw new ArgumentNullException(nameof(command));
+
+ if (guild is null)
+ throw new ArgumentNullException(nameof(guild));
+
+ return await ModifyApplicationCommandPermissionsAsync(command, guild.Id, permissions).ConfigureAwait(false);
+ }
+
+ ///
+ /// Modify the command permissions of the matching Discord Slash Command.
+ ///
+ /// The Slash Command.
+ /// Target guild ID.
+ /// New permission values.
+ ///
+ /// The active command permissions after the modification.
+ ///
+ public async Task ModifySlashCommandPermissionsAsync(SlashCommandInfo command, ulong guildId,
+ params ApplicationCommandPermission[] permissions) => await ModifyApplicationCommandPermissionsAsync(command, guildId, permissions).ConfigureAwait(false);
///
/// Modify the command permissions of the matching Discord Slash Command.
@@ -1119,20 +1203,40 @@ namespace Discord.Interactions
///
/// The active command permissions after the modification.
///
- public async Task ModifyContextCommandPermissionsAsync (ContextCommandInfo command, IGuild guild,
- params ApplicationCommandPermission[] permissions) =>
- await ModifyApplicationCommandPermissionsAsync(command, guild, permissions).ConfigureAwait(false);
+ public async Task ModifyContextCommandPermissionsAsync(ContextCommandInfo command, IGuild guild,
+ params ApplicationCommandPermission[] permissions)
+ {
+ if (command is null)
+ throw new ArgumentNullException(nameof(command));
+
+ if (guild is null)
+ throw new ArgumentNullException(nameof(guild));
+
+ return await ModifyApplicationCommandPermissionsAsync(command, guild.Id, permissions).ConfigureAwait(false);
+ }
- private async Task ModifyApplicationCommandPermissionsAsync (T command, IGuild guild,
+ ///
+ /// Modify the command permissions of the matching Discord Slash Command.
+ ///
+ /// The Context Command.
+ /// Target guild ID.
+ /// New permission values.
+ ///
+ /// The active command permissions after the modification.
+ ///
+ public async Task ModifyContextCommandPermissionsAsync(ContextCommandInfo command, ulong guildId,
+ params ApplicationCommandPermission[] permissions) => await ModifyApplicationCommandPermissionsAsync(command, guildId, permissions).ConfigureAwait(false);
+
+ private async Task ModifyApplicationCommandPermissionsAsync (T command, ulong guildId,
params ApplicationCommandPermission[] permissions) where T : class, IApplicationCommandInfo, ICommandInfo
{
+ if (command is null)
+ throw new ArgumentNullException(nameof(command));
+
if (!command.IsTopLevelCommand)
throw new InvalidOperationException("This command is not a top level application command. You cannot change its permissions");
- if (guild is null)
- throw new ArgumentNullException("guild");
-
- var commands = await RestClient.GetGuildApplicationCommands(guild.Id).ConfigureAwait(false);
+ var commands = await RestClient.GetGuildApplicationCommands(guildId).ConfigureAwait(false);
var appCommand = commands.First(x => x.Name == ( command as IApplicationCommandInfo ).Name);
return await appCommand.ModifyCommandPermissions(permissions).ConfigureAwait(false);
diff --git a/src/Discord.Net.Interactions/RestInteractionModuleBase.cs b/src/Discord.Net.Interactions/RestInteractionModuleBase.cs
index e83c91fef..b570e6d84 100644
--- a/src/Discord.Net.Interactions/RestInteractionModuleBase.cs
+++ b/src/Discord.Net.Interactions/RestInteractionModuleBase.cs
@@ -87,12 +87,12 @@ namespace Discord.Interactions
await InteractionService._restResponseCallback(Context, payload).ConfigureAwait(false);
}
- protected override async Task RespondWithModalAsync(string customId, RequestOptions options = null)
+ protected override async Task RespondWithModalAsync(string customId, RequestOptions options = null)
{
if (Context.Interaction is not RestInteraction restInteraction)
throw new InvalidOperationException($"Invalid interaction type. Interaction must be a type of {nameof(RestInteraction)} in order to execute this method");
- var payload = restInteraction.RespondWithModal(customId, options);
+ var payload = restInteraction.RespondWithModal(customId, options);
if (Context is IRestInteractionContext restContext && restContext.InteractionResponseCallback != null)
await restContext.InteractionResponseCallback.Invoke(payload).ConfigureAwait(false);
diff --git a/src/Discord.Net.Interactions/Results/TypeConverterResult.cs b/src/Discord.Net.Interactions/Results/TypeConverterResult.cs
index bd89bf6b7..a9a12ee33 100644
--- a/src/Discord.Net.Interactions/Results/TypeConverterResult.cs
+++ b/src/Discord.Net.Interactions/Results/TypeConverterResult.cs
@@ -3,7 +3,7 @@ using System;
namespace Discord.Interactions
{
///
- /// Represents a result type for .
+ /// Represents a result type for .
///
public struct TypeConverterResult : IResult
{
diff --git a/src/Discord.Net.Interactions/TypeConverters/ComponentInteractions/NullableComponentConverter.cs b/src/Discord.Net.Interactions/TypeConverters/ComponentInteractions/NullableComponentConverter.cs
new file mode 100644
index 000000000..ba6568ad1
--- /dev/null
+++ b/src/Discord.Net.Interactions/TypeConverters/ComponentInteractions/NullableComponentConverter.cs
@@ -0,0 +1,23 @@
+using System;
+using System.Threading.Tasks;
+
+namespace Discord.Interactions
+{
+ internal class NullableComponentConverter : ComponentTypeConverter
+ {
+ private readonly ComponentTypeConverter _typeConverter;
+
+ public NullableComponentConverter(InteractionService interactionService, IServiceProvider services)
+ {
+ var type = Nullable.GetUnderlyingType(typeof(T));
+
+ if (type is null)
+ throw new ArgumentException($"No type {nameof(TypeConverter)} is defined for this {type.FullName}", "type");
+
+ _typeConverter = interactionService.GetComponentTypeConverter(type, services);
+ }
+
+ public override Task ReadAsync(IInteractionContext context, IComponentInteractionData option, IServiceProvider services)
+ => string.IsNullOrEmpty(option.Value) ? Task.FromResult(TypeConverterResult.FromSuccess(null)) : _typeConverter.ReadAsync(context, option, services);
+ }
+}
diff --git a/src/Discord.Net.Interactions/TypeReaders/NullableReader.cs b/src/Discord.Net.Interactions/TypeReaders/NullableReader.cs
new file mode 100644
index 000000000..ed88dc64a
--- /dev/null
+++ b/src/Discord.Net.Interactions/TypeReaders/NullableReader.cs
@@ -0,0 +1,23 @@
+using System;
+using System.Threading.Tasks;
+
+namespace Discord.Interactions
+{
+ internal class NullableReader : TypeReader
+ {
+ private readonly TypeReader _typeReader;
+
+ public NullableReader(InteractionService interactionService, IServiceProvider services)
+ {
+ var type = Nullable.GetUnderlyingType(typeof(T));
+
+ if (type is null)
+ throw new ArgumentException($"No type {nameof(TypeConverter)} is defined for this {type.FullName}", "type");
+
+ _typeReader = interactionService.GetTypeReader(type, services);
+ }
+
+ public override Task ReadAsync(IInteractionContext context, string option, IServiceProvider services)
+ => string.IsNullOrEmpty(option) ? Task.FromResult(TypeConverterResult.FromSuccess(null)) : _typeReader.ReadAsync(context, option, services);
+ }
+}
diff --git a/src/Discord.Net.Interactions/Utilities/ApplicationCommandRestUtil.cs b/src/Discord.Net.Interactions/Utilities/ApplicationCommandRestUtil.cs
index 60980c065..e4b6f893c 100644
--- a/src/Discord.Net.Interactions/Utilities/ApplicationCommandRestUtil.cs
+++ b/src/Discord.Net.Interactions/Utilities/ApplicationCommandRestUtil.cs
@@ -41,7 +41,7 @@ namespace Discord.Interactions
Name = commandInfo.Name,
Description = commandInfo.Description,
IsDMEnabled = commandInfo.IsEnabledInDm,
- DefaultMemberPermissions = (commandInfo.DefaultMemberPermissions ?? 0) | (commandInfo.Module.DefaultMemberPermissions ?? 0)
+ DefaultMemberPermissions = ((commandInfo.DefaultMemberPermissions ?? 0) | (commandInfo.Module.DefaultMemberPermissions ?? 0)).SanitizeGuildPermissions(),
}.Build();
if (commandInfo.Parameters.Count > SlashCommandBuilder.MaxOptionsCount)
@@ -69,14 +69,14 @@ namespace Discord.Interactions
{
Name = commandInfo.Name,
IsDefaultPermission = commandInfo.DefaultPermission,
- DefaultMemberPermissions = (commandInfo.DefaultMemberPermissions ?? 0) | (commandInfo.Module.DefaultMemberPermissions ?? 0),
+ 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),
+ DefaultMemberPermissions = ((commandInfo.DefaultMemberPermissions ?? 0) | (commandInfo.Module.DefaultMemberPermissions ?? 0)).SanitizeGuildPermissions(),
IsDMEnabled = commandInfo.IsEnabledInDm
}.Build(),
_ => throw new InvalidOperationException($"{commandInfo.CommandType} isn't a supported command type.")
@@ -232,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/Channel.cs b/src/Discord.Net.Rest/API/Common/Channel.cs
index d565b269a..d9d7d469c 100644
--- a/src/Discord.Net.Rest/API/Common/Channel.cs
+++ b/src/Discord.Net.Rest/API/Common/Channel.cs
@@ -66,5 +66,12 @@ namespace Discord.API
[JsonProperty("member_count")]
public Optional MemberCount { get; set; }
+
+ //ForumChannel
+ [JsonProperty("available_tags")]
+ public Optional ForumTags { get; set; }
+
+ [JsonProperty("default_auto_archive_duration")]
+ public Optional AutoArchiveDuration { get; set; }
}
}
diff --git a/src/Discord.Net.Rest/API/Common/ChannelThreads.cs b/src/Discord.Net.Rest/API/Common/ChannelThreads.cs
index 94b2396bf..9fa3e38ce 100644
--- a/src/Discord.Net.Rest/API/Common/ChannelThreads.cs
+++ b/src/Discord.Net.Rest/API/Common/ChannelThreads.cs
@@ -9,8 +9,5 @@ namespace Discord.API.Rest
[JsonProperty("members")]
public ThreadMember[] Members { get; set; }
-
- [JsonProperty("has_more")]
- public bool HasMore { get; set; }
}
}
diff --git a/src/Discord.Net.Rest/API/Common/ForumTags.cs b/src/Discord.Net.Rest/API/Common/ForumTags.cs
new file mode 100644
index 000000000..18354e7b2
--- /dev/null
+++ b/src/Discord.Net.Rest/API/Common/ForumTags.cs
@@ -0,0 +1,21 @@
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Discord.API
+{
+ internal class ForumTags
+ {
+ [JsonProperty("id")]
+ public ulong Id { get; set; }
+ [JsonProperty("name")]
+ public string Name { get; set; }
+ [JsonProperty("emoji_id")]
+ public Optional EmojiId { get; set; }
+ [JsonProperty("emoji_name")]
+ public Optional EmojiName { get; set; }
+ }
+}
diff --git a/src/Discord.Net.Rest/API/Common/ForumThreadMessage.cs b/src/Discord.Net.Rest/API/Common/ForumThreadMessage.cs
new file mode 100644
index 000000000..132e38e5f
--- /dev/null
+++ b/src/Discord.Net.Rest/API/Common/ForumThreadMessage.cs
@@ -0,0 +1,33 @@
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Discord.API
+{
+ internal class ForumThreadMessage
+ {
+ [JsonProperty("content")]
+ public Optional Content { get; set; }
+
+ [JsonProperty("nonce")]
+ public Optional Nonce { get; set; }
+
+ [JsonProperty("embeds")]
+ public Optional Embeds { get; set; }
+
+ [JsonProperty("allowed_mentions")]
+ public Optional AllowedMentions { get; set; }
+
+ [JsonProperty("components")]
+ public Optional Components { get; set; }
+
+ [JsonProperty("sticker_ids")]
+ public Optional Stickers { get; set; }
+
+ [JsonProperty("flags")]
+ public Optional Flags { get; set; }
+ }
+}
diff --git a/src/Discord.Net.Rest/API/Common/MessageReference.cs b/src/Discord.Net.Rest/API/Common/MessageReference.cs
index 6cc7603e0..70ef4e678 100644
--- a/src/Discord.Net.Rest/API/Common/MessageReference.cs
+++ b/src/Discord.Net.Rest/API/Common/MessageReference.cs
@@ -12,5 +12,8 @@ namespace Discord.API
[JsonProperty("guild_id")]
public Optional GuildId { get; set; }
+
+ [JsonProperty("fail_if_not_exists")]
+ public Optional FailIfNotExists { get; set; }
}
}
diff --git a/src/Discord.Net.Rest/API/Rest/CreateMultipartPostAsync.cs b/src/Discord.Net.Rest/API/Rest/CreateMultipartPostAsync.cs
new file mode 100644
index 000000000..0c8bc5494
--- /dev/null
+++ b/src/Discord.Net.Rest/API/Rest/CreateMultipartPostAsync.cs
@@ -0,0 +1,96 @@
+using Discord.Net.Converters;
+using Discord.Net.Rest;
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Discord.API.Rest
+{
+ internal class CreateMultipartPostAsync
+ {
+ private static JsonSerializer _serializer = new JsonSerializer { ContractResolver = new DiscordContractResolver() };
+
+ public FileAttachment[] Files { get; }
+
+ public string Title { get; set; }
+ public ThreadArchiveDuration ArchiveDuration { get; set; }
+ public Optional Slowmode { get; set; }
+
+
+ public Optional Content { get; set; }
+ public Optional Embeds { get; set; }
+ public Optional AllowedMentions { get; set; }
+ public Optional MessageComponent { get; set; }
+ public Optional Flags { get; set; }
+ public Optional Stickers { get; set; }
+
+ public CreateMultipartPostAsync(params FileAttachment[] attachments)
+ {
+ Files = attachments;
+ }
+
+ public IReadOnlyDictionary ToDictionary()
+ {
+ var d = new Dictionary();
+
+ var payload = new Dictionary();
+ var message = new Dictionary();
+
+ payload["name"] = Title;
+ payload["auto_archive_duration"] = ArchiveDuration;
+
+ if (Slowmode.IsSpecified)
+ payload["rate_limit_per_user"] = Slowmode.Value;
+
+ // message
+ if (Content.IsSpecified)
+ message["content"] = Content.Value;
+ if (Embeds.IsSpecified)
+ message["embeds"] = Embeds.Value;
+ if (AllowedMentions.IsSpecified)
+ message["allowed_mentions"] = AllowedMentions.Value;
+ if (MessageComponent.IsSpecified)
+ message["components"] = MessageComponent.Value;
+ if (Stickers.IsSpecified)
+ message["sticker_ids"] = Stickers.Value;
+ if (Flags.IsSpecified)
+ message["flags"] = Flags.Value;
+
+ List
/// The configuration to be used with the client.
- public DiscordRestClient(DiscordRestConfig config) : base(config, CreateApiClient(config)) { }
+ public DiscordRestClient(DiscordRestConfig config) : base(config, CreateApiClient(config))
+ {
+ _apiOnCreation = config.APIOnRestInteractionCreation;
+ }
// used for socket client rest access
- internal DiscordRestClient(DiscordRestConfig config, API.DiscordRestApiClient api) : base(config, api) { }
+ internal DiscordRestClient(DiscordRestConfig config, API.DiscordRestApiClient api) : base(config, api)
+ {
+ _apiOnCreation = config.APIOnRestInteractionCreation;
+ }
private static API.DiscordRestApiClient CreateApiClient(DiscordRestConfig config)
=> new API.DiscordRestApiClient(config.RestClientProvider, DiscordRestConfig.UserAgent, serializer: Serializer, useSystemClock: config.UseSystemClock, defaultRatelimitCallback: config.DefaultRatelimitCallback);
@@ -82,6 +88,8 @@ namespace Discord.Rest
#region Rest interactions
+ private readonly bool _apiOnCreation;
+
public bool IsValidHttpInteraction(string publicKey, string signature, string timestamp, string body)
=> IsValidHttpInteraction(publicKey, signature, timestamp, Encoding.UTF8.GetBytes(body));
public bool IsValidHttpInteraction(string publicKey, string signature, string timestamp, byte[] body)
@@ -113,8 +121,8 @@ namespace Discord.Rest
/// A that represents the incoming http interaction.
///
/// Thrown when the signature doesn't match the public key.
- public Task ParseHttpInteractionAsync(string publicKey, string signature, string timestamp, string body)
- => ParseHttpInteractionAsync(publicKey, signature, timestamp, Encoding.UTF8.GetBytes(body));
+ public Task ParseHttpInteractionAsync(string publicKey, string signature, string timestamp, string body, Func doApiCallOnCreation = null)
+ => ParseHttpInteractionAsync(publicKey, signature, timestamp, Encoding.UTF8.GetBytes(body), doApiCallOnCreation);
///
/// Creates a from a http message.
@@ -127,7 +135,7 @@ namespace Discord.Rest
/// A that represents the incoming http interaction.
///
/// Thrown when the signature doesn't match the public key.
- public async Task ParseHttpInteractionAsync(string publicKey, string signature, string timestamp, byte[] body)
+ public async Task ParseHttpInteractionAsync(string publicKey, string signature, string timestamp, byte[] body, Func doApiCallOnCreation = null)
{
if (!IsValidHttpInteraction(publicKey, signature, timestamp, body))
{
@@ -138,12 +146,12 @@ namespace Discord.Rest
using (var jsonReader = new JsonTextReader(textReader))
{
var model = Serializer.Deserialize(jsonReader);
- return await RestInteraction.CreateAsync(this, model);
+ return await RestInteraction.CreateAsync(this, model, doApiCallOnCreation is not null ? doApiCallOnCreation(new InteractionProperties(model)) : _apiOnCreation);
}
}
#endregion
-
+
public async Task GetApplicationInfoAsync(RequestOptions options = null)
{
return _applicationInfo ??= await ClientHelper.GetApplicationInfoAsync(this, options).ConfigureAwait(false);
diff --git a/src/Discord.Net.Rest/DiscordRestConfig.cs b/src/Discord.Net.Rest/DiscordRestConfig.cs
index 7bf7440ce..a09d9ee98 100644
--- a/src/Discord.Net.Rest/DiscordRestConfig.cs
+++ b/src/Discord.Net.Rest/DiscordRestConfig.cs
@@ -9,5 +9,7 @@ namespace Discord.Rest
{
/// Gets or sets the provider used to generate new REST connections.
public RestClientProvider RestClientProvider { get; set; } = DefaultRestClientProvider.Instance;
+
+ public bool APIOnRestInteractionCreation { get; set; } = true;
}
}
diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/BanAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/BanAuditLogData.cs
index fc807cac0..7246ac197 100644
--- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/BanAuditLogData.cs
+++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/BanAuditLogData.cs
@@ -18,12 +18,15 @@ namespace Discord.Rest
internal static BanAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry)
{
var userInfo = log.Users.FirstOrDefault(x => x.Id == entry.TargetId);
- return new BanAuditLogData(RestUser.Create(discord, userInfo));
+ return new BanAuditLogData((userInfo != null) ? RestUser.Create(discord, userInfo) : null);
}
///
/// Gets the user that was banned.
///
+ ///
+ /// Will be if the user is a 'Deleted User#....' because Discord does send user data for deleted users.
+ ///
///
/// A user object representing the banned user.
///
diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/BotAddAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/BotAddAuditLogData.cs
index 0d12e4609..288cb9d0a 100644
--- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/BotAddAuditLogData.cs
+++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/BotAddAuditLogData.cs
@@ -18,12 +18,15 @@ namespace Discord.Rest
internal static BotAddAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry)
{
var userInfo = log.Users.FirstOrDefault(x => x.Id == entry.TargetId);
- return new BotAddAuditLogData(RestUser.Create(discord, userInfo));
+ return new BotAddAuditLogData((userInfo != null) ? RestUser.Create(discord, userInfo) : null);
}
///
/// Gets the bot that was added.
///
+ ///
+ /// Will be if the bot is a 'Deleted User#....' because Discord does send user data for deleted users.
+ ///
///
/// A user object representing the bot.
///
diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteCreateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteCreateAuditLogData.cs
index b177b2435..3560b9a27 100644
--- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteCreateAuditLogData.cs
+++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteCreateAuditLogData.cs
@@ -45,7 +45,7 @@ namespace Discord.Rest
{
var inviterId = inviterIdModel.NewValue.ToObject(discord.ApiClient.Serializer);
var inviterInfo = log.Users.FirstOrDefault(x => x.Id == inviterId);
- inviter = RestUser.Create(discord, inviterInfo);
+ inviter = (inviterInfo != null) ? RestUser.Create(discord, inviterInfo) : null;
}
return new InviteCreateAuditLogData(maxAge, code, temporary, inviter, channelId, uses, maxUses);
@@ -76,6 +76,9 @@ namespace Discord.Rest
///
/// Gets the user that created this invite if available.
///
+ ///
+ /// Will be if the user is a 'Deleted User#....' because Discord does send user data for deleted users.
+ ///
///
/// A user that created this invite or .
///
diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteDeleteAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteDeleteAuditLogData.cs
index 9d0aed12b..2dc2f22f6 100644
--- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteDeleteAuditLogData.cs
+++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteDeleteAuditLogData.cs
@@ -45,7 +45,7 @@ namespace Discord.Rest
{
var inviterId = inviterIdModel.OldValue.ToObject(discord.ApiClient.Serializer);
var inviterInfo = log.Users.FirstOrDefault(x => x.Id == inviterId);
- inviter = RestUser.Create(discord, inviterInfo);
+ inviter = (inviterInfo != null) ? RestUser.Create(discord, inviterInfo) : null;
}
return new InviteDeleteAuditLogData(maxAge, code, temporary, inviter, channelId, uses, maxUses);
@@ -76,6 +76,9 @@ namespace Discord.Rest
///
/// Gets the user that created this invite if available.
///
+ ///
+ /// Will be if the user is a 'Deleted User#....' because Discord does send user data for deleted users.
+ ///
///
/// A user that created this invite or .
///
diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/KickAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/KickAuditLogData.cs
index dceb73d0a..b533f0268 100644
--- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/KickAuditLogData.cs
+++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/KickAuditLogData.cs
@@ -1,4 +1,4 @@
-using System.Linq;
+using System.Linq;
using Model = Discord.API.AuditLog;
using EntryModel = Discord.API.AuditLogEntry;
@@ -18,12 +18,15 @@ namespace Discord.Rest
internal static KickAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry)
{
var userInfo = log.Users.FirstOrDefault(x => x.Id == entry.TargetId);
- return new KickAuditLogData(RestUser.Create(discord, userInfo));
+ return new KickAuditLogData((userInfo != null) ? RestUser.Create(discord, userInfo) : null);
}
///
/// Gets the user that was kicked.
///
+ ///
+ /// Will be if the user is a 'Deleted User#....' because Discord does send user data for deleted users.
+ ///
///
/// A user object representing the kicked user.
///
diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberRoleAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberRoleAuditLogData.cs
index 763c90c68..276604d03 100644
--- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberRoleAuditLogData.cs
+++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberRoleAuditLogData.cs
@@ -27,7 +27,7 @@ namespace Discord.Rest
.ToList();
var userInfo = log.Users.FirstOrDefault(x => x.Id == entry.TargetId);
- var user = RestUser.Create(discord, userInfo);
+ RestUser user = (userInfo != null) ? RestUser.Create(discord, userInfo) : null;
return new MemberRoleAuditLogData(roleInfos.ToReadOnlyCollection(), user);
}
diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberUpdateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberUpdateAuditLogData.cs
index f22b83e4c..f3437e621 100644
--- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberUpdateAuditLogData.cs
+++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberUpdateAuditLogData.cs
@@ -33,7 +33,7 @@ namespace Discord.Rest
newMute = muteModel?.NewValue?.ToObject(discord.ApiClient.Serializer);
var targetInfo = log.Users.FirstOrDefault(x => x.Id == entry.TargetId);
- var user = RestUser.Create(discord, targetInfo);
+ RestUser user = (targetInfo != null) ? RestUser.Create(discord, targetInfo) : null;
var before = new MemberInfo(oldNick, oldDeaf, oldMute);
var after = new MemberInfo(newNick, newDeaf, newMute);
@@ -44,6 +44,9 @@ namespace Discord.Rest
///
/// Gets the user that the changes were performed on.
///
+ ///
+ /// Will be if the user is a 'Deleted User#....' because Discord does send user data for deleted users.
+ ///
///
/// A user object representing the user who the changes were performed on.
///
diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MessageDeleteAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MessageDeleteAuditLogData.cs
index 66b3f7d83..746fc2ea6 100644
--- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MessageDeleteAuditLogData.cs
+++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MessageDeleteAuditLogData.cs
@@ -2,6 +2,7 @@ using System.Linq;
using Model = Discord.API.AuditLog;
using EntryModel = Discord.API.AuditLogEntry;
+using System;
namespace Discord.Rest
{
@@ -20,7 +21,7 @@ namespace Discord.Rest
internal static MessageDeleteAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry)
{
var userInfo = log.Users.FirstOrDefault(x => x.Id == entry.TargetId);
- return new MessageDeleteAuditLogData(entry.Options.ChannelId.Value, entry.Options.Count.Value, RestUser.Create(discord, userInfo));
+ return new MessageDeleteAuditLogData(entry.Options.ChannelId.Value, entry.Options.Count.Value, userInfo != null ? RestUser.Create(discord, userInfo) : null);
}
///
@@ -41,6 +42,9 @@ namespace Discord.Rest
///
/// Gets the user of the messages that were deleted.
///
+ ///
+ /// Will be if the user is a 'Deleted User#....' because Discord does send user data for deleted users.
+ ///
///
/// A user object representing the user that created the deleted messages.
///
diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MessagePinAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MessagePinAuditLogData.cs
index be66ac846..c33fd5f44 100644
--- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MessagePinAuditLogData.cs
+++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MessagePinAuditLogData.cs
@@ -23,7 +23,7 @@ namespace Discord.Rest
if (entry.TargetId.HasValue)
{
var userInfo = log.Users.FirstOrDefault(x => x.Id == entry.TargetId);
- user = RestUser.Create(discord, userInfo);
+ user = (userInfo != null) ? RestUser.Create(discord, userInfo) : null;
}
return new MessagePinAuditLogData(entry.Options.MessageId.Value, entry.Options.ChannelId.Value, user);
@@ -46,6 +46,9 @@ namespace Discord.Rest
///
/// Gets the user of the message that was pinned if available.
///
+ ///
+ /// Will be if the user is a 'Deleted User#....' because Discord does send user data for deleted users.
+ ///
///
/// A user object representing the user that created the pinned message or .
///
diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MessageUnpinAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MessageUnpinAuditLogData.cs
index b4fa389cc..f6fd31771 100644
--- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MessageUnpinAuditLogData.cs
+++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MessageUnpinAuditLogData.cs
@@ -23,7 +23,7 @@ namespace Discord.Rest
if (entry.TargetId.HasValue)
{
var userInfo = log.Users.FirstOrDefault(x => x.Id == entry.TargetId);
- user = RestUser.Create(discord, userInfo);
+ user = (userInfo != null) ? RestUser.Create(discord, userInfo) : null;
}
return new MessageUnpinAuditLogData(entry.Options.MessageId.Value, entry.Options.ChannelId.Value, user);
@@ -46,6 +46,9 @@ namespace Discord.Rest
///
/// Gets the user of the message that was unpinned if available.
///
+ ///
+ /// Will be if the user is a 'Deleted User#....' because Discord does send user data for deleted users.
+ ///
///
/// A user object representing the user that created the unpinned message or .
///
diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/UnbanAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/UnbanAuditLogData.cs
index bc7e7fd4f..f12d9a1af 100644
--- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/UnbanAuditLogData.cs
+++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/UnbanAuditLogData.cs
@@ -1,4 +1,4 @@
-using System.Linq;
+using System.Linq;
using Model = Discord.API.AuditLog;
using EntryModel = Discord.API.AuditLogEntry;
@@ -18,7 +18,7 @@ namespace Discord.Rest
internal static UnbanAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry)
{
var userInfo = log.Users.FirstOrDefault(x => x.Id == entry.TargetId);
- return new UnbanAuditLogData(RestUser.Create(discord, userInfo));
+ return new UnbanAuditLogData((userInfo != null) ? RestUser.Create(discord, userInfo) : null);
}
///
diff --git a/src/Discord.Net.Rest/Entities/Channels/RestForumChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestForumChannel.cs
new file mode 100644
index 000000000..aff8400aa
--- /dev/null
+++ b/src/Discord.Net.Rest/Entities/Channels/RestForumChannel.cs
@@ -0,0 +1,131 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Model = Discord.API.Channel;
+
+namespace Discord.Rest
+{
+ ///
+ /// Represents a REST-based forum channel in a guild.
+ ///
+ public class RestForumChannel : RestGuildChannel, IForumChannel
+ {
+ ///
+ public bool IsNsfw { get; private set; }
+
+ ///
+ public string Topic { get; private set; }
+
+ ///
+ public ThreadArchiveDuration DefaultAutoArchiveDuration { get; private set; }
+
+ ///
+ public IReadOnlyCollection Tags { get; private set; }
+
+ ///
+ public string Mention => MentionUtils.MentionChannel(Id);
+
+ internal RestForumChannel(BaseDiscordClient client, IGuild guild, ulong id)
+ : base(client, guild, id)
+ {
+
+ }
+
+ internal new static RestStageChannel Create(BaseDiscordClient discord, IGuild guild, Model model)
+ {
+ var entity = new RestStageChannel(discord, guild, model.Id);
+ entity.Update(model);
+ return entity;
+ }
+
+ internal override void Update(Model model)
+ {
+ base.Update(model);
+ IsNsfw = model.Nsfw.GetValueOrDefault(false);
+ Topic = model.Topic.GetValueOrDefault();
+ DefaultAutoArchiveDuration = model.AutoArchiveDuration.GetValueOrDefault(ThreadArchiveDuration.OneDay);
+
+ Tags = model.ForumTags.GetValueOrDefault(Array.Empty()).Select(
+ x => new ForumTag(x.Id, x.Name, x.EmojiId.GetValueOrDefault(null), x.EmojiName.GetValueOrDefault())
+ ).ToImmutableArray();
+ }
+
+ ///
+ public Task CreatePostAsync(string title, ThreadArchiveDuration archiveDuration = ThreadArchiveDuration.OneDay, int? slowmode = null, string text = null, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None)
+ => ThreadHelper.CreatePostAsync(this, Discord, title, archiveDuration, slowmode, text, embed, options, allowedMentions, components, stickers, embeds, flags);
+
+ ///
+ public async Task CreatePostWithFileAsync(string title, string filePath, ThreadArchiveDuration archiveDuration = ThreadArchiveDuration.OneDay,
+ int? slowmode = null, string text = null, Embed embed = null, RequestOptions options = null, bool isSpoiler = false,
+ AllowedMentions allowedMentions = null, MessageComponent components = null,
+ ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None)
+ {
+ using var file = new FileAttachment(filePath, isSpoiler: isSpoiler);
+ return await ThreadHelper.CreatePostAsync(this, Discord, title, new FileAttachment[] { file }, archiveDuration, slowmode, text, embed, options, allowedMentions, components, stickers, embeds, flags).ConfigureAwait(false);
+ }
+
+ ///
+ public async Task CreatePostWithFileAsync(string title, Stream stream, string filename, ThreadArchiveDuration archiveDuration = ThreadArchiveDuration.OneDay,
+ int? slowmode = null, string text = null, Embed embed = null, RequestOptions options = null, bool isSpoiler = false,
+ AllowedMentions allowedMentions = null, MessageComponent components = null,
+ ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None)
+ {
+ using var file = new FileAttachment(stream, filename, isSpoiler: isSpoiler);
+ return await ThreadHelper.CreatePostAsync(this, Discord, title, new FileAttachment[] { file }, archiveDuration, slowmode, text, embed, options, allowedMentions, components, stickers, embeds, flags).ConfigureAwait(false);
+ }
+
+ ///
+ public Task CreatePostWithFileAsync(string title, FileAttachment attachment, ThreadArchiveDuration archiveDuration = ThreadArchiveDuration.OneDay,
+ int? slowmode = null, string text = null, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null,
+ MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None)
+ => ThreadHelper.CreatePostAsync(this, Discord, title, new FileAttachment[] { attachment }, archiveDuration, slowmode, text, embed, options, allowedMentions, components, stickers, embeds, flags);
+
+ ///
+ public Task CreatePostWithFilesAsync(string title, IEnumerable attachments, ThreadArchiveDuration archiveDuration = ThreadArchiveDuration.OneDay,
+ int? slowmode = null, string text = null, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null,
+ MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None)
+ => ThreadHelper.CreatePostAsync(this, Discord, title, attachments, archiveDuration, slowmode, text, embed, options, allowedMentions, components, stickers, embeds, flags);
+
+ ///
+ public Task> GetActiveThreadsAsync(RequestOptions options = null)
+ => ThreadHelper.GetActiveThreadsAsync(Guild, Discord, options);
+
+ ///
+ public Task> GetJoinedPrivateArchivedThreadsAsync(int? limit = null, DateTimeOffset? before = null, RequestOptions options = null)
+ => ThreadHelper.GetJoinedPrivateArchivedThreadsAsync(this, Discord, limit, before, options);
+
+ ///
+ public Task> GetPrivateArchivedThreadsAsync(int? limit = null, DateTimeOffset? before = null, RequestOptions options = null)
+ => ThreadHelper.GetPrivateArchivedThreadsAsync(this, Discord, limit, before, options);
+
+ ///
+ public Task> GetPublicArchivedThreadsAsync(int? limit = null, DateTimeOffset? before = null, RequestOptions options = null)
+ => ThreadHelper.GetPublicArchivedThreadsAsync(this, Discord, limit, before, options);
+
+ #region IForumChannel
+ async Task> IForumChannel.GetActiveThreadsAsync(RequestOptions options)
+ => await GetActiveThreadsAsync(options).ConfigureAwait(false);
+ async Task> IForumChannel.GetPublicArchivedThreadsAsync(int? limit, DateTimeOffset? before, RequestOptions options)
+ => await GetPublicArchivedThreadsAsync(limit, before, options).ConfigureAwait(false);
+ async Task> IForumChannel.GetPrivateArchivedThreadsAsync(int? limit, DateTimeOffset? before, RequestOptions options)
+ => await GetPrivateArchivedThreadsAsync(limit, before, options).ConfigureAwait(false);
+ async Task> IForumChannel.GetJoinedPrivateArchivedThreadsAsync(int? limit, DateTimeOffset? before, RequestOptions options)
+ => await GetJoinedPrivateArchivedThreadsAsync(limit, before, options).ConfigureAwait(false);
+ async Task IForumChannel.CreatePostAsync(string title, ThreadArchiveDuration archiveDuration, int? slowmode, string text, Embed embed, RequestOptions options, AllowedMentions allowedMentions, MessageComponent components, ISticker[] stickers, Embed[] embeds, MessageFlags flags)
+ => await CreatePostAsync(title, archiveDuration, slowmode, text, embed, options, allowedMentions, components, stickers, embeds, flags).ConfigureAwait(false);
+ async Task IForumChannel.CreatePostWithFileAsync(string title, string filePath, ThreadArchiveDuration archiveDuration, int? slowmode, string text, Embed embed, RequestOptions options, bool isSpoiler, AllowedMentions allowedMentions, MessageComponent components, ISticker[] stickers, Embed[] embeds, MessageFlags flags)
+ => await CreatePostWithFileAsync(title, filePath, archiveDuration, slowmode, text, embed, options, isSpoiler, allowedMentions, components, stickers, embeds, flags).ConfigureAwait(false);
+ async Task IForumChannel.CreatePostWithFileAsync(string title, Stream stream, string filename, ThreadArchiveDuration archiveDuration, int? slowmode, string text, Embed embed, RequestOptions options, bool isSpoiler, AllowedMentions allowedMentions, MessageComponent components, ISticker[] stickers, Embed[] embeds, MessageFlags flags)
+ => await CreatePostWithFileAsync(title, stream, filename, archiveDuration, slowmode, text, embed, options, isSpoiler, allowedMentions, components, stickers, embeds, flags).ConfigureAwait(false);
+ async Task IForumChannel.CreatePostWithFileAsync(string title, FileAttachment attachment, ThreadArchiveDuration archiveDuration, int? slowmode, string text, Embed embed, RequestOptions options, AllowedMentions allowedMentions, MessageComponent components, ISticker[] stickers, Embed[] embeds, MessageFlags flags)
+ => await CreatePostWithFileAsync(title, attachment, archiveDuration, slowmode, text, embed, options, allowedMentions, components, stickers, embeds, flags).ConfigureAwait(false);
+ async Task IForumChannel.CreatePostWithFilesAsync(string title, IEnumerable attachments, ThreadArchiveDuration archiveDuration, int? slowmode, string text, Embed embed, RequestOptions options, AllowedMentions allowedMentions, MessageComponent components, ISticker[] stickers, Embed[] embeds, MessageFlags flags)
+ => await CreatePostWithFilesAsync(title, attachments, archiveDuration, slowmode, text, embed, options, allowedMentions, components, stickers, embeds, flags);
+
+ #endregion
+ }
+}
diff --git a/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs
index fa2362854..4f9af0335 100644
--- a/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs
+++ b/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs
@@ -39,6 +39,7 @@ namespace Discord.Rest
ChannelType.Text => RestTextChannel.Create(discord, guild, model),
ChannelType.Voice => RestVoiceChannel.Create(discord, guild, model),
ChannelType.Stage => RestStageChannel.Create(discord, guild, model),
+ ChannelType.Forum => RestForumChannel.Create(discord, guild, model),
ChannelType.Category => RestCategoryChannel.Create(discord, guild, model),
ChannelType.PublicThread or ChannelType.PrivateThread or ChannelType.NewsThread => RestThreadChannel.Create(discord, guild, model),
_ => new RestGuildChannel(discord, guild, model.Id),
diff --git a/src/Discord.Net.Rest/Entities/Channels/RestStageChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestStageChannel.cs
index c01df96fd..b34afd027 100644
--- a/src/Discord.Net.Rest/Entities/Channels/RestStageChannel.cs
+++ b/src/Discord.Net.Rest/Entities/Channels/RestStageChannel.cs
@@ -12,7 +12,11 @@ namespace Discord.Rest
public class RestStageChannel : RestVoiceChannel, IStageChannel
{
///
- public string Topic { get; private set; }
+ ///
+ /// This field is always false for stage channels.
+ ///
+ public override bool IsTextInVoice
+ => false;
///
public StagePrivacyLevel? PrivacyLevel { get; private set; }
@@ -37,13 +41,11 @@ namespace Discord.Rest
IsLive = isLive;
if(isLive)
{
- Topic = model.Topic;
PrivacyLevel = model.PrivacyLevel;
IsDiscoverableDisabled = model.DiscoverableDisabled;
}
else
{
- Topic = null;
PrivacyLevel = null;
IsDiscoverableDisabled = null;
}
diff --git a/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs
index 76c75ab6e..81f21bcd7 100644
--- a/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs
+++ b/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs
@@ -21,11 +21,12 @@ namespace Discord.Rest
public virtual int SlowModeInterval { get; private set; }
///
public ulong? CategoryId { get; private set; }
-
///
public string Mention => MentionUtils.MentionChannel(Id);
///
public bool IsNsfw { get; private set; }
+ ///
+ public ThreadArchiveDuration DefaultArchiveDuration { get; private set; }
internal RestTextChannel(BaseDiscordClient discord, IGuild guild, ulong id)
: base(discord, guild, id)
@@ -46,6 +47,12 @@ namespace Discord.Rest
if (model.SlowMode.IsSpecified)
SlowModeInterval = model.SlowMode.Value;
IsNsfw = model.Nsfw.GetValueOrDefault();
+
+ if (model.AutoArchiveDuration.IsSpecified)
+ DefaultArchiveDuration = model.AutoArchiveDuration.Value;
+ else
+ DefaultArchiveDuration = ThreadArchiveDuration.OneDay;
+ // basic value at channel creation. Shouldn't be called since guild text channels always have this property
}
///
@@ -86,25 +93,25 @@ namespace Discord.Rest
=> ChannelHelper.GetUsersAsync(this, Guild, Discord, null, null, options);
///
- public Task GetMessageAsync(ulong id, RequestOptions options = null)
+ public virtual Task GetMessageAsync(ulong id, RequestOptions options = null)
=> ChannelHelper.GetMessageAsync(this, Discord, id, options);
///
- public IAsyncEnumerable> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null)
+ public virtual IAsyncEnumerable> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null)
=> ChannelHelper.GetMessagesAsync(this, Discord, null, Direction.Before, limit, options);
///
- public IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null)
+ public virtual IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null)
=> ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit, options);
///
- public IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null)
+ public virtual IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null)
=> ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit, options);
///
- public Task> GetPinnedMessagesAsync(RequestOptions options = null)
+ public virtual Task> GetPinnedMessagesAsync(RequestOptions options = null)
=> ChannelHelper.GetPinnedMessagesAsync(this, Discord, options);
///
/// Message content is too long, length must be less or equal to .
/// The only valid are and .
- public Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null,
+ public virtual Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null,
RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null,
MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None)
=> ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, allowedMentions, messageReference,
@@ -136,7 +143,7 @@ namespace Discord.Rest
/// An I/O error occurred while opening the file.
/// Message content is too long, length must be less or equal to .
/// The only valid are and .
- public Task SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null,
+ public virtual Task SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null,
RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null,
MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null,
Embed[] embeds = null, MessageFlags flags = MessageFlags.None)
@@ -146,7 +153,7 @@ namespace Discord.Rest
///
/// Message content is too long, length must be less or equal to .
/// The only valid are and .
- public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false,
+ public virtual Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false,
Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null,
MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null,
Embed[] embeds = null, MessageFlags flags = MessageFlags.None)
@@ -156,7 +163,7 @@ namespace Discord.Rest
///
/// Message content is too long, length must be less or equal to .
/// The only valid are and .
- public Task SendFileAsync(FileAttachment attachment, string text, bool isTTS = false,
+ public virtual Task SendFileAsync(FileAttachment attachment, string text, bool isTTS = false,
Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null,
MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null,
Embed[] embeds = null, MessageFlags flags = MessageFlags.None)
@@ -166,35 +173,35 @@ namespace Discord.Rest
///
/// Message content is too long, length must be less or equal to .
/// The only valid are and .
- public Task SendFilesAsync(IEnumerable attachments, string text, bool isTTS = false,
+ public virtual Task SendFilesAsync(IEnumerable attachments, string text, bool isTTS = false,
Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null,
MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null,
Embed[] embeds = null, MessageFlags flags = MessageFlags.None)
=> ChannelHelper.SendFilesAsync(this, Discord, attachments, text, isTTS, embed, allowedMentions, messageReference, components, stickers, options, embeds, flags);
///
- public Task DeleteMessageAsync(ulong messageId, RequestOptions options = null)
+ public virtual Task DeleteMessageAsync(ulong messageId, RequestOptions options = null)
=> ChannelHelper.DeleteMessageAsync(this, messageId, Discord, options);
///
- public Task DeleteMessageAsync(IMessage message, RequestOptions options = null)
+ public virtual Task DeleteMessageAsync(IMessage message, RequestOptions options = null)
=> ChannelHelper.DeleteMessageAsync(this, message.Id, Discord, options);
///
- public Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null)
+ public virtual Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null)
=> ChannelHelper.DeleteMessagesAsync(this, Discord, messages.Select(x => x.Id), options);
///
- public Task DeleteMessagesAsync(IEnumerable messageIds, RequestOptions options = null)
+ public virtual Task DeleteMessagesAsync(IEnumerable messageIds, RequestOptions options = null)
=> ChannelHelper.DeleteMessagesAsync(this, Discord, messageIds, options);
///
- public async Task ModifyMessageAsync(ulong messageId, Action func, RequestOptions options = null)
+ public virtual async Task ModifyMessageAsync(ulong messageId, Action func, RequestOptions options = null)
=> await ChannelHelper.ModifyMessageAsync(this, messageId, func, Discord, options).ConfigureAwait(false);
///
- public Task TriggerTypingAsync(RequestOptions options = null)
+ public virtual Task TriggerTypingAsync(RequestOptions options = null)
=> ChannelHelper.TriggerTypingAsync(this, Discord, options);
///
- public IDisposable EnterTypingState(RequestOptions options = null)
+ public virtual IDisposable EnterTypingState(RequestOptions options = null)
=> ChannelHelper.EnterTypingState(this, Discord, options);
///
@@ -231,38 +238,6 @@ namespace Discord.Rest
public virtual Task> GetWebhooksAsync(RequestOptions options = null)
=> ChannelHelper.GetWebhooksAsync(this, Discord, options);
- ///
- /// Gets the parent (category) channel of this channel.
- ///
- /// The options to be used when sending the request.
- ///
- /// A task that represents the asynchronous get operation. The task result contains the category channel
- /// representing the parent of this channel; null if none is set.
- ///
- public virtual Task GetCategoryAsync(RequestOptions options = null)
- => ChannelHelper.GetCategoryAsync(this, Discord, options);
- ///
- public Task SyncPermissionsAsync(RequestOptions options = null)
- => ChannelHelper.SyncPermissionsAsync(this, Discord, options);
- #endregion
-
- #region Invites
- ///
- public virtual async Task CreateInviteAsync(int? maxAge = 86400, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null)
- => await ChannelHelper.CreateInviteAsync(this, Discord, maxAge, maxUses, isTemporary, isUnique, options).ConfigureAwait(false);
- public virtual async Task CreateInviteToApplicationAsync(ulong applicationId, int? maxAge = 86400, int? maxUses = default(int?), bool isTemporary = false, bool isUnique = false, RequestOptions options = null)
- => await ChannelHelper.CreateInviteToApplicationAsync(this, Discord, maxAge, maxUses, isTemporary, isUnique, applicationId, options);
- ///
- public virtual async Task CreateInviteToApplicationAsync(DefaultApplications application, int? maxAge = 86400, int? maxUses = default(int?), bool isTemporary = false, bool isUnique = false, RequestOptions options = null)
- => await ChannelHelper.CreateInviteToApplicationAsync(this, Discord, maxAge, maxUses, isTemporary, isUnique, (ulong)application, options);
- public virtual Task CreateInviteToStreamAsync(IUser user, int? maxAge, int? maxUses = default(int?), bool isTemporary = false, bool isUnique = false, RequestOptions options = null)
- => throw new NotImplementedException();
- ///
- public virtual async Task> GetInvitesAsync(RequestOptions options = null)
- => await ChannelHelper.GetInvitesAsync(this, Discord, options).ConfigureAwait(false);
-
- private string DebuggerDisplay => $"{Name} ({Id}, Text)";
-
///
/// Creates a thread within this .
///
@@ -299,6 +274,38 @@ namespace Discord.Rest
var model = await ThreadHelper.CreateThreadAsync(Discord, this, name, type, autoArchiveDuration, message, invitable, slowmode, options);
return RestThreadChannel.Create(Discord, Guild, model);
}
+
+ ///
+ /// Gets the parent (category) channel of this channel.
+ ///
+ /// The options to be used when sending the request.
+ ///
+ /// A task that represents the asynchronous get operation. The task result contains the category channel
+ /// representing the parent of this channel; null if none is set.
+ ///
+ public virtual Task GetCategoryAsync(RequestOptions options = null)
+ => ChannelHelper.GetCategoryAsync(this, Discord, options);
+ ///
+ public Task SyncPermissionsAsync(RequestOptions options = null)
+ => ChannelHelper.SyncPermissionsAsync(this, Discord, options);
+ #endregion
+
+ #region Invites
+ ///
+ public virtual async Task CreateInviteAsync(int? maxAge = 86400, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null)
+ => await ChannelHelper.CreateInviteAsync(this, Discord, maxAge, maxUses, isTemporary, isUnique, options).ConfigureAwait(false);
+ public virtual async Task CreateInviteToApplicationAsync(ulong applicationId, int? maxAge = 86400, int? maxUses = default(int?), bool isTemporary = false, bool isUnique = false, RequestOptions options = null)
+ => await ChannelHelper.CreateInviteToApplicationAsync(this, Discord, maxAge, maxUses, isTemporary, isUnique, applicationId, options);
+ ///
+ public virtual async Task CreateInviteToApplicationAsync(DefaultApplications application, int? maxAge = 86400, int? maxUses = default(int?), bool isTemporary = false, bool isUnique = false, RequestOptions options = null)
+ => await ChannelHelper.CreateInviteToApplicationAsync(this, Discord, maxAge, maxUses, isTemporary, isUnique, (ulong)application, options);
+ public virtual Task CreateInviteToStreamAsync(IUser user, int? maxAge, int? maxUses = default(int?), bool isTemporary = false, bool isUnique = false, RequestOptions options = null)
+ => throw new NotImplementedException();
+ ///
+ public virtual async Task> GetInvitesAsync(RequestOptions options = null)
+ => await ChannelHelper.GetInvitesAsync(this, Discord, options).ConfigureAwait(false);
+
+ private string DebuggerDisplay => $"{Name} ({Id}, Text)";
#endregion
#region ITextChannel
diff --git a/src/Discord.Net.Rest/Entities/Channels/RestVoiceChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestVoiceChannel.cs
index bcf03a5bc..31d313a48 100644
--- a/src/Discord.Net.Rest/Entities/Channels/RestVoiceChannel.cs
+++ b/src/Discord.Net.Rest/Entities/Channels/RestVoiceChannel.cs
@@ -2,6 +2,7 @@ using Discord.Audio;
using System;
using System.Collections.Generic;
using System.Diagnostics;
+using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Model = Discord.API.Channel;
@@ -12,21 +13,21 @@ namespace Discord.Rest
/// Represents a REST-based voice channel in a guild.
///
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
- public class RestVoiceChannel : RestGuildChannel, IVoiceChannel, IRestAudioChannel
+ public class RestVoiceChannel : RestTextChannel, IVoiceChannel, IRestAudioChannel
{
#region RestVoiceChannel
+ ///
+ /// Gets whether or not the guild has Text-In-Voice enabled and the voice channel is a TiV channel.
+ ///
+ public virtual bool IsTextInVoice
+ => Guild.Features.HasTextInVoice;
///
public int Bitrate { get; private set; }
///
public int? UserLimit { get; private set; }
- ///
- public ulong? CategoryId { get; private set; }
///
public string RTCRegion { get; private set; }
- ///
- public string Mention => MentionUtils.MentionChannel(Id);
-
internal RestVoiceChannel(BaseDiscordClient discord, IGuild guild, ulong id)
: base(discord, guild, id)
{
@@ -41,7 +42,6 @@ namespace Discord.Rest
internal override void Update(Model model)
{
base.Update(model);
- CategoryId = model.CategoryId;
if(model.Bitrate.IsSpecified)
Bitrate = model.Bitrate.Value;
@@ -59,41 +59,185 @@ namespace Discord.Rest
Update(model);
}
- ///
- /// Gets the parent (category) channel of this channel.
- ///
- /// The options to be used when sending the request.
- ///
- /// A task that represents the asynchronous get operation. The task result contains the category channel
- /// representing the parent of this channel; null if none is set.
- ///
- public Task GetCategoryAsync(RequestOptions options = null)
- => ChannelHelper.GetCategoryAsync(this, Discord, options);
- ///
- public Task SyncPermissionsAsync(RequestOptions options = null)
- => ChannelHelper.SyncPermissionsAsync(this, Discord, options);
- #endregion
+ ///
+ /// Cannot modify text channel properties of a voice channel.
+ public override Task ModifyAsync(Action func, RequestOptions options = null)
+ => throw new InvalidOperationException("Cannot modify text channel properties of a voice channel");
- #region Invites
- ///
- public async Task CreateInviteAsync(int? maxAge = 86400, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null)
- => await ChannelHelper.CreateInviteAsync(this, Discord, maxAge, maxUses, isTemporary, isUnique, options).ConfigureAwait(false);
- ///
- public async Task CreateInviteToApplicationAsync(ulong applicationId, int? maxAge, int? maxUses = default(int?), bool isTemporary = false, bool isUnique = false, RequestOptions options = null)
- => await ChannelHelper.CreateInviteToApplicationAsync(this, Discord, maxAge, maxUses, isTemporary, isUnique, applicationId, options).ConfigureAwait(false);
- ///
- public virtual async Task CreateInviteToApplicationAsync(DefaultApplications application, int? maxAge = 86400, int? maxUses = default(int?), bool isTemporary = false, bool isUnique = false, RequestOptions options = null)
- => await ChannelHelper.CreateInviteToApplicationAsync(this, Discord, maxAge, maxUses, isTemporary, isUnique, (ulong)application, options);
- ///
- public async Task CreateInviteToStreamAsync(IUser user, int? maxAge, int? maxUses = default(int?), bool isTemporary = false, bool isUnique = false, RequestOptions options = null)
- => await ChannelHelper.CreateInviteToStreamAsync(this, Discord, maxAge, maxUses, isTemporary, isUnique, user, options).ConfigureAwait(false);
- ///
- public async Task> GetInvitesAsync(RequestOptions options = null)
- => await ChannelHelper.GetInvitesAsync(this, Discord, options).ConfigureAwait(false);
+ ///
+ /// Cannot create a thread within a voice channel.
+ public override Task CreateThreadAsync(string name, ThreadType type = ThreadType.PublicThread, ThreadArchiveDuration autoArchiveDuration = ThreadArchiveDuration.OneDay, IMessage message = null, bool? invitable = null, int? slowmode = null, RequestOptions options = null)
+ => throw new InvalidOperationException("Cannot create a thread within a voice channel");
+
+ #endregion
private string DebuggerDisplay => $"{Name} ({Id}, Voice)";
+
+ #region TextOverrides
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override Task GetMessageAsync(ulong id, RequestOptions options = null)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.GetMessageAsync(id, options);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override Task DeleteMessageAsync(IMessage message, RequestOptions options = null)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.DeleteMessageAsync(message, options);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override Task DeleteMessageAsync(ulong messageId, RequestOptions options = null)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.DeleteMessageAsync(messageId, options);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.DeleteMessagesAsync(messages, options);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override Task DeleteMessagesAsync(IEnumerable messageIds, RequestOptions options = null)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.DeleteMessagesAsync(messageIds, options);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override IDisposable EnterTypingState(RequestOptions options = null)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.EnterTypingState(options);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = 100, RequestOptions options = null)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.GetMessagesAsync(fromMessage, dir, limit, options);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override IAsyncEnumerable> GetMessagesAsync(int limit = 100, RequestOptions options = null)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.GetMessagesAsync(limit, options);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = 100, RequestOptions options = null)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.GetMessagesAsync(fromMessageId, dir, limit, options);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override Task> GetPinnedMessagesAsync(RequestOptions options = null)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.GetPinnedMessagesAsync(options);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override Task GetWebhookAsync(ulong id, RequestOptions options = null)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.GetWebhookAsync(id, options);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override Task> GetWebhooksAsync(RequestOptions options = null)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.GetWebhooksAsync(options);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override Task CreateWebhookAsync(string name, Stream avatar = null, RequestOptions options = null)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.CreateWebhookAsync(name, avatar, options);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override Task ModifyMessageAsync(ulong messageId, Action func, RequestOptions options = null)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.ModifyMessageAsync(messageId, func, options);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override Task SendFileAsync(FileAttachment attachment, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.SendFileAsync(attachment, text, isTTS, embed, options, allowedMentions, messageReference, components, stickers, embeds, flags);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.SendFileAsync(stream, filename, text, isTTS, embed, options, isSpoiler, allowedMentions, messageReference, components, stickers, embeds, flags);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override Task SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.SendFileAsync(filePath, text, isTTS, embed, options, isSpoiler, allowedMentions, messageReference, components, stickers, embeds, flags);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override Task SendFilesAsync(IEnumerable attachments, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.SendFilesAsync(attachments, text, isTTS, embed, options, allowedMentions, messageReference, components, stickers, embeds, flags);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.SendMessageAsync(text, isTTS, embed, options, allowedMentions, messageReference, components, stickers, embeds, flags);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override Task TriggerTypingAsync(RequestOptions options = null)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.TriggerTypingAsync(options);
+ }
+
#endregion
+
#region IAudioChannel
///
/// Connecting to a REST-based channel is not supported.
diff --git a/src/Discord.Net.Rest/Entities/Channels/ThreadHelper.cs b/src/Discord.Net.Rest/Entities/Channels/ThreadHelper.cs
index e0074ecff..f5fce5a50 100644
--- a/src/Discord.Net.Rest/Entities/Channels/ThreadHelper.cs
+++ b/src/Discord.Net.Rest/Entities/Channels/ThreadHelper.cs
@@ -1,5 +1,7 @@
using Discord.API.Rest;
using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
using System.Linq;
using System.Threading.Tasks;
using Model = Discord.API.Channel;
@@ -60,6 +62,33 @@ namespace Discord.Rest
return await client.ApiClient.ModifyThreadAsync(channel.Id, apiArgs, options).ConfigureAwait(false);
}
+ public static async Task> GetActiveThreadsAsync(IGuild guild, BaseDiscordClient client, RequestOptions options)
+ {
+ var result = await client.ApiClient.GetActiveThreadsAsync(guild.Id, options).ConfigureAwait(false);
+ return result.Threads.Select(x => RestThreadChannel.Create(client, guild, x)).ToImmutableArray();
+ }
+
+ public static async Task> GetPublicArchivedThreadsAsync(IGuildChannel channel, BaseDiscordClient client, int? limit = null,
+ DateTimeOffset? before = null, RequestOptions options = null)
+ {
+ var result = await client.ApiClient.GetPublicArchivedThreadsAsync(channel.Id, before, limit, options);
+ return result.Threads.Select(x => RestThreadChannel.Create(client, channel.Guild, x)).ToImmutableArray();
+ }
+
+ public static async Task> GetPrivateArchivedThreadsAsync(IGuildChannel channel, BaseDiscordClient client, int? limit = null,
+ DateTimeOffset? before = null, RequestOptions options = null)
+ {
+ var result = await client.ApiClient.GetPrivateArchivedThreadsAsync(channel.Id, before, limit, options);
+ return result.Threads.Select(x => RestThreadChannel.Create(client, channel.Guild, x)).ToImmutableArray();
+ }
+
+ public static async Task> GetJoinedPrivateArchivedThreadsAsync(IGuildChannel channel, BaseDiscordClient client, int? limit = null,
+ DateTimeOffset? before = null, RequestOptions options = null)
+ {
+ var result = await client.ApiClient.GetJoinedPrivateArchivedThreadsAsync(channel.Id, before, limit, options);
+ return result.Threads.Select(x => RestThreadChannel.Create(client, channel.Guild, x)).ToImmutableArray();
+ }
+
public static async Task GetUsersAsync(IThreadChannel channel, BaseDiscordClient client, RequestOptions options = null)
{
var users = await client.ApiClient.ListThreadMembersAsync(channel.Id, options);
@@ -73,5 +102,114 @@ namespace Discord.Rest
return RestThreadUser.Create(client, channel.Guild, model, channel);
}
+
+ public static async Task CreatePostAsync(IForumChannel channel, BaseDiscordClient client, string title, ThreadArchiveDuration archiveDuration = ThreadArchiveDuration.OneDay, int? slowmode = null, string text = null, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None)
+ {
+ embeds ??= Array.Empty();
+ if (embed != null)
+ embeds = new[] { embed }.Concat(embeds).ToArray();
+
+ Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed.");
+ Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed.");
+ Preconditions.AtMost(embeds.Length, 10, nameof(embeds), "A max of 10 embeds are allowed.");
+
+ // check that user flag and user Id list are exclusive, same with role flag and role Id list
+ if (allowedMentions != null && allowedMentions.AllowedTypes.HasValue)
+ {
+ if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Users) &&
+ allowedMentions.UserIds != null && allowedMentions.UserIds.Count > 0)
+ {
+ throw new ArgumentException("The Users flag is mutually exclusive with the list of User Ids.", nameof(allowedMentions));
+ }
+
+ if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Roles) &&
+ allowedMentions.RoleIds != null && allowedMentions.RoleIds.Count > 0)
+ {
+ throw new ArgumentException("The Roles flag is mutually exclusive with the list of Role Ids.", nameof(allowedMentions));
+ }
+ }
+
+ if (stickers != null)
+ {
+ Preconditions.AtMost(stickers.Length, 3, nameof(stickers), "A max of 3 stickers are allowed.");
+ }
+
+
+ if (flags is not MessageFlags.None and not MessageFlags.SuppressEmbeds)
+ throw new ArgumentException("The only valid MessageFlags are SuppressEmbeds and none.", nameof(flags));
+
+ var args = new CreatePostParams()
+ {
+ Title = title,
+ ArchiveDuration = archiveDuration,
+ Slowmode = slowmode,
+ Message = new()
+ {
+ AllowedMentions = allowedMentions.ToModel(),
+ Content = text,
+ Embeds = embeds.Any() ? embeds.Select(x => x.ToModel()).ToArray() : Optional.Unspecified,
+ Flags = flags,
+ Components = components?.Components?.Any() ?? false ? components.Components.Select(x => new API.ActionRowComponent(x)).ToArray() : Optional.Unspecified,
+ Stickers = stickers?.Any() ?? false ? stickers.Select(x => x.Id).ToArray() : Optional.Unspecified,
+ }
+ };
+
+ var model = await client.ApiClient.CreatePostAsync(channel.Id, args, options).ConfigureAwait(false);
+
+ return RestThreadChannel.Create(client, channel.Guild, model);
+ }
+
+ public static async Task CreatePostAsync(IForumChannel channel, BaseDiscordClient client, string title, IEnumerable attachments, ThreadArchiveDuration archiveDuration, int? slowmode, string text, Embed embed, RequestOptions options, AllowedMentions allowedMentions, MessageComponent components, ISticker[] stickers, Embed[] embeds, MessageFlags flags)
+ {
+ embeds ??= Array.Empty();
+ if (embed != null)
+ embeds = new[] { embed }.Concat(embeds).ToArray();
+
+ Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed.");
+ Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed.");
+ Preconditions.AtMost(embeds.Length, 10, nameof(embeds), "A max of 10 embeds are allowed.");
+
+ // check that user flag and user Id list are exclusive, same with role flag and role Id list
+ if (allowedMentions != null && allowedMentions.AllowedTypes.HasValue)
+ {
+ if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Users) &&
+ allowedMentions.UserIds != null && allowedMentions.UserIds.Count > 0)
+ {
+ throw new ArgumentException("The Users flag is mutually exclusive with the list of User Ids.", nameof(allowedMentions));
+ }
+
+ if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Roles) &&
+ allowedMentions.RoleIds != null && allowedMentions.RoleIds.Count > 0)
+ {
+ throw new ArgumentException("The Roles flag is mutually exclusive with the list of Role Ids.", nameof(allowedMentions));
+ }
+ }
+
+ if (stickers != null)
+ {
+ Preconditions.AtMost(stickers.Length, 3, nameof(stickers), "A max of 3 stickers are allowed.");
+ }
+
+
+ if (flags is not MessageFlags.None and not MessageFlags.SuppressEmbeds)
+ throw new ArgumentException("The only valid MessageFlags are SuppressEmbeds and none.", nameof(flags));
+
+ var args = new CreateMultipartPostAsync(attachments.ToArray())
+ {
+ AllowedMentions = allowedMentions.ToModel(),
+ ArchiveDuration = archiveDuration,
+ Content = text,
+ Embeds = embeds.Any() ? embeds.Select(x => x.ToModel()).ToArray() : Optional.Unspecified,
+ Flags = flags,
+ MessageComponent = components?.Components?.Any() ?? false ? components.Components.Select(x => new API.ActionRowComponent(x)).ToArray() : Optional.Unspecified,
+ Slowmode = slowmode,
+ Stickers = stickers?.Any() ?? false ? stickers.Select(x => x.Id).ToArray() : Optional.Unspecified,
+ Title = title
+ };
+
+ var model = await client.ApiClient.CreatePostAsync(channel.Id, args, options);
+
+ return RestThreadChannel.Create(client, channel.Guild, model);
+ }
}
}
diff --git a/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs
index 469e93db4..8bab35937 100644
--- a/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs
+++ b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs
@@ -132,12 +132,15 @@ namespace Discord.Rest
}
public static ulong GetUploadLimit(IGuild guild)
{
- return guild.PremiumTier switch
+ var tierFactor = guild.PremiumTier switch
{
- PremiumTier.Tier2 => 50ul * 1000000,
- PremiumTier.Tier3 => 100ul * 1000000,
- _ => 8ul * 1000000
+ PremiumTier.Tier2 => 50,
+ PremiumTier.Tier3 => 100,
+ _ => 8
};
+
+ var mebibyte = Math.Pow(2, 20);
+ return (ulong) (tierFactor * mebibyte);
}
#endregion
@@ -151,7 +154,7 @@ namespace Discord.Rest
if (fromUserId.HasValue)
return GetBansAsync(guild, client, fromUserId.Value + 1, Direction.Before, around + 1, options)
.Concat(GetBansAsync(guild, client, fromUserId.Value, Direction.After, around, options));
- else
+ else
return GetBansAsync(guild, client, null, Direction.Before, around + 1, options);
}
@@ -908,7 +911,7 @@ namespace Discord.Rest
if (endTime != null && endTime <= startTime)
throw new ArgumentOutOfRangeException(nameof(endTime), $"{nameof(endTime)} cannot be before the start time");
-
+
var apiArgs = new CreateGuildScheduledEventParams()
{
ChannelId = channelId ?? Optional.Unspecified,
diff --git a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs
index 92d598466..974ea69ad 100644
--- a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs
+++ b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs
@@ -1161,7 +1161,6 @@ namespace Discord.Rest
/// in order to use this property.
///
///
- /// A collection of speakers for the event.
/// The location of the event; links are supported
/// The optional banner image for the event.
/// The options to be used when sending the request.
diff --git a/src/Discord.Net.Rest/Entities/Interactions/CommandBase/RestCommandBase.cs b/src/Discord.Net.Rest/Entities/Interactions/CommandBase/RestCommandBase.cs
index 196416f0e..22e56a733 100644
--- a/src/Discord.Net.Rest/Entities/Interactions/CommandBase/RestCommandBase.cs
+++ b/src/Discord.Net.Rest/Entities/Interactions/CommandBase/RestCommandBase.cs
@@ -39,16 +39,16 @@ namespace Discord.Rest
{
}
- internal new static async Task CreateAsync(DiscordRestClient client, Model model)
+ internal new static async Task CreateAsync(DiscordRestClient client, Model model, bool doApiCall)
{
var entity = new RestCommandBase(client, model);
- await entity.UpdateAsync(client, model).ConfigureAwait(false);
+ await entity.UpdateAsync(client, model, doApiCall).ConfigureAwait(false);
return entity;
}
- internal override async Task UpdateAsync(DiscordRestClient client, Model model)
+ internal override async Task UpdateAsync(DiscordRestClient client, Model model, bool doApiCall)
{
- await base.UpdateAsync(client, model).ConfigureAwait(false);
+ await base.UpdateAsync(client, model, doApiCall).ConfigureAwait(false);
}
///
diff --git a/src/Discord.Net.Rest/Entities/Interactions/CommandBase/RestCommandBaseData.cs b/src/Discord.Net.Rest/Entities/Interactions/CommandBase/RestCommandBaseData.cs
index 4227c802a..828299d22 100644
--- a/src/Discord.Net.Rest/Entities/Interactions/CommandBase/RestCommandBaseData.cs
+++ b/src/Discord.Net.Rest/Entities/Interactions/CommandBase/RestCommandBaseData.cs
@@ -27,20 +27,20 @@ namespace Discord.Rest
{
}
- internal static async Task CreateAsync(DiscordRestClient client, Model model, RestGuild guild, IRestMessageChannel channel)
+ internal static async Task CreateAsync(DiscordRestClient client, Model model, RestGuild guild, IRestMessageChannel channel, bool doApiCall)
{
var entity = new RestCommandBaseData(client, model);
- await entity.UpdateAsync(client, model, guild, channel).ConfigureAwait(false);
+ await entity.UpdateAsync(client, model, guild, channel, doApiCall).ConfigureAwait(false);
return entity;
}
- internal virtual async Task UpdateAsync(DiscordRestClient client, Model model, RestGuild guild, IRestMessageChannel channel)
+ internal virtual async Task UpdateAsync(DiscordRestClient client, Model model, RestGuild guild, IRestMessageChannel channel, bool doApiCall)
{
Name = model.Name;
if (model.Resolved.IsSpecified && ResolvableData == null)
{
ResolvableData = new RestResolvableData();
- await ResolvableData.PopulateAsync(client, guild, channel, model).ConfigureAwait(false);
+ await ResolvableData.PopulateAsync(client, guild, channel, model, doApiCall).ConfigureAwait(false);
}
}
diff --git a/src/Discord.Net.Rest/Entities/Interactions/CommandBase/RestResolvableData.cs b/src/Discord.Net.Rest/Entities/Interactions/CommandBase/RestResolvableData.cs
index 9353a8530..72b894729 100644
--- a/src/Discord.Net.Rest/Entities/Interactions/CommandBase/RestResolvableData.cs
+++ b/src/Discord.Net.Rest/Entities/Interactions/CommandBase/RestResolvableData.cs
@@ -22,7 +22,7 @@ namespace Discord.Rest
internal readonly Dictionary Attachments
= new Dictionary();
- internal async Task PopulateAsync(DiscordRestClient discord, RestGuild guild, IRestMessageChannel channel, T model)
+ internal async Task PopulateAsync(DiscordRestClient discord, RestGuild guild, IRestMessageChannel channel, T model, bool doApiCall)
{
var resolved = model.Resolved.Value;
@@ -38,15 +38,26 @@ namespace Discord.Rest
if (resolved.Channels.IsSpecified)
{
- var channels = await guild.GetChannelsAsync().ConfigureAwait(false);
+ var channels = doApiCall ? await guild.GetChannelsAsync().ConfigureAwait(false) : null;
foreach (var channelModel in resolved.Channels.Value)
{
- var restChannel = channels.FirstOrDefault(x => x.Id == channelModel.Value.Id);
+ if (channels != null)
+ {
+ var guildChannel = channels.FirstOrDefault(x => x.Id == channelModel.Value.Id);
- restChannel.Update(channelModel.Value);
+ guildChannel.Update(channelModel.Value);
- Channels.Add(ulong.Parse(channelModel.Key), restChannel);
+ Channels.Add(ulong.Parse(channelModel.Key), guildChannel);
+ }
+ else
+ {
+ var restChannel = RestChannel.Create(discord, channelModel.Value);
+
+ restChannel.Update(channelModel.Value);
+
+ Channels.Add(ulong.Parse(channelModel.Key), restChannel);
+ }
}
}
@@ -76,7 +87,10 @@ namespace Discord.Rest
{
foreach (var msg in resolved.Messages.Value)
{
- channel ??= (IRestMessageChannel)(Channels.FirstOrDefault(x => x.Key == msg.Value.ChannelId).Value ?? await discord.GetChannelAsync(msg.Value.ChannelId).ConfigureAwait(false));
+ channel ??= (IRestMessageChannel)(Channels.FirstOrDefault(x => x.Key == msg.Value.ChannelId).Value
+ ?? (doApiCall
+ ? await discord.GetChannelAsync(msg.Value.ChannelId).ConfigureAwait(false)
+ : null));
RestUser author;
diff --git a/src/Discord.Net.Rest/Entities/Interactions/ContextMenuCommands/MessageCommands/RestMessageCommand.cs b/src/Discord.Net.Rest/Entities/Interactions/ContextMenuCommands/MessageCommands/RestMessageCommand.cs
index 609fe0829..34c664b09 100644
--- a/src/Discord.Net.Rest/Entities/Interactions/ContextMenuCommands/MessageCommands/RestMessageCommand.cs
+++ b/src/Discord.Net.Rest/Entities/Interactions/ContextMenuCommands/MessageCommands/RestMessageCommand.cs
@@ -20,22 +20,22 @@ namespace Discord.Rest
}
- internal new static async Task CreateAsync(DiscordRestClient client, Model model)
+ internal new static async Task CreateAsync(DiscordRestClient client, Model model, bool doApiCall)
{
var entity = new RestMessageCommand(client, model);
- await entity.UpdateAsync(client, model).ConfigureAwait(false);
+ await entity.UpdateAsync(client, model, doApiCall).ConfigureAwait(false);
return entity;
}
- internal override async Task UpdateAsync(DiscordRestClient client, Model model)
+ internal override async Task UpdateAsync(DiscordRestClient client, Model model, bool doApiCall)
{
- await base.UpdateAsync(client, model).ConfigureAwait(false);
+ await base.UpdateAsync(client, model, doApiCall).ConfigureAwait(false);
var dataModel = model.Data.IsSpecified
? (DataModel)model.Data.Value
: null;
- Data = await RestMessageCommandData.CreateAsync(client, dataModel, Guild, Channel).ConfigureAwait(false);
+ Data = await RestMessageCommandData.CreateAsync(client, dataModel, Guild, Channel, doApiCall).ConfigureAwait(false);
}
//IMessageCommandInteraction
diff --git a/src/Discord.Net.Rest/Entities/Interactions/ContextMenuCommands/MessageCommands/RestMessageCommandData.cs b/src/Discord.Net.Rest/Entities/Interactions/ContextMenuCommands/MessageCommands/RestMessageCommandData.cs
index 127d539d9..d2968a38a 100644
--- a/src/Discord.Net.Rest/Entities/Interactions/ContextMenuCommands/MessageCommands/RestMessageCommandData.cs
+++ b/src/Discord.Net.Rest/Entities/Interactions/ContextMenuCommands/MessageCommands/RestMessageCommandData.cs
@@ -23,15 +23,15 @@ namespace Discord.Rest
/// Note Not implemented for
///
public override IReadOnlyCollection Options
- => throw new System.NotImplementedException();
+ => throw new NotImplementedException();
internal RestMessageCommandData(DiscordRestClient client, Model model)
: base(client, model) { }
- internal new static async Task CreateAsync(DiscordRestClient client, Model model, RestGuild guild, IRestMessageChannel channel)
+ internal new static async Task CreateAsync(DiscordRestClient client, Model model, RestGuild guild, IRestMessageChannel channel, bool doApiCall)
{
var entity = new RestMessageCommandData(client, model);
- await entity.UpdateAsync(client, model, guild, channel).ConfigureAwait(false);
+ await entity.UpdateAsync(client, model, guild, channel, doApiCall).ConfigureAwait(false);
return entity;
}
diff --git a/src/Discord.Net.Rest/Entities/Interactions/ContextMenuCommands/UserCommands/RestUserCommand.cs b/src/Discord.Net.Rest/Entities/Interactions/ContextMenuCommands/UserCommands/RestUserCommand.cs
index 7f55fd61b..91319a649 100644
--- a/src/Discord.Net.Rest/Entities/Interactions/ContextMenuCommands/UserCommands/RestUserCommand.cs
+++ b/src/Discord.Net.Rest/Entities/Interactions/ContextMenuCommands/UserCommands/RestUserCommand.cs
@@ -23,22 +23,22 @@ namespace Discord.Rest
{
}
- internal new static async Task CreateAsync(DiscordRestClient client, Model model)
+ internal new static async Task CreateAsync(DiscordRestClient client, Model model, bool doApiCall)
{
var entity = new RestUserCommand(client, model);
- await entity.UpdateAsync(client, model).ConfigureAwait(false);
+ await entity.UpdateAsync(client, model, doApiCall).ConfigureAwait(false);
return entity;
}
- internal override async Task UpdateAsync(DiscordRestClient client, Model model)
+ internal override async Task UpdateAsync(DiscordRestClient client, Model model, bool doApiCall)
{
- await base.UpdateAsync(client, model).ConfigureAwait(false);
+ await base.UpdateAsync(client, model, doApiCall).ConfigureAwait(false);
var dataModel = model.Data.IsSpecified
? (DataModel)model.Data.Value
: null;
- Data = await RestUserCommandData.CreateAsync(client, dataModel, Guild, Channel).ConfigureAwait(false);
+ Data = await RestUserCommandData.CreateAsync(client, dataModel, Guild, Channel, doApiCall).ConfigureAwait(false);
}
//IUserCommandInteractionData
diff --git a/src/Discord.Net.Rest/Entities/Interactions/ContextMenuCommands/UserCommands/RestUserCommandData.cs b/src/Discord.Net.Rest/Entities/Interactions/ContextMenuCommands/UserCommands/RestUserCommandData.cs
index e18499d42..61b291f7c 100644
--- a/src/Discord.Net.Rest/Entities/Interactions/ContextMenuCommands/UserCommands/RestUserCommandData.cs
+++ b/src/Discord.Net.Rest/Entities/Interactions/ContextMenuCommands/UserCommands/RestUserCommandData.cs
@@ -26,10 +26,10 @@ namespace Discord.Rest
internal RestUserCommandData(DiscordRestClient client, Model model)
: base(client, model) { }
- internal new static async Task CreateAsync(DiscordRestClient client, Model model, RestGuild guild, IRestMessageChannel channel)
+ internal new static async Task CreateAsync(DiscordRestClient client, Model model, RestGuild guild, IRestMessageChannel channel, bool doApiCall)
{
var entity = new RestUserCommandData(client, model);
- await entity.UpdateAsync(client, model, guild, channel).ConfigureAwait(false);
+ await entity.UpdateAsync(client, model, guild, channel, doApiCall).ConfigureAwait(false);
return entity;
}
diff --git a/src/Discord.Net.Rest/Entities/Interactions/InteractionHelper.cs b/src/Discord.Net.Rest/Entities/Interactions/InteractionHelper.cs
index 74d7953ad..522c098e6 100644
--- a/src/Discord.Net.Rest/Entities/Interactions/InteractionHelper.cs
+++ b/src/Discord.Net.Rest/Entities/Interactions/InteractionHelper.cs
@@ -369,7 +369,7 @@ namespace Discord.Rest
#endregion
#region Responses
- public static async Task ModifyFollowupMessageAsync(BaseDiscordClient client, RestFollowupMessage message, Action func,
+ public static async Task ModifyFollowupMessageAsync(BaseDiscordClient client, RestFollowupMessage message, Action func,
RequestOptions options = null)
{
var args = new MessageProperties();
@@ -411,7 +411,7 @@ namespace Discord.Rest
}
public static async Task DeleteFollowupMessageAsync(BaseDiscordClient client, RestFollowupMessage message, RequestOptions options = null)
=> await client.ApiClient.DeleteInteractionFollowupMessageAsync(message.Id, message.Token, options);
- public static async Task ModifyInteractionResponseAsync(BaseDiscordClient client, string token, Action func,
+ public static async Task ModifyInteractionResponseAsync(BaseDiscordClient client, string token, Action func,
RequestOptions options = null)
{
var args = new MessageProperties();
diff --git a/src/Discord.Net.Rest/Entities/Interactions/InteractionProperties.cs b/src/Discord.Net.Rest/Entities/Interactions/InteractionProperties.cs
new file mode 100644
index 000000000..03750d7d9
--- /dev/null
+++ b/src/Discord.Net.Rest/Entities/Interactions/InteractionProperties.cs
@@ -0,0 +1,101 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Discord.Rest
+{
+ ///
+ /// Represents a class that contains data present in all interactions to evaluate against at rest-interaction creation.
+ ///
+ public readonly struct InteractionProperties
+ {
+ ///
+ /// The type of this interaction.
+ ///
+ public InteractionType Type { get; }
+
+ ///
+ /// Gets the type of application command this interaction represents.
+ ///
+ ///
+ /// This will be if the is not .
+ ///
+ public ApplicationCommandType? CommandType { get; }
+
+ ///
+ /// Gets the name of the interaction.
+ ///
+ ///
+ /// This will be if the is not .
+ ///
+ public string Name { get; } = string.Empty;
+
+ ///
+ /// Gets the custom ID of the interaction.
+ ///
+ ///
+ /// This will be if the is not or .
+ ///
+ public string CustomId { get; } = string.Empty;
+
+ ///
+ /// Gets the guild ID of the interaction.
+ ///
+ ///
+ /// This will be if this interaction was not executed in a guild.
+ ///
+ public ulong? GuildId { get; }
+
+ ///
+ /// Gets the channel ID of the interaction.
+ ///
+ ///
+ /// This will be if this interaction is .
+ ///
+ public ulong? ChannelId { get; }
+
+ internal InteractionProperties(API.Interaction model)
+ {
+ Type = model.Type;
+ CommandType = null;
+
+ if (model.GuildId.IsSpecified)
+ GuildId = model.GuildId.Value;
+ else
+ GuildId = null;
+
+ if (model.ChannelId.IsSpecified)
+ ChannelId = model.ChannelId.Value;
+ else
+ ChannelId = null;
+
+ switch (Type)
+ {
+ case InteractionType.ApplicationCommand:
+ {
+ var data = (API.ApplicationCommandInteractionData)model.Data;
+
+ CommandType = data.Type;
+ Name = data.Name;
+ }
+ break;
+ case InteractionType.MessageComponent:
+ {
+ var data = (API.MessageComponentInteractionData)model.Data;
+
+ CustomId = data.CustomId;
+ }
+ break;
+ case InteractionType.ModalSubmit:
+ {
+ var data = (API.ModalInteractionData)model.Data;
+
+ CustomId = data.CustomId;
+ }
+ break;
+ }
+ }
+ }
+}
diff --git a/src/Discord.Net.Rest/Entities/Interactions/MessageComponents/RestMessageComponent.cs b/src/Discord.Net.Rest/Entities/Interactions/MessageComponents/RestMessageComponent.cs
index 002510eac..e0eab6051 100644
--- a/src/Discord.Net.Rest/Entities/Interactions/MessageComponents/RestMessageComponent.cs
+++ b/src/Discord.Net.Rest/Entities/Interactions/MessageComponents/RestMessageComponent.cs
@@ -37,15 +37,15 @@ namespace Discord.Rest
Data = new RestMessageComponentData(dataModel);
}
- internal new static async Task CreateAsync(DiscordRestClient client, Model model)
+ internal new static async Task CreateAsync(DiscordRestClient client, Model model, bool doApiCall)
{
var entity = new RestMessageComponent(client, model);
- await entity.UpdateAsync(client, model).ConfigureAwait(false);
+ await entity.UpdateAsync(client, model, doApiCall).ConfigureAwait(false);
return entity;
}
- internal override async Task UpdateAsync(DiscordRestClient discord, Model model)
+ internal override async Task UpdateAsync(DiscordRestClient discord, Model model, bool doApiCall)
{
- await base.UpdateAsync(discord, model).ConfigureAwait(false);
+ await base.UpdateAsync(discord, model, doApiCall).ConfigureAwait(false);
if (model.Message.IsSpecified && model.ChannelId.IsSpecified)
{
diff --git a/src/Discord.Net.Rest/Entities/Interactions/Modals/RestModal.cs b/src/Discord.Net.Rest/Entities/Interactions/Modals/RestModal.cs
index 5f54fe051..9229b63b5 100644
--- a/src/Discord.Net.Rest/Entities/Interactions/Modals/RestModal.cs
+++ b/src/Discord.Net.Rest/Entities/Interactions/Modals/RestModal.cs
@@ -26,10 +26,10 @@ namespace Discord.Rest
Data = new RestModalData(dataModel);
}
- internal new static async Task CreateAsync(DiscordRestClient client, ModelBase model)
+ internal new static async Task CreateAsync(DiscordRestClient client, ModelBase model, bool doApiCall)
{
var entity = new RestModal(client, model);
- await entity.UpdateAsync(client, model);
+ await entity.UpdateAsync(client, model, doApiCall);
return entity;
}
diff --git a/src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommand.cs b/src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommand.cs
index 9e2bab2c2..667609ef4 100644
--- a/src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommand.cs
+++ b/src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommand.cs
@@ -65,8 +65,7 @@ namespace Discord.Rest
: ImmutableArray.Create();
IsEnabledInDm = model.DmPermission.GetValueOrDefault(true).GetValueOrDefault(true);
- DefaultMemberPermissions = model.DefaultMemberPermission.IsSpecified
- ? new GuildPermissions((ulong)model.DefaultMemberPermission.Value) : GuildPermissions.None;
+ DefaultMemberPermissions = new GuildPermissions((ulong)model.DefaultMemberPermission.GetValueOrDefault(0).GetValueOrDefault(0));
}
///
diff --git a/src/Discord.Net.Rest/Entities/Interactions/RestInteraction.cs b/src/Discord.Net.Rest/Entities/Interactions/RestInteraction.cs
index 8a8921abe..43d13f521 100644
--- a/src/Discord.Net.Rest/Entities/Interactions/RestInteraction.cs
+++ b/src/Discord.Net.Rest/Entities/Interactions/RestInteraction.cs
@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
-using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Model = Discord.API.Interaction;
@@ -16,6 +15,10 @@ namespace Discord.Rest
///
public abstract class RestInteraction : RestEntity, IDiscordInteraction
{
+ // Added so channel & guild methods don't need a client reference
+ private Func> _getChannel;
+ private Func> _getGuild;
+
///
public InteractionType Type { get; private set; }
@@ -31,6 +34,10 @@ namespace Discord.Rest
///
/// Gets the user who invoked the interaction.
///
+ ///
+ /// If this user is an and is set to false,
+ /// will return
+ ///
public RestUser User { get; private set; }
///
@@ -51,19 +58,36 @@ namespace Discord.Rest
///
/// Gets the channel that this interaction was executed in.
///
+ ///
+ /// This property will be if is set to false.
+ /// Call to set this property and get the interaction channel.
+ ///
public IRestMessageChannel Channel { get; private set; }
+ ///
+ public ulong? ChannelId { get; private set; }
+
///
- /// Gets the guild this interaction was executed in.
+ /// Gets the guild this interaction was executed in if applicable.
///
+ ///
+ /// This property will be if is set to false
+ /// or if the interaction was not executed in a guild.
+ ///
public RestGuild Guild { get; private set; }
+ ///
+ public ulong? GuildId { get; private set; }
+
///
public bool HasResponded { get; protected set; }
///
public bool IsDMInteraction { get; private set; }
+ ///
+ public ulong ApplicationId { get; private set; }
+
internal RestInteraction(BaseDiscordClient discord, ulong id)
: base(discord, id)
{
@@ -72,11 +96,11 @@ namespace Discord.Rest
: DateTime.UtcNow;
}
- internal static async Task CreateAsync(DiscordRestClient client, Model model)
+ internal static async Task CreateAsync(DiscordRestClient client, Model model, bool doApiCall)
{
if(model.Type == InteractionType.Ping)
{
- return await RestPingInteraction.CreateAsync(client, model);
+ return await RestPingInteraction.CreateAsync(client, model, doApiCall);
}
if (model.Type == InteractionType.ApplicationCommand)
@@ -90,65 +114,97 @@ namespace Discord.Rest
return dataModel.Type switch
{
- ApplicationCommandType.Slash => await RestSlashCommand.CreateAsync(client, model).ConfigureAwait(false),
- ApplicationCommandType.Message => await RestMessageCommand.CreateAsync(client, model).ConfigureAwait(false),
- ApplicationCommandType.User => await RestUserCommand.CreateAsync(client, model).ConfigureAwait(false),
+ ApplicationCommandType.Slash => await RestSlashCommand.CreateAsync(client, model, doApiCall).ConfigureAwait(false),
+ ApplicationCommandType.Message => await RestMessageCommand.CreateAsync(client, model, doApiCall).ConfigureAwait(false),
+ ApplicationCommandType.User => await RestUserCommand.CreateAsync(client, model, doApiCall).ConfigureAwait(false),
_ => null
};
}
if (model.Type == InteractionType.MessageComponent)
- return await RestMessageComponent.CreateAsync(client, model).ConfigureAwait(false);
+ return await RestMessageComponent.CreateAsync(client, model, doApiCall).ConfigureAwait(false);
if (model.Type == InteractionType.ApplicationCommandAutocomplete)
- return await RestAutocompleteInteraction.CreateAsync(client, model).ConfigureAwait(false);
+ return await RestAutocompleteInteraction.CreateAsync(client, model, doApiCall).ConfigureAwait(false);
if (model.Type == InteractionType.ModalSubmit)
- return await RestModal.CreateAsync(client, model).ConfigureAwait(false);
+ return await RestModal.CreateAsync(client, model, doApiCall).ConfigureAwait(false);
return null;
}
- internal virtual async Task UpdateAsync(DiscordRestClient discord, Model model)
+ internal virtual async Task UpdateAsync(DiscordRestClient discord, Model model, bool doApiCall)
{
- IsDMInteraction = !model.GuildId.IsSpecified;
+ ChannelId = model.ChannelId.IsSpecified
+ ? model.ChannelId.Value
+ : null;
+
+ GuildId = model.GuildId.IsSpecified
+ ? model.GuildId.Value
+ : null;
+
+ IsDMInteraction = GuildId is null;
Data = model.Data.IsSpecified
? model.Data.Value
: null;
+
Token = model.Token;
Version = model.Version;
Type = model.Type;
+ ApplicationId = model.ApplicationId;
- if(Guild == null && model.GuildId.IsSpecified)
+ if (Guild is null && GuildId is not null)
{
- Guild = await discord.GetGuildAsync(model.GuildId.Value);
+ if (doApiCall)
+ Guild = await discord.GetGuildAsync(GuildId.Value);
+ else
+ {
+ Guild = null;
+ _getGuild = async (opt, ul) => await discord.GetGuildAsync(ul, opt);
+ }
}
- if (User == null)
+ if (User is null)
{
- if (model.Member.IsSpecified && model.GuildId.IsSpecified)
+ if (model.Member.IsSpecified && GuildId is not null)
{
- User = RestGuildUser.Create(Discord, Guild, model.Member.Value);
+ User = RestGuildUser.Create(Discord, Guild, model.Member.Value, GuildId);
}
else
{
User = RestUser.Create(Discord, model.User.Value);
}
}
+
- if(Channel == null && model.ChannelId.IsSpecified)
+ if (Channel is null && ChannelId is not null)
{
try
{
- Channel = (IRestMessageChannel)await discord.GetChannelAsync(model.ChannelId.Value);
+ if (doApiCall)
+ Channel = (IRestMessageChannel)await discord.GetChannelAsync(ChannelId.Value);
+ else
+ {
+ Channel = null;
+
+ _getChannel = async (opt, ul) =>
+ {
+ if (Guild is null)
+ return (IRestMessageChannel)await discord.GetChannelAsync(ul, opt);
+
+ // get a guild channel if the guild is set.
+ return (IRestMessageChannel)await Guild.GetChannelAsync(ul, opt);
+ };
+ }
}
- catch(HttpException x) when(x.DiscordCode == DiscordErrorCode.MissingPermissions) { } // ignore
+ catch (HttpException x) when (x.DiscordCode == DiscordErrorCode.MissingPermissions) { } // ignore
}
UserLocale = model.UserLocale.IsSpecified
- ? model.UserLocale.Value
- : null;
+ ? model.UserLocale.Value
+ : null;
+
GuildLocale = model.GuildLocale.IsSpecified
? model.GuildLocale.Value
: null;
@@ -164,6 +220,54 @@ namespace Discord.Rest
return json.ToString();
}
+ ///
+ /// Gets the channel this interaction was executed in. Will be a DM channel if the interaction was executed in DM.
+ ///
+ ///
+ /// Calling this method successfully will populate the property.
+ /// After this, further calls to this method will no longer call the API, and depend on the value set in .
+ ///
+ /// The request options for this request.
+ /// A Rest channel to send messages to.
+ /// Thrown if no channel can be received.
+ public async Task GetChannelAsync(RequestOptions options = null)
+ {
+ if (Channel is not null)
+ return Channel;
+
+ if (IsDMInteraction)
+ {
+ Channel = await User.CreateDMChannelAsync(options);
+ }
+ else if (ChannelId is not null)
+ {
+ Channel = await _getChannel(options, ChannelId.Value) ?? throw new InvalidOperationException("The interaction channel was not able to be retrieved.");
+ _getChannel = null; // get rid of it, we don't need it anymore.
+ }
+
+ return Channel;
+ }
+
+ ///
+ /// Gets the guild this interaction was executed in if applicable.
+ ///
+ ///
+ /// Calling this method successfully will populate the property.
+ /// After this, further calls to this method will no longer call the API, and depend on the value set in .
+ ///
+ /// The request options for this request.
+ /// The guild this interaction was executed in. if the interaction was executed inside DM.
+ public async Task GetGuildAsync(RequestOptions options)
+ {
+ if (GuildId is null)
+ return null;
+
+ Guild ??= await _getGuild(options, GuildId.Value);
+ _getGuild = null; // get rid of it, we don't need it anymore.
+
+ return Guild;
+ }
+
///
public abstract string Defer(bool ephemeral = false, RequestOptions options = null);
///
@@ -333,7 +437,6 @@ namespace Discord.Rest
=> await FollowupWithFilesAsync(attachments, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options).ConfigureAwait(false);
///
Task IDiscordInteraction.RespondWithFilesAsync(IEnumerable attachments, string text, Embed[] embeds, bool isTTS, bool ephemeral, AllowedMentions allowedMentions, MessageComponent components, Embed embed, RequestOptions options) => throw new NotSupportedException("REST-Based interactions don't support files.");
- ///
#if NETCOREAPP3_0_OR_GREATER != true
///
Task IDiscordInteraction.RespondWithFileAsync(Stream fileStream, string fileName, string text, Embed[] embeds, bool isTTS, bool ephemeral, AllowedMentions allowedMentions, MessageComponent components, Embed embed, RequestOptions options) => throw new NotSupportedException("REST-Based interactions don't support files.");
diff --git a/src/Discord.Net.Rest/Entities/Interactions/RestPingInteraction.cs b/src/Discord.Net.Rest/Entities/Interactions/RestPingInteraction.cs
index bd15bc2d3..47e1a3b0f 100644
--- a/src/Discord.Net.Rest/Entities/Interactions/RestPingInteraction.cs
+++ b/src/Discord.Net.Rest/Entities/Interactions/RestPingInteraction.cs
@@ -18,10 +18,10 @@ namespace Discord.Rest
{
}
- internal static new async Task CreateAsync(DiscordRestClient client, Model model)
+ internal static new async Task CreateAsync(DiscordRestClient client, Model model, bool doApiCall)
{
var entity = new RestPingInteraction(client, model.Id);
- await entity.UpdateAsync(client, model);
+ await entity.UpdateAsync(client, model, doApiCall);
return entity;
}
diff --git a/src/Discord.Net.Rest/Entities/Interactions/SlashCommands/RestAutocompleteInteraction.cs b/src/Discord.Net.Rest/Entities/Interactions/SlashCommands/RestAutocompleteInteraction.cs
index 24dbae37a..27c536240 100644
--- a/src/Discord.Net.Rest/Entities/Interactions/SlashCommands/RestAutocompleteInteraction.cs
+++ b/src/Discord.Net.Rest/Entities/Interactions/SlashCommands/RestAutocompleteInteraction.cs
@@ -32,10 +32,10 @@ namespace Discord.Rest
Data = new RestAutocompleteInteractionData(dataModel);
}
- internal new static async Task CreateAsync(DiscordRestClient client, Model model)
+ internal new static async Task CreateAsync(DiscordRestClient client, Model model, bool doApiCall)
{
var entity = new RestAutocompleteInteraction(client, model);
- await entity.UpdateAsync(client, model).ConfigureAwait(false);
+ await entity.UpdateAsync(client, model, doApiCall).ConfigureAwait(false);
return entity;
}
diff --git a/src/Discord.Net.Rest/Entities/Interactions/SlashCommands/RestSlashCommand.cs b/src/Discord.Net.Rest/Entities/Interactions/SlashCommands/RestSlashCommand.cs
index 21184fcf6..f955e7855 100644
--- a/src/Discord.Net.Rest/Entities/Interactions/SlashCommands/RestSlashCommand.cs
+++ b/src/Discord.Net.Rest/Entities/Interactions/SlashCommands/RestSlashCommand.cs
@@ -23,22 +23,22 @@ namespace Discord.Rest
{
}
- internal new static async Task CreateAsync(DiscordRestClient client, Model model)
+ internal new static async Task CreateAsync(DiscordRestClient client, Model model, bool doApiCall)
{
var entity = new RestSlashCommand(client, model);
- await entity.UpdateAsync(client, model).ConfigureAwait(false);
+ await entity.UpdateAsync(client, model, doApiCall).ConfigureAwait(false);
return entity;
}
- internal override async Task UpdateAsync(DiscordRestClient client, Model model)
+ internal override async Task UpdateAsync(DiscordRestClient client, Model model, bool doApiCall)
{
- await base.UpdateAsync(client, model).ConfigureAwait(false);
+ await base.UpdateAsync(client, model, doApiCall).ConfigureAwait(false);
var dataModel = model.Data.IsSpecified
? (DataModel)model.Data.Value
: null;
- Data = await RestSlashCommandData.CreateAsync(client, dataModel, Guild, Channel).ConfigureAwait(false);
+ Data = await RestSlashCommandData.CreateAsync(client, dataModel, Guild, Channel, doApiCall).ConfigureAwait(false);
}
//ISlashCommandInteraction
diff --git a/src/Discord.Net.Rest/Entities/Interactions/SlashCommands/RestSlashCommandData.cs b/src/Discord.Net.Rest/Entities/Interactions/SlashCommands/RestSlashCommandData.cs
index f967cc628..19a819ab4 100644
--- a/src/Discord.Net.Rest/Entities/Interactions/SlashCommands/RestSlashCommandData.cs
+++ b/src/Discord.Net.Rest/Entities/Interactions/SlashCommands/RestSlashCommandData.cs
@@ -14,15 +14,15 @@ namespace Discord.Rest
internal RestSlashCommandData(DiscordRestClient client, Model model)
: base(client, model) { }
- internal static new async Task CreateAsync(DiscordRestClient client, Model model, RestGuild guild, IRestMessageChannel channel)
+ internal static new async Task CreateAsync(DiscordRestClient client, Model model, RestGuild guild, IRestMessageChannel channel, bool doApiCall)
{
var entity = new RestSlashCommandData(client, model);
- await entity.UpdateAsync(client, model, guild, channel).ConfigureAwait(false);
+ await entity.UpdateAsync(client, model, guild, channel, doApiCall).ConfigureAwait(false);
return entity;
}
- internal override async Task UpdateAsync(DiscordRestClient client, Model model, RestGuild guild, IRestMessageChannel channel)
+ internal override async Task UpdateAsync(DiscordRestClient client, Model model, RestGuild guild, IRestMessageChannel channel, bool doApiCall)
{
- await base.UpdateAsync(client, model, guild, channel).ConfigureAwait(false);
+ await base.UpdateAsync(client, model, guild, channel, doApiCall).ConfigureAwait(false);
Options = model.Options.IsSpecified
? model.Options.Value.Select(x => new RestSlashCommandDataOption(this, x)).ToImmutableArray()
diff --git a/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs
index c48a60aac..69e038fd2 100644
--- a/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs
+++ b/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs
@@ -144,7 +144,8 @@ namespace Discord.Rest
{
GuildId = model.Reference.Value.GuildId,
InternalChannelId = model.Reference.Value.ChannelId,
- MessageId = model.Reference.Value.MessageId
+ MessageId = model.Reference.Value.MessageId,
+ FailIfNotExists = model.Reference.Value.FailIfNotExists
};
}
diff --git a/src/Discord.Net.Rest/Entities/Roles/RestRole.cs b/src/Discord.Net.Rest/Entities/Roles/RestRole.cs
index a2ad4fd77..df629bec7 100644
--- a/src/Discord.Net.Rest/Entities/Roles/RestRole.cs
+++ b/src/Discord.Net.Rest/Entities/Roles/RestRole.cs
@@ -25,7 +25,7 @@ namespace Discord.Rest
public string Name { get; private set; }
///
public string Icon { get; private set; }
- /// />
+ ///
public Emoji Emoji { get; private set; }
///
public GuildPermissions Permissions { get; private set; }
diff --git a/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs b/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs
index 0a4a33099..6c311b6b5 100644
--- a/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs
+++ b/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs
@@ -35,7 +35,7 @@ namespace Discord.Rest
///
public DateTimeOffset? PremiumSince => DateTimeUtils.FromTicks(_premiumSinceTicks);
///
- public ulong GuildId => Guild.Id;
+ public ulong GuildId { get; }
///
public bool? IsPending { get; private set; }
///
@@ -80,14 +80,16 @@ namespace Discord.Rest
///
public DateTimeOffset? JoinedAt => DateTimeUtils.FromTicks(_joinedAtTicks);
- internal RestGuildUser(BaseDiscordClient discord, IGuild guild, ulong id)
+ internal RestGuildUser(BaseDiscordClient discord, IGuild guild, ulong id, ulong? guildId = null)
: base(discord, id)
{
- Guild = guild;
+ if (guild is not null)
+ Guild = guild;
+ GuildId = guildId ?? Guild.Id;
}
- internal static RestGuildUser Create(BaseDiscordClient discord, IGuild guild, Model model)
+ internal static RestGuildUser Create(BaseDiscordClient discord, IGuild guild, Model model, ulong? guildId = null)
{
- var entity = new RestGuildUser(discord, guild, model.User.Id);
+ var entity = new RestGuildUser(discord, guild, model.User.Id, guildId);
entity.Update(model);
return entity;
}
@@ -116,7 +118,7 @@ namespace Discord.Rest
private void UpdateRoles(ulong[] roleIds)
{
var roles = ImmutableArray.CreateBuilder(roleIds.Length + 1);
- roles.Add(Guild.Id);
+ roles.Add(GuildId);
for (int i = 0; i < roleIds.Length; i++)
roles.Add(roleIds[i]);
_roleIds = roles.ToImmutable();
diff --git a/src/Discord.Net.Rest/Extensions/EntityExtensions.cs b/src/Discord.Net.Rest/Extensions/EntityExtensions.cs
index 4062cda3d..f5a88486b 100644
--- a/src/Discord.Net.Rest/Extensions/EntityExtensions.cs
+++ b/src/Discord.Net.Rest/Extensions/EntityExtensions.cs
@@ -87,6 +87,7 @@ namespace Discord.Rest
ChannelId = entity.InternalChannelId,
GuildId = entity.GuildId,
MessageId = entity.MessageId,
+ FailIfNotExists = entity.FailIfNotExists
};
}
public static IEnumerable EnumerateMentionTypes(this AllowedMentionTypes mentionTypes)
diff --git a/src/Discord.Net.Rest/Extensions/StringExtensions.cs b/src/Discord.Net.Rest/Extensions/StringExtensions.cs
new file mode 100644
index 000000000..4981a4298
--- /dev/null
+++ b/src/Discord.Net.Rest/Extensions/StringExtensions.cs
@@ -0,0 +1,47 @@
+using Discord.Net.Converters;
+using Newtonsoft.Json;
+using System.Linq;
+using System;
+
+namespace Discord.Rest
+{
+ ///
+ /// Responsible for formatting certain entities as Json , to reuse later on.
+ ///
+ public static class StringExtensions
+ {
+ private static Lazy _settings = new(() =>
+ {
+ var serializer = new JsonSerializerSettings()
+ {
+ ContractResolver = new DiscordContractResolver()
+ };
+ serializer.Converters.Add(new EmbedTypeConverter());
+ return serializer;
+ });
+
+ ///
+ /// Gets a Json formatted from an .
+ ///
+ ///
+ /// See to parse Json back into embed.
+ ///
+ /// The builder to format as Json .
+ /// The formatting in which the Json will be returned.
+ /// A Json containing the data from the .
+ public static string ToJsonString(this EmbedBuilder builder, Formatting formatting = Formatting.Indented)
+ => ToJsonString(builder.Build(), formatting);
+
+ ///
+ /// Gets a Json formatted from an .
+ ///
+ ///
+ /// See to parse Json back into embed.
+ ///
+ /// The embed to format as Json .
+ /// The formatting in which the Json will be returned.
+ /// A Json containing the data from the .
+ public static string ToJsonString(this Embed embed, Formatting formatting = Formatting.Indented)
+ => JsonConvert.SerializeObject(embed.ToModel(), formatting, _settings.Value);
+ }
+}
diff --git a/src/Discord.Net.Rest/Net/Converters/UInt64Converter.cs b/src/Discord.Net.Rest/Net/Converters/UInt64Converter.cs
index 27cbe9290..d7655a30a 100644
--- a/src/Discord.Net.Rest/Net/Converters/UInt64Converter.cs
+++ b/src/Discord.Net.Rest/Net/Converters/UInt64Converter.cs
@@ -1,4 +1,4 @@
-using Newtonsoft.Json;
+using Newtonsoft.Json;
using System;
using System.Globalization;
@@ -14,7 +14,7 @@ namespace Discord.Net.Converters
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
- return ulong.Parse((string)reader.Value, NumberStyles.None, CultureInfo.InvariantCulture);
+ return ulong.Parse(reader.Value?.ToString(), NumberStyles.None, CultureInfo.InvariantCulture);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
diff --git a/src/Discord.Net.Rest/Net/ED25519/CryptoBytes.cs b/src/Discord.Net.Rest/Net/ED25519/CryptoBytes.cs
index cfd64104d..43cd3f902 100644
--- a/src/Discord.Net.Rest/Net/ED25519/CryptoBytes.cs
+++ b/src/Discord.Net.Rest/Net/ED25519/CryptoBytes.cs
@@ -243,7 +243,7 @@ namespace Discord.Net.ED25519
///
/// // Decode a base58-encoded string into byte array
///
- /// Base58 data string
+ /// Base58 data string
/// Byte array
public static byte[] Base58Decode(string input)
{
diff --git a/src/Discord.Net.Rest/Net/Queue/RequestQueue.cs b/src/Discord.Net.Rest/Net/Queue/RequestQueue.cs
index 75e79eec2..4915a5c39 100644
--- a/src/Discord.Net.Rest/Net/Queue/RequestQueue.cs
+++ b/src/Discord.Net.Rest/Net/Queue/RequestQueue.cs
@@ -60,14 +60,9 @@ namespace Discord.Net.Queue
_clearToken?.Cancel();
_clearToken?.Dispose();
_clearToken = new CancellationTokenSource();
- if (_parentToken != null)
- {
- _requestCancelTokenSource?.Dispose();
- _requestCancelTokenSource = CancellationTokenSource.CreateLinkedTokenSource(_clearToken.Token, _parentToken);
- _requestCancelToken = _requestCancelTokenSource.Token;
- }
- else
- _requestCancelToken = _clearToken.Token;
+ _requestCancelTokenSource?.Dispose();
+ _requestCancelTokenSource = CancellationTokenSource.CreateLinkedTokenSource(_clearToken.Token, _parentToken);
+ _requestCancelToken = _requestCancelTokenSource.Token;
}
finally { _tokenLock.Release(); }
}
diff --git a/src/Discord.Net.WebSocket/Discord.Net.WebSocket.csproj b/src/Discord.Net.WebSocket/Discord.Net.WebSocket.csproj
index 2ce89be5b..a4355bc02 100644
--- a/src/Discord.Net.WebSocket/Discord.Net.WebSocket.csproj
+++ b/src/Discord.Net.WebSocket/Discord.Net.WebSocket.csproj
@@ -8,6 +8,8 @@
net6.0;net5.0;net461;netstandard2.0;netstandard2.1
net6.0;net5.0;netstandard2.0;netstandard2.1
true
+ 5
+ True
diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs
index aaef4656a..5743d9abd 100644
--- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs
+++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs
@@ -403,7 +403,7 @@ namespace Discord.WebSocket
/// the snowflake identifier; null if the user is not found.
///
public async ValueTask GetUserAsync(ulong id, RequestOptions options = null)
- => await ClientHelper.GetUserAsync(this, id, options).ConfigureAwait(false);
+ => await ((IDiscordClient)this).GetUserAsync(id, CacheMode.AllowDownload, options).ConfigureAwait(false);
///
/// Clears all cached channels from the client.
///
@@ -1305,13 +1305,13 @@ namespace Discord.WebSocket
user.Update(State, data);
- var cacheableBefore = new Cacheable(before, user.Id, true, () => null);
+ var cacheableBefore = new Cacheable(before, user.Id, true, () => Task.FromResult(null));
await TimedInvokeAsync(_guildMemberUpdatedEvent, nameof(GuildMemberUpdated), cacheableBefore, user).ConfigureAwait(false);
}
else
{
user = guild.AddOrUpdateUser(data);
- var cacheableBefore = new Cacheable(null, user.Id, false, () => null);
+ var cacheableBefore = new Cacheable(null, user.Id, false, () => Task.FromResult(null));
await TimedInvokeAsync(_guildMemberUpdatedEvent, nameof(GuildMemberUpdated), cacheableBefore, user).ConfigureAwait(false);
}
}
@@ -2331,7 +2331,9 @@ namespace Discord.WebSocket
SocketUser user = data.User.IsSpecified
? State.GetOrAddUser(data.User.Value.Id, (_) => SocketGlobalUser.Create(this, State, data.User.Value))
- : guild?.AddOrUpdateUser(data.Member.Value); // null if the bot scope isn't set, so the guild cannot be retrieved.
+ : guild != null
+ ? guild.AddOrUpdateUser(data.Member.Value) // null if the bot scope isn't set, so the guild cannot be retrieved.
+ : State.GetOrAddUser(data.Member.Value.User.Id, (_) => SocketGlobalUser.Create(this, State, data.Member.Value.User));
SocketChannel channel = null;
if(data.ChannelId.IsSpecified)
diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketForumChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketForumChannel.cs
new file mode 100644
index 000000000..bc6e28442
--- /dev/null
+++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketForumChannel.cs
@@ -0,0 +1,128 @@
+using Discord.Rest;
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Model = Discord.API.Channel;
+
+namespace Discord.WebSocket
+{
+ ///
+ /// Represents a forum channel in a guild.
+ ///
+ public class SocketForumChannel : SocketGuildChannel, IForumChannel
+ {
+ ///
+ public bool IsNsfw { get; private set; }
+
+ ///
+ public string Topic { get; private set; }
+
+ ///
+ public ThreadArchiveDuration DefaultAutoArchiveDuration { get; private set; }
+
+ ///
+ public IReadOnlyCollection Tags { get; private set; }
+
+ ///
+ public string Mention => MentionUtils.MentionChannel(Id);
+
+ internal SocketForumChannel(DiscordSocketClient discord, ulong id, SocketGuild guild) : base(discord, id, guild) { }
+
+ internal new static SocketForumChannel Create(SocketGuild guild, ClientState state, Model model)
+ {
+ var entity = new SocketForumChannel(guild.Discord, model.Id, guild);
+ entity.Update(state, model);
+ return entity;
+ }
+
+ internal override void Update(ClientState state, Model model)
+ {
+ base.Update(state, model);
+ IsNsfw = model.Nsfw.GetValueOrDefault(false);
+ Topic = model.Topic.GetValueOrDefault();
+ DefaultAutoArchiveDuration = model.AutoArchiveDuration.GetValueOrDefault(ThreadArchiveDuration.OneDay);
+
+ Tags = model.ForumTags.GetValueOrDefault(Array.Empty()).Select(
+ x => new ForumTag(x.Id, x.Name, x.EmojiId.GetValueOrDefault(null), x.EmojiName.GetValueOrDefault())
+ ).ToImmutableArray();
+ }
+
+ ///
+ public Task CreatePostAsync(string title, ThreadArchiveDuration archiveDuration = ThreadArchiveDuration.OneDay, int? slowmode = null, string text = null, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None)
+ => ThreadHelper.CreatePostAsync(this, Discord, title, archiveDuration, slowmode, text, embed, options, allowedMentions, components, stickers, embeds, flags);
+
+ ///
+ public async Task CreatePostWithFileAsync(string title, string filePath, ThreadArchiveDuration archiveDuration = ThreadArchiveDuration.OneDay,
+ int? slowmode = null, string text = null, Embed embed = null, RequestOptions options = null, bool isSpoiler = false,
+ AllowedMentions allowedMentions = null, MessageComponent components = null,
+ ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None)
+ {
+ using var file = new FileAttachment(filePath, isSpoiler: isSpoiler);
+ return await ThreadHelper.CreatePostAsync(this, Discord, title, new FileAttachment[] { file }, archiveDuration, slowmode, text, embed, options, allowedMentions, components, stickers, embeds, flags).ConfigureAwait(false);
+ }
+
+ ///
+ public async Task CreatePostWithFileAsync(string title, Stream stream, string filename, ThreadArchiveDuration archiveDuration = ThreadArchiveDuration.OneDay,
+ int? slowmode = null, string text = null, Embed embed = null, RequestOptions options = null, bool isSpoiler = false,
+ AllowedMentions allowedMentions = null, MessageComponent components = null,
+ ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None)
+ {
+ using var file = new FileAttachment(stream, filename, isSpoiler: isSpoiler);
+ return await ThreadHelper.CreatePostAsync(this, Discord, title, new FileAttachment[] { file }, archiveDuration, slowmode, text, embed, options, allowedMentions, components, stickers, embeds, flags).ConfigureAwait(false);
+ }
+
+ ///
+ public Task CreatePostWithFileAsync(string title, FileAttachment attachment, ThreadArchiveDuration archiveDuration = ThreadArchiveDuration.OneDay,
+ int? slowmode = null, string text = null, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null,
+ MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None)
+ => ThreadHelper.CreatePostAsync(this, Discord, title, new FileAttachment[] { attachment }, archiveDuration, slowmode, text, embed, options, allowedMentions, components, stickers, embeds, flags);
+
+ ///
+ public Task CreatePostWithFilesAsync(string title, IEnumerable attachments, ThreadArchiveDuration archiveDuration = ThreadArchiveDuration.OneDay,
+ int? slowmode = null, string text = null, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null,
+ MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None)
+ => ThreadHelper.CreatePostAsync(this, Discord, title, attachments, archiveDuration, slowmode, text, embed, options, allowedMentions, components, stickers, embeds, flags);
+
+ ///
+ public Task> GetActiveThreadsAsync(RequestOptions options = null)
+ => ThreadHelper.GetActiveThreadsAsync(Guild, Discord, options);
+
+ ///
+ public Task> GetJoinedPrivateArchivedThreadsAsync(int? limit = null, DateTimeOffset? before = null, RequestOptions options = null)
+ => ThreadHelper.GetJoinedPrivateArchivedThreadsAsync(this, Discord, limit, before, options);
+
+ ///
+ public Task> GetPrivateArchivedThreadsAsync(int? limit = null, DateTimeOffset? before = null, RequestOptions options = null)
+ => ThreadHelper.GetPrivateArchivedThreadsAsync(this, Discord, limit, before, options);
+
+ ///
+ public Task> GetPublicArchivedThreadsAsync(int? limit = null, DateTimeOffset? before = null, RequestOptions options = null)
+ => ThreadHelper.GetPublicArchivedThreadsAsync(this, Discord, limit, before, options);
+
+ #region IForumChannel
+ async Task> IForumChannel.GetActiveThreadsAsync(RequestOptions options)
+ => await GetActiveThreadsAsync(options).ConfigureAwait(false);
+ async Task> IForumChannel.GetPublicArchivedThreadsAsync(int? limit, DateTimeOffset? before, RequestOptions options)
+ => await GetPublicArchivedThreadsAsync(limit, before, options).ConfigureAwait(false);
+ async Task> IForumChannel.GetPrivateArchivedThreadsAsync(int? limit, DateTimeOffset? before, RequestOptions options)
+ => await GetPrivateArchivedThreadsAsync(limit, before, options).ConfigureAwait(false);
+ async Task> IForumChannel.GetJoinedPrivateArchivedThreadsAsync(int? limit, DateTimeOffset? before, RequestOptions options)
+ => await GetJoinedPrivateArchivedThreadsAsync(limit, before, options).ConfigureAwait(false);
+ async Task IForumChannel.CreatePostAsync(string title, ThreadArchiveDuration archiveDuration, int? slowmode, string text, Embed embed, RequestOptions options, AllowedMentions allowedMentions, MessageComponent components, ISticker[] stickers, Embed[] embeds, MessageFlags flags)
+ => await CreatePostAsync(title, archiveDuration, slowmode, text, embed, options, allowedMentions, components, stickers, embeds, flags).ConfigureAwait(false);
+ async Task IForumChannel.CreatePostWithFileAsync(string title, string filePath, ThreadArchiveDuration archiveDuration, int? slowmode, string text, Embed embed, RequestOptions options, bool isSpoiler, AllowedMentions allowedMentions, MessageComponent components, ISticker[] stickers, Embed[] embeds, MessageFlags flags)
+ => await CreatePostWithFileAsync(title, filePath, archiveDuration, slowmode, text, embed, options, isSpoiler, allowedMentions, components, stickers, embeds, flags).ConfigureAwait(false);
+ async Task IForumChannel.CreatePostWithFileAsync(string title, Stream stream, string filename, ThreadArchiveDuration archiveDuration, int? slowmode, string text, Embed embed, RequestOptions options, bool isSpoiler, AllowedMentions allowedMentions, MessageComponent components, ISticker[] stickers, Embed[] embeds, MessageFlags flags)
+ => await CreatePostWithFileAsync(title, stream, filename, archiveDuration, slowmode, text, embed, options, isSpoiler, allowedMentions, components, stickers, embeds, flags).ConfigureAwait(false);
+ async Task IForumChannel.CreatePostWithFileAsync(string title, FileAttachment attachment, ThreadArchiveDuration archiveDuration, int? slowmode, string text, Embed embed, RequestOptions options, AllowedMentions allowedMentions, MessageComponent components, ISticker[] stickers, Embed[] embeds, MessageFlags flags)
+ => await CreatePostWithFileAsync(title, attachment, archiveDuration, slowmode, text, embed, options, allowedMentions, components, stickers, embeds, flags).ConfigureAwait(false);
+ async Task IForumChannel.CreatePostWithFilesAsync(string title, IEnumerable attachments, ThreadArchiveDuration archiveDuration, int? slowmode, string text, Embed embed, RequestOptions options, AllowedMentions allowedMentions, MessageComponent components, ISticker[] stickers, Embed[] embeds, MessageFlags flags)
+ => await CreatePostWithFilesAsync(title, attachments, archiveDuration, slowmode, text, embed, options, allowedMentions, components, stickers, embeds, flags);
+
+ #endregion
+ }
+}
diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs
index 79f02fe1c..16ed7b32d 100644
--- a/src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs
+++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs
@@ -59,6 +59,7 @@ namespace Discord.WebSocket
ChannelType.Category => SocketCategoryChannel.Create(guild, state, model),
ChannelType.PrivateThread or ChannelType.PublicThread or ChannelType.NewsThread => SocketThreadChannel.Create(guild, state, model),
ChannelType.Stage => SocketStageChannel.Create(guild, state, model),
+ ChannelType.Forum => SocketForumChannel.Create(guild, state, model),
_ => new SocketGuildChannel(guild.Discord, model.Id, guild),
};
}
@@ -222,6 +223,8 @@ namespace Discord.WebSocket
#region IChannel
///
+ string IChannel.Name => Name;
+ ///
IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options)
=> ImmutableArray.Create>(Users).ToAsyncEnumerable(); //Overridden in Text/Voice
///
diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketStageChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketStageChannel.cs
index 91bca5054..56cd92185 100644
--- a/src/Discord.Net.WebSocket/Entities/Channels/SocketStageChannel.cs
+++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketStageChannel.cs
@@ -15,7 +15,11 @@ namespace Discord.WebSocket
public class SocketStageChannel : SocketVoiceChannel, IStageChannel
{
///
- public string Topic { get; private set; }
+ ///
+ /// This field is always false for stage channels.
+ ///
+ public override bool IsTextInVoice
+ => false;
///
public StagePrivacyLevel? PrivacyLevel { get; private set; }
@@ -49,19 +53,16 @@ namespace Discord.WebSocket
entity.Update(state, model);
return entity;
}
-
internal void Update(StageInstance model, bool isLive = false)
{
IsLive = isLive;
if (isLive)
{
- Topic = model.Topic;
PrivacyLevel = model.PrivacyLevel;
IsDiscoverableDisabled = model.DiscoverableDisabled;
}
else
{
- Topic = null;
PrivacyLevel = null;
IsDiscoverableDisabled = null;
}
diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs
index e4a299edc..6aece7d78 100644
--- a/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs
+++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs
@@ -40,7 +40,8 @@ namespace Discord.WebSocket
private bool _nsfw;
///
public bool IsNsfw => _nsfw;
-
+ ///
+ public ThreadArchiveDuration DefaultArchiveDuration { get; private set; }
///
public string Mention => MentionUtils.MentionChannel(Id);
///
@@ -76,6 +77,11 @@ namespace Discord.WebSocket
Topic = model.Topic.GetValueOrDefault();
SlowModeInterval = model.SlowMode.GetValueOrDefault(); // some guilds haven't been patched to include this yet?
_nsfw = model.Nsfw.GetValueOrDefault();
+ if (model.AutoArchiveDuration.IsSpecified)
+ DefaultArchiveDuration = model.AutoArchiveDuration.Value;
+ else
+ DefaultArchiveDuration = ThreadArchiveDuration.OneDay;
+ // basic value at channel creation. Shouldn't be called since guild text channels always have this property
}
///
@@ -128,7 +134,7 @@ namespace Discord.WebSocket
#region Messages
///
- public SocketMessage GetCachedMessage(ulong id)
+ public virtual SocketMessage GetCachedMessage(ulong id)
=> _messages?.Get(id);
///
/// Gets a message from this message channel.
@@ -143,7 +149,7 @@ namespace Discord.WebSocket
/// A task that represents an asynchronous get operation for retrieving the message. The task result contains
/// the retrieved message; null if no message is found with the specified identifier.
///
- public async Task GetMessageAsync(ulong id, RequestOptions options = null)
+ public virtual async Task GetMessageAsync(ulong id, RequestOptions options = null)
{
IMessage msg = _messages?.Get(id);
if (msg == null)
@@ -163,7 +169,7 @@ namespace Discord.WebSocket
///
/// Paged collection of messages.
///
- public IAsyncEnumerable> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null)
+ public virtual IAsyncEnumerable> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null)
=> SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, CacheMode.AllowDownload, options);
///
/// Gets a collection of messages in this channel.
@@ -179,7 +185,7 @@ namespace Discord.WebSocket
///
/// Paged collection of messages.
///
- public IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null)
+ public virtual IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null)
=> SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, CacheMode.AllowDownload, options);
///
/// Gets a collection of messages in this channel.
@@ -195,25 +201,25 @@ namespace Discord.WebSocket
///
/// Paged collection of messages.
///
- public IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null)
+ public virtual IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null)
=> SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, CacheMode.AllowDownload, options);
///
- public IReadOnlyCollection GetCachedMessages(int limit = DiscordConfig.MaxMessagesPerBatch)
+ public virtual IReadOnlyCollection GetCachedMessages(int limit = DiscordConfig.MaxMessagesPerBatch)
=> SocketChannelHelper.GetCachedMessages(this, Discord, _messages, null, Direction.Before, limit);
///
- public IReadOnlyCollection GetCachedMessages(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch)
+ public virtual IReadOnlyCollection GetCachedMessages(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch)
=> SocketChannelHelper.GetCachedMessages(this, Discord, _messages, fromMessageId, dir, limit);
///
- public IReadOnlyCollection GetCachedMessages(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch)
+ public virtual IReadOnlyCollection GetCachedMessages(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch)
=> SocketChannelHelper.GetCachedMessages(this, Discord, _messages, fromMessage.Id, dir, limit);
///
- public Task> GetPinnedMessagesAsync(RequestOptions options = null)
+ public virtual Task> GetPinnedMessagesAsync(RequestOptions options = null)
=> ChannelHelper.GetPinnedMessagesAsync(this, Discord, options);
///
/// Message content is too long, length must be less or equal to .
/// The only valid are and .
- public Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null,
+ public virtual Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null,
RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null,
MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None)
=> ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, allowedMentions, messageReference,
@@ -221,7 +227,7 @@ namespace Discord.WebSocket
///
/// The only valid are and .
- public Task SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null,
+ public virtual Task SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null,
RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null,
MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null,
Embed[] embeds = null, MessageFlags flags = MessageFlags.None)
@@ -230,7 +236,7 @@ namespace Discord.WebSocket
///
/// Message content is too long, length must be less or equal to .
/// The only valid are and .
- public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false,
+ public virtual Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false,
Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null,
MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null,
Embed[] embeds = null, MessageFlags flags = MessageFlags.None)
@@ -239,7 +245,7 @@ namespace Discord.WebSocket
///
/// Message content is too long, length must be less or equal to .
/// The only valid are and .
- public Task SendFileAsync(FileAttachment attachment, string text, bool isTTS = false,
+ public virtual Task SendFileAsync(FileAttachment attachment, string text, bool isTTS = false,
Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null,
MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null,
Embed[] embeds = null, MessageFlags flags = MessageFlags.None)
@@ -248,7 +254,7 @@ namespace Discord.WebSocket
///
/// Message content is too long, length must be less or equal to .
/// The only valid are and .
- public Task SendFilesAsync(IEnumerable attachments, string text, bool isTTS = false,
+ public virtual Task SendFilesAsync(IEnumerable attachments, string text, bool isTTS = false,
Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null,
MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null,
Embed[] embeds = null, MessageFlags flags = MessageFlags.None)
@@ -256,28 +262,28 @@ namespace Discord.WebSocket
messageReference, components, stickers, options, embeds, flags);
///
- public Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null)
+ public virtual Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null)
=> ChannelHelper.DeleteMessagesAsync(this, Discord, messages.Select(x => x.Id), options);
///
- public Task DeleteMessagesAsync(IEnumerable messageIds, RequestOptions options = null)
+ public virtual Task DeleteMessagesAsync(IEnumerable messageIds, RequestOptions options = null)
=> ChannelHelper.DeleteMessagesAsync(this, Discord, messageIds, options);
///
- public async Task ModifyMessageAsync(ulong messageId, Action func, RequestOptions options = null)
+ public virtual async Task ModifyMessageAsync(ulong messageId, Action func, RequestOptions options = null)
=> await ChannelHelper.ModifyMessageAsync(this, messageId, func, Discord, options).ConfigureAwait(false);
///
- public Task DeleteMessageAsync(ulong messageId, RequestOptions options = null)
+ public virtual Task DeleteMessageAsync(ulong messageId, RequestOptions options = null)
=> ChannelHelper.DeleteMessageAsync(this, messageId, Discord, options);
///
- public Task DeleteMessageAsync(IMessage message, RequestOptions options = null)
+ public virtual Task DeleteMessageAsync(IMessage message, RequestOptions options = null)
=> ChannelHelper.DeleteMessageAsync(this, message.Id, Discord, options);
///
- public Task TriggerTypingAsync(RequestOptions options = null)
+ public virtual Task TriggerTypingAsync(RequestOptions options = null)
=> ChannelHelper.TriggerTypingAsync(this, Discord, options);
///
- public IDisposable EnterTypingState(RequestOptions options = null)
+ public virtual IDisposable EnterTypingState(RequestOptions options = null)
=> ChannelHelper.EnterTypingState(this, Discord, options);
internal void AddMessage(SocketMessage msg)
diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketVoiceChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketVoiceChannel.cs
index 00003d4ed..7bf65d638 100644
--- a/src/Discord.Net.WebSocket/Entities/Channels/SocketVoiceChannel.cs
+++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketVoiceChannel.cs
@@ -4,6 +4,7 @@ using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
+using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Model = Discord.API.Channel;
@@ -14,33 +15,25 @@ namespace Discord.WebSocket
/// Represents a WebSocket-based voice channel in a guild.
///
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
- public class SocketVoiceChannel : SocketGuildChannel, IVoiceChannel, ISocketAudioChannel
+ public class SocketVoiceChannel : SocketTextChannel, IVoiceChannel, ISocketAudioChannel
{
#region SocketVoiceChannel
- ///
- public int Bitrate { get; private set; }
- ///
- public int? UserLimit { get; private set; }
- ///
- public string RTCRegion { get; private set; }
-
- ///
- public ulong? CategoryId { get; private set; }
///
- /// Gets the parent (category) channel of this channel.
+ /// Gets whether or not the guild has Text-In-Voice enabled and the voice channel is a TiV channel.
///
- ///
- /// A category channel representing the parent of this channel; null if none is set.
- ///
- public ICategoryChannel Category
- => CategoryId.HasValue ? Guild.GetChannel(CategoryId.Value) as ICategoryChannel : null;
+ ///
+ /// Discord currently doesn't have a way to disable Text-In-Voice yet so this field is always
+ /// on s and on
+ /// s.
+ ///
+ public virtual bool IsTextInVoice => true;
///
- public string Mention => MentionUtils.MentionChannel(Id);
-
+ public int Bitrate { get; private set; }
+ ///
+ public int? UserLimit { get; private set; }
///
- public Task SyncPermissionsAsync(RequestOptions options = null)
- => ChannelHelper.SyncPermissionsAsync(this, Discord, options);
+ public string RTCRegion { get; private set; }
///
/// Gets a collection of users that are currently connected to this voice channel.
@@ -48,7 +41,7 @@ namespace Discord.WebSocket
///
/// A read-only collection of users that are currently connected to this voice channel.
///
- public override IReadOnlyCollection Users
+ public IReadOnlyCollection ConnectedUsers
=> Guild.Users.Where(x => x.VoiceChannel?.Id == Id).ToImmutableArray();
internal SocketVoiceChannel(DiscordSocketClient discord, ulong id, SocketGuild guild)
@@ -65,7 +58,6 @@ namespace Discord.WebSocket
internal override void Update(ClientState state, Model model)
{
base.Update(state, model);
- CategoryId = model.CategoryId;
Bitrate = model.Bitrate.Value;
UserLimit = model.UserLimit.Value != 0 ? model.UserLimit.Value : (int?)null;
RTCRegion = model.RTCRegion.GetValueOrDefault(null);
@@ -99,28 +91,215 @@ namespace Discord.WebSocket
return user;
return null;
}
-#endregion
- #region Invites
- ///
- public async Task CreateInviteAsync(int? maxAge = 86400, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null)
- => await ChannelHelper.CreateInviteAsync(this, Discord, maxAge, maxUses, isTemporary, isUnique, options).ConfigureAwait(false);
- ///
- public async Task CreateInviteToApplicationAsync(ulong applicationId, int? maxAge, int? maxUses = default(int?), bool isTemporary = false, bool isUnique = false, RequestOptions options = null)
- => await ChannelHelper.CreateInviteToApplicationAsync(this, Discord, maxAge, maxUses, isTemporary, isUnique, applicationId, options).ConfigureAwait(false);
- ///
- public virtual async Task CreateInviteToApplicationAsync(DefaultApplications application, int? maxAge = 86400, int? maxUses = default(int?), bool isTemporary = false, bool isUnique = false, RequestOptions options = null)
- => await ChannelHelper.CreateInviteToApplicationAsync(this, Discord, maxAge, maxUses, isTemporary, isUnique, (ulong)application, options);
- ///
- public async Task CreateInviteToStreamAsync(IUser user, int? maxAge, int? maxUses = default(int?), bool isTemporary = false, bool isUnique = false, RequestOptions options = null)
- => await ChannelHelper.CreateInviteToStreamAsync(this, Discord, maxAge, maxUses, isTemporary, isUnique, user, options).ConfigureAwait(false);
- ///
- public async Task> GetInvitesAsync(RequestOptions options = null)
- => await ChannelHelper.GetInvitesAsync(this, Discord, options).ConfigureAwait(false);
+ /// Cannot create threads in voice channels.
+ public override Task CreateThreadAsync(string name, ThreadType type = ThreadType.PublicThread, ThreadArchiveDuration autoArchiveDuration = ThreadArchiveDuration.OneDay, IMessage message = null, bool? invitable = null, int? slowmode = null, RequestOptions options = null)
+ => throw new InvalidOperationException("Voice channels cannot contain threads.");
+
+ /// Cannot modify text channel properties for voice channels.
+ public override Task ModifyAsync(Action func, RequestOptions options = null)
+ => throw new InvalidOperationException("Cannot modify text channel properties for voice channels.");
+
+ #endregion
+
+ #region TextOverrides
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override Task GetMessageAsync(ulong id, RequestOptions options = null)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.GetMessageAsync(id, options);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override Task DeleteMessageAsync(IMessage message, RequestOptions options = null)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.DeleteMessageAsync(message, options);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override Task DeleteMessageAsync(ulong messageId, RequestOptions options = null)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.DeleteMessageAsync(messageId, options);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.DeleteMessagesAsync(messages, options);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override Task DeleteMessagesAsync(IEnumerable messageIds, RequestOptions options = null)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.DeleteMessagesAsync(messageIds, options);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override IDisposable EnterTypingState(RequestOptions options = null)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.EnterTypingState(options);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override SocketMessage GetCachedMessage(ulong id)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.GetCachedMessage(id);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override IReadOnlyCollection GetCachedMessages(IMessage fromMessage, Direction dir, int limit = 100)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.GetCachedMessages(fromMessage, dir, limit);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override IReadOnlyCollection GetCachedMessages(int limit = 100)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.GetCachedMessages(limit);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override IReadOnlyCollection GetCachedMessages(ulong fromMessageId, Direction dir, int limit = 100)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.GetCachedMessages(fromMessageId, dir, limit);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = 100, RequestOptions options = null)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.GetMessagesAsync(fromMessage, dir, limit, options);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override IAsyncEnumerable> GetMessagesAsync(int limit = 100, RequestOptions options = null)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.GetMessagesAsync(limit, options);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = 100, RequestOptions options = null)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.GetMessagesAsync(fromMessageId, dir, limit, options);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override Task> GetPinnedMessagesAsync(RequestOptions options = null)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.GetPinnedMessagesAsync(options);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override Task GetWebhookAsync(ulong id, RequestOptions options = null)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.GetWebhookAsync(id, options);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override Task> GetWebhooksAsync(RequestOptions options = null)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.GetWebhooksAsync(options);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override Task CreateWebhookAsync(string name, Stream avatar = null, RequestOptions options = null)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.CreateWebhookAsync(name, avatar, options);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override Task ModifyMessageAsync(ulong messageId, Action func, RequestOptions options = null)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.ModifyMessageAsync(messageId, func, options);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override Task SendFileAsync(FileAttachment attachment, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.SendFileAsync(attachment, text, isTTS, embed, options, allowedMentions, messageReference, components, stickers, embeds, flags);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.SendFileAsync(stream, filename, text, isTTS, embed, options, isSpoiler, allowedMentions, messageReference, components, stickers, embeds, flags);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override Task SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.SendFileAsync(filePath, text, isTTS, embed, options, isSpoiler, allowedMentions, messageReference, components, stickers, embeds, flags);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override Task SendFilesAsync(IEnumerable attachments, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.SendFilesAsync(attachments, text, isTTS, embed, options, allowedMentions, messageReference, components, stickers, embeds, flags);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.SendMessageAsync(text, isTTS, embed, options, allowedMentions, messageReference, components, stickers, embeds, flags);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override Task TriggerTypingAsync(RequestOptions options = null)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.TriggerTypingAsync(options);
+ }
+
+ #endregion
private string DebuggerDisplay => $"{Name} ({Id}, Voice)";
internal new SocketVoiceChannel Clone() => MemberwiseClone() as SocketVoiceChannel;
- #endregion
#region IGuildChannel
///
diff --git a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs
index 8b376b3ed..9ce2f507a 100644
--- a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs
+++ b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs
@@ -705,7 +705,15 @@ namespace Discord.WebSocket
///
public SocketThreadChannel GetThreadChannel(ulong id)
=> GetChannel(id) as SocketThreadChannel;
-
+ ///
+ /// Gets a forum channel in this guild.
+ ///
+ /// The snowflake identifier for the forum channel.
+ ///
+ /// A forum channel associated with the specified ; if none is found.
+ ///
+ public SocketForumChannel GetForumChannel(ulong id)
+ => GetChannel(id) as SocketForumChannel;
///
/// Gets a voice channel in this guild.
///
@@ -1291,7 +1299,6 @@ namespace Discord.WebSocket
/// in order to use this property.
///
///
- /// A collection of speakers for the event.
/// The location of the event; links are supported
/// The optional banner image for the event.
/// The options to be used when sending the request.
diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/MessageComponents/SocketMessageComponent.cs b/src/Discord.Net.WebSocket/Entities/Interaction/MessageComponents/SocketMessageComponent.cs
index aeff465bd..4f9a769c2 100644
--- a/src/Discord.Net.WebSocket/Entities/Interaction/MessageComponents/SocketMessageComponent.cs
+++ b/src/Discord.Net.WebSocket/Entities/Interaction/MessageComponents/SocketMessageComponent.cs
@@ -226,8 +226,12 @@ namespace Discord.WebSocket
bool hasText = args.Content.IsSpecified ? !string.IsNullOrEmpty(args.Content.Value) : !string.IsNullOrEmpty(Message.Content);
bool hasEmbeds = embed.IsSpecified && embed.Value != null || embeds.IsSpecified && embeds.Value?.Length > 0 || Message.Embeds.Any();
+ bool hasComponents = args.Components.IsSpecified && args.Components.Value != null;
+ bool hasAttachments = args.Attachments.IsSpecified;
+ bool hasFlags = args.Flags.IsSpecified;
- if (!hasText && !hasEmbeds)
+ // No content needed if modifying flags
+ if ((!hasComponents && !hasText && !hasEmbeds && !hasAttachments) && !hasFlags)
Preconditions.NotNullOrEmpty(args.Content.IsSpecified ? args.Content.Value : string.Empty, nameof(args.Content));
var apiEmbeds = embed.IsSpecified || embeds.IsSpecified ? new List() : null;
@@ -261,20 +265,41 @@ namespace Discord.WebSocket
}
}
- var response = new API.InteractionResponse
+ if (!args.Attachments.IsSpecified)
{
- Type = InteractionResponseType.UpdateMessage,
- Data = new API.InteractionCallbackData
+ var response = new API.InteractionResponse
{
+ Type = InteractionResponseType.UpdateMessage,
+ Data = new API.InteractionCallbackData
+ {
+ Content = args.Content,
+ AllowedMentions = args.AllowedMentions.IsSpecified ? args.AllowedMentions.Value?.ToModel() : Optional.Unspecified,
+ Embeds = apiEmbeds?.ToArray() ?? Optional.Unspecified,
+ Components = args.Components.IsSpecified
+ ? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Array.Empty()
+ : Optional.Unspecified,
+ Flags = args.Flags.IsSpecified ? args.Flags.Value ?? Optional.Unspecified : Optional.Unspecified
+ }
+ };
+
+ await InteractionHelper.SendInteractionResponseAsync(Discord, response, this, Channel, options).ConfigureAwait(false);
+ }
+ else
+ {
+ var response = new API.Rest.UploadInteractionFileParams(args.Attachments.Value.ToArray())
+ {
+ Type = InteractionResponseType.UpdateMessage,
Content = args.Content,
AllowedMentions = args.AllowedMentions.IsSpecified ? args.AllowedMentions.Value?.ToModel() : Optional.Unspecified,
Embeds = apiEmbeds?.ToArray() ?? Optional.Unspecified,
- Components = args.Components.IsSpecified
+ MessageComponents = args.Components.IsSpecified
? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Array.Empty()
: Optional.Unspecified,
Flags = args.Flags.IsSpecified ? args.Flags.Value ?? Optional.Unspecified : Optional.Unspecified
- }
- };
+ };
+
+ await InteractionHelper.SendInteractionResponseAsync(Discord, response, this, Channel, options).ConfigureAwait(false);
+ }
lock (_lock)
{
@@ -284,7 +309,6 @@ namespace Discord.WebSocket
}
}
- await InteractionHelper.SendInteractionResponseAsync(Discord, response, this, Channel, options).ConfigureAwait(false);
HasResponded = true;
}
diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/Modals/SocketModal.cs b/src/Discord.Net.WebSocket/Entities/Interaction/Modals/SocketModal.cs
index cfbd3096d..647544b48 100644
--- a/src/Discord.Net.WebSocket/Entities/Interaction/Modals/SocketModal.cs
+++ b/src/Discord.Net.WebSocket/Entities/Interaction/Modals/SocketModal.cs
@@ -174,6 +174,91 @@ namespace Discord.WebSocket
HasResponded = true;
}
+ public async Task UpdateAsync(Action func, RequestOptions options = null)
+ {
+ var args = new MessageProperties();
+ func(args);
+
+ if (!IsValidToken)
+ throw new InvalidOperationException("Interaction token is no longer valid");
+
+ if (!InteractionHelper.CanSendResponse(this))
+ throw new TimeoutException($"Cannot respond to an interaction after {InteractionHelper.ResponseTimeLimit} seconds!");
+
+ if (args.AllowedMentions.IsSpecified)
+ {
+ var allowedMentions = args.AllowedMentions.Value;
+ Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions), "A max of 100 role Ids are allowed.");
+ Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions), "A max of 100 user Ids are allowed.");
+ }
+
+ var embed = args.Embed;
+ var embeds = args.Embeds;
+
+ bool hasText = args.Content.IsSpecified ? !string.IsNullOrEmpty(args.Content.Value) : false;
+ bool hasEmbeds = embed.IsSpecified && embed.Value != null || embeds.IsSpecified && embeds.Value?.Length > 0;
+
+ if (!hasText && !hasEmbeds)
+ Preconditions.NotNullOrEmpty(args.Content.IsSpecified ? args.Content.Value : string.Empty, nameof(args.Content));
+
+ var apiEmbeds = embed.IsSpecified || embeds.IsSpecified ? new List() : null;
+
+ if (embed.IsSpecified && embed.Value != null)
+ {
+ apiEmbeds.Add(embed.Value.ToModel());
+ }
+
+ if (embeds.IsSpecified && embeds.Value != null)
+ {
+ apiEmbeds.AddRange(embeds.Value.Select(x => x.ToModel()));
+ }
+
+ Preconditions.AtMost(apiEmbeds?.Count ?? 0, 10, nameof(args.Embeds), "A max of 10 embeds are allowed.");
+
+ // check that user flag and user Id list are exclusive, same with role flag and role Id list
+ if (args.AllowedMentions.IsSpecified && args.AllowedMentions.Value != null && args.AllowedMentions.Value.AllowedTypes.HasValue)
+ {
+ var allowedMentions = args.AllowedMentions.Value;
+ if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Users)
+ && allowedMentions.UserIds != null && allowedMentions.UserIds.Count > 0)
+ {
+ throw new ArgumentException("The Users flag is mutually exclusive with the list of User Ids.", nameof(args.AllowedMentions));
+ }
+
+ if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Roles)
+ && allowedMentions.RoleIds != null && allowedMentions.RoleIds.Count > 0)
+ {
+ throw new ArgumentException("The Roles flag is mutually exclusive with the list of Role Ids.", nameof(args.AllowedMentions));
+ }
+ }
+
+ var response = new API.InteractionResponse
+ {
+ Type = InteractionResponseType.UpdateMessage,
+ Data = new API.InteractionCallbackData
+ {
+ Content = args.Content,
+ AllowedMentions = args.AllowedMentions.IsSpecified ? args.AllowedMentions.Value?.ToModel() : Optional.Unspecified,
+ Embeds = apiEmbeds?.ToArray() ?? Optional.Unspecified,
+ Components = args.Components.IsSpecified
+ ? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Array.Empty()
+ : Optional.Unspecified,
+ Flags = args.Flags.IsSpecified ? args.Flags.Value ?? Optional.Unspecified : Optional.Unspecified
+ }
+ };
+
+ lock (_lock)
+ {
+ if (HasResponded)
+ {
+ throw new InvalidOperationException("Cannot respond, update, or defer twice to the same interaction");
+ }
+ }
+
+ await InteractionHelper.SendInteractionResponseAsync(Discord, response, this, Channel, options).ConfigureAwait(false);
+ HasResponded = true;
+ }
+
///
public override async Task FollowupAsync(
string text = null,
diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketApplicationCommand.cs b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketApplicationCommand.cs
index 40ec17f5b..8f27b65f4 100644
--- a/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketApplicationCommand.cs
+++ b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketApplicationCommand.cs
@@ -94,8 +94,7 @@ namespace Discord.WebSocket
: ImmutableArray.Create();
IsEnabledInDm = model.DmPermission.GetValueOrDefault(true).GetValueOrDefault(true);
- DefaultMemberPermissions = model.DefaultMemberPermission.IsSpecified
- ? new GuildPermissions((ulong)model.DefaultMemberPermission.Value) : GuildPermissions.None;
+ DefaultMemberPermissions = new GuildPermissions((ulong)model.DefaultMemberPermission.GetValueOrDefault(0).GetValueOrDefault(0));
}
///
diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketResolvableData.cs b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketResolvableData.cs
index d722c5a13..a629fd069 100644
--- a/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketResolvableData.cs
+++ b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketResolvableData.cs
@@ -59,7 +59,7 @@ namespace Discord.WebSocket
}
}
- if (resolved.Members.IsSpecified)
+ if (resolved.Members.IsSpecified && guild != null)
{
foreach (var member in resolved.Members.Value)
{
diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs b/src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs
index 5b2da04f5..f8eb6b12e 100644
--- a/src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs
+++ b/src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs
@@ -24,20 +24,11 @@ namespace Discord.WebSocket
///
public ISocketMessageChannel Channel { get; private set; }
- ///
- /// Gets the ID of the channel this interaction was used in.
- ///
- ///
- /// This property is exposed in cases where the bot scope is not provided, so the channel entity cannot be retrieved.
- ///
- /// To get the channel, you can call
- /// as this method makes a request for a if nothing was found in cache.
- ///
+ ///
public ulong? ChannelId { get; private set; }
///
/// Gets the who triggered this interaction.
- /// This property will be if the bot scope isn't used.
///
public SocketUser User { get; private set; }
@@ -74,6 +65,12 @@ namespace Discord.WebSocket
///
public bool IsDMInteraction { get; private set; }
+ ///
+ public ulong? GuildId { get; private set; }
+
+ ///
+ public ulong ApplicationId { get; private set; }
+
internal SocketInteraction(DiscordSocketClient client, ulong id, ISocketMessageChannel channel, SocketUser user)
: base(client, id)
{
@@ -119,13 +116,21 @@ namespace Discord.WebSocket
internal virtual void Update(Model model)
{
- IsDMInteraction = !model.GuildId.IsSpecified;
+ ChannelId = model.ChannelId.IsSpecified
+ ? model.ChannelId.Value
+ : null;
- ChannelId = model.ChannelId.ToNullable();
+ GuildId = model.GuildId.IsSpecified
+ ? model.GuildId.Value
+ : null;
+
+ IsDMInteraction = GuildId is null;
+ ApplicationId = model.ApplicationId;
Data = model.Data.IsSpecified
? model.Data.Value
: null;
+
Token = model.Token;
Version = model.Version;
Type = model.Type;
@@ -133,6 +138,7 @@ namespace Discord.WebSocket
UserLocale = model.UserLocale.IsSpecified
? model.UserLocale.Value
: null;
+
GuildLocale = model.GuildLocale.IsSpecified
? model.GuildLocale.Value
: null;
@@ -392,7 +398,7 @@ namespace Discord.WebSocket
/// The request options for this request.
/// A task that represents the asynchronous operation of responding to the interaction.
public abstract Task RespondWithModalAsync(Modal modal, RequestOptions options = null);
- #endregion
+#endregion
///
/// Attepts to get the channel this interaction was executed in.
@@ -416,7 +422,7 @@ namespace Discord.WebSocket
catch(HttpException ex) when (ex.DiscordCode == DiscordErrorCode.MissingPermissions) { return null; } // bot can't view that channel, return null instead of throwing.
}
- #region IDiscordInteraction
+#region IDiscordInteraction
///
IUser IDiscordInteraction.User => User;
@@ -446,6 +452,6 @@ namespace Discord.WebSocket
async Task IDiscordInteraction.FollowupWithFileAsync(FileAttachment attachment, string text, Embed[] embeds, bool isTTS, bool ephemeral, AllowedMentions allowedMentions, MessageComponent components, Embed embed, RequestOptions options)
=> await FollowupWithFileAsync(attachment, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options).ConfigureAwait(false);
#endif
- #endregion
+#endregion
}
}
diff --git a/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs
index 6668426e1..3cd67beb5 100644
--- a/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs
+++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs
@@ -182,7 +182,8 @@ namespace Discord.WebSocket
{
GuildId = model.Reference.Value.GuildId,
InternalChannelId = model.Reference.Value.ChannelId,
- MessageId = model.Reference.Value.MessageId
+ MessageId = model.Reference.Value.MessageId,
+ FailIfNotExists = model.Reference.Value.FailIfNotExists
};
}
diff --git a/src/Discord.Net.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 c41f844e1..1a61ff97a 100644
--- a/src/Discord.Net/Discord.Net.nuspec
+++ b/src/Discord.Net/Discord.Net.nuspec
@@ -2,57 +2,57 @@
Discord.Net
- 3.6.0$suffix$
+ 3.7.2$suffix$
Discord.Net
Discord.Net Contributors
foxbot
An asynchronous API wrapper for Discord. This metapackage includes all of the optional Discord.Net components.
discord;discordapp
- https://github.com/RogueException/Discord.Net
+ https://github.com/discord-net/Discord.Net
http://opensource.org/licenses/MIT
false
- https://github.com/RogueException/Discord.Net/raw/dev/docs/marketing/logo/PackageLogo.png
+ https://github.com/discord-net/Discord.Net/raw/dev/docs/marketing/logo/PackageLogo.png
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
\ No newline at end of file
+
diff --git a/test/Discord.Net.Tests.Integration/Discord.Net.Tests.Integration.csproj b/test/Discord.Net.Tests.Integration/Discord.Net.Tests.Integration.csproj
index 0f399ab68..7b8257bfb 100644
--- a/test/Discord.Net.Tests.Integration/Discord.Net.Tests.Integration.csproj
+++ b/test/Discord.Net.Tests.Integration/Discord.Net.Tests.Integration.csproj
@@ -14,6 +14,7 @@
+
diff --git a/test/Discord.Net.Tests.Integration/DiscordRestApiClientTests.cs b/test/Discord.Net.Tests.Integration/DiscordRestApiClientTests.cs
new file mode 100644
index 000000000..96b33b141
--- /dev/null
+++ b/test/Discord.Net.Tests.Integration/DiscordRestApiClientTests.cs
@@ -0,0 +1,53 @@
+using Discord.API;
+using Discord.API.Rest;
+using Discord.Net;
+using Discord.Rest;
+using FluentAssertions;
+using System;
+using System.IO;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace Discord;
+
+[CollectionDefinition(nameof(DiscordRestApiClientTests), DisableParallelization = true)]
+public class DiscordRestApiClientTests : IClassFixture, IAsyncDisposable
+{
+ private readonly DiscordRestApiClient _apiClient;
+ private readonly IGuild _guild;
+ private readonly ITextChannel _channel;
+
+ public DiscordRestApiClientTests(RestGuildFixture guildFixture)
+ {
+ _guild = guildFixture.Guild;
+ _apiClient = guildFixture.Client.ApiClient;
+ _channel = _guild.CreateTextChannelAsync("testChannel").Result;
+ }
+
+ public async ValueTask DisposeAsync()
+ {
+ await _channel.DeleteAsync();
+ }
+
+ [Fact]
+ public async Task UploadFile_WithMaximumSize_DontThrowsException()
+ {
+ var fileSize = GuildHelper.GetUploadLimit(_guild);
+ using var stream = new MemoryStream(new byte[fileSize]);
+
+ await _apiClient.UploadFileAsync(_channel.Id, new UploadFileParams(new FileAttachment(stream, "filename")));
+ }
+
+ [Fact]
+ public async Task UploadFile_WithOverSize_ThrowsException()
+ {
+ var fileSize = GuildHelper.GetUploadLimit(_guild) + 1;
+ using var stream = new MemoryStream(new byte[fileSize]);
+
+ Func upload = async () =>
+ await _apiClient.UploadFileAsync(_channel.Id, new UploadFileParams(new FileAttachment(stream, "filename")));
+
+ await upload.Should().ThrowExactlyAsync()
+ .Where(e => e.DiscordCode == DiscordErrorCode.RequestEntityTooLarge);
+ }
+}
diff --git a/test/Discord.Net.Tests.Unit/Discord.Net.Tests.Unit.csproj b/test/Discord.Net.Tests.Unit/Discord.Net.Tests.Unit.csproj
index ec06c3c3d..087a64d83 100644
--- a/test/Discord.Net.Tests.Unit/Discord.Net.Tests.Unit.csproj
+++ b/test/Discord.Net.Tests.Unit/Discord.Net.Tests.Unit.csproj
@@ -12,7 +12,9 @@
+
+
all
diff --git a/test/Discord.Net.Tests.Unit/GuildHelperTests.cs b/test/Discord.Net.Tests.Unit/GuildHelperTests.cs
new file mode 100644
index 000000000..c68f415fe
--- /dev/null
+++ b/test/Discord.Net.Tests.Unit/GuildHelperTests.cs
@@ -0,0 +1,25 @@
+using Discord.Rest;
+using FluentAssertions;
+using Moq;
+using System;
+using Xunit;
+
+namespace Discord;
+
+public class GuildHelperTests
+{
+ [Theory]
+ [InlineData(PremiumTier.None, 8)]
+ [InlineData(PremiumTier.Tier1, 8)]
+ [InlineData(PremiumTier.Tier2, 50)]
+ [InlineData(PremiumTier.Tier3, 100)]
+ public void GetUploadLimit(PremiumTier tier, ulong factor)
+ {
+ var guild = Mock.Of(g => g.PremiumTier == tier);
+ var expected = factor * (ulong)Math.Pow(2, 20);
+
+ var actual = GuildHelper.GetUploadLimit(guild);
+
+ actual.Should().Be(expected);
+ }
+}
diff --git a/test/Discord.Net.Tests.Unit/MockedEntities/MockedTextChannel.cs b/test/Discord.Net.Tests.Unit/MockedEntities/MockedTextChannel.cs
index 0dfcab7a5..ab1d3e534 100644
--- a/test/Discord.Net.Tests.Unit/MockedEntities/MockedTextChannel.cs
+++ b/test/Discord.Net.Tests.Unit/MockedEntities/MockedTextChannel.cs
@@ -10,6 +10,8 @@ namespace Discord
{
public bool IsNsfw => throw new NotImplementedException();
+ public ThreadArchiveDuration DefaultArchiveDuration => throw new NotImplementedException();
+
public string Topic => throw new NotImplementedException();
public int SlowModeInterval => throw new NotImplementedException();
diff --git a/test/Discord.Net.Tests.Unit/MockedEntities/MockedVoiceChannel.cs b/test/Discord.Net.Tests.Unit/MockedEntities/MockedVoiceChannel.cs
index 533b1b1b5..fdbdeda5e 100644
--- a/test/Discord.Net.Tests.Unit/MockedEntities/MockedVoiceChannel.cs
+++ b/test/Discord.Net.Tests.Unit/MockedEntities/MockedVoiceChannel.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.IO;
using System.Text;
using System.Threading.Tasks;
using Discord.Audio;
@@ -12,8 +13,6 @@ namespace Discord
public int? UserLimit => throw new NotImplementedException();
- public string Mention => throw new NotImplementedException();
-
public ulong? CategoryId => throw new NotImplementedException();
public int Position => throw new NotImplementedException();
@@ -24,116 +23,53 @@ namespace Discord
public IReadOnlyCollection PermissionOverwrites => throw new NotImplementedException();
+ public string RTCRegion => throw new NotImplementedException();
+
public string Name => throw new NotImplementedException();
public DateTimeOffset CreatedAt => throw new NotImplementedException();
- public ulong Id => throw new NotImplementedException();
-
- public string RTCRegion => throw new NotImplementedException();
- public Task AddPermissionOverwriteAsync(IRole role, OverwritePermissions permissions, RequestOptions options = null)
- {
- throw new NotImplementedException();
- }
-
- public Task AddPermissionOverwriteAsync(IUser user, OverwritePermissions permissions, RequestOptions options = null)
- {
- throw new NotImplementedException();
- }
+ public ulong Id => throw new NotImplementedException();
- public Task ConnectAsync(bool selfDeaf = false, bool selfMute = false, bool external = false)
- {
- throw new NotImplementedException();
- }
+ public string Mention => throw new NotImplementedException();
- public Task CreateInviteAsync(int? maxAge = 86400, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null)
- {
- throw new NotImplementedException();
- }
- public Task CreateInviteToApplicationAsync(ulong applicationId, int? maxAge, int? maxUses = default(int?), bool isTemporary = false, bool isUnique = false, RequestOptions options = null)
- => throw new NotImplementedException();
+ public Task AddPermissionOverwriteAsync(IRole role, OverwritePermissions permissions, RequestOptions options = null) => throw new NotImplementedException();
+ public Task AddPermissionOverwriteAsync(IUser user, OverwritePermissions permissions, RequestOptions options = null) => throw new NotImplementedException();
+ public Task ConnectAsync(bool selfDeaf = false, bool selfMute = false, bool external = false) => throw new NotImplementedException();
+ public Task CreateInviteAsync(int? maxAge = 86400, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null) => throw new NotImplementedException();
+ public Task CreateInviteToApplicationAsync(ulong applicationId, int? maxAge = 86400, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null) => throw new NotImplementedException();
public Task CreateInviteToApplicationAsync(DefaultApplications application, int? maxAge = 86400, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null) => throw new NotImplementedException();
- public Task CreateInviteToStreamAsync(IUser user, int? maxAge, int? maxUses = default(int?), bool isTemporary = false, bool isUnique = false, RequestOptions options = null)
- => throw new NotImplementedException();
-
- public Task DeleteAsync(RequestOptions options = null)
- {
- throw new NotImplementedException();
- }
-
- public Task DisconnectAsync()
- {
- throw new NotImplementedException();
- }
-
- public Task ModifyAsync(Action func, RequestOptions options)
- {
- throw new NotImplementedException();
- }
-
- public Task GetCategoryAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null)
- {
- throw new NotImplementedException();
- }
-
- public Task> GetInvitesAsync(RequestOptions options = null)
- {
- throw new NotImplementedException();
- }
-
- public OverwritePermissions? GetPermissionOverwrite(IRole role)
- {
- throw new NotImplementedException();
- }
-
- public OverwritePermissions? GetPermissionOverwrite(IUser user)
- {
- throw new NotImplementedException();
- }
-
- public Task GetUserAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null)
- {
- throw new NotImplementedException();
- }
-
- public IAsyncEnumerable> GetUsersAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null)
- {
- throw new NotImplementedException();
- }
-
- public Task ModifyAsync(Action func, RequestOptions options = null)
- {
- throw new NotImplementedException();
- }
-
- public Task ModifyAsync(Action func, RequestOptions options = null)
- {
- throw new NotImplementedException();
- }
-
- public Task RemovePermissionOverwriteAsync(IRole role, RequestOptions options = null)
- {
- throw new NotImplementedException();
- }
-
- public Task RemovePermissionOverwriteAsync(IUser user, RequestOptions options = null)
- {
- throw new NotImplementedException();
- }
-
- public Task SyncPermissionsAsync(RequestOptions options = null)
- {
- throw new NotImplementedException();
- }
-
- Task IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options)
- {
- throw new NotImplementedException();
- }
-
- IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options)
- {
- throw new NotImplementedException();
- }
+ public Task CreateInviteToStreamAsync(IUser user, int? maxAge = 86400, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null) => throw new NotImplementedException();
+ public Task DeleteAsync(RequestOptions options = null) => throw new NotImplementedException();
+ public Task DeleteMessageAsync(ulong messageId, RequestOptions options = null) => throw new NotImplementedException();
+ public Task DeleteMessageAsync(IMessage message, RequestOptions options = null) => throw new NotImplementedException();
+ public Task DisconnectAsync() => throw new NotImplementedException();
+ public IDisposable EnterTypingState(RequestOptions options = null) => throw new NotImplementedException();
+ public Task GetCategoryAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null) => throw new NotImplementedException();
+ public Task> GetInvitesAsync(RequestOptions options = null) => throw new NotImplementedException();
+ public Task GetMessageAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null) => throw new NotImplementedException();
+ public IAsyncEnumerable> GetMessagesAsync(int limit = 100, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null) => throw new NotImplementedException();
+ public IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = 100, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null) => throw new NotImplementedException();
+ public IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = 100, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null) => throw new NotImplementedException();
+ public OverwritePermissions? GetPermissionOverwrite(IRole role) => throw new NotImplementedException();
+ public OverwritePermissions? GetPermissionOverwrite(IUser user) => throw new NotImplementedException();
+ public Task> GetPinnedMessagesAsync(RequestOptions options = null) => throw new NotImplementedException();
+ public Task GetUserAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null) => throw new NotImplementedException();
+ public IAsyncEnumerable> GetUsersAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null) => throw new NotImplementedException();
+ public Task ModifyAsync(Action func, RequestOptions options = null) => throw new NotImplementedException();
+ public Task ModifyAsync(Action func, RequestOptions options = null) => throw new NotImplementedException();
+ public Task ModifyAsync(Action func, RequestOptions options = null) => throw new NotImplementedException();
+ public Task ModifyMessageAsync(ulong messageId, Action func, RequestOptions options = null) => throw new NotImplementedException();
+ public Task RemovePermissionOverwriteAsync(IRole role, RequestOptions options = null) => throw new NotImplementedException();
+ public Task RemovePermissionOverwriteAsync(IUser user, RequestOptions options = null) => throw new NotImplementedException();
+ public Task SendFileAsync(string filePath, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None) => throw new NotImplementedException();
+ public Task SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None) => throw new NotImplementedException();
+ public Task SendFileAsync(FileAttachment attachment, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None) => throw new NotImplementedException();
+ public Task SendFilesAsync(IEnumerable attachments, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None) => throw new NotImplementedException();
+ public Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None) => throw new NotImplementedException();
+ public Task SyncPermissionsAsync(RequestOptions options = null) => throw new NotImplementedException();
+ public Task TriggerTypingAsync(RequestOptions options = null) => throw new NotImplementedException();
+ Task IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) => throw new NotImplementedException();
+ IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) => throw new NotImplementedException();
}
}