From a8f607553b1db4209912c3fbfba1c3eb8e5d57df Mon Sep 17 00:00:00 2001
From: Cenk Ergen <57065323+Cenngo@users.noreply.github.com>
Date: Fri, 29 Apr 2022 17:53:14 +0300
Subject: [PATCH 01/84] fix: Permissions v2 Invalid Operation Exception (#2267)
* implement fix
* implement fix
---
.../Entities/Interactions/RestApplicationCommand.cs | 3 +--
.../Interaction/SocketBaseCommand/SocketApplicationCommand.cs | 3 +--
2 files changed, 2 insertions(+), 4 deletions(-)
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.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));
}
///
From 0d74c5cc629e0bb176734a5f2350ecef04de3a94 Mon Sep 17 00:00:00 2001
From: Cenk Ergen <57065323+Cenngo@users.noreply.github.com>
Date: Sat, 30 Apr 2022 04:37:22 +0300
Subject: [PATCH 02/84] fix: Implement fix for Custom Id Segments NRE (#2274)
---
src/Discord.Net.Interactions/InteractionService.cs | 13 +++++++++----
1 file changed, 9 insertions(+), 4 deletions(-)
diff --git a/src/Discord.Net.Interactions/InteractionService.cs b/src/Discord.Net.Interactions/InteractionService.cs
index 8eb5799d6..24302dfc7 100644
--- a/src/Discord.Net.Interactions/InteractionService.cs
+++ b/src/Discord.Net.Interactions/InteractionService.cs
@@ -834,11 +834,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)
From 503e720d2141ff3b731b50c09aaae8a69b6223ae Mon Sep 17 00:00:00 2001
From: Discord-NET-Robot <95661365+Discord-NET-Robot@users.noreply.github.com>
Date: Sat, 30 Apr 2022 19:02:41 -0300
Subject: [PATCH 03/84] feature: add 50080 Error code (#2272)
Co-authored-by: Discord.Net Robot
---
src/Discord.Net.Core/DiscordErrorCode.cs | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/Discord.Net.Core/DiscordErrorCode.cs b/src/Discord.Net.Core/DiscordErrorCode.cs
index 51fd736f6..a6861c10c 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,
From f2bb55e8041fb3aac1208d3d666c842019e3f1ae Mon Sep 17 00:00:00 2001
From: Quin Lynch <49576606+quinchs@users.noreply.github.com>
Date: Sat, 30 Apr 2022 19:02:57 -0300
Subject: [PATCH 04/84] fix: null user on interaction without bot scope (#2271)
---
src/Discord.Net.WebSocket/DiscordSocketClient.cs | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs
index aaef4656a..57d58a8b1 100644
--- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs
+++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs
@@ -2331,7 +2331,9 @@ namespace Discord.WebSocket
SocketUser user = data.User.IsSpecified
? State.GetOrAddUser(data.User.Value.Id, (_) => SocketGlobalUser.Create(this, State, data.User.Value))
- : guild?.AddOrUpdateUser(data.Member.Value); // null if the bot scope isn't set, so the guild cannot be retrieved.
+ : guild != null
+ ? guild.AddOrUpdateUser(data.Member.Value) // null if the bot scope isn't set, so the guild cannot be retrieved.
+ : State.GetOrAddUser(data.Member.Value.User.Id, (_) => SocketGlobalUser.Create(this, State, data.Member.Value.User));
SocketChannel channel = null;
if(data.ChannelId.IsSpecified)
From 2f58ddc6a09b22443acbb246ea7baa7ba80708ba Mon Sep 17 00:00:00 2001
From: Quin Lynch
Date: Sat, 30 Apr 2022 19:06:23 -0300
Subject: [PATCH 05/84] meta: 3.6.1
---
CHANGELOG.md | 13 +++++++
Discord.Net.targets | 2 +-
docs/docfx.json | 2 +-
src/Discord.Net/Discord.Net.nuspec | 62 +++++++++++++++---------------
4 files changed, 46 insertions(+), 33 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ac5547568..023400c80 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,18 @@
# Changelog
+## [3.6.1] - 2022-04-30
+### Added
+- #2272 add 50080 Error code (503e720)
+
+### Fixed
+- #2267 Permissions v2 Invalid Operation Exception (a8f6075)
+- #2271 null user on interaction without bot scope (f2bb55e)
+- #2274 Implement fix for Custom Id Segments NRE (0d74c5c)
+
+### Misc
+- 3.6.0 (27226f0)
+
+
## [3.6.0] - 2022-04-28
### Added
- #2136 Passing CustomId matches into contexts (4ce1801)
diff --git a/Discord.Net.targets b/Discord.Net.targets
index e17f6de98..adb0a338c 100644
--- a/Discord.Net.targets
+++ b/Discord.Net.targets
@@ -1,6 +1,6 @@
- 3.6.0
+ 3.6.1latestDiscord.Net Contributorsdiscord;discordapp
diff --git a/docs/docfx.json b/docs/docfx.json
index 585d4dbec..105aa0493 100644
--- a/docs/docfx.json
+++ b/docs/docfx.json
@@ -60,7 +60,7 @@
"overwrite": "_overwrites/**/**.md",
"globalMetadata": {
"_appTitle": "Discord.Net Documentation",
- "_appFooter": "Discord.Net (c) 2015-2022 3.6.0",
+ "_appFooter": "Discord.Net (c) 2015-2022 3.6.1",
"_enableSearch": true,
"_appLogoPath": "marketing/logo/SVG/Logomark Purple.svg",
"_appFaviconPath": "favicon.ico"
diff --git a/src/Discord.Net/Discord.Net.nuspec b/src/Discord.Net/Discord.Net.nuspec
index c41f844e1..269657771 100644
--- a/src/Discord.Net/Discord.Net.nuspec
+++ b/src/Discord.Net/Discord.Net.nuspec
@@ -2,7 +2,7 @@
Discord.Net
- 3.6.0$suffix$
+ 3.6.1$suffix$Discord.NetDiscord.Net Contributorsfoxbot
@@ -14,44 +14,44 @@
https://github.com/RogueException/Discord.Net/raw/dev/docs/marketing/logo/PackageLogo.png
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
From 6470c64b2d344c2b68d5ea4b6227f6c23298d082 Mon Sep 17 00:00:00 2001
From: Quin Lynch <49576606+quinchs@users.noreply.github.com>
Date: Sun, 1 May 2022 14:30:42 -0300
Subject: [PATCH 06/84] Update FUNDING.yml
---
.github/FUNDING.yml | 2 ++
1 file changed, 2 insertions(+)
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
From 5546c705caf1954e72b494e367fd83e636ab9dd8 Mon Sep 17 00:00:00 2001
From: Nikita Petko
Date: Mon, 9 May 2022 05:50:26 +0100
Subject: [PATCH 07/84] Remove old url reference in Discord.Net.nuspec (#2286)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Change organization name within Nuget manifest from RogueException to discord-net.
If there’s any other ones I missed, please point it out to me.
---
src/Discord.Net/Discord.Net.nuspec | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/Discord.Net/Discord.Net.nuspec b/src/Discord.Net/Discord.Net.nuspec
index 269657771..3985536f4 100644
--- a/src/Discord.Net/Discord.Net.nuspec
+++ b/src/Discord.Net/Discord.Net.nuspec
@@ -8,10 +8,10 @@
foxbotAn 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.Nethttp://opensource.org/licenses/MITfalse
- 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
@@ -55,4 +55,4 @@
-
\ No newline at end of file
+
From 0ec8938a67f3feda5720ecdda8303595d6d48423 Mon Sep 17 00:00:00 2001
From: moiph
Date: Mon, 9 May 2022 18:55:17 -0700
Subject: [PATCH 08/84] feature: Support FailIfNotExists on MessageReference
(#2283)
Fixes #2282
---
.../Entities/Messages/MessageReference.cs | 15 +++++++++++++--
.../API/Common/MessageReference.cs | 3 +++
.../Entities/Messages/RestMessage.cs | 3 ++-
.../Extensions/EntityExtensions.cs | 1 +
.../Entities/Messages/SocketMessage.cs | 3 ++-
5 files changed, 21 insertions(+), 4 deletions(-)
diff --git a/src/Discord.Net.Core/Entities/Messages/MessageReference.cs b/src/Discord.Net.Core/Entities/Messages/MessageReference.cs
index 029910e56..7fdc448ad 100644
--- a/src/Discord.Net.Core/Entities/Messages/MessageReference.cs
+++ b/src/Discord.Net.Core/Entities/Messages/MessageReference.cs
@@ -27,6 +27,12 @@ namespace Discord
///
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.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/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/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.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
};
}
From e13675907301820c5bf3cbb7b63f14be095389af Mon Sep 17 00:00:00 2001
From: Quin Lynch <49576606+quinchs@users.noreply.github.com>
Date: Mon, 9 May 2022 22:56:22 -0300
Subject: [PATCH 09/84] feature: Treat warnings as errors and set warning level
to 5 (#2270)
---
src/Discord.Net.Commands/Discord.Net.Commands.csproj | 2 ++
src/Discord.Net.Commands/Results/MatchResult.cs | 6 +++---
src/Discord.Net.Core/Discord.Net.Core.csproj | 2 ++
src/Discord.Net.Core/Entities/Guilds/IGuild.cs | 1 -
.../Entities/Guilds/IGuildScheduledEvent.cs | 2 +-
.../Interactions/ApplicationCommandOptionType.cs | 2 +-
.../Entities/Interactions/IDiscordInteraction.cs | 2 +-
.../MessageComponents/ComponentBuilder.cs | 10 +++++-----
.../Entities/Interactions/Modals/ModalBuilder.cs | 8 ++++----
.../Entities/Users/GuildUserProperties.cs | 2 +-
src/Discord.Net.Core/Entities/Users/IGuildUser.cs | 6 +++---
src/Discord.Net.Core/Utils/UrlValidation.cs | 2 +-
.../Attributes/AutocompleteAttribute.cs | 6 +++---
.../Attributes/Modals/ModalInputAttribute.cs | 2 --
.../Attributes/Modals/ModalTextInputAttribute.cs | 2 +-
.../Preconditions/RequireUserPermissionAttribute.cs | 4 ++--
.../Builders/Commands/SlashCommandBuilder.cs | 2 +-
.../Modals/Inputs/TextInputComponentBuilder.cs | 2 +-
.../Builders/Modals/ModalBuilder.cs | 2 +-
.../Builders/ModuleBuilder.cs | 3 ++-
.../Builders/Parameters/ParameterBuilder.cs | 2 +-
.../Discord.Net.Interactions.csproj | 4 +++-
src/Discord.Net.Interactions/InteractionContext.cs | 3 +--
.../InteractionModuleBase.cs | 12 ++++++------
src/Discord.Net.Interactions/InteractionService.cs | 12 ++++++------
.../RestInteractionModuleBase.cs | 4 ++--
.../Results/TypeConverterResult.cs | 2 +-
src/Discord.Net.Rest/Discord.Net.Rest.csproj | 2 ++
src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs | 1 -
.../Entities/Interactions/RestInteraction.cs | 1 -
src/Discord.Net.Rest/Entities/Roles/RestRole.cs | 2 +-
src/Discord.Net.Rest/Net/ED25519/CryptoBytes.cs | 2 +-
src/Discord.Net.Rest/Net/Queue/RequestQueue.cs | 11 +++--------
.../Discord.Net.WebSocket.csproj | 2 ++
.../Entities/Guilds/SocketGuild.cs | 1 -
src/Discord.Net.Webhook/Discord.Net.Webhook.csproj | 2 ++
36 files changed, 66 insertions(+), 65 deletions(-)
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.1net6.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.1net6.0;net5.0;netstandard2.0;netstandard2.1
+ 5
+ True
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..9017d310f 100644
--- a/src/Discord.Net.Core/Entities/Interactions/IDiscordInteraction.cs
+++ b/src/Discord.Net.Core/Entities/Interactions/IDiscordInteraction.cs
@@ -55,7 +55,7 @@ namespace Discord
string UserLocale { get; }
///
- /// Gets the preferred locale of the guild this interaction was executed in. if not executed in a guild.
+ /// Gets the preferred locale of the guild this interaction was executed in. if not executed in a guild.
///
///
/// Non-community guilds (With no locale setting available) will have en-US as the default value sent by Discord.
diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ComponentBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ComponentBuilder.cs
index 7becca0e0..9c529f469 100644
--- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ComponentBuilder.cs
+++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ComponentBuilder.cs
@@ -1194,9 +1194,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
{
@@ -1306,18 +1306,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/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/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.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.InteractionsDiscord.Net.InteractionsA Discord.Net extension adding support for Application Commands.
+ 5
+ True
-
+
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 24302dfc7..6afa5c086 100644
--- a/src/Discord.Net.Interactions/InteractionService.cs
+++ b/src/Discord.Net.Interactions/InteractionService.cs
@@ -421,7 +421,7 @@ namespace Discord.Interactions
///
///
/// Commands will be registered as standalone commands, if you want the to take effect,
- /// use . Registering a commands without group names might cause the command traversal to fail.
+ /// use . Registering a commands without group names might cause the command traversal to fail.
///
/// The target guild.
/// Commands to be registered to Discord.
@@ -517,7 +517,7 @@ namespace Discord.Interactions
///
///
/// Commands will be registered as standalone commands, if you want the to take effect,
- /// use . Registering a commands without group names might cause the command traversal to fail.
+ /// use . Registering a commands without group names might cause the command traversal to fail.
///
/// Commands to be registered to Discord.
///
@@ -965,7 +965,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.
@@ -978,7 +978,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.
@@ -991,7 +991,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.
@@ -1004,7 +1004,7 @@ namespace Discord.Interactions
/// Serialize an object using a into a to be placed in a Component CustomId.
///
///
- /// Removing a from the will not dereference the from the loaded module/command instances.
+ /// Removing a from the will not dereference the from the loaded module/command instances.
/// You need to reload the modules for the changes to take effect.
///
/// Type of the object to be serialized.
diff --git a/src/Discord.Net.Interactions/RestInteractionModuleBase.cs b/src/Discord.Net.Interactions/RestInteractionModuleBase.cs
index e83c91fef..b570e6d84 100644
--- a/src/Discord.Net.Interactions/RestInteractionModuleBase.cs
+++ b/src/Discord.Net.Interactions/RestInteractionModuleBase.cs
@@ -87,12 +87,12 @@ namespace Discord.Interactions
await InteractionService._restResponseCallback(Context, payload).ConfigureAwait(false);
}
- protected override async Task RespondWithModalAsync(string customId, RequestOptions options = null)
+ protected override async Task RespondWithModalAsync(string customId, RequestOptions options = null)
{
if (Context.Interaction is not RestInteraction restInteraction)
throw new InvalidOperationException($"Invalid interaction type. Interaction must be a type of {nameof(RestInteraction)} in order to execute this method");
- var payload = restInteraction.RespondWithModal(customId, options);
+ var payload = restInteraction.RespondWithModal(customId, options);
if (Context is IRestInteractionContext restContext && restContext.InteractionResponseCallback != null)
await restContext.InteractionResponseCallback.Invoke(payload).ConfigureAwait(false);
diff --git a/src/Discord.Net.Interactions/Results/TypeConverterResult.cs b/src/Discord.Net.Interactions/Results/TypeConverterResult.cs
index bd89bf6b7..a9a12ee33 100644
--- a/src/Discord.Net.Interactions/Results/TypeConverterResult.cs
+++ b/src/Discord.Net.Interactions/Results/TypeConverterResult.cs
@@ -3,7 +3,7 @@ using System;
namespace Discord.Interactions
{
///
- /// Represents a result type for .
+ /// Represents a result type for .
///
public struct TypeConverterResult : IResult
{
diff --git a/src/Discord.Net.Rest/Discord.Net.Rest.csproj b/src/Discord.Net.Rest/Discord.Net.Rest.csproj
index 98692998f..bec2396ef 100644
--- a/src/Discord.Net.Rest/Discord.Net.Rest.csproj
+++ b/src/Discord.Net.Rest/Discord.Net.Rest.csproj
@@ -7,6 +7,8 @@
A core Discord.Net library containing the REST client and models.net6.0;net5.0;net461;netstandard2.0;netstandard2.1net6.0;net5.0;netstandard2.0;netstandard2.1
+ 5
+ True
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/RestInteraction.cs b/src/Discord.Net.Rest/Entities/Interactions/RestInteraction.cs
index 8a8921abe..b8c0f961d 100644
--- a/src/Discord.Net.Rest/Entities/Interactions/RestInteraction.cs
+++ b/src/Discord.Net.Rest/Entities/Interactions/RestInteraction.cs
@@ -333,7 +333,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/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/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.1net6.0;net5.0;netstandard2.0;netstandard2.1true
+ 5
+ True
diff --git a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs
index 8b376b3ed..e12f3d1ef 100644
--- a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs
+++ b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs
@@ -1291,7 +1291,6 @@ namespace Discord.WebSocket
/// in order to use this property.
///
///
- /// A collection of speakers for the event.
/// The location of the event; links are supported
/// The optional banner image for the event.
/// The options to be used when sending the request.
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.WebhookA core Discord.Net library containing the Webhook client and models.net6.0;net5.0;netstandard2.0;netstandard2.1
+ 5
+ True
From 23656e844ee45f4f3a37b3da887f10fb3e6b9a37 Mon Sep 17 00:00:00 2001
From: Quin Lynch <49576606+quinchs@users.noreply.github.com>
Date: Mon, 9 May 2022 22:57:28 -0300
Subject: [PATCH 10/84] feature: Text-In-Voice (#2269)
* Initial implementation
* Remove blocking webhooks
* add safeguard for tiv
* fix tests
---
.../Entities/Channels/IVoiceChannel.cs | 2 +-
.../Entities/Channels/RestStageChannel.cs | 8 +-
.../Entities/Channels/RestTextChannel.cs | 98 +++----
.../Entities/Channels/RestVoiceChannel.cs | 218 ++++++++++++---
.../Entities/Channels/SocketGuildChannel.cs | 2 +
.../Entities/Channels/SocketStageChannel.cs | 9 +-
.../Entities/Channels/SocketTextChannel.cs | 42 +--
.../Entities/Channels/SocketVoiceChannel.cs | 255 +++++++++++++++---
.../MockedEntities/MockedVoiceChannel.cs | 148 +++-------
9 files changed, 521 insertions(+), 261 deletions(-)
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.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..a73bda334 100644
--- a/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs
+++ b/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs
@@ -86,25 +86,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 +136,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 +146,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 +156,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 +166,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 +231,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 +267,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.WebSocket/Entities/Channels/SocketGuildChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs
index 79f02fe1c..6d9e759b4 100644
--- a/src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs
+++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs
@@ -222,6 +222,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..e8454ecf8 100644
--- a/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs
+++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs
@@ -128,7 +128,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 +143,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 +163,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 +179,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 +195,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 +221,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 +230,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 +239,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 +248,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 +256,28 @@ namespace Discord.WebSocket
messageReference, components, stickers, options, embeds, flags);
///
- public Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null)
+ public virtual Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null)
=> ChannelHelper.DeleteMessagesAsync(this, Discord, messages.Select(x => x.Id), options);
///
- public Task DeleteMessagesAsync(IEnumerable messageIds, RequestOptions options = null)
+ public virtual Task DeleteMessagesAsync(IEnumerable messageIds, RequestOptions options = null)
=> ChannelHelper.DeleteMessagesAsync(this, Discord, messageIds, options);
///
- public async Task ModifyMessageAsync(ulong messageId, Action func, RequestOptions options = null)
+ public virtual async Task ModifyMessageAsync(ulong messageId, Action func, RequestOptions options = null)
=> await ChannelHelper.ModifyMessageAsync(this, messageId, func, Discord, options).ConfigureAwait(false);
///
- public Task DeleteMessageAsync(ulong messageId, RequestOptions options = null)
+ public virtual Task DeleteMessageAsync(ulong messageId, RequestOptions options = null)
=> ChannelHelper.DeleteMessageAsync(this, messageId, Discord, options);
///
- public Task DeleteMessageAsync(IMessage message, RequestOptions options = null)
+ public virtual Task DeleteMessageAsync(IMessage message, RequestOptions options = null)
=> ChannelHelper.DeleteMessageAsync(this, message.Id, Discord, options);
///
- public Task TriggerTypingAsync(RequestOptions options = null)
+ public virtual Task TriggerTypingAsync(RequestOptions options = null)
=> ChannelHelper.TriggerTypingAsync(this, Discord, options);
///
- public IDisposable EnterTypingState(RequestOptions options = null)
+ public virtual IDisposable EnterTypingState(RequestOptions options = null)
=> ChannelHelper.EnterTypingState(this, Discord, options);
internal void AddMessage(SocketMessage msg)
diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketVoiceChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketVoiceChannel.cs
index 00003d4ed..5fc99c3f1 100644
--- a/src/Discord.Net.WebSocket/Entities/Channels/SocketVoiceChannel.cs
+++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketVoiceChannel.cs
@@ -4,6 +4,7 @@ using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
+using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Model = Discord.API.Channel;
@@ -14,33 +15,21 @@ namespace Discord.WebSocket
/// Represents a WebSocket-based voice channel in a guild.
///
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
- public class SocketVoiceChannel : SocketGuildChannel, IVoiceChannel, ISocketAudioChannel
+ public class SocketVoiceChannel : SocketTextChannel, IVoiceChannel, ISocketAudioChannel
{
#region SocketVoiceChannel
- ///
- public int Bitrate { get; private set; }
- ///
- public int? UserLimit { get; private set; }
- ///
- public string RTCRegion { get; private set; }
-
- ///
- public ulong? CategoryId { get; private set; }
///
- /// Gets the parent (category) channel of this channel.
+ /// Gets whether or not the guild has Text-In-Voice enabled and the voice channel is a TiV channel.
///
- ///
- /// A category channel representing the parent of this channel; null if none is set.
- ///
- public ICategoryChannel Category
- => CategoryId.HasValue ? Guild.GetChannel(CategoryId.Value) as ICategoryChannel : null;
+ public virtual bool IsTextInVoice
+ => Guild.Features.HasTextInVoice;
///
- public string Mention => MentionUtils.MentionChannel(Id);
-
+ public int Bitrate { get; private set; }
+ ///
+ public int? UserLimit { get; private set; }
///
- public Task SyncPermissionsAsync(RequestOptions options = null)
- => ChannelHelper.SyncPermissionsAsync(this, Discord, options);
+ public string RTCRegion { get; private set; }
///
/// Gets a collection of users that are currently connected to this voice channel.
@@ -48,7 +37,7 @@ namespace Discord.WebSocket
///
/// A read-only collection of users that are currently connected to this voice channel.
///
- public override IReadOnlyCollection Users
+ public IReadOnlyCollection ConnectedUsers
=> Guild.Users.Where(x => x.VoiceChannel?.Id == Id).ToImmutableArray();
internal SocketVoiceChannel(DiscordSocketClient discord, ulong id, SocketGuild guild)
@@ -65,7 +54,6 @@ namespace Discord.WebSocket
internal override void Update(ClientState state, Model model)
{
base.Update(state, model);
- CategoryId = model.CategoryId;
Bitrate = model.Bitrate.Value;
UserLimit = model.UserLimit.Value != 0 ? model.UserLimit.Value : (int?)null;
RTCRegion = model.RTCRegion.GetValueOrDefault(null);
@@ -99,28 +87,215 @@ namespace Discord.WebSocket
return user;
return null;
}
-#endregion
- #region Invites
- ///
- public async Task CreateInviteAsync(int? maxAge = 86400, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null)
- => await ChannelHelper.CreateInviteAsync(this, Discord, maxAge, maxUses, isTemporary, isUnique, options).ConfigureAwait(false);
- ///
- public async Task CreateInviteToApplicationAsync(ulong applicationId, int? maxAge, int? maxUses = default(int?), bool isTemporary = false, bool isUnique = false, RequestOptions options = null)
- => await ChannelHelper.CreateInviteToApplicationAsync(this, Discord, maxAge, maxUses, isTemporary, isUnique, applicationId, options).ConfigureAwait(false);
- ///
- public virtual async Task CreateInviteToApplicationAsync(DefaultApplications application, int? maxAge = 86400, int? maxUses = default(int?), bool isTemporary = false, bool isUnique = false, RequestOptions options = null)
- => await ChannelHelper.CreateInviteToApplicationAsync(this, Discord, maxAge, maxUses, isTemporary, isUnique, (ulong)application, options);
- ///
- public async Task CreateInviteToStreamAsync(IUser user, int? maxAge, int? maxUses = default(int?), bool isTemporary = false, bool isUnique = false, RequestOptions options = null)
- => await ChannelHelper.CreateInviteToStreamAsync(this, Discord, maxAge, maxUses, isTemporary, isUnique, user, options).ConfigureAwait(false);
- ///
- public async Task> GetInvitesAsync(RequestOptions options = null)
- => await ChannelHelper.GetInvitesAsync(this, Discord, options).ConfigureAwait(false);
+ /// Cannot create threads in voice channels.
+ public override Task CreateThreadAsync(string name, ThreadType type = ThreadType.PublicThread, ThreadArchiveDuration autoArchiveDuration = ThreadArchiveDuration.OneDay, IMessage message = null, bool? invitable = null, int? slowmode = null, RequestOptions options = null)
+ => throw new InvalidOperationException("Voice channels cannot contain threads.");
+
+ /// Cannot modify text channel properties for voice channels.
+ public override Task ModifyAsync(Action func, RequestOptions options = null)
+ => throw new InvalidOperationException("Cannot modify text channel properties for voice channels.");
+
+ #endregion
+
+ #region TextOverrides
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override Task GetMessageAsync(ulong id, RequestOptions options = null)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.GetMessageAsync(id, options);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override Task DeleteMessageAsync(IMessage message, RequestOptions options = null)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.DeleteMessageAsync(message, options);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override Task DeleteMessageAsync(ulong messageId, RequestOptions options = null)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.DeleteMessageAsync(messageId, options);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.DeleteMessagesAsync(messages, options);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override Task DeleteMessagesAsync(IEnumerable messageIds, RequestOptions options = null)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.DeleteMessagesAsync(messageIds, options);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override IDisposable EnterTypingState(RequestOptions options = null)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.EnterTypingState(options);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override SocketMessage GetCachedMessage(ulong id)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.GetCachedMessage(id);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override IReadOnlyCollection GetCachedMessages(IMessage fromMessage, Direction dir, int limit = 100)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.GetCachedMessages(fromMessage, dir, limit);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override IReadOnlyCollection GetCachedMessages(int limit = 100)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.GetCachedMessages(limit);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override IReadOnlyCollection GetCachedMessages(ulong fromMessageId, Direction dir, int limit = 100)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.GetCachedMessages(fromMessageId, dir, limit);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = 100, RequestOptions options = null)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.GetMessagesAsync(fromMessage, dir, limit, options);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override IAsyncEnumerable> GetMessagesAsync(int limit = 100, RequestOptions options = null)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.GetMessagesAsync(limit, options);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = 100, RequestOptions options = null)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.GetMessagesAsync(fromMessageId, dir, limit, options);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override Task> GetPinnedMessagesAsync(RequestOptions options = null)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.GetPinnedMessagesAsync(options);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override Task GetWebhookAsync(ulong id, RequestOptions options = null)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.GetWebhookAsync(id, options);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override Task> GetWebhooksAsync(RequestOptions options = null)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.GetWebhooksAsync(options);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override Task CreateWebhookAsync(string name, Stream avatar = null, RequestOptions options = null)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.CreateWebhookAsync(name, avatar, options);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override Task ModifyMessageAsync(ulong messageId, Action func, RequestOptions options = null)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.ModifyMessageAsync(messageId, func, options);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override Task SendFileAsync(FileAttachment attachment, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.SendFileAsync(attachment, text, isTTS, embed, options, allowedMentions, messageReference, components, stickers, embeds, flags);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.SendFileAsync(stream, filename, text, isTTS, embed, options, isSpoiler, allowedMentions, messageReference, components, stickers, embeds, flags);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override Task SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.SendFileAsync(filePath, text, isTTS, embed, options, isSpoiler, allowedMentions, messageReference, components, stickers, embeds, flags);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override Task SendFilesAsync(IEnumerable attachments, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.SendFilesAsync(attachments, text, isTTS, embed, options, allowedMentions, messageReference, components, stickers, embeds, flags);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.SendMessageAsync(text, isTTS, embed, options, allowedMentions, messageReference, components, stickers, embeds, flags);
+ }
+
+ /// This function is only supported in Text-In-Voice channels.
+ public override Task TriggerTypingAsync(RequestOptions options = null)
+ {
+ if (!IsTextInVoice)
+ throw new NotSupportedException("This function is only supported in Text-In-Voice channels");
+ return base.TriggerTypingAsync(options);
+ }
+
+ #endregion
private string DebuggerDisplay => $"{Name} ({Id}, Voice)";
internal new SocketVoiceChannel Clone() => MemberwiseClone() as SocketVoiceChannel;
- #endregion
#region IGuildChannel
///
diff --git a/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();
}
}
From 20ffa645257315c1b273f82222b380c5ff79c560 Mon Sep 17 00:00:00 2001
From: Paulo
Date: Fri, 13 May 2022 13:03:45 -0300
Subject: [PATCH 11/84] fix: Possible NRE in Sanitize (#2290)
---
src/Discord.Net.Core/Format.cs | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
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;
}
From b0a3b65bc05e220dbb003b28caaaccd3bce0633e Mon Sep 17 00:00:00 2001
From: openmilk <33862452+openmilk@users.noreply.github.com>
Date: Sat, 14 May 2022 08:28:46 +1000
Subject: [PATCH 12/84] feature: Webhook support for threads (#2291)
* Added thread support to webhooks
Added thread support to delete/send messages for webhooks
* Revert "Added thread support to webhooks"
This reverts commit c45ef389c5df6a924b6ea5d46d5507386904f965.
* read added threads as im a dummy
* fixed formating
* Fixed modify EmbedMessage
---
src/Discord.Net.Rest/DiscordRestApiClient.cs | 52 +++++++++++++------
.../DiscordWebhookClient.cs | 28 +++++-----
.../WebhookClientHelper.cs | 33 ++++++------
3 files changed, 66 insertions(+), 47 deletions(-)
diff --git a/src/Discord.Net.Rest/DiscordRestApiClient.cs b/src/Discord.Net.Rest/DiscordRestApiClient.cs
index 3b829ee17..55e9e13dc 100644
--- a/src/Discord.Net.Rest/DiscordRestApiClient.cs
+++ b/src/Discord.Net.Rest/DiscordRestApiClient.cs
@@ -173,10 +173,12 @@ namespace Discord.API
private async Task LogoutInternalAsync()
{
//An exception here will lock the client into the unusable LoggingOut state, but that's probably fine since our client is in an undefined state too.
- if (LoginState == LoginState.LoggedOut) return;
+ if (LoginState == LoginState.LoggedOut)
+ return;
LoginState = LoginState.LoggingOut;
- try { _loginCancelToken?.Cancel(false); }
+ try
+ { _loginCancelToken?.Cancel(false); }
catch { }
await DisconnectInternalAsync(null).ConfigureAwait(false);
@@ -398,7 +400,7 @@ namespace Discord.API
Preconditions.AtLeast(args.Position, 0, nameof(args.Position));
Preconditions.NotNullOrWhitespace(args.Name, nameof(args.Name));
- if(args.Name.IsSpecified)
+ if (args.Name.IsSpecified)
Preconditions.AtMost(args.Name.Value.Length, 100, nameof(args.Name));
options = RequestOptions.CreateOrClone(options);
@@ -414,9 +416,9 @@ namespace Discord.API
Preconditions.AtLeast(args.Position, 0, nameof(args.Position));
Preconditions.NotNullOrWhitespace(args.Name, nameof(args.Name));
- if(args.Name.IsSpecified)
+ if (args.Name.IsSpecified)
Preconditions.AtMost(args.Name.Value.Length, 100, nameof(args.Name));
- if(args.Topic.IsSpecified)
+ if (args.Topic.IsSpecified)
Preconditions.AtMost(args.Topic.Value.Length, 1024, nameof(args.Name));
Preconditions.AtLeast(args.SlowModeInterval, 0, nameof(args.SlowModeInterval));
@@ -798,9 +800,11 @@ namespace Discord.API
var ids = new BucketIds(channelId: channelId);
return await SendJsonAsync("POST", () => $"channels/{channelId}/messages", args, ids, clientBucket: ClientBucketType.SendEdit, options: options).ConfigureAwait(false);
}
+
+
/// Message content is too long, length must be less or equal to .
/// This operation may only be called with a token.
- public async Task CreateWebhookMessageAsync(ulong webhookId, CreateWebhookMessageParams args, RequestOptions options = null)
+ public async Task CreateWebhookMessageAsync(ulong webhookId, CreateWebhookMessageParams args, RequestOptions options = null, ulong? threadId = null)
{
if (AuthTokenType != TokenType.Webhook)
throw new InvalidOperationException($"This operation may only be called with a {nameof(TokenType.Webhook)} token.");
@@ -816,12 +820,12 @@ namespace Discord.API
options = RequestOptions.CreateOrClone(options);
var ids = new BucketIds(webhookId: webhookId);
- return await SendJsonAsync("POST", () => $"webhooks/{webhookId}/{AuthToken}?wait=true", args, ids, clientBucket: ClientBucketType.SendEdit, options: options).ConfigureAwait(false);
+ return await SendJsonAsync("POST", () => $"webhooks/{webhookId}/{AuthToken}?{WebhookQuery(true, threadId)}", args, ids, clientBucket: ClientBucketType.SendEdit, options: options).ConfigureAwait(false);
}
/// Message content is too long, length must be less or equal to .
/// This operation may only be called with a token.
- public async Task ModifyWebhookMessageAsync(ulong webhookId, ulong messageId, ModifyWebhookMessageParams args, RequestOptions options = null)
+ public async Task ModifyWebhookMessageAsync(ulong webhookId, ulong messageId, ModifyWebhookMessageParams args, RequestOptions options = null, ulong? threadId = null)
{
if (AuthTokenType != TokenType.Webhook)
throw new InvalidOperationException($"This operation may only be called with a {nameof(TokenType.Webhook)} token.");
@@ -837,11 +841,11 @@ namespace Discord.API
options = RequestOptions.CreateOrClone(options);
var ids = new BucketIds(webhookId: webhookId);
- await SendJsonAsync("PATCH", () => $"webhooks/{webhookId}/{AuthToken}/messages/{messageId}", args, ids, clientBucket: ClientBucketType.SendEdit, options: options).ConfigureAwait(false);
+ await SendJsonAsync("PATCH", () => $"webhooks/{webhookId}/{AuthToken}/messages/{messageId}${WebhookQuery(false, threadId)}", args, ids, clientBucket: ClientBucketType.SendEdit, options: options).ConfigureAwait(false);
}
/// This operation may only be called with a token.
- public async Task DeleteWebhookMessageAsync(ulong webhookId, ulong messageId, RequestOptions options = null)
+ public async Task DeleteWebhookMessageAsync(ulong webhookId, ulong messageId, RequestOptions options = null, ulong? threadId = null)
{
if (AuthTokenType != TokenType.Webhook)
throw new InvalidOperationException($"This operation may only be called with a {nameof(TokenType.Webhook)} token.");
@@ -852,7 +856,7 @@ namespace Discord.API
options = RequestOptions.CreateOrClone(options);
var ids = new BucketIds(webhookId: webhookId);
- await SendAsync("DELETE", () => $"webhooks/{webhookId}/{AuthToken}/messages/{messageId}", ids, options: options).ConfigureAwait(false);
+ await SendAsync("DELETE", () => $"webhooks/{webhookId}/{AuthToken}/messages/{messageId}?{WebhookQuery(false, threadId)}", ids, options: options).ConfigureAwait(false);
}
/// Message content is too long, length must be less or equal to .
@@ -873,7 +877,7 @@ namespace Discord.API
/// Message content is too long, length must be less or equal to .
/// This operation may only be called with a token.
- public async Task UploadWebhookFileAsync(ulong webhookId, UploadWebhookFileParams args, RequestOptions options = null)
+ public async Task UploadWebhookFileAsync(ulong webhookId, UploadWebhookFileParams args, RequestOptions options = null, ulong? threadId = null)
{
if (AuthTokenType != TokenType.Webhook)
throw new InvalidOperationException($"This operation may only be called with a {nameof(TokenType.Webhook)} token.");
@@ -893,7 +897,7 @@ namespace Discord.API
}
var ids = new BucketIds(webhookId: webhookId);
- return await SendMultipartAsync("POST", () => $"webhooks/{webhookId}/{AuthToken}?wait=true", args.ToDictionary(), ids, clientBucket: ClientBucketType.SendEdit, options: options).ConfigureAwait(false);
+ return await SendMultipartAsync("POST", () => $"webhooks/{webhookId}/{AuthToken}?{WebhookQuery(true, threadId)}", args.ToDictionary(), ids, clientBucket: ClientBucketType.SendEdit, options: options).ConfigureAwait(false);
}
public async Task DeleteMessageAsync(ulong channelId, ulong messageId, RequestOptions options = null)
{
@@ -1380,7 +1384,7 @@ namespace Discord.API
if ((!args.Embeds.IsSpecified || args.Embeds.Value == null || args.Embeds.Value.Length == 0) && !args.File.IsSpecified)
Preconditions.NotNullOrEmpty(args.Content, nameof(args.Content));
- if(args.Content.IsSpecified && args.Content.Value?.Length > DiscordConfig.MaxMessageSize)
+ if (args.Content.IsSpecified && args.Content.Value?.Length > DiscordConfig.MaxMessageSize)
throw new ArgumentException(message: $"Message content is too long, length must be less or equal to {DiscordConfig.MaxMessageSize}.", paramName: nameof(args.Content));
options = RequestOptions.CreateOrClone(options);
@@ -1400,7 +1404,7 @@ namespace Discord.API
throw new ArgumentException(message: $"Message content is too long, length must be less or equal to {DiscordConfig.MaxMessageSize}.", paramName: nameof(args.Content));
options = RequestOptions.CreateOrClone(options);
-
+
var ids = new BucketIds();
return await SendMultipartAsync("POST", () => $"webhooks/{CurrentApplicationId}/{token}?wait=true", args.ToDictionary(), ids, clientBucket: ClientBucketType.SendEdit, options: options).ConfigureAwait(false);
}
@@ -1729,8 +1733,10 @@ namespace Discord.API
if (args.TargetType.IsSpecified)
{
Preconditions.NotEqual((int)args.TargetType.Value, (int)TargetUserType.Undefined, nameof(args.TargetType));
- if (args.TargetType.Value == TargetUserType.Stream) Preconditions.GreaterThan(args.TargetUserId, 0, nameof(args.TargetUserId));
- if (args.TargetType.Value == TargetUserType.EmbeddedApplication) Preconditions.GreaterThan(args.TargetApplicationId, 0, nameof(args.TargetUserId));
+ if (args.TargetType.Value == TargetUserType.Stream)
+ Preconditions.GreaterThan(args.TargetUserId, 0, nameof(args.TargetUserId));
+ if (args.TargetType.Value == TargetUserType.EmbeddedApplication)
+ Preconditions.GreaterThan(args.TargetApplicationId, 0, nameof(args.TargetUserId));
}
options = RequestOptions.CreateOrClone(options);
@@ -2414,6 +2420,18 @@ namespace Discord.API
return (expr as MemberExpression).Member.Name;
}
+
+ private static string WebhookQuery(bool wait = false, ulong? threadId = null)
+ {
+ List querys = new List() { };
+ if (wait)
+ querys.Add("wait=true");
+ if (threadId.HasValue)
+ querys.Add($"thread_id={threadId}");
+
+ return $"{string.Join("&", querys)}";
+ }
+
#endregion
}
}
diff --git a/src/Discord.Net.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
-
-
-
-
-
-
+
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.0ShardedClient
-
-
-
-
-
-
+
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.0TextCommandFramework
-
-
-
-
-
+
+
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.0WebHookClient
-
+
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
From 13ccc7c9972c2b55983de9d75cf35b29db5fd30b Mon Sep 17 00:00:00 2001
From: Misha133 <61027276+Misha-133@users.noreply.github.com>
Date: Wed, 18 May 2022 10:50:55 +0300
Subject: [PATCH 20/84] feature: Add `.With` methods to ActionRowBuilder
(#2296)
* Added `.With` methods to `ActionRowBuilder`
- Added `.WithButton` & `.WithSelectMenu` methods to `ActionRowBuilder`
- fixed a typo
* removed `` from methods which don't directly throw an exception
* Update src/Discord.Net.Core/Entities/Interactions/MessageComponents/ComponentBuilder.cs
* Update src/Discord.Net.Core/Entities/Interactions/MessageComponents/ComponentBuilder.cs
Co-authored-by: Quin Lynch <49576606+quinchs@users.noreply.github.com>
---
.../MessageComponents/ComponentBuilder.cs | 100 +++++++++++++++++-
1 file changed, 97 insertions(+), 3 deletions(-)
diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ComponentBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ComponentBuilder.cs
index 9c529f469..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
///
@@ -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.
///
From 1f01881bebfc6565ce74c11fe260bcdf15a98cca Mon Sep 17 00:00:00 2001
From: Armano den Boef <68127614+Rozen4334@users.noreply.github.com>
Date: Wed, 18 May 2022 09:51:37 +0200
Subject: [PATCH 21/84] feature: Add DefaultArchiveDuration to ITextChannel
(#2295)
---
.../Entities/Channels/ITextChannel.cs | 11 +++++++++++
src/Discord.Net.Rest/API/Common/Channel.cs | 3 +++
.../Entities/Channels/RestTextChannel.cs | 9 ++++++++-
.../Entities/Channels/SocketTextChannel.cs | 8 +++++++-
.../MockedEntities/MockedTextChannel.cs | 2 ++
5 files changed, 31 insertions(+), 2 deletions(-)
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.Rest/API/Common/Channel.cs b/src/Discord.Net.Rest/API/Common/Channel.cs
index d565b269a..0eab65686 100644
--- a/src/Discord.Net.Rest/API/Common/Channel.cs
+++ b/src/Discord.Net.Rest/API/Common/Channel.cs
@@ -66,5 +66,8 @@ namespace Discord.API
[JsonProperty("member_count")]
public Optional MemberCount { get; set; }
+
+ [JsonProperty("default_auto_archive_duration")]
+ public Optional AutoArchiveDuration { get; set; }
}
}
diff --git a/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs
index a73bda334..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
}
///
diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs
index e8454ecf8..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
}
///
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();
From b465d609f08822e58fec10fcf9e60f918b408ca8 Mon Sep 17 00:00:00 2001
From: Cenk Ergen <57065323+Cenngo@users.noreply.github.com>
Date: Wed, 18 May 2022 10:52:14 +0300
Subject: [PATCH 22/84] fix: Application commands are disabled to everyone
except admins by default (#2293)
---
src/Discord.Net.Interactions/Info/ModuleInfo.cs | 2 +-
.../Utilities/ApplicationCommandRestUtil.cs | 9 ++++++---
2 files changed, 7 insertions(+), 4 deletions(-)
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/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;
}
}
From 20bd2e9e2f8383fd101c88865d22dea02004280b Mon Sep 17 00:00:00 2001
From: Misha133 <61027276+Misha-133@users.noreply.github.com>
Date: Wed, 18 May 2022 10:52:38 +0300
Subject: [PATCH 23/84] [Docs] Autocomplete examples (#2288)
* Improved example in int.framework intro
* Added example to `autocompletion`
* modified example to utilise user's input
* added case insensetive matching; mentioned that 25 suggestions is an API limit
---
docs/guides/int_framework/autocompletion.md | 2 ++
.../autocompletion/autocomplete-example.cs | 20 +++++++++++++++++++
.../samples/intro/autocomplete.cs | 18 ++++++++++++++---
3 files changed, 37 insertions(+), 3 deletions(-)
create mode 100644 docs/guides/int_framework/samples/autocompletion/autocomplete-example.cs
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/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
From a24dde4b19adf7a8615f59b9ab9713e5bbb08833 Mon Sep 17 00:00:00 2001
From: Armano den Boef <68127614+Rozen4334@users.noreply.github.com>
Date: Wed, 18 May 2022 09:56:57 +0200
Subject: [PATCH 24/84] feature: optional API calling to RestInteraction
(#2281)
* Take 2
* Expose channel & guild Id for manual calling
* Make api calling optional at runtime
* Resolve build errors
* Bind runtime option to interaction type
* Expose methods to get channel & guild from API
* Patch out NRE's, test on all int types
---
src/Discord.Net.Rest/DiscordRestClient.cs | 22 ++-
src/Discord.Net.Rest/DiscordRestConfig.cs | 2 +
.../CommandBase/RestCommandBase.cs | 8 +-
.../CommandBase/RestCommandBaseData.cs | 8 +-
.../CommandBase/RestResolvableData.cs | 26 +++-
.../MessageCommands/RestMessageCommand.cs | 10 +-
.../MessageCommands/RestMessageCommandData.cs | 6 +-
.../UserCommands/RestUserCommand.cs | 10 +-
.../UserCommands/RestUserCommandData.cs | 4 +-
.../MessageComponents/RestMessageComponent.cs | 8 +-
.../Entities/Interactions/Modals/RestModal.cs | 4 +-
.../Entities/Interactions/RestInteraction.cs | 143 +++++++++++++++---
.../Interactions/RestPingInteraction.cs | 4 +-
.../RestAutocompleteInteraction.cs | 4 +-
.../SlashCommands/RestSlashCommand.cs | 10 +-
.../SlashCommands/RestSlashCommandData.cs | 8 +-
.../Entities/Users/RestGuildUser.cs | 14 +-
17 files changed, 212 insertions(+), 79 deletions(-)
diff --git a/src/Discord.Net.Rest/DiscordRestClient.cs b/src/Discord.Net.Rest/DiscordRestClient.cs
index b1948f80a..7cb15bed1 100644
--- a/src/Discord.Net.Rest/DiscordRestClient.cs
+++ b/src/Discord.Net.Rest/DiscordRestClient.cs
@@ -32,9 +32,15 @@ namespace Discord.Rest
/// Initializes a new with the provided configuration.
///
/// The configuration to be used with the client.
- public DiscordRestClient(DiscordRestConfig config) : base(config, CreateApiClient(config)) { }
+ public DiscordRestClient(DiscordRestConfig config) : base(config, CreateApiClient(config))
+ {
+ _apiOnCreation = config.APIOnRestInteractionCreation;
+ }
// used for socket client rest access
- internal DiscordRestClient(DiscordRestConfig config, API.DiscordRestApiClient api) : base(config, api) { }
+ internal DiscordRestClient(DiscordRestConfig config, API.DiscordRestApiClient api) : base(config, api)
+ {
+ _apiOnCreation = config.APIOnRestInteractionCreation;
+ }
private static API.DiscordRestApiClient CreateApiClient(DiscordRestConfig config)
=> new API.DiscordRestApiClient(config.RestClientProvider, DiscordRestConfig.UserAgent, serializer: Serializer, useSystemClock: config.UseSystemClock, defaultRatelimitCallback: config.DefaultRatelimitCallback);
@@ -82,6 +88,8 @@ namespace Discord.Rest
#region Rest interactions
+ private readonly bool _apiOnCreation;
+
public bool IsValidHttpInteraction(string publicKey, string signature, string timestamp, string body)
=> IsValidHttpInteraction(publicKey, signature, timestamp, Encoding.UTF8.GetBytes(body));
public bool IsValidHttpInteraction(string publicKey, string signature, string timestamp, byte[] body)
@@ -113,8 +121,8 @@ namespace Discord.Rest
/// A that represents the incoming http interaction.
///
/// Thrown when the signature doesn't match the public key.
- public Task ParseHttpInteractionAsync(string publicKey, string signature, string timestamp, string body)
- => ParseHttpInteractionAsync(publicKey, signature, timestamp, Encoding.UTF8.GetBytes(body));
+ public Task ParseHttpInteractionAsync(string publicKey, string signature, string timestamp, string body, Func doApiCallOnCreation = null)
+ => ParseHttpInteractionAsync(publicKey, signature, timestamp, Encoding.UTF8.GetBytes(body), doApiCallOnCreation);
///
/// Creates a from a http message.
@@ -127,7 +135,7 @@ namespace Discord.Rest
/// A that represents the incoming http interaction.
///
/// Thrown when the signature doesn't match the public key.
- public async Task ParseHttpInteractionAsync(string publicKey, string signature, string timestamp, byte[] body)
+ public async Task ParseHttpInteractionAsync(string publicKey, string signature, string timestamp, byte[] body, Func doApiCallOnCreation = null)
{
if (!IsValidHttpInteraction(publicKey, signature, timestamp, body))
{
@@ -138,12 +146,12 @@ namespace Discord.Rest
using (var jsonReader = new JsonTextReader(textReader))
{
var model = Serializer.Deserialize(jsonReader);
- return await RestInteraction.CreateAsync(this, model);
+ return await RestInteraction.CreateAsync(this, model, doApiCallOnCreation != null ? doApiCallOnCreation(model.Type) : _apiOnCreation);
}
}
#endregion
-
+
public async Task GetApplicationInfoAsync(RequestOptions options = null)
{
return _applicationInfo ??= await ClientHelper.GetApplicationInfoAsync(this, options).ConfigureAwait(false);
diff --git a/src/Discord.Net.Rest/DiscordRestConfig.cs b/src/Discord.Net.Rest/DiscordRestConfig.cs
index 7bf7440ce..a09d9ee98 100644
--- a/src/Discord.Net.Rest/DiscordRestConfig.cs
+++ b/src/Discord.Net.Rest/DiscordRestConfig.cs
@@ -9,5 +9,7 @@ namespace Discord.Rest
{
/// Gets or sets the provider used to generate new REST connections.
public RestClientProvider RestClientProvider { get; set; } = DefaultRestClientProvider.Instance;
+
+ public bool APIOnRestInteractionCreation { get; set; } = true;
}
}
diff --git a/src/Discord.Net.Rest/Entities/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/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/RestInteraction.cs b/src/Discord.Net.Rest/Entities/Interactions/RestInteraction.cs
index b8c0f961d..59adc0347 100644
--- a/src/Discord.Net.Rest/Entities/Interactions/RestInteraction.cs
+++ b/src/Discord.Net.Rest/Entities/Interactions/RestInteraction.cs
@@ -16,6 +16,10 @@ namespace Discord.Rest
///
public abstract class RestInteraction : RestEntity, IDiscordInteraction
{
+ // Added so channel & guild methods don't need a client reference
+ private Func> _getChannel = null;
+ private Func> _getGuild = null;
+
///
public InteractionType Type { get; private set; }
@@ -31,6 +35,10 @@ namespace Discord.Rest
///
/// Gets the user who invoked the interaction.
///
+ ///
+ /// If this user is an and is set to false,
+ /// will return
+ ///
public RestUser User { get; private set; }
///
@@ -48,14 +56,38 @@ namespace Discord.Rest
public bool IsValidToken
=> InteractionHelper.CanRespondOrFollowup(this);
+ ///
+ /// Gets the ID of the channel this interaction was executed in.
+ ///
+ ///
+ /// if the interaction was not executed in a guild.
+ ///
+ public ulong? ChannelId { get; private set; } = null;
+
///
/// Gets the channel that this interaction was executed in.
///
+ ///
+ /// if is set to false.
+ /// Call to set this property and get the interaction channel.
+ ///
public IRestMessageChannel Channel { get; private set; }
///
- /// Gets the guild this interaction was executed in.
+ /// Gets the ID of the guild this interaction was executed in if applicable.
///
+ ///
+ /// if the interaction was not executed in a guild.
+ ///
+ public ulong? GuildId { get; private set; } = null;
+
+ ///
+ /// Gets the guild this interaction was executed in if applicable.
+ ///
+ ///
+ /// This property will be if is set to false
+ /// or if the interaction was not executed in a guild.
+ ///
public RestGuild Guild { get; private set; }
///
@@ -72,11 +104,11 @@ namespace Discord.Rest
: DateTime.UtcNow;
}
- internal static async Task CreateAsync(DiscordRestClient client, Model model)
+ internal static async Task CreateAsync(DiscordRestClient client, Model model, bool doApiCall)
{
if(model.Type == InteractionType.Ping)
{
- return await RestPingInteraction.CreateAsync(client, model);
+ return await RestPingInteraction.CreateAsync(client, model, doApiCall);
}
if (model.Type == InteractionType.ApplicationCommand)
@@ -90,26 +122,26 @@ namespace Discord.Rest
return dataModel.Type switch
{
- ApplicationCommandType.Slash => await RestSlashCommand.CreateAsync(client, model).ConfigureAwait(false),
- ApplicationCommandType.Message => await RestMessageCommand.CreateAsync(client, model).ConfigureAwait(false),
- ApplicationCommandType.User => await RestUserCommand.CreateAsync(client, model).ConfigureAwait(false),
+ ApplicationCommandType.Slash => await RestSlashCommand.CreateAsync(client, model, doApiCall).ConfigureAwait(false),
+ ApplicationCommandType.Message => await RestMessageCommand.CreateAsync(client, model, doApiCall).ConfigureAwait(false),
+ ApplicationCommandType.User => await RestUserCommand.CreateAsync(client, model, doApiCall).ConfigureAwait(false),
_ => null
};
}
if (model.Type == InteractionType.MessageComponent)
- return await RestMessageComponent.CreateAsync(client, model).ConfigureAwait(false);
+ return await RestMessageComponent.CreateAsync(client, model, doApiCall).ConfigureAwait(false);
if (model.Type == InteractionType.ApplicationCommandAutocomplete)
- return await RestAutocompleteInteraction.CreateAsync(client, model).ConfigureAwait(false);
+ return await RestAutocompleteInteraction.CreateAsync(client, model, doApiCall).ConfigureAwait(false);
if (model.Type == InteractionType.ModalSubmit)
- return await RestModal.CreateAsync(client, model).ConfigureAwait(false);
+ return await RestModal.CreateAsync(client, model, doApiCall).ConfigureAwait(false);
return null;
}
- internal virtual async Task UpdateAsync(DiscordRestClient discord, Model model)
+ internal virtual async Task UpdateAsync(DiscordRestClient discord, Model model, bool doApiCall)
{
IsDMInteraction = !model.GuildId.IsSpecified;
@@ -120,16 +152,23 @@ namespace Discord.Rest
Version = model.Version;
Type = model.Type;
- if(Guild == null && model.GuildId.IsSpecified)
+ if (Guild == null && model.GuildId.IsSpecified)
{
- Guild = await discord.GetGuildAsync(model.GuildId.Value);
+ GuildId = model.GuildId.Value;
+ if (doApiCall)
+ Guild = await discord.GetGuildAsync(model.GuildId.Value);
+ else
+ {
+ Guild = null;
+ _getGuild = new(async (opt, ul) => await discord.GetGuildAsync(ul, opt));
+ }
}
if (User == null)
{
if (model.Member.IsSpecified && model.GuildId.IsSpecified)
{
- User = RestGuildUser.Create(Discord, Guild, model.Member.Value);
+ User = RestGuildUser.Create(Discord, Guild, model.Member.Value, (Guild is null) ? model.GuildId.Value : null);
}
else
{
@@ -137,18 +176,33 @@ namespace Discord.Rest
}
}
- if(Channel == null && model.ChannelId.IsSpecified)
+ if (Channel == null && model.ChannelId.IsSpecified)
{
try
{
- Channel = (IRestMessageChannel)await discord.GetChannelAsync(model.ChannelId.Value);
+ ChannelId = model.ChannelId.Value;
+ if (doApiCall)
+ Channel = (IRestMessageChannel)await discord.GetChannelAsync(model.ChannelId.Value);
+ else
+ {
+ _getChannel = new(async (opt, ul) =>
+ {
+ if (Guild is null)
+ return (IRestMessageChannel)await discord.GetChannelAsync(ul, opt);
+ else // get a guild channel if the guild is set.
+ return (IRestMessageChannel)await Guild.GetChannelAsync(ul, opt);
+ });
+
+ Channel = null;
+ }
}
- catch(HttpException x) when(x.DiscordCode == DiscordErrorCode.MissingPermissions) { } // ignore
+ catch (HttpException x) when (x.DiscordCode == DiscordErrorCode.MissingPermissions) { } // ignore
}
UserLocale = model.UserLocale.IsSpecified
- ? model.UserLocale.Value
- : null;
+ ? model.UserLocale.Value
+ : null;
+
GuildLocale = model.GuildLocale.IsSpecified
? model.GuildLocale.Value
: null;
@@ -164,6 +218,59 @@ namespace Discord.Rest
return json.ToString();
}
+ ///
+ /// Gets the channel this interaction was executed in. Will be a DM channel if the interaction was executed in DM.
+ ///
+ ///
+ /// Calling this method succesfully will populate the property.
+ /// After this, further calls to this method will no longer call the API, and depend on the value set in .
+ ///
+ /// The request options for this request.
+ /// A Rest channel to send messages to.
+ /// Thrown if no channel can be received.
+ public async Task GetChannelAsync(RequestOptions options = null)
+ {
+ if (IsDMInteraction && Channel is null)
+ {
+ var channel = await User.CreateDMChannelAsync(options);
+ Channel = channel;
+ }
+
+ else if (Channel is null)
+ {
+ var channel = await _getChannel(options, ChannelId.Value);
+
+ if (channel is null)
+ throw new InvalidOperationException("The interaction channel was not able to be retrieved.");
+ Channel = channel;
+
+ _getChannel = null; // get rid of it, we don't need it anymore.
+ }
+
+ return Channel;
+ }
+
+ ///
+ /// Gets the guild this interaction was executed in if applicable.
+ ///
+ ///
+ /// Calling this method succesfully will populate the property.
+ /// After this, further calls to this method will no longer call the API, and depend on the value set in .
+ ///
+ /// The request options for this request.
+ /// The guild this interaction was executed in. if the interaction was executed inside DM.
+ public async Task GetGuildAsync(RequestOptions options)
+ {
+ if (IsDMInteraction)
+ return null;
+
+ if (Guild is null)
+ Guild = await _getGuild(options, GuildId.Value);
+
+ _getGuild = null; // get rid of it, we don't need it anymore.
+ return Guild;
+ }
+
///
public abstract string Defer(bool ephemeral = false, RequestOptions options = null);
///
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/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();
From cea59b55bab36397e8f18b06999797f409ed6086 Mon Sep 17 00:00:00 2001
From: Armano den Boef <68127614+Rozen4334@users.noreply.github.com>
Date: Wed, 18 May 2022 09:57:37 +0200
Subject: [PATCH 25/84] feature: Add Parse & TryParse to EmbedBuilder & Add
ToJsonString extension (#2284)
* Add parse & tryparse to embedbuilder.
* Add tostring extension for embeds
* Modify comments
* Resolve suggestions
* Update src/Discord.Net.Rest/Extensions/StringExtensions.cs
Co-authored-by: Quin Lynch <49576606+quinchs@users.noreply.github.com>
---
.../Entities/Messages/EmbedBuilder.cs | 50 +++++++++++++++++++
.../Extensions/StringExtensions.cs | 47 +++++++++++++++++
2 files changed, 97 insertions(+)
create mode 100644 src/Discord.Net.Rest/Extensions/StringExtensions.cs
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(json);
+
+ if (model is not null)
+ {
+ builder = model.ToEmbedBuilder();
+ return true;
+ }
+ return false;
+ }
+ catch
+ {
+ return false;
+ }
+ }
+
+ ///
+ /// Parses a string into an .
+ ///
+ /// The json string to parse.
+ /// An with populated values from the passed .
+ /// Thrown if the string passed is not valid json.
+ public static EmbedBuilder Parse(string json)
+ {
+ try
+ {
+ var model = JsonConvert.DeserializeObject(json);
+
+ if (model is not null)
+ return model.ToEmbedBuilder();
+
+ return new EmbedBuilder();
+ }
+ catch
+ {
+ throw;
+ }
+ }
+
///
/// Sets the title of an .
///
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);
+ }
+}
From 437c8a7f438a67512555e3ab0453d7b1224a2470 Mon Sep 17 00:00:00 2001
From: Quin Lynch <49576606+quinchs@users.noreply.github.com>
Date: Sun, 22 May 2022 08:05:05 -0300
Subject: [PATCH 26/84] Update README.md
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
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
From 54a5af7db40188b94adc263141955b8a896f82dc Mon Sep 17 00:00:00 2001
From: Tripletri
Date: Mon, 23 May 2022 11:43:38 +0500
Subject: [PATCH 27/84] fix: Upload file size limit (#2313)
---
src/Discord.Net.Rest/AssemblyInfo.cs | 1 +
.../Entities/Guilds/GuildHelper.cs | 15 +++---
.../Discord.Net.Tests.Integration.csproj | 1 +
.../DiscordRestApiClientTests.cs | 53 +++++++++++++++++++
.../Discord.Net.Tests.Unit.csproj | 2 +
.../GuildHelperTests.cs | 25 +++++++++
6 files changed, 91 insertions(+), 6 deletions(-)
create mode 100644 test/Discord.Net.Tests.Integration/DiscordRestApiClientTests.cs
create mode 100644 test/Discord.Net.Tests.Unit/GuildHelperTests.cs
diff --git a/src/Discord.Net.Rest/AssemblyInfo.cs b/src/Discord.Net.Rest/AssemblyInfo.cs
index 837fd1d04..59e1f0b4b 100644
--- a/src/Discord.Net.Rest/AssemblyInfo.cs
+++ b/src/Discord.Net.Rest/AssemblyInfo.cs
@@ -6,6 +6,7 @@ using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Discord.Net.Commands")]
[assembly: InternalsVisibleTo("Discord.Net.Tests")]
[assembly: InternalsVisibleTo("Discord.Net.Tests.Unit")]
+[assembly: InternalsVisibleTo("Discord.Net.Tests.Integration")]
[assembly: InternalsVisibleTo("Discord.Net.Interactions")]
[assembly: TypeForwardedTo(typeof(Discord.Embed))]
diff --git a/src/Discord.Net.Rest/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/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);
+ }
+}
From f47f3190d0950cc6314d2bbbf1fed75edf617084 Mon Sep 17 00:00:00 2001
From: sabihoshi <25006819+sabihoshi@users.noreply.github.com>
Date: Tue, 24 May 2022 13:29:16 +0800
Subject: [PATCH 28/84] fix: Use IDiscordClient.GetUserAsync impl in
DiscordSocketClient (#2319)
---
src/Discord.Net.WebSocket/DiscordSocketClient.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs
index 57d58a8b1..b0bd6f621 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.
///
From aae549a9764ce71493d9252569d4a1edc4191ac0 Mon Sep 17 00:00:00 2001
From: ShineSyndrome
Date: Tue, 24 May 2022 06:29:42 +0100
Subject: [PATCH 29/84] fix: typo in modal section of docs (#2318)
---
docs/guides/int_basics/modals/intro.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
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
From 7a07fd62e41bb62026bfac3bf7cc6697b7e77db2 Mon Sep 17 00:00:00 2001
From: Quin Lynch <49576606+quinchs@users.noreply.github.com>
Date: Tue, 24 May 2022 02:30:25 -0300
Subject: [PATCH 30/84] feature: Forum channels (#2316)
* initial implementation
* Update SocketForumChannel.cs
* rest forum channel and remove message builder for 4.x
* Update src/Discord.Net.Core/DiscordConfig.cs
Co-authored-by: Jared L <48422312+lhjt@users.noreply.github.com>
* Update src/Discord.Net.Core/Entities/Channels/IForumChannel.cs
Co-authored-by: Jared L <48422312+lhjt@users.noreply.github.com>
* Update src/Discord.Net.Core/DiscordConfig.cs
Co-authored-by: Jared L <48422312+lhjt@users.noreply.github.com>
* Update src/Discord.Net.Core/Entities/Channels/IForumChannel.cs
Co-authored-by: Jared L <48422312+lhjt@users.noreply.github.com>
* Update src/Discord.Net.Core/Entities/Channels/IForumChannel.cs
Co-authored-by: Jared L <48422312+lhjt@users.noreply.github.com>
* Update src/Discord.Net.Core/Entities/Channels/IForumChannel.cs
Co-authored-by: Jared L <48422312+lhjt@users.noreply.github.com>
Co-authored-by: Jared L <48422312+lhjt@users.noreply.github.com>
---
src/Discord.Net.Core/DiscordConfig.cs | 10 +
.../Entities/Channels/ChannelType.cs | 4 +-
.../Entities/Channels/IForumChannel.cs | 216 ++++++++++++++++++
src/Discord.Net.Core/Entities/ForumTag.cs | 42 ++++
src/Discord.Net.Rest/API/Common/Channel.cs | 4 +
.../API/Common/ChannelThreads.cs | 3 -
src/Discord.Net.Rest/API/Common/ForumTags.cs | 21 ++
.../API/Common/ForumThreadMessage.cs | 33 +++
.../API/Rest/CreateMultipartPostAsync.cs | 96 ++++++++
.../API/Rest/CreatePostParams.cs | 25 ++
.../API/Rest/UploadFileParams.cs | 2 +-
.../API/Rest/UploadInteractionFileParams.cs | 2 +-
.../API/Rest/UploadWebhookFileParams.cs | 2 +-
src/Discord.Net.Rest/DiscordRestApiClient.cs | 26 ++-
.../Entities/Channels/RestForumChannel.cs | 131 +++++++++++
.../Entities/Channels/RestGuildChannel.cs | 1 +
.../Entities/Channels/ThreadHelper.cs | 138 +++++++++++
.../Interactions/InteractionHelper.cs | 4 +-
.../Net/Converters/UInt64Converter.cs | 4 +-
.../Entities/Channels/SocketForumChannel.cs | 128 +++++++++++
.../Entities/Channels/SocketGuildChannel.cs | 1 +
.../Entities/Guilds/SocketGuild.cs | 10 +-
22 files changed, 887 insertions(+), 16 deletions(-)
create mode 100644 src/Discord.Net.Core/Entities/Channels/IForumChannel.cs
create mode 100644 src/Discord.Net.Core/Entities/ForumTag.cs
create mode 100644 src/Discord.Net.Rest/API/Common/ForumTags.cs
create mode 100644 src/Discord.Net.Rest/API/Common/ForumThreadMessage.cs
create mode 100644 src/Discord.Net.Rest/API/Rest/CreateMultipartPostAsync.cs
create mode 100644 src/Discord.Net.Rest/API/Rest/CreatePostParams.cs
create mode 100644 src/Discord.Net.Rest/Entities/Channels/RestForumChannel.cs
create mode 100644 src/Discord.Net.WebSocket/Entities/Channels/SocketForumChannel.cs
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/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/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.Rest/API/Common/Channel.cs b/src/Discord.Net.Rest/API/Common/Channel.cs
index 0eab65686..d9d7d469c 100644
--- a/src/Discord.Net.Rest/API/Common/Channel.cs
+++ b/src/Discord.Net.Rest/API/Common/Channel.cs
@@ -67,6 +67,10 @@ 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/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