Browse Source

Merge branch 'release/3.x' into feature/stickers-expansion

pull/1923/head
quin lynch 4 years ago
parent
commit
6c918e6186
89 changed files with 2543 additions and 228 deletions
  1. +5
    -3
      docs/guides/emoji/emoji.md
  2. +1
    -1
      docs/guides/slash-commands/03-responding-to-slash-commands.md
  3. +18
    -0
      src/Discord.Net.Core/CDN.cs
  4. +3
    -3
      src/Discord.Net.Core/Discord.Net.Core.csproj
  5. +410
    -22
      src/Discord.Net.Core/Discord.Net.Core.xml
  6. +120
    -0
      src/Discord.Net.Core/Entities/Channels/IStageChannel.cs
  7. +24
    -0
      src/Discord.Net.Core/Entities/Channels/StageInstanceProperties.cs
  8. +14
    -0
      src/Discord.Net.Core/Entities/Channels/StagePrivacyLevel.cs
  9. +24
    -4
      src/Discord.Net.Core/Entities/Emotes/Emoji.cs
  10. +59
    -0
      src/Discord.Net.Core/Entities/Guilds/IGuild.cs
  11. +22
    -0
      src/Discord.Net.Core/Entities/Guilds/NsfwLevel.cs
  12. +1
    -8
      src/Discord.Net.Core/Entities/Interactions/IApplicationCommand.cs
  13. +54
    -0
      src/Discord.Net.Core/Entities/Interactions/IDiscordInteraction.cs
  14. +25
    -10
      src/Discord.Net.Core/Entities/Interactions/Message Components/ComponentBuilder.cs
  15. +1
    -1
      src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs
  16. +8
    -0
      src/Discord.Net.Core/Entities/Messages/MessageProperties.cs
  17. +49
    -6
      src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs
  18. +59
    -42
      src/Discord.Net.Core/Entities/Roles/Color.cs
  19. +27
    -5
      src/Discord.Net.Core/Entities/Users/IUser.cs
  20. +6
    -0
      src/Discord.Net.Core/Entities/Users/IVoiceState.cs
  21. +6
    -1
      src/Discord.Net.Core/Entities/Webhooks/IWebhook.cs
  22. +2
    -0
      src/Discord.Net.Rest/API/Common/Game.cs
  23. +2
    -0
      src/Discord.Net.Rest/API/Common/Guild.cs
  24. +1
    -1
      src/Discord.Net.Rest/API/Common/InteractionCallbackData.cs
  25. +30
    -0
      src/Discord.Net.Rest/API/Common/StageInstance.cs
  26. +4
    -0
      src/Discord.Net.Rest/API/Common/User.cs
  27. +3
    -0
      src/Discord.Net.Rest/API/Common/VoiceState.cs
  28. +2
    -0
      src/Discord.Net.Rest/API/Common/Webhook.cs
  29. +21
    -0
      src/Discord.Net.Rest/API/Rest/CreateStageInstanceParams.cs
  30. +1
    -1
      src/Discord.Net.Rest/API/Rest/CreateWebhookMessageParams.cs
  31. +19
    -0
      src/Discord.Net.Rest/API/Rest/ModifyStageInstanceParams.cs
  32. +17
    -0
      src/Discord.Net.Rest/API/Rest/ModifyVoiceStateParams.cs
  33. +2
    -0
      src/Discord.Net.Rest/API/Rest/ModifyWebhookMessageParams.cs
  34. +3
    -3
      src/Discord.Net.Rest/Discord.Net.Rest.csproj
  35. +138
    -2
      src/Discord.Net.Rest/Discord.Net.Rest.xml
  36. +87
    -6
      src/Discord.Net.Rest/DiscordRestApiClient.cs
  37. +16
    -0
      src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs
  38. +4
    -0
      src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs
  39. +155
    -0
      src/Discord.Net.Rest/Entities/Channels/RestStageChannel.cs
  40. +3
    -0
      src/Discord.Net.Rest/Entities/Channels/ThreadHelper.cs
  41. +108
    -0
      src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs
  42. +45
    -4
      src/Discord.Net.Rest/Entities/Interactions/InteractionHelper.cs
  43. +45
    -5
      src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs
  44. +3
    -0
      src/Discord.Net.Rest/Entities/Users/RestGroupUser.cs
  45. +2
    -0
      src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs
  46. +2
    -2
      src/Discord.Net.Rest/Entities/Users/RestThreadUser.cs
  47. +12
    -0
      src/Discord.Net.Rest/Entities/Users/RestUser.cs
  48. +2
    -0
      src/Discord.Net.Rest/Entities/Users/RestWebhookUser.cs
  49. +4
    -0
      src/Discord.Net.Rest/Entities/Webhooks/RestWebhook.cs
  50. +1
    -0
      src/Discord.Net.Rest/Extensions/EntityExtensions.cs
  51. +1
    -1
      src/Discord.Net.Rest/Net/Converters/ArrayConverter.cs
  52. +2
    -2
      src/Discord.Net.WebSocket/API/Gateway/GatewayOpCode.cs
  53. +5
    -1
      src/Discord.Net.WebSocket/API/Gateway/GuildMemberUpdateEvent.cs
  54. +1
    -1
      src/Discord.Net.WebSocket/API/Gateway/IdentifyParams.cs
  55. +5
    -4
      src/Discord.Net.WebSocket/API/Gateway/StatusUpdateParams.cs
  56. +60
    -1
      src/Discord.Net.WebSocket/BaseSocketClient.Events.cs
  57. +5
    -0
      src/Discord.Net.WebSocket/ConnectionManager.cs
  58. +8
    -2
      src/Discord.Net.WebSocket/Discord.Net.WebSocket.csproj
  59. +219
    -20
      src/Discord.Net.WebSocket/Discord.Net.WebSocket.xml
  60. +2
    -2
      src/Discord.Net.WebSocket/DiscordShardedClient.Events.cs
  61. +24
    -1
      src/Discord.Net.WebSocket/DiscordShardedClient.cs
  62. +35
    -7
      src/Discord.Net.WebSocket/DiscordSocketApiClient.cs
  63. +67
    -2
      src/Discord.Net.WebSocket/DiscordSocketClient.cs
  64. +2
    -0
      src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs
  65. +168
    -0
      src/Discord.Net.WebSocket/Entities/Channels/SocketStageChannel.cs
  66. +2
    -2
      src/Discord.Net.WebSocket/Entities/Channels/SocketThreadChannel.cs
  67. +47
    -0
      src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs
  68. +49
    -12
      src/Discord.Net.WebSocket/Entities/Interaction/Message Components/SocketMessageComponent.cs
  69. +5
    -9
      src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketSlashCommand.cs
  70. +3
    -3
      src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketSlashCommandDataOption.cs
  71. +19
    -2
      src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs
  72. +4
    -1
      src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs
  73. +3
    -1
      src/Discord.Net.WebSocket/Entities/Users/SocketGlobalUser.cs
  74. +7
    -0
      src/Discord.Net.WebSocket/Entities/Users/SocketGroupUser.cs
  75. +10
    -1
      src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs
  76. +4
    -0
      src/Discord.Net.WebSocket/Entities/Users/SocketSelfUser.cs
  77. +19
    -1
      src/Discord.Net.WebSocket/Entities/Users/SocketThreadUser.cs
  78. +8
    -1
      src/Discord.Net.WebSocket/Entities/Users/SocketUnknownUser.cs
  79. +18
    -0
      src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs
  80. +7
    -3
      src/Discord.Net.WebSocket/Entities/Users/SocketVoiceState.cs
  81. +19
    -0
      src/Discord.Net.WebSocket/Entities/Users/SocketWebhookUser.cs
  82. +1
    -1
      src/Discord.Net.Webhook/Discord.Net.Webhook.csproj
  83. +6
    -1
      src/Discord.Net.Webhook/Discord.Net.Webhook.xml
  84. +2
    -2
      src/Discord.Net.Webhook/DiscordWebhookClient.cs
  85. +4
    -0
      src/Discord.Net.Webhook/Entities/Messages/WebhookMessageProperties.cs
  86. +3
    -0
      src/Discord.Net.Webhook/Entities/Webhooks/RestInternalWebhook.cs
  87. +5
    -2
      src/Discord.Net.Webhook/WebhookClientHelper.cs
  88. +10
    -10
      src/Discord.Net/Discord.Net.nuspec
  89. +14
    -2
      test/Discord.Net.Tests.Unit/GuildPermissionsTests.cs

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

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


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


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

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


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

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


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


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

+ 1
- 1
docs/guides/slash-commands/03-responding-to-slash-commands.md View File

@@ -45,6 +45,6 @@ Let's try this out!


Let's go over the response types quickly, as you would only change them for style points :P Let's go over the response types quickly, as you would only change them for style points :P


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


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

+ 18
- 0
src/Discord.Net.Core/CDN.cs View File

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

/// <summary>
/// Returns a user banner URL.
/// </summary>
/// <param name="userId">The user snowflake identifier.</param>
/// <param name="bannerId">The banner identifier.</param>
/// <param name="size">The size of the image to return in horizontal pixels. This can be any power of two between 16 and 2048.</param>
/// <param name="format">The format to return.</param>
/// <returns>
/// A URL pointing to the user's banner in the specified size.
/// </returns>
public static string GetUserBannerUrl(ulong userId, string bannerId, ushort size, ImageFormat format)
{
if (bannerId == null)
return null;
string extension = FormatToExtension(format, bannerId);
return $"{DiscordConfig.CDNUrl}banners/{userId}/{bannerId}.{extension}?size={size}";
}
/// <summary> /// <summary>
/// Returns the default user avatar URL. /// Returns the default user avatar URL.
/// </summary> /// </summary>


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

@@ -8,12 +8,12 @@
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">net461;netstandard2.0;netstandard2.1</TargetFrameworks> <TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">net461;netstandard2.0;netstandard2.1</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' != 'Windows_NT' ">netstandard2.0;netstandard2.1</TargetFrameworks> <TargetFrameworks Condition=" '$(OS)' != 'Windows_NT' ">netstandard2.0;netstandard2.1</TargetFrameworks>
<PackageId>Discord.Net.Labs.Core</PackageId> <PackageId>Discord.Net.Labs.Core</PackageId>
<Version>2.4.6</Version>
<Version>3.0.1-pre</Version>
<Product>Discord.Net.Labs.Core</Product> <Product>Discord.Net.Labs.Core</Product>
<RepositoryUrl>https://github.com/Discord-Net-Labs/Discord.Net-Labs</RepositoryUrl> <RepositoryUrl>https://github.com/Discord-Net-Labs/Discord.Net-Labs</RepositoryUrl>
<PackageIcon>Temporary.png</PackageIcon> <PackageIcon>Temporary.png</PackageIcon>
<AssemblyVersion>2.3.8</AssemblyVersion>
<FileVersion>2.3.8</FileVersion>
<AssemblyVersion>3.3.1</AssemblyVersion>
<FileVersion>3.0.1</FileVersion>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild> <GeneratePackageOnBuild>false</GeneratePackageOnBuild>
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>


+ 410
- 22
src/Discord.Net.Core/Discord.Net.Core.xml View File

@@ -100,6 +100,18 @@
A URL pointing to the user's avatar in the specified size. A URL pointing to the user's avatar in the specified size.
</returns> </returns>
</member> </member>
<member name="M:Discord.CDN.GetUserBannerUrl(System.UInt64,System.String,System.UInt16,Discord.ImageFormat)">
<summary>
Returns a user banner URL.
</summary>
<param name="userId">The user snowflake identifier.</param>
<param name="bannerId">The banner identifier.</param>
<param name="size">The size of the image to return in horizontal pixels. This can be any power of two between 16 and 2048.</param>
<param name="format">The format to return.</param>
<returns>
A URL pointing to the user's banner in the specified size.
</returns>
</member>
<member name="M:Discord.CDN.GetDefaultUserAvatarUrl(System.UInt16)"> <member name="M:Discord.CDN.GetDefaultUserAvatarUrl(System.UInt16)">
<summary> <summary>
Returns the default user avatar URL. Returns the default user avatar URL.
@@ -1914,6 +1926,117 @@
A read-only collection of users that can access this channel. A read-only collection of users that can access this channel.
</returns> </returns>
</member> </member>
<member name="T:Discord.IStageChannel">
<summary>
Represents a generic Stage Channel.
</summary>
</member>
<member name="P:Discord.IStageChannel.Topic">
<summary>
Gets the topic of the Stage instance.
</summary>
<remarks>
If the stage isn't live then this property will be set to <see langword="null"/>.
</remarks>
</member>
<member name="P:Discord.IStageChannel.PrivacyLevel">
<summary>
The <see cref="T:Discord.StagePrivacyLevel"/> of the current stage.
</summary>
<remarks>
If the stage isn't live then this property will be set to <see langword="null"/>.
</remarks>
</member>
<member name="P:Discord.IStageChannel.DiscoverableDisabled">
<summary>
<see langword="true"/> if stage discovery is disabled, otherwise <see langword="false"/>.
</summary>
</member>
<member name="P:Discord.IStageChannel.Live">
<summary>
<see langword="true"/> when the stage is live, otherwise <see langword="false"/>.
</summary>
<remarks>
If the stage isn't live then this property will be set to <see langword="null"/>.
</remarks>
</member>
<member name="M:Discord.IStageChannel.StartStageAsync(System.String,Discord.StagePrivacyLevel,Discord.RequestOptions)">
<summary>
Starts the stage, creating a stage instance.
</summary>
<param name="topic">The topic for the stage/</param>
<param name="privacyLevel">The privacy level of the stage</param>
<param name="options">The options to be used when sending the request.</param>
<returns>
A task that represents the asynchronous start operation.
</returns>
</member>
<member name="M:Discord.IStageChannel.ModifyInstanceAsync(System.Action{Discord.StageInstanceProperties},Discord.RequestOptions)">
<summary>
Modifies the current stage instance.
</summary>
<param name="func">The properties to modify the stage instance with.</param>
<param name="options">The options to be used when sending the request.</param>
<returns>
A task that represents the asynchronous modify operation.
</returns>
</member>
<member name="M:Discord.IStageChannel.StopStageAsync(Discord.RequestOptions)">
<summary>
Stops the stage, deleting the stage instance.
</summary>
<param name="options">The options to be used when sending the request.</param>
<returns>
A task that represents the asynchronous stop operation.
</returns>
</member>
<member name="M:Discord.IStageChannel.RequestToSpeak(Discord.RequestOptions)">
<summary>
Indicates that the bot would like to speak within a stage channel.
</summary>
<param name="options">The options to be used when sending the request.</param>
<returns>
A task that represents the asynchronous request to speak operation.
</returns>
</member>
<member name="M:Discord.IStageChannel.BecomeSpeakerAsync(Discord.RequestOptions)">
<summary>
Makes the current user become a speaker within a stage.
</summary>
<param name="options">The options to be used when sending the request.</param>
<returns>
A task that represents the asynchronous speaker modify operation.
</returns>
</member>
<member name="M:Discord.IStageChannel.StopSpeakingAsync(Discord.RequestOptions)">
<summary>
Makes the current user a listener.
</summary>
<param name="options">The options to be used when sending the request.</param>
<returns>
A task that represents the asynchronous stop operation.
</returns>
</member>
<member name="M:Discord.IStageChannel.MoveToSpeaker(Discord.IGuildUser,Discord.RequestOptions)">
<summary>
Makes a user a speaker within a stage.
</summary>
<param name="user">The user to make the speaker.</param>
<param name="options">The options to be used when sending the request.</param>
<returns>
A task that represents the asynchronous move operation.
</returns>
</member>
<member name="M:Discord.IStageChannel.RemoveFromSpeaker(Discord.IGuildUser,Discord.RequestOptions)">
<summary>
Removes a user from speaking.
</summary>
<param name="user">The user to remove from speaking.</param>
<param name="options">The options to be used when sending the request.</param>
<returns>
A task that represents the asynchronous remove operation.
</returns>
</member>
<member name="T:Discord.ITextChannel"> <member name="T:Discord.ITextChannel">
<summary> <summary>
Represents a generic channel in a guild that can send and receive messages. Represents a generic channel in a guild that can send and receive messages.
@@ -2202,6 +2325,21 @@
<param name="id"> Sets the ID of the channel to apply this position to. </param> <param name="id"> Sets the ID of the channel to apply this position to. </param>
<param name="position"> Sets the new zero-based position of this channel. </param> <param name="position"> Sets the new zero-based position of this channel. </param>
</member> </member>
<member name="T:Discord.StageInstanceProperties">
<summary>
Represents properties to use when modifying a stage instance.
</summary>
</member>
<member name="P:Discord.StageInstanceProperties.Topic">
<summary>
Gets or sets the topic of the stage.
</summary>
</member>
<member name="P:Discord.StageInstanceProperties.PrivacyLevel">
<summary>
Gets or sets the privacy level of the stage.
</summary>
</member>
<member name="T:Discord.TextChannelProperties"> <member name="T:Discord.TextChannelProperties">
<summary> <summary>
Provides properties that are used to modify an <see cref="T:Discord.ITextChannel"/> with the specified changes. Provides properties that are used to modify an <see cref="T:Discord.ITextChannel"/> with the specified changes.
@@ -3114,6 +3252,14 @@
language tag format. language tag format.
</returns> </returns>
</member> </member>
<member name="P:Discord.IGuild.NsfwLevel">
<summary>
Gets the NSFW level of this guild.
</summary>
<returns>
The NSFW level of this guild.
</returns>
</member>
<member name="P:Discord.IGuild.PreferredCulture"> <member name="P:Discord.IGuild.PreferredCulture">
<summary> <summary>
Gets the preferred culture of this guild. Gets the preferred culture of this guild.
@@ -3336,6 +3482,29 @@
with the specified <paramref name="id"/>; <see langword="null" /> if none is found. with the specified <paramref name="id"/>; <see langword="null" /> if none is found.
</returns> </returns>
</member> </member>
<member name="M:Discord.IGuild.GetStageChannelAsync(System.UInt64,Discord.CacheMode,Discord.RequestOptions)">
<summary>
Gets a stage channel in this guild
</summary>
<param name="id">The snowflake identifier for the stage channel.</param>
<param name="mode">The <see cref="T:Discord.CacheMode"/> that determines whether the object should be fetched from cache.</param>
<param name="options">The options to be used when sending the request.</param>
<returns>
A task that represents the asynchronous get operation. The task result contains the stage channel associated
with the specified <paramref name="id"/>; <see langword="null" /> if none is found.
</returns>
</member>
<member name="M:Discord.IGuild.GetStageChannelsAsync(Discord.CacheMode,Discord.RequestOptions)">
<summary>
Gets a collection of all stage channels in this guild.
</summary>
<param name="mode">The <see cref="T:Discord.CacheMode"/> that determines whether the object should be fetched from cache.</param>
<param name="options">The options to be used when sending the request.</param>
<returns>
A task that represents the asynchronous get operation. The task result contains a read-only collection of
stage channels found within this guild.
</returns>
</member>
<member name="M:Discord.IGuild.GetAFKChannelAsync(Discord.CacheMode,Discord.RequestOptions)"> <member name="M:Discord.IGuild.GetAFKChannelAsync(Discord.CacheMode,Discord.RequestOptions)">
<summary> <summary>
Gets the AFK voice channel in this guild. Gets the AFK voice channel in this guild.
@@ -3402,6 +3571,28 @@
admins and moderators of Community guilds receive notices from Discord; <see langword="null" /> if none is set. admins and moderators of Community guilds receive notices from Discord; <see langword="null" /> if none is set.
</returns> </returns>
</member> </member>
<member name="M:Discord.IGuild.GetThreadChannelAsync(System.UInt64,Discord.CacheMode,Discord.RequestOptions)">
<summary>
Gets a thread channel within this guild.
</summary>
<param name="id">The id of the thread channel.</param>
<param name="mode">The <see cref="T:Discord.CacheMode"/> that determines whether the object should be fetched from cache.</param>
<param name="options">The options to be used when sending the request.</param>
<returns>
A task that represents the asynchronous get operation. The task result contains the thread channel.
</returns>
</member>
<member name="M:Discord.IGuild.GetThreadChannelsAsync(Discord.CacheMode,Discord.RequestOptions)">
<summary>
Gets a collection of all thread channels in this guild.
</summary>
<param name="mode">The <see cref="T:Discord.CacheMode" /> that determines whether the object should be fetched from cache.</param>
<param name="options">The options to be used when sending the request.</param>
<returns>
A task that represents the asynchronous get operation. The task result contains a read-only collection of
thread channels found within this guild.
</returns>
</member>
<member name="M:Discord.IGuild.CreateTextChannelAsync(System.String,System.Action{Discord.TextChannelProperties},Discord.RequestOptions)"> <member name="M:Discord.IGuild.CreateTextChannelAsync(System.String,System.Action{Discord.TextChannelProperties},Discord.RequestOptions)">
<summary> <summary>
Creates a new text channel in this guild. Creates a new text channel in this guild.
@@ -3723,6 +3914,16 @@
A task that represents the asynchronous removal operation. A task that represents the asynchronous removal operation.
</returns> </returns>
</member> </member>
<member name="M:Discord.IGuild.GetApplicationCommandsAsync(Discord.RequestOptions)">
<summary>
Gets this guilds slash commands commands
</summary>
<param name="options">The options to be used when sending the request.</param>
<returns>
A task that represents the asynchronous get operation. The task result contains a read-only collection
of application commands found within the guild.
</returns>
</member>
<member name="T:Discord.IGuildIntegration"> <member name="T:Discord.IGuildIntegration">
<summary> <summary>
Holds information for a guild integration feature. Holds information for a guild integration feature.
@@ -3891,6 +4092,26 @@
Users must have MFA enabled on their account to perform administrative actions. Users must have MFA enabled on their account to perform administrative actions.
</summary> </summary>
</member> </member>
<member name="F:Discord.NsfwLevel.Default">
<summary>
Default or unset.
</summary>
</member>
<member name="F:Discord.NsfwLevel.Explicit">
<summary>
Guild has extremely suggestive or mature content that would only be suitable for users 18 or over.
</summary>
</member>
<member name="F:Discord.NsfwLevel.Safe">
<summary>
Guild has no content that could be deemed NSFW; in other words, SFW.
</summary>
</member>
<member name="F:Discord.NsfwLevel.AgeRestricted">
<summary>
Guild has mildly NSFW content that may not be suitable for users under 18.
</summary>
</member>
<member name="T:Discord.PermissionTarget"> <member name="T:Discord.PermissionTarget">
<summary> <summary>
Specifies the target of the permission. Specifies the target of the permission.
@@ -4293,13 +4514,6 @@
If the option is a subcommand or subcommand group type, this nested options will be the parameters. If the option is a subcommand or subcommand group type, this nested options will be the parameters.
</summary> </summary>
</member> </member>
<member name="M:Discord.IApplicationCommand.DeleteAsync(Discord.RequestOptions)">
<summary>
Deletes this command
</summary>
<param name="options">The options to be used when sending the request.</param>
<returns>A task that represents the asynchronous delete operation.</returns>
</member>
<member name="T:Discord.IApplicationCommandInteractionData"> <member name="T:Discord.IApplicationCommandInteractionData">
<summary> <summary>
Represents data of an Interaction Command, see <see href="https://discord.com/developers/docs/interactions/slash-commands#interaction-applicationcommandinteractiondata"/>. Represents data of an Interaction Command, see <see href="https://discord.com/developers/docs/interactions/slash-commands#interaction-applicationcommandinteractiondata"/>.
@@ -4433,6 +4647,58 @@
read-only property, always 1. read-only property, always 1.
</summary> </summary>
</member> </member>
<member name="M:Discord.IDiscordInteraction.RespondAsync(System.String,Discord.Embed[],System.Boolean,System.Boolean,Discord.AllowedMentions,Discord.RequestOptions,Discord.MessageComponent,Discord.Embed)">
<summary>
Responds to an Interaction with type <see cref="F:Discord.InteractionResponseType.ChannelMessageWithSource"/>.
</summary>
<param name="text">The text of the message to be sent.</param>
<param name="embeds">A array of embeds to send with this response. Max 10</param>
<param name="isTTS"><see langword="true"/> if the message should be read out by a text-to-speech reader, otherwise <see langword="false"/>.</param>
<param name="ephemeral"><see langword="true"/> if the response should be hidden to everyone besides the invoker of the command, otherwise <see langword="false"/>.</param>
<param name="allowedMentions">The allowed mentions for this response.</param>
<param name="options">The request options for this response.</param>
<param name="component">A <see cref="T:Discord.MessageComponent"/> to be sent with this response</param>
<param name="embed">A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored.</param>
</member>
<member name="M:Discord.IDiscordInteraction.FollowupAsync(System.String,Discord.Embed[],System.Boolean,System.Boolean,Discord.AllowedMentions,Discord.RequestOptions,Discord.MessageComponent,Discord.Embed)">
<summary>
Sends a followup message for this interaction.
</summary>
<param name="text">The text of the message to be sent</param>
<param name="embeds">A array of embeds to send with this response. Max 10</param>
<param name="isTTS"><see langword="true"/> if the message should be read out by a text-to-speech reader, otherwise <see langword="false"/>.</param>
<param name="ephemeral"><see langword="true"/> if the response should be hidden to everyone besides the invoker of the command, otherwise <see langword="false"/>.</param>
<param name="allowedMentions">The allowed mentions for this response.</param>
<param name="options">The request options for this response.</param>
<param name="component">A <see cref="T:Discord.MessageComponent"/> to be sent with this response</param>
<param name="embed">A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored.</param>
<returns>
The sent message.
</returns>
</member>
<member name="M:Discord.IDiscordInteraction.GetOriginalResponseAsync(Discord.RequestOptions)">
<summary>
Gets the original response for this interaction.
</summary>
<param name="options">The request options for this async request.</param>
<returns>A <see cref="T:Discord.IUserMessage"/> that represents the initial response.</returns>
</member>
<member name="M:Discord.IDiscordInteraction.ModifyOriginalResponseAsync(System.Action{Discord.MessageProperties},Discord.RequestOptions)">
<summary>
Edits original response for this interaction.
</summary>
<param name="func">A delegate containing the properties to modify the message with.</param>
<param name="options">The request options for this async request.</param>
<returns>A <see cref="T:Discord.IUserMessage"/> that represents the initial response.</returns>
</member>
<member name="M:Discord.IDiscordInteraction.DeferAsync(System.Boolean,Discord.RequestOptions)">
<summary>
Acknowledges this interaction.
</summary>
<returns>
A task that represents the asynchronous operation of acknowledging the interaction.
</returns>
</member>
<member name="T:Discord.IDiscordInteractionData"> <member name="T:Discord.IDiscordInteractionData">
<summary> <summary>
Represents an interface used to specify classes that they are a vaild dataype of a <see cref="T:Discord.IDiscordInteraction"/> class. Represents an interface used to specify classes that they are a vaild dataype of a <see cref="T:Discord.IDiscordInteraction"/> class.
@@ -4583,7 +4849,7 @@
Represents a builder for creating a <see cref="T:Discord.MessageComponent"/>. Represents a builder for creating a <see cref="T:Discord.MessageComponent"/>.
</summary> </summary>
</member> </member>
<member name="F:Discord.ComponentBuilder.MaxLabelLength">
<member name="F:Discord.ComponentBuilder.MaxButtonLabelLength">
<summary> <summary>
The max length of a <see cref="P:Discord.ButtonComponent.Label"/>. The max length of a <see cref="P:Discord.ButtonComponent.Label"/>.
</summary> </summary>
@@ -4706,11 +4972,16 @@
Represents a class used to build <see cref="T:Discord.ButtonComponent"/>'s. Represents a class used to build <see cref="T:Discord.ButtonComponent"/>'s.
</summary> </summary>
</member> </member>
<member name="F:Discord.ButtonBuilder.MaxLabelLength">
<summary>
The max length of a <see cref="P:Discord.ButtonComponent.Label"/>.
</summary>
</member>
<member name="P:Discord.ButtonBuilder.Label"> <member name="P:Discord.ButtonBuilder.Label">
<summary> <summary>
Gets or sets the label of the current button. Gets or sets the label of the current button.
</summary> </summary>
<exception cref="T:System.ArgumentException" accessor="set"><see cref="P:Discord.ButtonBuilder.Label"/> length exceeds <see cref="F:Discord.ComponentBuilder.MaxLabelLength"/>.</exception>
<exception cref="T:System.ArgumentException" accessor="set"><see cref="P:Discord.ButtonBuilder.Label"/> length exceeds <see cref="F:Discord.ComponentBuilder.MaxButtonLabelLength"/>.</exception>
</member> </member>
<member name="P:Discord.ButtonBuilder.CustomId"> <member name="P:Discord.ButtonBuilder.CustomId">
<summary> <summary>
@@ -5030,16 +5301,26 @@
Represents a class used to build <see cref="T:Discord.SelectMenuOption"/>'s. Represents a class used to build <see cref="T:Discord.SelectMenuOption"/>'s.
</summary> </summary>
</member> </member>
<member name="F:Discord.SelectMenuOptionBuilder.MaxLabelLength">
<summary>
The maximum length of a <see cref="P:Discord.SelectMenuOption.Label"/>.
</summary>
</member>
<member name="F:Discord.SelectMenuOptionBuilder.MaxDescriptionLength"> <member name="F:Discord.SelectMenuOptionBuilder.MaxDescriptionLength">
<summary> <summary>
The maximum length of a <see cref="P:Discord.SelectMenuOption.Description"/>. The maximum length of a <see cref="P:Discord.SelectMenuOption.Description"/>.
</summary> </summary>
</member> </member>
<member name="F:Discord.SelectMenuOptionBuilder.MaxSelectLabelLength">
<summary>
The maximum length of a <see cref="P:Discord.SelectMenuOption.Label"/>.
</summary>
</member>
<member name="P:Discord.SelectMenuOptionBuilder.Label"> <member name="P:Discord.SelectMenuOptionBuilder.Label">
<summary> <summary>
Gets or sets the label of the current select menu. Gets or sets the label of the current select menu.
</summary> </summary>
<exception cref="T:System.ArgumentException" accessor="set"><see cref="P:Discord.SelectMenuOptionBuilder.Label"/> length exceeds <see cref="F:Discord.ComponentBuilder.MaxLabelLength"/></exception>
<exception cref="T:System.ArgumentException" accessor="set"><see cref="P:Discord.SelectMenuOptionBuilder.Label"/> length exceeds <see cref="F:Discord.SelectMenuOptionBuilder.MaxSelectLabelLength"/></exception>
</member> </member>
<member name="P:Discord.SelectMenuOptionBuilder.Value"> <member name="P:Discord.SelectMenuOptionBuilder.Value">
<summary> <summary>
@@ -7398,6 +7679,14 @@
This must be less than the constant defined by <see cref="F:Discord.DiscordConfig.MaxMessageSize"/>. This must be less than the constant defined by <see cref="F:Discord.DiscordConfig.MaxMessageSize"/>.
</remarks> </remarks>
</member> </member>
<member name="P:Discord.MessageProperties.Embed">
<summary>
Gets or sets a single embed for this message.
</summary>
<remarks>
This property will be added to the <see cref="P:Discord.MessageProperties.Embeds"/> array, in the future please use the array rather than this property.
</remarks>
</member>
<member name="P:Discord.MessageProperties.Embeds"> <member name="P:Discord.MessageProperties.Embeds">
<summary> <summary>
Gets or sets the embeds of the message. Gets or sets the embeds of the message.
@@ -7554,10 +7843,38 @@
The message for when a news channel subscription is added to a text channel. The message for when a news channel subscription is added to a text channel.
</summary> </summary>
</member> </member>
<member name="F:Discord.MessageType.GuildDiscoveryDisqualified">
<summary>
The message for when a guild is disqualified from discovery.
</summary>
</member>
<member name="F:Discord.MessageType.GuildDiscoveryRequalified">
<summary>
The message for when a guild is requalified for discovery.
</summary>
</member>
<member name="F:Discord.MessageType.GuildDiscoveryGracePeriodInitialWarning">
<summary>
The message for when the initial warning is sent for the initial grace period discovery.
</summary>
</member>
<member name="F:Discord.MessageType.GuildDiscoveryGracePeriodFinalWarning">
<summary>
The message for when the final warning is sent for the initial grace period discovery.
</summary>
</member>
<member name="F:Discord.MessageType.ThreadCreated">
<summary>
The message for when a thread is created.
</summary>
</member>
<member name="F:Discord.MessageType.Reply"> <member name="F:Discord.MessageType.Reply">
<summary> <summary>
The message is an inline reply. The message is an inline reply.
</summary> </summary>
<remarks>
Only available in API v8
</remarks>
</member> </member>
<member name="F:Discord.MessageType.ApplicationCommand"> <member name="F:Discord.MessageType.ApplicationCommand">
<summary> <summary>
@@ -7567,6 +7884,19 @@
Only available in API v8 Only available in API v8
</remarks> </remarks>
</member> </member>
<member name="F:Discord.MessageType.ThreadStarterMessage">
<summary>
The message that starts a thread.
</summary>
<remarks>
Only available in API v9
</remarks>
</member>
<member name="F:Discord.MessageType.GuildInviteReminder">
<summary>
The message for a invite reminder
</summary>
</member>
<member name="T:Discord.ReactionMetadata"> <member name="T:Discord.ReactionMetadata">
<summary> <summary>
A metadata containing reaction information. A metadata containing reaction information.
@@ -8397,7 +8727,25 @@
<summary> If <c>true</c>, a user may edit the webhooks for this guild. </summary> <summary> If <c>true</c>, a user may edit the webhooks for this guild. </summary>
</member> </member>
<member name="P:Discord.GuildPermissions.ManageEmojisAndStickers"> <member name="P:Discord.GuildPermissions.ManageEmojisAndStickers">
<summary> If <c>true</c>, a user may edit the emojis for this guild. </summary>
<summary> If <c>true</c>, a user may edit the emojis and stickers for this guild. </summary>
</member>
<member name="P:Discord.GuildPermissions.UseSlashCommands">
<summary> If <c>true</c>, a user may use slash commands in this guild. </summary>
</member>
<member name="P:Discord.GuildPermissions.RequestToSpeak">
<summary> If <c>true</c>, a user may request to speak in stage channels. </summary>
</member>
<member name="P:Discord.GuildPermissions.ManageThreads">
<summary> If <c>true</c>, a user may manage threads in this guild. </summary>
</member>
<member name="P:Discord.GuildPermissions.UsePublicThreads">
<summary> If <c>true</c>, a user may create public threads in this guild. </summary>
</member>
<member name="P:Discord.GuildPermissions.UsePrivateThreads">
<summary> If <c>true</c>, a user may create private threads in this guild. </summary>
</member>
<member name="P:Discord.GuildPermissions.UseExternalStickers">
<summary> If <c>true</c>, a user may use external stickers in this guild. </summary>
</member> </member>
<member name="M:Discord.GuildPermissions.#ctor(System.UInt64)"> <member name="M:Discord.GuildPermissions.#ctor(System.UInt64)">
<summary> Creates a new <see cref="T:Discord.GuildPermissions"/> with the provided packed value. </summary> <summary> Creates a new <see cref="T:Discord.GuildPermissions"/> with the provided packed value. </summary>
@@ -8405,10 +8753,10 @@
<member name="M:Discord.GuildPermissions.#ctor(System.String)"> <member name="M:Discord.GuildPermissions.#ctor(System.String)">
<summary> Creates a new <see cref="T:Discord.GuildPermissions"/> with the provided packed value after converting to ulong. </summary> <summary> Creates a new <see cref="T:Discord.GuildPermissions"/> with the provided packed value after converting to ulong. </summary>
</member> </member>
<member name="M:Discord.GuildPermissions.#ctor(System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Boolean)">
<member name="M:Discord.GuildPermissions.#ctor(System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Boolean)">
<summary> Creates a new <see cref="T:Discord.GuildPermissions"/> structure with the provided permissions. </summary> <summary> Creates a new <see cref="T:Discord.GuildPermissions"/> structure with the provided permissions. </summary>
</member> </member>
<member name="M:Discord.GuildPermissions.Modify(System.Nullable{System.Boolean},System.Nullable{System.Boolean},System.Nullable{System.Boolean},System.Nullable{System.Boolean},System.Nullable{System.Boolean},System.Nullable{System.Boolean},System.Nullable{System.Boolean},System.Nullable{System.Boolean},System.Nullable{System.Boolean},System.Nullable{System.Boolean},System.Nullable{System.Boolean},System.Nullable{System.Boolean},System.Nullable{System.Boolean},System.Nullable{System.Boolean},System.Nullable{System.Boolean},System.Nullable{System.Boolean},System.Nullable{System.Boolean},System.Nullable{System.Boolean},System.Nullable{System.Boolean},System.Nullable{System.Boolean},System.Nullable{System.Boolean},System.Nullable{System.Boolean},System.Nullable{System.Boolean},System.Nullable{System.Boolean},System.Nullable{System.Boolean},System.Nullable{System.Boolean},System.Nullable{System.Boolean},System.Nullable{System.Boolean},System.Nullable{System.Boolean},System.Nullable{System.Boolean},System.Nullable{System.Boolean})">
<member name="M:Discord.GuildPermissions.Modify(System.Nullable{System.Boolean},System.Nullable{System.Boolean},System.Nullable{System.Boolean},System.Nullable{System.Boolean},System.Nullable{System.Boolean},System.Nullable{System.Boolean},System.Nullable{System.Boolean},System.Nullable{System.Boolean},System.Nullable{System.Boolean},System.Nullable{System.Boolean},System.Nullable{System.Boolean},System.Nullable{System.Boolean},System.Nullable{System.Boolean},System.Nullable{System.Boolean},System.Nullable{System.Boolean},System.Nullable{System.Boolean},System.Nullable{System.Boolean},System.Nullable{System.Boolean},System.Nullable{System.Boolean},System.Nullable{System.Boolean},System.Nullable{System.Boolean},System.Nullable{System.Boolean},System.Nullable{System.Boolean},System.Nullable{System.Boolean},System.Nullable{System.Boolean},System.Nullable{System.Boolean},System.Nullable{System.Boolean},System.Nullable{System.Boolean},System.Nullable{System.Boolean},System.Nullable{System.Boolean},System.Nullable{System.Boolean},System.Nullable{System.Boolean},System.Nullable{System.Boolean},System.Nullable{System.Boolean},System.Nullable{System.Boolean},System.Nullable{System.Boolean},System.Nullable{System.Boolean})">
<summary> Creates a new <see cref="T:Discord.GuildPermissions"/> from this one, changing the provided non-null permissions. </summary> <summary> Creates a new <see cref="T:Discord.GuildPermissions"/> from this one, changing the provided non-null permissions. </summary>
</member> </member>
<member name="M:Discord.GuildPermissions.Has(Discord.GuildPermission)"> <member name="M:Discord.GuildPermissions.Has(Discord.GuildPermission)">
@@ -8595,6 +8943,9 @@
Represents a color used in Discord. Represents a color used in Discord.
</summary> </summary>
</member> </member>
<member name="F:Discord.Color.MaxDecimalValue">
<summary> Gets the max decimal value of color. </summary>
</member>
<member name="F:Discord.Color.Default"> <member name="F:Discord.Color.Default">
<summary> Gets the default user color value. </summary> <summary> Gets the default user color value. </summary>
</member> </member>
@@ -8699,20 +9050,21 @@
Initializes a <see cref="T:Discord.Color"/> struct with the given raw value. Initializes a <see cref="T:Discord.Color"/> struct with the given raw value.
</summary> </summary>
<example> <example>
The following will create a color that has a hex value of
The following will create a color that has a hex value of
<see href="http://www.color-hex.com/color/607d8b">#607D8B</see>. <see href="http://www.color-hex.com/color/607d8b">#607D8B</see>.
<code language="cs"> <code language="cs">
Color darkGrey = new Color(0x607D8B); Color darkGrey = new Color(0x607D8B);
</code> </code>
</example> </example>
<param name="rawValue">The raw value of the color (e.g. <c>0x607D8B</c>).</param> <param name="rawValue">The raw value of the color (e.g. <c>0x607D8B</c>).</param>
<exception cref="T:System.ArgumentException">Value exceeds <see cref="F:Discord.Color.MaxDecimalValue"/>.</exception>
</member> </member>
<member name="M:Discord.Color.#ctor(System.Byte,System.Byte,System.Byte)"> <member name="M:Discord.Color.#ctor(System.Byte,System.Byte,System.Byte)">
<summary> <summary>
Initializes a <see cref="T:Discord.Color" /> struct with the given RGB bytes. Initializes a <see cref="T:Discord.Color" /> struct with the given RGB bytes.
</summary> </summary>
<example> <example>
The following will create a color that has a value of
The following will create a color that has a value of
<see href="http://www.color-hex.com/color/607d8b">#607D8B</see>. <see href="http://www.color-hex.com/color/607d8b">#607D8B</see>.
<code language="cs"> <code language="cs">
Color darkGrey = new Color((byte)0b_01100000, (byte)0b_01111101, (byte)0b_10001011); Color darkGrey = new Color((byte)0b_01100000, (byte)0b_01111101, (byte)0b_10001011);
@@ -8721,13 +9073,14 @@
<param name="r">The byte that represents the red color.</param> <param name="r">The byte that represents the red color.</param>
<param name="g">The byte that represents the green color.</param> <param name="g">The byte that represents the green color.</param>
<param name="b">The byte that represents the blue color.</param> <param name="b">The byte that represents the blue color.</param>
<exception cref="T:System.ArgumentException">Value exceeds <see cref="F:Discord.Color.MaxDecimalValue"/>.</exception>
</member> </member>
<member name="M:Discord.Color.#ctor(System.Int32,System.Int32,System.Int32)"> <member name="M:Discord.Color.#ctor(System.Int32,System.Int32,System.Int32)">
<summary> <summary>
Initializes a <see cref="T:Discord.Color"/> struct with the given RGB value. Initializes a <see cref="T:Discord.Color"/> struct with the given RGB value.
</summary> </summary>
<example> <example>
The following will create a color that has a value of
The following will create a color that has a value of
<see href="http://www.color-hex.com/color/607d8b">#607D8B</see>. <see href="http://www.color-hex.com/color/607d8b">#607D8B</see>.
<code language="cs"> <code language="cs">
Color darkGrey = new Color(96, 125, 139); Color darkGrey = new Color(96, 125, 139);
@@ -8743,7 +9096,7 @@
Initializes a <see cref="T:Discord.Color"/> struct with the given RGB float value. Initializes a <see cref="T:Discord.Color"/> struct with the given RGB float value.
</summary> </summary>
<example> <example>
The following will create a color that has a value of
The following will create a color that has a value of
<see href="http://www.color-hex.com/color/607c8c">#607c8c</see>. <see href="http://www.color-hex.com/color/607c8c">#607c8c</see>.
<code language="cs"> <code language="cs">
Color darkGrey = new Color(0.38f, 0.49f, 0.55f); Color darkGrey = new Color(0.38f, 0.49f, 0.55f);
@@ -9511,19 +9864,33 @@
Gets the identifier of this user's avatar. Gets the identifier of this user's avatar.
</summary> </summary>
</member> </member>
<member name="P:Discord.IUser.BannerId">
<summary>
Gets the identifier of this user's banner.
</summary>
</member>
<member name="P:Discord.IUser.AccentColor">
<summary>
Gets the user's banner color.
</summary>
<returns>
A <see cref="T:Discord.Color"/> struct representing the accent color of this user's banner.
</returns>
</member>
<member name="M:Discord.IUser.GetAvatarUrl(Discord.ImageFormat,System.UInt16)"> <member name="M:Discord.IUser.GetAvatarUrl(Discord.ImageFormat,System.UInt16)">
<summary> <summary>
Gets the avatar URL for this user. Gets the avatar URL for this user.
</summary> </summary>
<remarks> <remarks>
This property retrieves a URL for this user's avatar. In event that the user does not have a valid avatar This property retrieves a URL for this user's avatar. In event that the user does not have a valid avatar
(i.e. their avatar identifier is not set), this property will return <c>null</c>. If you wish to
(i.e. their avatar identifier is not set), this method will return <c>null</c>. If you wish to
retrieve the default avatar for this user, consider using <see cref="M:Discord.IUser.GetDefaultAvatarUrl"/> (see retrieve the default avatar for this user, consider using <see cref="M:Discord.IUser.GetDefaultAvatarUrl"/> (see
example). example).
</remarks> </remarks>
<example> <example>
<para>The following example attempts to retrieve the user's current avatar and send it to a channel; if one is
not set, a default avatar for this user will be returned instead.</para>
<para
>The following example attempts to retrieve the user's current avatar and send it to a channel; if one is
not set, a default avatar for this user will be returned instead.</para>
<code language="cs" region="GetAvatarUrl" <code language="cs" region="GetAvatarUrl"
source="..\..\..\Discord.Net.Examples\Core\Entities\Users\IUser.Examples.cs"/> source="..\..\..\Discord.Net.Examples\Core\Entities\Users\IUser.Examples.cs"/>
</example> </example>
@@ -9534,6 +9901,17 @@
A string representing the user's avatar URL; <c>null</c> if the user does not have an avatar in place. A string representing the user's avatar URL; <c>null</c> if the user does not have an avatar in place.
</returns> </returns>
</member> </member>
<member name="M:Discord.IUser.GetBannerUrl(Discord.ImageFormat,System.UInt16)">
<summary>
Gets the banner URL for this user.
</summary>
<param name="format">The format to return.</param>
<param name="size">The size of the image to return in. This can be any power of two between 16 and 2048.
</param>
<returns>
A string representing the user's avatar URL; <c>null</c> if the user does not have an banner in place.
</returns>
</member>
<member name="M:Discord.IUser.GetDefaultAvatarUrl"> <member name="M:Discord.IUser.GetDefaultAvatarUrl">
<summary> <summary>
Gets the default avatar URL for this user. Gets the default avatar URL for this user.
@@ -9601,8 +9979,8 @@
This method is used to obtain or create a channel used to send a direct message. This method is used to obtain or create a channel used to send a direct message.
<note type="warning"> <note type="warning">
In event that the current user cannot send a message to the target user, a channel can and will In event that the current user cannot send a message to the target user, a channel can and will
still be created by Discord. However, attempting to send a message will yield a
<see cref="T:Discord.Net.HttpException"/> with a 403 as its
still be created by Discord. However, attempting to send a message will yield a
<see cref="T:Discord.Net.HttpException"/> with a 403 as its
<see cref="P:Discord.Net.HttpException.HttpCode"/>. There are currently no official workarounds by <see cref="P:Discord.Net.HttpException.HttpCode"/>. There are currently no official workarounds by
Discord. Discord.
</note> </note>
@@ -9689,6 +10067,11 @@
<c>true</c> if the user is streaming; otherwise <c>false</c>. <c>true</c> if the user is streaming; otherwise <c>false</c>.
</returns> </returns>
</member> </member>
<member name="P:Discord.IVoiceState.RequestToSpeakTimestamp">
<summary>
Gets the time on which the user requested to speak.
</summary>
</member>
<member name="T:Discord.IWebhookUser"> <member name="T:Discord.IWebhookUser">
<summary> Represents a Webhook Discord user. </summary> <summary> Represents a Webhook Discord user. </summary>
</member> </member>
@@ -9891,6 +10274,11 @@
Gets the user that created this webhook. Gets the user that created this webhook.
</summary> </summary>
</member> </member>
<member name="P:Discord.IWebhook.ApplicationId">
<summary>
Gets the ID of the application owning this webhook.
</summary>
</member>
<member name="M:Discord.IWebhook.ModifyAsync(System.Action{Discord.WebhookProperties},Discord.RequestOptions)"> <member name="M:Discord.IWebhook.ModifyAsync(System.Action{Discord.WebhookProperties},Discord.RequestOptions)">
<summary> <summary>
Modifies this webhook. Modifies this webhook.


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

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

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

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

/// <summary>
/// <see langword="true"/> if stage discovery is disabled, otherwise <see langword="false"/>.
/// </summary>
bool? DiscoverableDisabled { get; }

/// <summary>
/// <see langword="true"/> when the stage is live, otherwise <see langword="false"/>.
/// </summary>
/// <remarks>
/// If the stage isn't live then this property will be set to <see langword="null"/>.
/// </remarks>
bool Live { get; }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

namespace Discord
{
public enum StagePrivacyLevel
{
Public = 1,
GuildOnly = 2,
}
}

+ 24
- 4
src/Discord.Net.Core/Entities/Emotes/Emoji.cs View File

@@ -1,5 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable;
using System.Collections.ObjectModel;
using System.Linq; using System.Linq;


namespace Discord namespace Discord
@@ -56,7 +58,7 @@ namespace Discord
if (NamesAndUnicodes.ContainsKey(text)) if (NamesAndUnicodes.ContainsKey(text))
result = new Emoji(NamesAndUnicodes[text]); result = new Emoji(NamesAndUnicodes[text]);


if (UnicodesAndNames.ContainsKey(text))
if (Unicodes.Contains(text))
result = new Emoji(text); result = new Emoji(text);


return result != null; return result != null;
@@ -5942,12 +5944,30 @@ namespace Discord
["♡"] = "❤️" ["♡"] = "❤️"
}; };


private static IReadOnlyDictionary<string, string> _unicodesAndNames;
private static IReadOnlyDictionary<string, string> UnicodesAndNames
private static IReadOnlyCollection<string> _unicodes;
private static IReadOnlyCollection<string> Unicodes
{ {
get get
{ {
_unicodesAndNames ??= NamesAndUnicodes.ToDictionary(kvp => kvp.Value, kvp => kvp.Key);
_unicodes ??= NamesAndUnicodes.Select(kvp => kvp.Value).ToImmutableHashSet();
return _unicodes;
}
}

private static IReadOnlyDictionary<string, ReadOnlyCollection<string>> _unicodesAndNames;
private static IReadOnlyDictionary<string, ReadOnlyCollection<string>> UnicodesAndNames
{
get
{
_unicodesAndNames ??=
NamesAndUnicodes
.GroupBy(kvp => kvp.Value)
.ToImmutableDictionary(
grouping => grouping.Key,
grouping => grouping.Select(kvp => kvp.Key)
.ToList()
.AsReadOnly()
);
return _unicodesAndNames; return _unicodesAndNames;
} }
} }


+ 59
- 0
src/Discord.Net.Core/Entities/Guilds/IGuild.cs View File

@@ -316,6 +316,14 @@ namespace Discord
/// </returns> /// </returns>
string PreferredLocale { get; } string PreferredLocale { get; }


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

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


/// <summary> /// <summary>
/// Creates a new text channel in this guild. /// Creates a new text channel in this guild.
@@ -892,5 +941,15 @@ namespace Discord
/// A task that represents the asynchronous removal operation. /// A task that represents the asynchronous removal operation.
/// </returns> /// </returns>
Task DeleteEmoteAsync(GuildEmote emote, RequestOptions options = null); Task DeleteEmoteAsync(GuildEmote emote, RequestOptions options = null);

/// <summary>
/// Gets this guilds slash commands commands
/// </summary>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous get operation. The task result contains a read-only collection
/// of application commands found within the guild.
/// </returns>
Task<IReadOnlyCollection<IApplicationCommand>> GetApplicationCommandsAsync (RequestOptions options = null);
} }
} }

+ 22
- 0
src/Discord.Net.Core/Entities/Guilds/NsfwLevel.cs View File

@@ -0,0 +1,22 @@
namespace Discord
{
public enum NsfwLevel
{
/// <summary>
/// Default or unset.
/// </summary>
Default = 0,
/// <summary>
/// Guild has extremely suggestive or mature content that would only be suitable for users 18 or over.
/// </summary>
Explicit = 1,
/// <summary>
/// Guild has no content that could be deemed NSFW; in other words, SFW.
/// </summary>
Safe = 2,
/// <summary>
/// Guild has mildly NSFW content that may not be suitable for users under 18.
/// </summary>
AgeRestricted = 3
}
}

+ 1
- 8
src/Discord.Net.Core/Entities/Interactions/IApplicationCommand.cs View File

@@ -9,7 +9,7 @@ namespace Discord
/// <summary> /// <summary>
/// The base command model that belongs to an application. see <see href="https://discord.com/developers/docs/interactions/slash-commands#applicationcommand"/> /// The base command model that belongs to an application. see <see href="https://discord.com/developers/docs/interactions/slash-commands#applicationcommand"/>
/// </summary> /// </summary>
public interface IApplicationCommand : ISnowflakeEntity
public interface IApplicationCommand : ISnowflakeEntity, IDeletable
{ {
/// <summary> /// <summary>
/// Gets the unique id of the parent application. /// Gets the unique id of the parent application.
@@ -35,12 +35,5 @@ namespace Discord
/// If the option is a subcommand or subcommand group type, this nested options will be the parameters. /// If the option is a subcommand or subcommand group type, this nested options will be the parameters.
/// </summary> /// </summary>
IReadOnlyCollection<IApplicationCommandOption> Options { get; } IReadOnlyCollection<IApplicationCommandOption> Options { get; }

/// <summary>
/// Deletes this command
/// </summary>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>A task that represents the asynchronous delete operation.</returns>
Task DeleteAsync(RequestOptions options = null);
} }
} }

+ 54
- 0
src/Discord.Net.Core/Entities/Interactions/IDiscordInteraction.cs View File

@@ -39,5 +39,59 @@ namespace Discord
/// read-only property, always 1. /// read-only property, always 1.
/// </summary> /// </summary>
int Version { get; } int Version { get; }

/// <summary>
/// Responds to an Interaction with type <see cref="InteractionResponseType.ChannelMessageWithSource"/>.
/// </summary>
/// <param name="text">The text of the message to be sent.</param>
/// <param name="embeds">A array of embeds to send with this response. Max 10</param>
/// <param name="isTTS"><see langword="true"/> if the message should be read out by a text-to-speech reader, otherwise <see langword="false"/>.</param>
/// <param name="ephemeral"><see langword="true"/> if the response should be hidden to everyone besides the invoker of the command, otherwise <see langword="false"/>.</param>
/// <param name="allowedMentions">The allowed mentions for this response.</param>
/// <param name="options">The request options for this response.</param>
/// <param name="component">A <see cref="MessageComponent"/> to be sent with this response</param>
/// <param name="embed">A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored.</param>
Task RespondAsync (string text = null, Embed[] embeds = null, bool isTTS = false,
bool ephemeral = false, AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent component = null, Embed embed = null);

/// <summary>
/// Sends a followup message for this interaction.
/// </summary>
/// <param name="text">The text of the message to be sent</param>
/// <param name="embeds">A array of embeds to send with this response. Max 10</param>
/// <param name="isTTS"><see langword="true"/> if the message should be read out by a text-to-speech reader, otherwise <see langword="false"/>.</param>
/// <param name="ephemeral"><see langword="true"/> if the response should be hidden to everyone besides the invoker of the command, otherwise <see langword="false"/>.</param>
/// <param name="allowedMentions">The allowed mentions for this response.</param>
/// <param name="options">The request options for this response.</param>
/// <param name="component">A <see cref="MessageComponent"/> to be sent with this response</param>
/// <param name="embed">A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored.</param>
/// <returns>
/// The sent message.
/// </returns>
Task<IUserMessage> FollowupAsync (string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false,
AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent component = null, Embed embed = null);

/// <summary>
/// Gets the original response for this interaction.
/// </summary>
/// <param name="options">The request options for this async request.</param>
/// <returns>A <see cref="IUserMessage"/> that represents the initial response.</returns>
Task<IUserMessage> GetOriginalResponseAsync (RequestOptions options = null);

/// <summary>
/// Edits original response for this interaction.
/// </summary>
/// <param name="func">A delegate containing the properties to modify the message with.</param>
/// <param name="options">The request options for this async request.</param>
/// <returns>A <see cref="IUserMessage"/> that represents the initial response.</returns>
Task<IUserMessage> ModifyOriginalResponseAsync (Action<MessageProperties> func, RequestOptions options = null);

/// <summary>
/// Acknowledges this interaction.
/// </summary>
/// <returns>
/// A task that represents the asynchronous operation of acknowledging the interaction.
/// </returns>
Task DeferAsync (bool ephemeral = false, RequestOptions options = null);
} }
} }

+ 25
- 10
src/Discord.Net.Core/Entities/Interactions/Message Components/ComponentBuilder.cs View File

@@ -13,7 +13,7 @@ namespace Discord
/// <summary> /// <summary>
/// The max length of a <see cref="ButtonComponent.Label"/>. /// The max length of a <see cref="ButtonComponent.Label"/>.
/// </summary> /// </summary>
public const int MaxLabelLength = 80;
public const int MaxButtonLabelLength = 80;


/// <summary> /// <summary>
/// The max length of a <see cref="ButtonComponent.CustomId"/>. /// The max length of a <see cref="ButtonComponent.CustomId"/>.
@@ -307,17 +307,22 @@ namespace Discord
/// </summary> /// </summary>
public class ButtonBuilder public class ButtonBuilder
{ {
/// <summary>
/// The max length of a <see cref="ButtonComponent.Label"/>.
/// </summary>
public const int MaxLabelLength = 80;

/// <summary> /// <summary>
/// Gets or sets the label of the current button. /// Gets or sets the label of the current button.
/// </summary> /// </summary>
/// <exception cref="ArgumentException" accessor="set"><see cref="Label"/> length exceeds <see cref="ComponentBuilder.MaxLabelLength"/>.</exception>
/// <exception cref="ArgumentException" accessor="set"><see cref="Label"/> length exceeds <see cref="ComponentBuilder.MaxButtonLabelLength"/>.</exception>
public string Label public string Label
{ {
get => _label; get => _label;
set set
{ {
if (value != null && value.Length > ComponentBuilder.MaxLabelLength)
throw new ArgumentException(message: $"Button label must be {ComponentBuilder.MaxLabelLength} characters or less!", paramName: nameof(Label));
if (value != null && value.Length > ComponentBuilder.MaxButtonLabelLength)
throw new ArgumentException(message: $"Button label must be {ComponentBuilder.MaxButtonLabelLength} characters or less!", paramName: nameof(Label));


_label = value; _label = value;
} }
@@ -539,8 +544,8 @@ namespace Discord
if (string.IsNullOrEmpty(this.Url)) if (string.IsNullOrEmpty(this.Url))
throw new InvalidOperationException("Link buttons must have a link associated with them"); throw new InvalidOperationException("Link buttons must have a link associated with them");
else else
UrlValidation.Validate(this.Url);
}
UrlValidation.Validate(this.Url);
}
else if (string.IsNullOrEmpty(this.CustomId)) else if (string.IsNullOrEmpty(this.CustomId))
throw new InvalidOperationException("Non-link buttons must have a custom id associated with them"); throw new InvalidOperationException("Non-link buttons must have a custom id associated with them");


@@ -831,23 +836,33 @@ namespace Discord
/// </summary> /// </summary>
public class SelectMenuOptionBuilder public class SelectMenuOptionBuilder
{ {
/// <summary>
/// The maximum length of a <see cref="SelectMenuOption.Label"/>.
/// </summary>
public const int MaxLabelLength = 100;

/// <summary> /// <summary>
/// The maximum length of a <see cref="SelectMenuOption.Description"/>. /// The maximum length of a <see cref="SelectMenuOption.Description"/>.
/// </summary> /// </summary>
public const int MaxDescriptionLength = 50;
public const int MaxDescriptionLength = 100;
/// <summary>
/// The maximum length of a <see cref="SelectMenuOption.Label"/>.
/// </summary>
public const int MaxSelectLabelLength = 100;


/// <summary> /// <summary>
/// Gets or sets the label of the current select menu. /// Gets or sets the label of the current select menu.
/// </summary> /// </summary>
/// <exception cref="ArgumentException" accessor="set"><see cref="Label"/> length exceeds <see cref="ComponentBuilder.MaxLabelLength"/></exception>
/// <exception cref="ArgumentException" accessor="set"><see cref="Label"/> length exceeds <see cref="MaxSelectLabelLength"/></exception>
public string Label public string Label
{ {
get => _label; get => _label;
set set
{ {
if (value != null) if (value != null)
if (value.Length > ComponentBuilder.MaxLabelLength)
throw new ArgumentException(message: $"Button label must be {ComponentBuilder.MaxLabelLength} characters or less!", paramName: nameof(Label));
if (value.Length > MaxSelectLabelLength)
throw new ArgumentException(message: $"Button label must be {MaxSelectLabelLength} characters or less!", paramName: nameof(Label));


_label = value; _label = value;
} }


+ 1
- 1
src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs View File

@@ -411,7 +411,7 @@ namespace Discord
UrlValidation.Validate(Url); UrlValidation.Validate(Url);
if (!string.IsNullOrEmpty(ThumbnailUrl)) if (!string.IsNullOrEmpty(ThumbnailUrl))
UrlValidation.Validate(ThumbnailUrl); UrlValidation.Validate(ThumbnailUrl);
if (!string.IsNullOrEmpty(ImageUrl))
if (!string.IsNullOrEmpty(ImageUrl) && !ImageUrl.StartsWith("attachment://", StringComparison.Ordinal))
UrlValidation.Validate(ImageUrl); UrlValidation.Validate(ImageUrl);
if (Author != null) if (Author != null)
{ {


+ 8
- 0
src/Discord.Net.Core/Entities/Messages/MessageProperties.cs View File

@@ -18,6 +18,14 @@ namespace Discord
/// </remarks> /// </remarks>
public Optional<string> Content { get; set; } public Optional<string> Content { get; set; }


/// <summary>
/// Gets or sets a single embed for this message.
/// </summary>
/// <remarks>
/// This property will be added to the <see cref="Embeds"/> array, in the future please use the array rather than this property.
/// </remarks>
public Optional<Embed> Embed { get; set; }

/// <summary> /// <summary>
/// Gets or sets the embeds of the message. /// Gets or sets the embeds of the message.
/// </summary> /// </summary>


+ 49
- 6
src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs View File

@@ -81,8 +81,20 @@ namespace Discord
public bool ManageRoles => Permissions.GetValue(RawValue, GuildPermission.ManageRoles); public bool ManageRoles => Permissions.GetValue(RawValue, GuildPermission.ManageRoles);
/// <summary> If <c>true</c>, a user may edit the webhooks for this guild. </summary> /// <summary> If <c>true</c>, a user may edit the webhooks for this guild. </summary>
public bool ManageWebhooks => Permissions.GetValue(RawValue, GuildPermission.ManageWebhooks); public bool ManageWebhooks => Permissions.GetValue(RawValue, GuildPermission.ManageWebhooks);
/// <summary> If <c>true</c>, a user may edit the emojis for this guild. </summary>
/// <summary> If <c>true</c>, a user may edit the emojis and stickers for this guild. </summary>
public bool ManageEmojisAndStickers => Permissions.GetValue(RawValue, GuildPermission.ManageEmojisAndStickers); public bool ManageEmojisAndStickers => Permissions.GetValue(RawValue, GuildPermission.ManageEmojisAndStickers);
/// <summary> If <c>true</c>, a user may use slash commands in this guild. </summary>
public bool UseSlashCommands => Permissions.GetValue(RawValue, GuildPermission.UseSlashCommands);
/// <summary> If <c>true</c>, a user may request to speak in stage channels. </summary>
public bool RequestToSpeak => Permissions.GetValue(RawValue, GuildPermission.RequestToSpeak);
/// <summary> If <c>true</c>, a user may manage threads in this guild. </summary>
public bool ManageThreads => Permissions.GetValue(RawValue, GuildPermission.ManageThreads);
/// <summary> If <c>true</c>, a user may create public threads in this guild. </summary>
public bool UsePublicThreads => Permissions.GetValue(RawValue, GuildPermission.UsePublicThreads);
/// <summary> If <c>true</c>, a user may create private threads in this guild. </summary>
public bool UsePrivateThreads => Permissions.GetValue(RawValue, GuildPermission.UsePrivateThreads);
/// <summary> If <c>true</c>, a user may use external stickers in this guild. </summary>
public bool UseExternalStickers => Permissions.GetValue(RawValue, GuildPermission.UseExternalStickers);


/// <summary> Creates a new <see cref="GuildPermissions"/> with the provided packed value. </summary> /// <summary> Creates a new <see cref="GuildPermissions"/> with the provided packed value. </summary>
public GuildPermissions(ulong rawValue) { RawValue = rawValue; } public GuildPermissions(ulong rawValue) { RawValue = rawValue; }
@@ -121,7 +133,13 @@ namespace Discord
bool? manageNicknames = null, bool? manageNicknames = null,
bool? manageRoles = null, bool? manageRoles = null,
bool? manageWebhooks = null, bool? manageWebhooks = null,
bool? manageEmojisAndStickers = null)
bool? manageEmojisAndStickers = null,
bool? useSlashCommands = null,
bool? requestToSpeak = null,
bool? manageThreads = null,
bool? usePublicThreads = null,
bool? usePrivateThreads = null,
bool? useExternalStickers = null)
{ {
ulong value = initialValue; ulong value = initialValue;


@@ -156,6 +174,12 @@ namespace Discord
Permissions.SetValue(ref value, manageRoles, GuildPermission.ManageRoles); Permissions.SetValue(ref value, manageRoles, GuildPermission.ManageRoles);
Permissions.SetValue(ref value, manageWebhooks, GuildPermission.ManageWebhooks); Permissions.SetValue(ref value, manageWebhooks, GuildPermission.ManageWebhooks);
Permissions.SetValue(ref value, manageEmojisAndStickers, GuildPermission.ManageEmojisAndStickers); Permissions.SetValue(ref value, manageEmojisAndStickers, GuildPermission.ManageEmojisAndStickers);
Permissions.SetValue(ref value, useSlashCommands, GuildPermission.UseSlashCommands);
Permissions.SetValue(ref value, requestToSpeak, GuildPermission.RequestToSpeak);
Permissions.SetValue(ref value, manageThreads, GuildPermission.ManageThreads);
Permissions.SetValue(ref value, usePublicThreads, GuildPermission.UsePublicThreads);
Permissions.SetValue(ref value, usePrivateThreads, GuildPermission.UseExternalStickers);
Permissions.SetValue(ref value, useExternalStickers, GuildPermission.UseExternalStickers);


RawValue = value; RawValue = value;
} }
@@ -192,7 +216,13 @@ namespace Discord
bool manageNicknames = false, bool manageNicknames = false,
bool manageRoles = false, bool manageRoles = false,
bool manageWebhooks = false, bool manageWebhooks = false,
bool manageEmojis = false)
bool manageEmojisAndStickers = false,
bool useSlashCommands = false,
bool requestToSpeak = false,
bool manageThreads = false,
bool usePublicThreads = false,
bool usePrivateThreads = false,
bool useExternalStickers = false)
: this(0, : this(0,
createInstantInvite: createInstantInvite, createInstantInvite: createInstantInvite,
manageRoles: manageRoles, manageRoles: manageRoles,
@@ -224,7 +254,13 @@ namespace Discord
changeNickname: changeNickname, changeNickname: changeNickname,
manageNicknames: manageNicknames, manageNicknames: manageNicknames,
manageWebhooks: manageWebhooks, manageWebhooks: manageWebhooks,
manageEmojisAndStickers: manageEmojis)
manageEmojisAndStickers: manageEmojisAndStickers,
useSlashCommands: useSlashCommands,
requestToSpeak: requestToSpeak,
manageThreads: manageThreads,
usePublicThreads: usePublicThreads,
usePrivateThreads: usePrivateThreads,
useExternalStickers: useExternalStickers)
{ } { }


/// <summary> Creates a new <see cref="GuildPermissions"/> from this one, changing the provided non-null permissions. </summary> /// <summary> Creates a new <see cref="GuildPermissions"/> from this one, changing the provided non-null permissions. </summary>
@@ -259,11 +295,18 @@ namespace Discord
bool? manageNicknames = null, bool? manageNicknames = null,
bool? manageRoles = null, bool? manageRoles = null,
bool? manageWebhooks = null, bool? manageWebhooks = null,
bool? manageEmojis = null)
bool? manageEmojisAndStickers = null,
bool? useSlashCommands = null,
bool? requestToSpeak = null,
bool? manageThreads = null,
bool? usePublicThreads = null,
bool? usePrivateThreads = null,
bool? useExternalStickers = null)
=> new GuildPermissions(RawValue, createInstantInvite, kickMembers, banMembers, administrator, manageChannels, manageGuild, addReactions, => new GuildPermissions(RawValue, createInstantInvite, kickMembers, banMembers, administrator, manageChannels, manageGuild, addReactions,
viewAuditLog, viewGuildInsights, viewChannel, sendMessages, sendTTSMessages, manageMessages, embedLinks, attachFiles, viewAuditLog, viewGuildInsights, viewChannel, sendMessages, sendTTSMessages, manageMessages, embedLinks, attachFiles,
readMessageHistory, mentionEveryone, useExternalEmojis, connect, speak, muteMembers, deafenMembers, moveMembers, readMessageHistory, mentionEveryone, useExternalEmojis, connect, speak, muteMembers, deafenMembers, moveMembers,
useVoiceActivation, prioritySpeaker, stream, changeNickname, manageNicknames, manageRoles, manageWebhooks, manageEmojis);
useVoiceActivation, prioritySpeaker, stream, changeNickname, manageNicknames, manageRoles, manageWebhooks, manageEmojisAndStickers,
useSlashCommands, requestToSpeak, manageThreads, usePublicThreads, usePrivateThreads, useExternalStickers);


/// <summary> /// <summary>
/// Returns a value that indicates if a specific <see cref="GuildPermission"/> is enabled /// Returns a value that indicates if a specific <see cref="GuildPermission"/> is enabled


+ 59
- 42
src/Discord.Net.Core/Entities/Roles/Color.cs View File

@@ -10,68 +10,70 @@ namespace Discord
[DebuggerDisplay(@"{DebuggerDisplay,nq}")] [DebuggerDisplay(@"{DebuggerDisplay,nq}")]
public struct Color public struct Color
{ {
/// <summary> Gets the max decimal value of color. </summary>
public const uint MaxDecimalValue = 0xFFFFFF;
/// <summary> Gets the default user color value. </summary> /// <summary> Gets the default user color value. </summary>
public static readonly Color Default = new Color(0);
public static readonly Color Default = new(0);
/// <summary> Gets the teal color value. </summary> /// <summary> Gets the teal color value. </summary>
/// <returns> A color struct with the hex value of <see href="http://www.color-hex.com/color/1ABC9C">1ABC9C</see>.</returns> /// <returns> A color struct with the hex value of <see href="http://www.color-hex.com/color/1ABC9C">1ABC9C</see>.</returns>
public static readonly Color Teal = new Color(0x1ABC9C);
public static readonly Color Teal = new(0x1ABC9C);
/// <summary> Gets the dark teal color value. </summary> /// <summary> Gets the dark teal color value. </summary>
/// <returns> A color struct with the hex value of <see href="http://www.color-hex.com/color/11806A">11806A</see>.</returns> /// <returns> A color struct with the hex value of <see href="http://www.color-hex.com/color/11806A">11806A</see>.</returns>
public static readonly Color DarkTeal = new Color(0x11806A);
public static readonly Color DarkTeal = new(0x11806A);
/// <summary> Gets the green color value. </summary> /// <summary> Gets the green color value. </summary>
/// <returns> A color struct with the hex value of <see href="http://www.color-hex.com/color/2ECC71">2ECC71</see>.</returns> /// <returns> A color struct with the hex value of <see href="http://www.color-hex.com/color/2ECC71">2ECC71</see>.</returns>
public static readonly Color Green = new Color(0x2ECC71);
public static readonly Color Green = new(0x2ECC71);
/// <summary> Gets the dark green color value. </summary> /// <summary> Gets the dark green color value. </summary>
/// <returns> A color struct with the hex value of <see href="http://www.color-hex.com/color/1F8B4C">1F8B4C</see>.</returns> /// <returns> A color struct with the hex value of <see href="http://www.color-hex.com/color/1F8B4C">1F8B4C</see>.</returns>
public static readonly Color DarkGreen = new Color(0x1F8B4C);
public static readonly Color DarkGreen = new(0x1F8B4C);
/// <summary> Gets the blue color value. </summary> /// <summary> Gets the blue color value. </summary>
/// <returns> A color struct with the hex value of <see href="http://www.color-hex.com/color/3498DB">3498DB</see>.</returns> /// <returns> A color struct with the hex value of <see href="http://www.color-hex.com/color/3498DB">3498DB</see>.</returns>
public static readonly Color Blue = new Color(0x3498DB);
public static readonly Color Blue = new(0x3498DB);
/// <summary> Gets the dark blue color value. </summary> /// <summary> Gets the dark blue color value. </summary>
/// <returns> A color struct with the hex value of <see href="http://www.color-hex.com/color/206694">206694</see>.</returns> /// <returns> A color struct with the hex value of <see href="http://www.color-hex.com/color/206694">206694</see>.</returns>
public static readonly Color DarkBlue = new Color(0x206694);
public static readonly Color DarkBlue = new(0x206694);
/// <summary> Gets the purple color value. </summary> /// <summary> Gets the purple color value. </summary>
/// <returns> A color struct with the hex value of <see href="http://www.color-hex.com/color/9B59B6">9B59B6</see>.</returns> /// <returns> A color struct with the hex value of <see href="http://www.color-hex.com/color/9B59B6">9B59B6</see>.</returns>
public static readonly Color Purple = new Color(0x9B59B6);
public static readonly Color Purple = new(0x9B59B6);
/// <summary> Gets the dark purple color value. </summary> /// <summary> Gets the dark purple color value. </summary>
/// <returns> A color struct with the hex value of <see href="http://www.color-hex.com/color/71368A">71368A</see>.</returns> /// <returns> A color struct with the hex value of <see href="http://www.color-hex.com/color/71368A">71368A</see>.</returns>
public static readonly Color DarkPurple = new Color(0x71368A);
public static readonly Color DarkPurple = new(0x71368A);
/// <summary> Gets the magenta color value. </summary> /// <summary> Gets the magenta color value. </summary>
/// <returns> A color struct with the hex value of <see href="http://www.color-hex.com/color/E91E63">E91E63</see>.</returns> /// <returns> A color struct with the hex value of <see href="http://www.color-hex.com/color/E91E63">E91E63</see>.</returns>
public static readonly Color Magenta = new Color(0xE91E63);
public static readonly Color Magenta = new(0xE91E63);
/// <summary> Gets the dark magenta color value. </summary> /// <summary> Gets the dark magenta color value. </summary>
/// <returns> A color struct with the hex value of <see href="http://www.color-hex.com/color/AD1457">AD1457</see>.</returns> /// <returns> A color struct with the hex value of <see href="http://www.color-hex.com/color/AD1457">AD1457</see>.</returns>
public static readonly Color DarkMagenta = new Color(0xAD1457);
public static readonly Color DarkMagenta = new(0xAD1457);
/// <summary> Gets the gold color value. </summary> /// <summary> Gets the gold color value. </summary>
/// <returns> A color struct with the hex value of <see href="http://www.color-hex.com/color/F1C40F">F1C40F</see>.</returns> /// <returns> A color struct with the hex value of <see href="http://www.color-hex.com/color/F1C40F">F1C40F</see>.</returns>
public static readonly Color Gold = new Color(0xF1C40F);
public static readonly Color Gold = new(0xF1C40F);
/// <summary> Gets the light orange color value. </summary> /// <summary> Gets the light orange color value. </summary>
/// <returns> A color struct with the hex value of <see href="http://www.color-hex.com/color/C27C0E">C27C0E</see>.</returns> /// <returns> A color struct with the hex value of <see href="http://www.color-hex.com/color/C27C0E">C27C0E</see>.</returns>
public static readonly Color LightOrange = new Color(0xC27C0E);
public static readonly Color LightOrange = new(0xC27C0E);
/// <summary> Gets the orange color value. </summary> /// <summary> Gets the orange color value. </summary>
/// <returns> A color struct with the hex value of <see href="http://www.color-hex.com/color/E67E22">E67E22</see>.</returns> /// <returns> A color struct with the hex value of <see href="http://www.color-hex.com/color/E67E22">E67E22</see>.</returns>
public static readonly Color Orange = new Color(0xE67E22);
public static readonly Color Orange = new(0xE67E22);
/// <summary> Gets the dark orange color value. </summary> /// <summary> Gets the dark orange color value. </summary>
/// <returns> A color struct with the hex value of <see href="http://www.color-hex.com/color/A84300">A84300</see>.</returns> /// <returns> A color struct with the hex value of <see href="http://www.color-hex.com/color/A84300">A84300</see>.</returns>
public static readonly Color DarkOrange = new Color(0xA84300);
public static readonly Color DarkOrange = new(0xA84300);
/// <summary> Gets the red color value. </summary> /// <summary> Gets the red color value. </summary>
/// <returns> A color struct with the hex value of <see href="http://www.color-hex.com/color/E74C3C">E74C3C</see>.</returns> /// <returns> A color struct with the hex value of <see href="http://www.color-hex.com/color/E74C3C">E74C3C</see>.</returns>
public static readonly Color Red = new Color(0xE74C3C);
public static readonly Color Red = new(0xE74C3C);
/// <summary> Gets the dark red color value. </summary> /// <summary> Gets the dark red color value. </summary>
/// <returns> A color struct with the hex value of <see href="http://www.color-hex.com/color/992D22">992D22</see>.</returns> /// <returns> A color struct with the hex value of <see href="http://www.color-hex.com/color/992D22">992D22</see>.</returns>
public static readonly Color DarkRed = new Color(0x992D22);
public static readonly Color DarkRed = new(0x992D22);
/// <summary> Gets the light grey color value. </summary> /// <summary> Gets the light grey color value. </summary>
/// <returns> A color struct with the hex value of <see href="http://www.color-hex.com/color/979C9F">979C9F</see>.</returns> /// <returns> A color struct with the hex value of <see href="http://www.color-hex.com/color/979C9F">979C9F</see>.</returns>
public static readonly Color LightGrey = new Color(0x979C9F);
public static readonly Color LightGrey = new(0x979C9F);
/// <summary> Gets the lighter grey color value. </summary> /// <summary> Gets the lighter grey color value. </summary>
/// <returns> A color struct with the hex value of <see href="http://www.color-hex.com/color/95A5A6">95A5A6</see>.</returns> /// <returns> A color struct with the hex value of <see href="http://www.color-hex.com/color/95A5A6">95A5A6</see>.</returns>
public static readonly Color LighterGrey = new Color(0x95A5A6);
public static readonly Color LighterGrey = new(0x95A5A6);
/// <summary> Gets the dark grey color value. </summary> /// <summary> Gets the dark grey color value. </summary>
/// <returns> A color struct with the hex value of <see href="http://www.color-hex.com/color/607D8B">607D8B</see>.</returns> /// <returns> A color struct with the hex value of <see href="http://www.color-hex.com/color/607D8B">607D8B</see>.</returns>
public static readonly Color DarkGrey = new Color(0x607D8B);
public static readonly Color DarkGrey = new(0x607D8B);
/// <summary> Gets the darker grey color value. </summary> /// <summary> Gets the darker grey color value. </summary>
/// <returns> A color struct with the hex value of <see href="http://www.color-hex.com/color/546E7A">546E7A</see>.</returns> /// <returns> A color struct with the hex value of <see href="http://www.color-hex.com/color/546E7A">546E7A</see>.</returns>
public static readonly Color DarkerGrey = new Color(0x546E7A);
public static readonly Color DarkerGrey = new(0x546E7A);


/// <summary> Gets the encoded value for this color. </summary> /// <summary> Gets the encoded value for this color. </summary>
/// <remarks> /// <remarks>
@@ -91,22 +93,27 @@ namespace Discord
/// Initializes a <see cref="Color"/> struct with the given raw value. /// Initializes a <see cref="Color"/> struct with the given raw value.
/// </summary> /// </summary>
/// <example> /// <example>
/// The following will create a color that has a hex value of
/// The following will create a color that has a hex value of
/// <see href="http://www.color-hex.com/color/607d8b">#607D8B</see>. /// <see href="http://www.color-hex.com/color/607d8b">#607D8B</see>.
/// <code language="cs"> /// <code language="cs">
/// Color darkGrey = new Color(0x607D8B); /// Color darkGrey = new Color(0x607D8B);
/// </code> /// </code>
/// </example> /// </example>
/// <param name="rawValue">The raw value of the color (e.g. <c>0x607D8B</c>).</param> /// <param name="rawValue">The raw value of the color (e.g. <c>0x607D8B</c>).</param>
/// <exception cref="ArgumentException">Value exceeds <see cref="MaxDecimalValue"/>.</exception>
public Color(uint rawValue) public Color(uint rawValue)
{ {
if (rawValue > MaxDecimalValue)
throw new ArgumentException($"{nameof(RawValue)} of color cannot be greater than {MaxDecimalValue}!", nameof(rawValue));

RawValue = rawValue; RawValue = rawValue;
} }

/// <summary> /// <summary>
/// Initializes a <see cref="Color" /> struct with the given RGB bytes. /// Initializes a <see cref="Color" /> struct with the given RGB bytes.
/// </summary> /// </summary>
/// <example> /// <example>
/// The following will create a color that has a value of
/// The following will create a color that has a value of
/// <see href="http://www.color-hex.com/color/607d8b">#607D8B</see>. /// <see href="http://www.color-hex.com/color/607d8b">#607D8B</see>.
/// <code language="cs"> /// <code language="cs">
/// Color darkGrey = new Color((byte)0b_01100000, (byte)0b_01111101, (byte)0b_10001011); /// Color darkGrey = new Color((byte)0b_01100000, (byte)0b_01111101, (byte)0b_10001011);
@@ -115,19 +122,24 @@ namespace Discord
/// <param name="r">The byte that represents the red color.</param> /// <param name="r">The byte that represents the red color.</param>
/// <param name="g">The byte that represents the green color.</param> /// <param name="g">The byte that represents the green color.</param>
/// <param name="b">The byte that represents the blue color.</param> /// <param name="b">The byte that represents the blue color.</param>
/// <exception cref="ArgumentException">Value exceeds <see cref="MaxDecimalValue"/>.</exception>
public Color(byte r, byte g, byte b) public Color(byte r, byte g, byte b)
{ {
RawValue =
((uint)r << 16) |
((uint)g << 8) |
(uint)b;
uint value = ((uint)r << 16)
| ((uint)g << 8)
| (uint)b;

if (value > MaxDecimalValue)
throw new ArgumentException($"{nameof(RawValue)} of color cannot be greater than {MaxDecimalValue}!");

RawValue = value;
} }


/// <summary> /// <summary>
/// Initializes a <see cref="Color"/> struct with the given RGB value. /// Initializes a <see cref="Color"/> struct with the given RGB value.
/// </summary> /// </summary>
/// <example> /// <example>
/// The following will create a color that has a value of
/// The following will create a color that has a value of
/// <see href="http://www.color-hex.com/color/607d8b">#607D8B</see>. /// <see href="http://www.color-hex.com/color/607d8b">#607D8B</see>.
/// <code language="cs"> /// <code language="cs">
/// Color darkGrey = new Color(96, 125, 139); /// Color darkGrey = new Color(96, 125, 139);
@@ -145,16 +157,15 @@ namespace Discord
throw new ArgumentOutOfRangeException(nameof(g), "Value must be within [0,255]."); throw new ArgumentOutOfRangeException(nameof(g), "Value must be within [0,255].");
if (b < 0 || b > 255) if (b < 0 || b > 255)
throw new ArgumentOutOfRangeException(nameof(b), "Value must be within [0,255]."); throw new ArgumentOutOfRangeException(nameof(b), "Value must be within [0,255].");
RawValue =
((uint)r << 16) |
((uint)g << 8) |
(uint)b;
RawValue = ((uint)r << 16)
| ((uint)g << 8)
| (uint)b;
} }
/// <summary> /// <summary>
/// Initializes a <see cref="Color"/> struct with the given RGB float value. /// Initializes a <see cref="Color"/> struct with the given RGB float value.
/// </summary> /// </summary>
/// <example> /// <example>
/// The following will create a color that has a value of
/// The following will create a color that has a value of
/// <see href="http://www.color-hex.com/color/607c8c">#607c8c</see>. /// <see href="http://www.color-hex.com/color/607c8c">#607c8c</see>.
/// <code language="cs"> /// <code language="cs">
/// Color darkGrey = new Color(0.38f, 0.49f, 0.55f); /// Color darkGrey = new Color(0.38f, 0.49f, 0.55f);
@@ -172,10 +183,9 @@ namespace Discord
throw new ArgumentOutOfRangeException(nameof(g), "Value must be within [0,1]."); throw new ArgumentOutOfRangeException(nameof(g), "Value must be within [0,1].");
if (b < 0.0f || b > 1.0f) if (b < 0.0f || b > 1.0f)
throw new ArgumentOutOfRangeException(nameof(b), "Value must be within [0,1]."); throw new ArgumentOutOfRangeException(nameof(b), "Value must be within [0,1].");
RawValue =
((uint)(r * 255.0f) << 16) |
((uint)(g * 255.0f) << 8) |
(uint)(b * 255.0f);
RawValue = ((uint)(r * 255.0f) << 16)
| ((uint)(g * 255.0f) << 8)
| (uint)(b * 255.0f);
} }


public static bool operator ==(Color lhs, Color rhs) public static bool operator ==(Color lhs, Color rhs)
@@ -184,15 +194,22 @@ namespace Discord
public static bool operator !=(Color lhs, Color rhs) public static bool operator !=(Color lhs, Color rhs)
=> lhs.RawValue != rhs.RawValue; => lhs.RawValue != rhs.RawValue;


public static implicit operator Color(uint rawValue)
=> new(rawValue);

public static implicit operator uint(Color color)
=> color.RawValue;

public override bool Equals(object obj) public override bool Equals(object obj)
=> (obj is Color c && RawValue == c.RawValue);
=> obj is Color c && RawValue == c.RawValue;


public override int GetHashCode() => RawValue.GetHashCode(); public override int GetHashCode() => RawValue.GetHashCode();


public static implicit operator StandardColor(Color color) =>
StandardColor.FromArgb((int)color.RawValue);
public static explicit operator Color(StandardColor color) =>
new Color((uint)color.ToArgb() << 8 >> 8);
public static implicit operator StandardColor(Color color)
=> StandardColor.FromArgb((int)color.RawValue);

public static explicit operator Color(StandardColor color)
=> new((uint)color.ToArgb() << 8 >> 8);


/// <summary> /// <summary>
/// Gets the hexadecimal representation of the color (e.g. <c>#000ccc</c>). /// Gets the hexadecimal representation of the color (e.g. <c>#000ccc</c>).


+ 27
- 5
src/Discord.Net.Core/Entities/Users/IUser.cs View File

@@ -12,17 +12,29 @@ namespace Discord
/// </summary> /// </summary>
string AvatarId { get; } string AvatarId { get; }
/// <summary> /// <summary>
/// Gets the identifier of this user's banner.
/// </summary>
string BannerId { get; }
/// <summary>
/// Gets the user's banner color.
/// </summary>
/// <returns>
/// A <see cref="Color"/> struct representing the accent color of this user's banner.
/// </returns>
Color? AccentColor { get; }
/// <summary>
/// Gets the avatar URL for this user. /// Gets the avatar URL for this user.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// This property retrieves a URL for this user's avatar. In event that the user does not have a valid avatar /// This property retrieves a URL for this user's avatar. In event that the user does not have a valid avatar
/// (i.e. their avatar identifier is not set), this property will return <c>null</c>. If you wish to
/// (i.e. their avatar identifier is not set), this method will return <c>null</c>. If you wish to
/// retrieve the default avatar for this user, consider using <see cref="IUser.GetDefaultAvatarUrl"/> (see /// retrieve the default avatar for this user, consider using <see cref="IUser.GetDefaultAvatarUrl"/> (see
/// example). /// example).
/// </remarks> /// </remarks>
/// <example> /// <example>
/// <para>The following example attempts to retrieve the user's current avatar and send it to a channel; if one is
/// not set, a default avatar for this user will be returned instead.</para>
/// <para
/// >The following example attempts to retrieve the user's current avatar and send it to a channel; if one is
/// not set, a default avatar for this user will be returned instead.</para>
/// <code language="cs" region="GetAvatarUrl" /// <code language="cs" region="GetAvatarUrl"
/// source="..\..\..\Discord.Net.Examples\Core\Entities\Users\IUser.Examples.cs"/> /// source="..\..\..\Discord.Net.Examples\Core\Entities\Users\IUser.Examples.cs"/>
/// </example> /// </example>
@@ -34,6 +46,16 @@ namespace Discord
/// </returns> /// </returns>
string GetAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128); string GetAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128);
/// <summary> /// <summary>
/// Gets the banner URL for this user.
/// </summary>
/// <param name="format">The format to return.</param>
/// <param name="size">The size of the image to return in. This can be any power of two between 16 and 2048.
/// </param>
/// <returns>
/// A string representing the user's avatar URL; <c>null</c> if the user does not have an banner in place.
/// </returns>
string GetBannerUrl(ImageFormat format = ImageFormat.Auto, ushort size = 256);
/// <summary>
/// Gets the default avatar URL for this user. /// Gets the default avatar URL for this user.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
@@ -93,8 +115,8 @@ namespace Discord
/// This method is used to obtain or create a channel used to send a direct message. /// This method is used to obtain or create a channel used to send a direct message.
/// <note type="warning"> /// <note type="warning">
/// In event that the current user cannot send a message to the target user, a channel can and will /// In event that the current user cannot send a message to the target user, a channel can and will
/// still be created by Discord. However, attempting to send a message will yield a
/// <see cref="Discord.Net.HttpException"/> with a 403 as its
/// still be created by Discord. However, attempting to send a message will yield a
/// <see cref="Discord.Net.HttpException"/> with a 403 as its
/// <see cref="Discord.Net.HttpException.HttpCode"/>. There are currently no official workarounds by /// <see cref="Discord.Net.HttpException.HttpCode"/>. There are currently no official workarounds by
/// Discord. /// Discord.
/// </note> /// </note>


+ 6
- 0
src/Discord.Net.Core/Entities/Users/IVoiceState.cs View File

@@ -1,3 +1,5 @@
using System;

namespace Discord namespace Discord
{ {
/// <summary> /// <summary>
@@ -62,5 +64,9 @@ namespace Discord
/// <c>true</c> if the user is streaming; otherwise <c>false</c>. /// <c>true</c> if the user is streaming; otherwise <c>false</c>.
/// </returns> /// </returns>
bool IsStreaming { get; } bool IsStreaming { get; }
/// <summary>
/// Gets the time on which the user requested to speak.
/// </summary>
DateTimeOffset? RequestToSpeakTimestamp { get; }
} }
} }

+ 6
- 1
src/Discord.Net.Core/Entities/Webhooks/IWebhook.cs View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Threading.Tasks; using System.Threading.Tasks;


namespace Discord namespace Discord
@@ -49,6 +49,11 @@ namespace Discord
/// </summary> /// </summary>
IUser Creator { get; } IUser Creator { get; }


/// <summary>
/// Gets the ID of the application owning this webhook.
/// </summary>
ulong? ApplicationId { get; }

/// <summary> /// <summary>
/// Modifies this webhook. /// Modifies this webhook.
/// </summary> /// </summary>


+ 2
- 0
src/Discord.Net.Rest/API/Common/Game.cs View File

@@ -41,6 +41,8 @@ namespace Discord.API
public Optional<Emoji> Emoji { get; set; } public Optional<Emoji> Emoji { get; set; }
[JsonProperty("created_at")] [JsonProperty("created_at")]
public Optional<long> CreatedAt { get; set; } public Optional<long> CreatedAt { get; set; }
//[JsonProperty("buttons")]
//public Optional<RichPresenceButton[]> Buttons { get; set; }


[OnError] [OnError]
internal void OnError(StreamingContext context, ErrorContext errorContext) internal void OnError(StreamingContext context, ErrorContext errorContext)


+ 2
- 0
src/Discord.Net.Rest/API/Common/Guild.cs View File

@@ -78,5 +78,7 @@ namespace Discord.API
public Optional<int> ApproximatePresenceCount { get; set; } public Optional<int> ApproximatePresenceCount { get; set; }
[JsonProperty("threads")] [JsonProperty("threads")]
public Optional<Channel[]> Threads { get; set; } public Optional<Channel[]> Threads { get; set; }
[JsonProperty("nsfw_level")]
public NsfwLevel NsfwLevel { get; set; }
} }
} }

+ 1
- 1
src/Discord.Net.Rest/API/Common/InteractionCallbackData.cs View File

@@ -18,7 +18,7 @@ namespace Discord.API


// New flags prop. this make the response "ephemeral". see https://discord.com/developers/docs/interactions/slash-commands#interaction-response-interactionapplicationcommandcallbackdata // New flags prop. this make the response "ephemeral". see https://discord.com/developers/docs/interactions/slash-commands#interaction-response-interactionapplicationcommandcallbackdata
[JsonProperty("flags")] [JsonProperty("flags")]
public Optional<int> Flags { get; set; }
public Optional<MessageFlags> Flags { get; set; }


[JsonProperty("components")] [JsonProperty("components")]
public Optional<API.ActionRowComponent[]> Components { get; set; } public Optional<API.ActionRowComponent[]> Components { get; set; }


+ 30
- 0
src/Discord.Net.Rest/API/Common/StageInstance.cs View File

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

namespace Discord.API
{
internal class StageInstance
{
[JsonProperty("id")]
public ulong Id { get; set; }

[JsonProperty("guild_id")]
public ulong GuildId { get; set; }

[JsonProperty("channel_id")]
public ulong ChannelId { get; set; }

[JsonProperty("topic")]
public string Topic { get; set; }

[JsonProperty("privacy_level")]
public StagePrivacyLevel PrivacyLevel { get; set; }

[JsonProperty("discoverable_disabled")]
public bool DiscoverableDisabled { get; set; }
}
}

+ 4
- 0
src/Discord.Net.Rest/API/Common/User.cs View File

@@ -15,6 +15,10 @@ namespace Discord.API
public Optional<bool> Bot { get; set; } public Optional<bool> Bot { get; set; }
[JsonProperty("avatar")] [JsonProperty("avatar")]
public Optional<string> Avatar { get; set; } public Optional<string> Avatar { get; set; }
[JsonProperty("banner")]
public Optional<string> Banner { get; set; }
[JsonProperty("accent_color")]
public Optional<uint?> AccentColor { get; set; }


//CurrentUser //CurrentUser
[JsonProperty("verified")] [JsonProperty("verified")]


+ 3
- 0
src/Discord.Net.Rest/API/Common/VoiceState.cs View File

@@ -1,5 +1,6 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
using Newtonsoft.Json; using Newtonsoft.Json;
using System;


namespace Discord.API namespace Discord.API
{ {
@@ -28,5 +29,7 @@ namespace Discord.API
public bool Suppress { get; set; } public bool Suppress { get; set; }
[JsonProperty("self_stream")] [JsonProperty("self_stream")]
public bool SelfStream { get; set; } public bool SelfStream { get; set; }
[JsonProperty("request_to_speak_timestamp")]
public Optional<DateTimeOffset?> RequestToSpeakTimestamp { get; set; }
} }
} }

+ 2
- 0
src/Discord.Net.Rest/API/Common/Webhook.cs View File

@@ -21,5 +21,7 @@ namespace Discord.API


[JsonProperty("user")] [JsonProperty("user")]
public Optional<User> Creator { get; set; } public Optional<User> Creator { get; set; }
[JsonProperty("application_id")]
public ulong? ApplicationId { get; set; }
} }
} }

+ 21
- 0
src/Discord.Net.Rest/API/Rest/CreateStageInstanceParams.cs View File

@@ -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.Rest
{
internal class CreateStageInstanceParams
{
[JsonProperty("channel_id")]
public ulong ChannelId { get; set; }

[JsonProperty("topic")]
public string Topic { get; set; }

[JsonProperty("privacy_level")]
public Optional<StagePrivacyLevel> PrivacyLevel { get; set; }
}
}

+ 1
- 1
src/Discord.Net.Rest/API/Rest/CreateWebhookMessageParams.cs View File

@@ -28,7 +28,7 @@ namespace Discord.API.Rest
public Optional<AllowedMentions> AllowedMentions { get; set; } public Optional<AllowedMentions> AllowedMentions { get; set; }


[JsonProperty("flags")] [JsonProperty("flags")]
public Optional<int> Flags { get; set; }
public Optional<MessageFlags> Flags { get; set; }


[JsonProperty("components")] [JsonProperty("components")]
public Optional<API.ActionRowComponent[]> Components { get; set; } public Optional<API.ActionRowComponent[]> Components { get; set; }


+ 19
- 0
src/Discord.Net.Rest/API/Rest/ModifyStageInstanceParams.cs View File

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

namespace Discord.API.Rest
{
internal class ModifyStageInstanceParams
{

[JsonProperty("topic")]
public Optional<string> Topic { get; set; }

[JsonProperty("privacy_level")]
public Optional<StagePrivacyLevel> PrivacyLevel { get; set; }
}
}

+ 17
- 0
src/Discord.Net.Rest/API/Rest/ModifyVoiceStateParams.cs View File

@@ -0,0 +1,17 @@
using Newtonsoft.Json;
using System;

namespace Discord.API.Rest
{
internal class ModifyVoiceStateParams
{
[JsonProperty("channel_id")]
public ulong ChannelId { get; set; }

[JsonProperty("suppress")]
public Optional<bool> Suppressed { get; set; }

[JsonProperty("request_to_speak_timestamp")]
public Optional<DateTimeOffset> RequestToSpeakTimestamp { get; set; }
}
}

+ 2
- 0
src/Discord.Net.Rest/API/Rest/ModifyWebhookMessageParams.cs View File

@@ -12,5 +12,7 @@ namespace Discord.API.Rest
public Optional<Embed[]> Embeds { get; set; } public Optional<Embed[]> Embeds { get; set; }
[JsonProperty("allowed_mentions")] [JsonProperty("allowed_mentions")]
public Optional<AllowedMentions> AllowedMentions { get; set; } public Optional<AllowedMentions> AllowedMentions { get; set; }
[JsonProperty("components")]
public Optional<API.ActionRowComponent[]> Components { get; set; }
} }
} }

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

@@ -9,11 +9,11 @@
<TargetFrameworks Condition=" '$(OS)' != 'Windows_NT' ">netstandard2.0;netstandard2.1</TargetFrameworks> <TargetFrameworks Condition=" '$(OS)' != 'Windows_NT' ">netstandard2.0;netstandard2.1</TargetFrameworks>
<PackageIcon>Temporary.png</PackageIcon> <PackageIcon>Temporary.png</PackageIcon>
<PackageProjectUrl>https://github.com/Discord-Net-Labs/Discord.Net-Labs</PackageProjectUrl> <PackageProjectUrl>https://github.com/Discord-Net-Labs/Discord.Net-Labs</PackageProjectUrl>
<Version>2.4.6</Version>
<Version>3.0.1-pre</Version>
<PackageId>Discord.Net.Labs.Rest</PackageId> <PackageId>Discord.Net.Labs.Rest</PackageId>
<RepositoryUrl>https://github.com/Discord-Net-Labs/Discord.Net-Labs</RepositoryUrl> <RepositoryUrl>https://github.com/Discord-Net-Labs/Discord.Net-Labs</RepositoryUrl>
<AssemblyVersion>2.3.4</AssemblyVersion>
<FileVersion>2.3.4</FileVersion>
<AssemblyVersion>3.0.1</AssemblyVersion>
<FileVersion>3.0.1</FileVersion>
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>
<DocumentationFile>..\Discord.Net.Rest\Discord.Net.Rest.xml</DocumentationFile> <DocumentationFile>..\Discord.Net.Rest\Discord.Net.Rest.xml</DocumentationFile>


+ 138
- 2
src/Discord.Net.Rest/Discord.Net.Rest.xml View File

@@ -2281,6 +2281,50 @@
Represents a REST-based news channel in a guild that has the same properties as a <see cref="T:Discord.Rest.RestTextChannel"/>. Represents a REST-based news channel in a guild that has the same properties as a <see cref="T:Discord.Rest.RestTextChannel"/>.
</summary> </summary>
</member> </member>
<member name="T:Discord.Rest.RestStageChannel">
<summary>
Represents a REST-based stage channel in a guild.
</summary>
</member>
<member name="P:Discord.Rest.RestStageChannel.Topic">
<inheritdoc/>
</member>
<member name="P:Discord.Rest.RestStageChannel.PrivacyLevel">
<inheritdoc/>
</member>
<member name="P:Discord.Rest.RestStageChannel.DiscoverableDisabled">
<inheritdoc/>
</member>
<member name="P:Discord.Rest.RestStageChannel.Live">
<inheritdoc/>
</member>
<member name="M:Discord.Rest.RestStageChannel.ModifyInstanceAsync(System.Action{Discord.StageInstanceProperties},Discord.RequestOptions)">
<inheritdoc/>
</member>
<member name="M:Discord.Rest.RestStageChannel.StartStageAsync(System.String,Discord.StagePrivacyLevel,Discord.RequestOptions)">
<inheritdoc/>
</member>
<member name="M:Discord.Rest.RestStageChannel.StopStageAsync(Discord.RequestOptions)">
<inheritdoc/>
</member>
<member name="M:Discord.Rest.RestStageChannel.UpdateAsync(Discord.RequestOptions)">
<inheritdoc/>
</member>
<member name="M:Discord.Rest.RestStageChannel.RequestToSpeak(Discord.RequestOptions)">
<inheritdoc/>
</member>
<member name="M:Discord.Rest.RestStageChannel.BecomeSpeakerAsync(Discord.RequestOptions)">
<inheritdoc/>
</member>
<member name="M:Discord.Rest.RestStageChannel.StopSpeakingAsync(Discord.RequestOptions)">
<inheritdoc/>
</member>
<member name="M:Discord.Rest.RestStageChannel.MoveToSpeaker(Discord.IGuildUser,Discord.RequestOptions)">
<inheritdoc/>
</member>
<member name="M:Discord.Rest.RestStageChannel.RemoveFromSpeaker(Discord.IGuildUser,Discord.RequestOptions)">
<inheritdoc/>
</member>
<member name="T:Discord.Rest.RestTextChannel"> <member name="T:Discord.Rest.RestTextChannel">
<summary> <summary>
Represents a REST-based channel in a guild that can send and receive messages. Represents a REST-based channel in a guild that can send and receive messages.
@@ -2896,6 +2940,9 @@
<member name="P:Discord.Rest.RestGuild.ApproximatePresenceCount"> <member name="P:Discord.Rest.RestGuild.ApproximatePresenceCount">
<inheritdoc /> <inheritdoc />
</member> </member>
<member name="P:Discord.Rest.RestGuild.NsfwLevel">
<inheritdoc />
</member>
<member name="P:Discord.Rest.RestGuild.PreferredCulture"> <member name="P:Discord.Rest.RestGuild.PreferredCulture">
<inheritdoc /> <inheritdoc />
</member> </member>
@@ -3085,6 +3132,27 @@
message channels found within this guild. message channels found within this guild.
</returns> </returns>
</member> </member>
<member name="M:Discord.Rest.RestGuild.GetThreadChannelAsync(System.UInt64,Discord.RequestOptions)">
<summary>
Gets a thread channel in this guild.
</summary>
<param name="id">The snowflake identifier for the thread channel.</param>
<param name="options">The options to be used when sending the request.</param>
<returns>
A task that represents the asynchronous get operation. The task result contains the thread channel associated
with the specified <paramref name="id"/>; <see langword="null"/> if none is found.
</returns>
</member>
<member name="M:Discord.Rest.RestGuild.GetThreadChannelsAsync(Discord.RequestOptions)">
<summary>
Gets a collection of all thread in this guild.
</summary>
<param name="options">The options to be used when sending the request.</param>
<returns>
A task that represents the asynchronous get operation. The task result contains a read-only collection of
threads found within this guild.
</returns>
</member>
<member name="M:Discord.Rest.RestGuild.GetVoiceChannelAsync(System.UInt64,Discord.RequestOptions)"> <member name="M:Discord.Rest.RestGuild.GetVoiceChannelAsync(System.UInt64,Discord.RequestOptions)">
<summary> <summary>
Gets a voice channel in this guild. Gets a voice channel in this guild.
@@ -3106,6 +3174,28 @@
voice channels found within this guild. voice channels found within this guild.
</returns> </returns>
</member> </member>
<member name="M:Discord.Rest.RestGuild.GetStageChannelAsync(System.UInt64,Discord.RequestOptions)">
<summary>
Gets a stage channel in this guild
</summary>
<param name="id">The snowflake identifier for the stage channel.</param>
<param name="options">The options to be used when sending the request.</param>
<returns>
A task that represents the asynchronous get operation. The task result contains the stage channel associated
with the specified <paramref name="id"/>; <see langword="null" /> if none is found.
</returns>
</member>
<member name="M:Discord.Rest.RestGuild.GetStageChannelsAsync(Discord.RequestOptions)">
<summary>
Gets a collection of all stage channels in this guild.
</summary>
<param name="mode">The <see cref="T:Discord.CacheMode"/> that determines whether the object should be fetched from cache.</param>
<param name="options">The options to be used when sending the request.</param>
<returns>
A task that represents the asynchronous get operation. The task result contains a read-only collection of
stage channels found within this guild.
</returns>
</member>
<member name="M:Discord.Rest.RestGuild.GetCategoryChannelsAsync(Discord.RequestOptions)"> <member name="M:Discord.Rest.RestGuild.GetCategoryChannelsAsync(Discord.RequestOptions)">
<summary> <summary>
Gets a collection of all category channels in this guild. Gets a collection of all category channels in this guild.
@@ -3403,6 +3493,16 @@
of webhooks found within the guild. of webhooks found within the guild.
</returns> </returns>
</member> </member>
<member name="M:Discord.Rest.RestGuild.GetApplicationCommandsAsync(Discord.RequestOptions)">
<summary>
Gets this guilds slash commands commands
</summary>
<param name="options">The options to be used when sending the request.</param>
<returns>
A task that represents the asynchronous get operation. The task result contains a read-only collection
of application commands found within the guild.
</returns>
</member>
<member name="M:Discord.Rest.RestGuild.ToString"> <member name="M:Discord.Rest.RestGuild.ToString">
<summary> <summary>
Returns the name of the guild. Returns the name of the guild.
@@ -3460,12 +3560,24 @@
<member name="M:Discord.Rest.RestGuild.Discord#IGuild#GetTextChannelAsync(System.UInt64,Discord.CacheMode,Discord.RequestOptions)"> <member name="M:Discord.Rest.RestGuild.Discord#IGuild#GetTextChannelAsync(System.UInt64,Discord.CacheMode,Discord.RequestOptions)">
<inheritdoc /> <inheritdoc />
</member> </member>
<member name="M:Discord.Rest.RestGuild.Discord#IGuild#GetThreadChannelAsync(System.UInt64,Discord.CacheMode,Discord.RequestOptions)">
<inheritdoc />
</member>
<member name="M:Discord.Rest.RestGuild.Discord#IGuild#GetThreadChannelsAsync(Discord.CacheMode,Discord.RequestOptions)">
<inheritdoc />
</member>
<member name="M:Discord.Rest.RestGuild.Discord#IGuild#GetVoiceChannelsAsync(Discord.CacheMode,Discord.RequestOptions)"> <member name="M:Discord.Rest.RestGuild.Discord#IGuild#GetVoiceChannelsAsync(Discord.CacheMode,Discord.RequestOptions)">
<inheritdoc /> <inheritdoc />
</member> </member>
<member name="M:Discord.Rest.RestGuild.Discord#IGuild#GetCategoriesAsync(Discord.CacheMode,Discord.RequestOptions)"> <member name="M:Discord.Rest.RestGuild.Discord#IGuild#GetCategoriesAsync(Discord.CacheMode,Discord.RequestOptions)">
<inheritdoc /> <inheritdoc />
</member> </member>
<member name="M:Discord.Rest.RestGuild.Discord#IGuild#GetStageChannelAsync(System.UInt64,Discord.CacheMode,Discord.RequestOptions)">
<inheritdoc />
</member>
<member name="M:Discord.Rest.RestGuild.Discord#IGuild#GetStageChannelsAsync(Discord.CacheMode,Discord.RequestOptions)">
<inheritdoc />
</member>
<member name="M:Discord.Rest.RestGuild.Discord#IGuild#GetVoiceChannelAsync(System.UInt64,Discord.CacheMode,Discord.RequestOptions)"> <member name="M:Discord.Rest.RestGuild.Discord#IGuild#GetVoiceChannelAsync(System.UInt64,Discord.CacheMode,Discord.RequestOptions)">
<inheritdoc /> <inheritdoc />
</member> </member>
@@ -3548,6 +3660,9 @@
<member name="M:Discord.Rest.RestGuild.Discord#IGuild#GetWebhooksAsync(Discord.RequestOptions)"> <member name="M:Discord.Rest.RestGuild.Discord#IGuild#GetWebhooksAsync(Discord.RequestOptions)">
<inheritdoc /> <inheritdoc />
</member> </member>
<member name="M:Discord.Rest.RestGuild.Discord#IGuild#GetApplicationCommandsAsync(Discord.RequestOptions)">
<inheritdoc />
</member>
<member name="P:Discord.Rest.RestGuildIntegration.Name"> <member name="P:Discord.Rest.RestGuildIntegration.Name">
<inheritdoc /> <inheritdoc />
</member> </member>
@@ -4335,6 +4450,9 @@
<member name="P:Discord.Rest.RestGroupUser.Discord#IVoiceState#IsStreaming"> <member name="P:Discord.Rest.RestGroupUser.Discord#IVoiceState#IsStreaming">
<inheritdoc /> <inheritdoc />
</member> </member>
<member name="P:Discord.Rest.RestGroupUser.Discord#IVoiceState#RequestToSpeakTimestamp">
<inheritdoc />
</member>
<member name="T:Discord.Rest.RestGuildUser"> <member name="T:Discord.Rest.RestGuildUser">
<summary> <summary>
Represents a REST-based guild user. Represents a REST-based guild user.
@@ -4426,6 +4544,9 @@
<member name="P:Discord.Rest.RestGuildUser.Discord#IVoiceState#IsStreaming"> <member name="P:Discord.Rest.RestGuildUser.Discord#IVoiceState#IsStreaming">
<inheritdoc /> <inheritdoc />
</member> </member>
<member name="P:Discord.Rest.RestGuildUser.Discord#IVoiceState#RequestToSpeakTimestamp">
<inheritdoc />
</member>
<member name="T:Discord.Rest.RestSelfUser"> <member name="T:Discord.Rest.RestSelfUser">
<summary> <summary>
Represents the logged-in REST-based user. Represents the logged-in REST-based user.
@@ -4462,7 +4583,7 @@
</member> </member>
<member name="T:Discord.Rest.RestThreadUser"> <member name="T:Discord.Rest.RestThreadUser">
<summary> <summary>
Represents a thread user recieved over the REST api.
Represents a thread user received over the REST api.
</summary> </summary>
</member> </member>
<member name="P:Discord.Rest.RestThreadUser.Thread"> <member name="P:Discord.Rest.RestThreadUser.Thread">
@@ -4485,7 +4606,7 @@
Gets the guild user for this thread user. Gets the guild user for this thread user.
</summary> </summary>
<returns> <returns>
A task representing the asyncronous get operation. The task returns a
A task representing the asynchronous get operation. The task returns a
<see cref="T:Discord.IGuildUser"/> that represents the current thread user. <see cref="T:Discord.IGuildUser"/> that represents the current thread user.
</returns> </returns>
</member> </member>
@@ -4506,6 +4627,12 @@
<member name="P:Discord.Rest.RestUser.AvatarId"> <member name="P:Discord.Rest.RestUser.AvatarId">
<inheritdoc /> <inheritdoc />
</member> </member>
<member name="P:Discord.Rest.RestUser.BannerId">
<inheritdoc />
</member>
<member name="P:Discord.Rest.RestUser.AccentColor">
<inheritdoc />
</member>
<member name="P:Discord.Rest.RestUser.PublicFlags"> <member name="P:Discord.Rest.RestUser.PublicFlags">
<inheritdoc /> <inheritdoc />
</member> </member>
@@ -4548,6 +4675,9 @@
<member name="M:Discord.Rest.RestUser.GetAvatarUrl(Discord.ImageFormat,System.UInt16)"> <member name="M:Discord.Rest.RestUser.GetAvatarUrl(Discord.ImageFormat,System.UInt16)">
<inheritdoc /> <inheritdoc />
</member> </member>
<member name="M:Discord.Rest.RestUser.GetBannerUrl(Discord.ImageFormat,System.UInt16)">
<inheritdoc />
</member>
<member name="M:Discord.Rest.RestUser.GetDefaultAvatarUrl"> <member name="M:Discord.Rest.RestUser.GetDefaultAvatarUrl">
<inheritdoc /> <inheritdoc />
</member> </member>
@@ -4649,6 +4779,9 @@
<member name="P:Discord.Rest.RestWebhookUser.Discord#IVoiceState#IsStreaming"> <member name="P:Discord.Rest.RestWebhookUser.Discord#IVoiceState#IsStreaming">
<inheritdoc /> <inheritdoc />
</member> </member>
<member name="P:Discord.Rest.RestWebhookUser.Discord#IVoiceState#RequestToSpeakTimestamp">
<inheritdoc />
</member>
<member name="P:Discord.Rest.RestWebhook.Token"> <member name="P:Discord.Rest.RestWebhook.Token">
<inheritdoc /> <inheritdoc />
</member> </member>
@@ -4667,6 +4800,9 @@
<member name="P:Discord.Rest.RestWebhook.Creator"> <member name="P:Discord.Rest.RestWebhook.Creator">
<inheritdoc /> <inheritdoc />
</member> </member>
<member name="P:Discord.Rest.RestWebhook.ApplicationId">
<inheritdoc />
</member>
<member name="P:Discord.Rest.RestWebhook.CreatedAt"> <member name="P:Discord.Rest.RestWebhook.CreatedAt">
<inheritdoc /> <inheritdoc />
</member> </member>


+ 87
- 6
src/Discord.Net.Rest/DiscordRestApiClient.cs View File

@@ -55,7 +55,7 @@ namespace Discord.API
_restClientProvider = restClientProvider; _restClientProvider = restClientProvider;
UserAgent = userAgent; UserAgent = userAgent;
DefaultRetryMode = defaultRetryMode; DefaultRetryMode = defaultRetryMode;
_serializer = serializer ?? new JsonSerializer { ContractResolver = new DiscordContractResolver(), NullValueHandling = NullValueHandling.Ignore };
_serializer = serializer ?? new JsonSerializer { ContractResolver = new DiscordContractResolver(), NullValueHandling = NullValueHandling.Include };
UseSystemClock = useSystemClock; UseSystemClock = useSystemClock;


RequestQueue = new RequestQueue(); RequestQueue = new RequestQueue();
@@ -442,7 +442,7 @@ namespace Discord.API


options = RequestOptions.CreateOrClone(options); options = RequestOptions.CreateOrClone(options);


var bucket = new BucketIds(0, channelId);
var bucket = new BucketIds(channelId: channelId);


return await SendJsonAsync<Channel>("POST", () => $"channels/{channelId}/threads", args, bucket, options: options).ConfigureAwait(false); return await SendJsonAsync<Channel>("POST", () => $"channels/{channelId}/threads", args, bucket, options: options).ConfigureAwait(false);
} }
@@ -453,7 +453,9 @@ namespace Discord.API


options = RequestOptions.CreateOrClone(options); options = RequestOptions.CreateOrClone(options);


await SendAsync("PUT", $"channels/{channelId}/thread-members/@me", options: options).ConfigureAwait(false);
var bucket = new BucketIds(channelId: channelId);

await SendAsync("PUT", () => $"channels/{channelId}/thread-members/@me", bucket, options: options).ConfigureAwait(false);
} }


public async Task AddThreadMemberAsync(ulong channelId, ulong userId, RequestOptions options = null) public async Task AddThreadMemberAsync(ulong channelId, ulong userId, RequestOptions options = null)
@@ -474,7 +476,9 @@ namespace Discord.API


options = RequestOptions.CreateOrClone(options); options = RequestOptions.CreateOrClone(options);


await SendAsync("DELETE", $"channels/{channelId}/thread-members/@me", options: options).ConfigureAwait(false);
var bucket = new BucketIds(channelId: channelId);

await SendAsync("DELETE", () => $"channels/{channelId}/thread-members/@me", bucket, options: options).ConfigureAwait(false);
} }


public async Task RemoveThreadMemberAsync(ulong channelId, ulong userId, RequestOptions options = null) public async Task RemoveThreadMemberAsync(ulong channelId, ulong userId, RequestOptions options = null)
@@ -483,8 +487,9 @@ namespace Discord.API
Preconditions.NotEqual(userId, 0, nameof(channelId)); Preconditions.NotEqual(userId, 0, nameof(channelId));


options = RequestOptions.CreateOrClone(options); options = RequestOptions.CreateOrClone(options);
var bucket = new BucketIds(channelId: channelId);


await SendAsync("DELETE", $"channels/{channelId}/thread-members/{userId}", options: options).ConfigureAwait(false);
await SendAsync("DELETE", () => $"channels/{channelId}/thread-members/{userId}", bucket, options: options).ConfigureAwait(false);
} }


public async Task<ThreadMember[]> ListThreadMembersAsync(ulong channelId, RequestOptions options = null) public async Task<ThreadMember[]> ListThreadMembersAsync(ulong channelId, RequestOptions options = null)
@@ -506,7 +511,7 @@ namespace Discord.API


var bucket = new BucketIds(channelId: channelId); var bucket = new BucketIds(channelId: channelId);


return await SendAsync<ChannelThreads>("GET", $"channels/{channelId}/threads/active");
return await SendAsync<ChannelThreads>("GET", () => $"channels/{channelId}/threads/active", bucket);
} }


public async Task<ChannelThreads> GetPublicArchivedThreadsAsync(ulong channelId, DateTimeOffset? before = null, int? limit = null, RequestOptions options = null) public async Task<ChannelThreads> GetPublicArchivedThreadsAsync(ulong channelId, DateTimeOffset? before = null, int? limit = null, RequestOptions options = null)
@@ -577,6 +582,82 @@ namespace Discord.API
return await SendAsync<ChannelThreads>("GET", () => $"channels/{channelId}/users/@me/threads/archived/private{query}", bucket, options: options); return await SendAsync<ChannelThreads>("GET", () => $"channels/{channelId}/users/@me/threads/archived/private{query}", bucket, options: options);
} }


// stage
public async Task<StageInstance> CreateStageInstanceAsync(CreateStageInstanceParams args, RequestOptions options = null)
{
options = RequestOptions.CreateOrClone(options);

var bucket = new BucketIds();

return await SendJsonAsync<StageInstance>("POST", () => $"stage-instances", args, bucket, options: options).ConfigureAwait(false);
}

public async Task<StageInstance> ModifyStageInstanceAsync(ulong channelId, ModifyStageInstanceParams args, RequestOptions options = null)
{
Preconditions.NotEqual(channelId, 0, nameof(channelId));

options = RequestOptions.CreateOrClone(options);

var bucket = new BucketIds(channelId: channelId);

return await SendJsonAsync<StageInstance>("PATCH", () => $"stage-instances/{channelId}", args, bucket, options: options).ConfigureAwait(false);
}

public async Task DeleteStageInstanceAsync(ulong channelId, RequestOptions options = null)
{
Preconditions.NotEqual(channelId, 0, nameof(channelId));

options = RequestOptions.CreateOrClone(options);

try
{
await SendAsync("DELETE", $"stage-instances/{channelId}", options: options).ConfigureAwait(false);
}
catch (HttpException httpEx) when (httpEx.HttpCode == HttpStatusCode.NotFound) { }
}

public async Task<StageInstance> GetStageInstanceAsync(ulong channelId, RequestOptions options = null)
{
Preconditions.NotEqual(channelId, 0, nameof(channelId));

options = RequestOptions.CreateOrClone(options);

var bucket = new BucketIds(channelId: channelId);

try
{
return await SendAsync<StageInstance>("POST", () => $"stage-instances/{channelId}", bucket, options: options).ConfigureAwait(false);
}
catch(HttpException httpEx) when (httpEx.HttpCode == HttpStatusCode.NotFound)
{
return null;
}
}

public async Task ModifyMyVoiceState(ulong guildId, ModifyVoiceStateParams args, RequestOptions options = null)
{
Preconditions.NotEqual(guildId, 0, nameof(guildId));

options = RequestOptions.CreateOrClone(options);

var bucket = new BucketIds();

await SendJsonAsync("PATCH", () => $"guilds/{guildId}/voice-states/@me", args, bucket, options: options).ConfigureAwait(false);
}

public async Task ModifyUserVoiceState(ulong guildId, ulong userId, ModifyVoiceStateParams args, RequestOptions options = null)
{
Preconditions.NotEqual(guildId, 0, nameof(guildId));
Preconditions.NotEqual(userId, 0, nameof(userId));

options = RequestOptions.CreateOrClone(options);

var bucket = new BucketIds();

await SendJsonAsync("PATCH", () => $"guilds/{guildId}/voice-states/{userId}", args, bucket, options: options).ConfigureAwait(false);
}

// roles // roles
public async Task AddRoleAsync(ulong guildId, ulong userId, ulong roleId, RequestOptions options = null) public async Task AddRoleAsync(ulong guildId, ulong userId, ulong roleId, RequestOptions options = null)
{ {


+ 16
- 0
src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs View File

@@ -6,6 +6,7 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Model = Discord.API.Channel; using Model = Discord.API.Channel;
using StageInstance = Discord.API.StageInstance;


namespace Discord.Rest namespace Discord.Rest
{ {
@@ -92,6 +93,21 @@ namespace Discord.Rest
return await client.ApiClient.ModifyGuildChannelAsync(channel.Id, apiArgs, options).ConfigureAwait(false); return await client.ApiClient.ModifyGuildChannelAsync(channel.Id, apiArgs, options).ConfigureAwait(false);
} }


public static async Task<StageInstance> ModifyAsync(IStageChannel channel, BaseDiscordClient client,
Action<StageInstanceProperties> func, RequestOptions options = null)
{
var args = new StageInstanceProperties();
func(args);

var apiArgs = new ModifyStageInstanceParams()
{
PrivacyLevel = args.PrivacyLevel,
Topic = args.Topic
};

return await client.ApiClient.ModifyStageInstanceAsync(channel.Id, apiArgs, options);
}

//Invites //Invites
public static async Task<IReadOnlyCollection<RestInviteMetadata>> GetInvitesAsync(IGuildChannel channel, BaseDiscordClient client, public static async Task<IReadOnlyCollection<RestInviteMetadata>> GetInvitesAsync(IGuildChannel channel, BaseDiscordClient client,
RequestOptions options) RequestOptions options)


+ 4
- 0
src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs View File

@@ -40,8 +40,12 @@ namespace Discord.Rest
return RestTextChannel.Create(discord, guild, model); return RestTextChannel.Create(discord, guild, model);
case ChannelType.Voice: case ChannelType.Voice:
return RestVoiceChannel.Create(discord, guild, model); return RestVoiceChannel.Create(discord, guild, model);
case ChannelType.Stage:
return RestStageChannel.Create(discord, guild, model);
case ChannelType.Category: case ChannelType.Category:
return RestCategoryChannel.Create(discord, guild, model); return RestCategoryChannel.Create(discord, guild, model);
case ChannelType.PublicThread or ChannelType.PrivateThread or ChannelType.NewsThread:
return RestThreadChannel.Create(discord, guild, model);
default: default:
return new RestGuildChannel(discord, guild, model.Id); return new RestGuildChannel(discord, guild, model.Id);
} }


+ 155
- 0
src/Discord.Net.Rest/Entities/Channels/RestStageChannel.cs View File

@@ -0,0 +1,155 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Model = Discord.API.Channel;
using StageInstance = Discord.API.StageInstance;

namespace Discord.Rest
{
/// <summary>
/// Represents a REST-based stage channel in a guild.
/// </summary>
public class RestStageChannel : RestVoiceChannel, IStageChannel
{
/// <inheritdoc/>
public string Topic { get; private set; }

/// <inheritdoc/>
public StagePrivacyLevel? PrivacyLevel { get; private set; }

/// <inheritdoc/>
public bool? DiscoverableDisabled { get; private set; }

/// <inheritdoc/>
public bool Live { get; private set; }
internal RestStageChannel(BaseDiscordClient discord, IGuild guild, ulong id)
: base(discord, guild, id)
{

}

internal static new RestStageChannel Create(BaseDiscordClient discord, IGuild guild, Model model)
{
var entity = new RestStageChannel(discord, guild, model.Id);
entity.Update(model);
return entity;
}

internal void Update(StageInstance model, bool isLive = false)
{
this.Live = isLive;
if(isLive)
{
this.Topic = model.Topic;
this.PrivacyLevel = model.PrivacyLevel;
this.DiscoverableDisabled = model.DiscoverableDisabled;
}
else
{
this.Topic = null;
this.PrivacyLevel = null;
this.DiscoverableDisabled = null;
}
}

/// <inheritdoc/>
public async Task ModifyInstanceAsync(Action<StageInstanceProperties> func, RequestOptions options = null)
{
var model = await ChannelHelper.ModifyAsync(this, Discord, func, options);

Update(model, true);
}

/// <inheritdoc/>
public async Task StartStageAsync(string topic, StagePrivacyLevel privacyLevel = StagePrivacyLevel.GuildOnly, RequestOptions options = null)
{
var args = new API.Rest.CreateStageInstanceParams()
{
ChannelId = this.Id,
PrivacyLevel = privacyLevel,
Topic = topic
};

var model = await Discord.ApiClient.CreateStageInstanceAsync(args, options);

Update(model, true);
}

/// <inheritdoc/>
public async Task StopStageAsync(RequestOptions options = null)
{
await Discord.ApiClient.DeleteStageInstanceAsync(this.Id, options);

Update(null, false);
}

/// <inheritdoc/>
public override async Task UpdateAsync(RequestOptions options = null)
{
await base.UpdateAsync(options);

var model = await Discord.ApiClient.GetStageInstanceAsync(this.Id, options);

Update(model, model != null);
}

/// <inheritdoc/>
public Task RequestToSpeak(RequestOptions options = null)
{
var args = new API.Rest.ModifyVoiceStateParams()
{
ChannelId = this.Id,
RequestToSpeakTimestamp = DateTimeOffset.UtcNow
};
return Discord.ApiClient.ModifyMyVoiceState(this.Guild.Id, args, options);
}

/// <inheritdoc/>
public Task BecomeSpeakerAsync(RequestOptions options = null)
{
var args = new API.Rest.ModifyVoiceStateParams()
{
ChannelId = this.Id,
Suppressed = false
};
return Discord.ApiClient.ModifyMyVoiceState(this.Guild.Id, args, options);
}

/// <inheritdoc/>
public Task StopSpeakingAsync(RequestOptions options = null)
{
var args = new API.Rest.ModifyVoiceStateParams()
{
ChannelId = this.Id,
Suppressed = true
};
return Discord.ApiClient.ModifyMyVoiceState(this.Guild.Id, args, options);
}

/// <inheritdoc/>
public Task MoveToSpeaker(IGuildUser user, RequestOptions options = null)
{
var args = new API.Rest.ModifyVoiceStateParams()
{
ChannelId = this.Id,
Suppressed = false
};

return Discord.ApiClient.ModifyUserVoiceState(this.Guild.Id, user.Id, args);
}

/// <inheritdoc/>
public Task RemoveFromSpeaker(IGuildUser user, RequestOptions options = null)
{
var args = new API.Rest.ModifyVoiceStateParams()
{
ChannelId = this.Id,
Suppressed = true
};

return Discord.ApiClient.ModifyUserVoiceState(this.Guild.Id, user.Id, args);
}
}
}

+ 3
- 0
src/Discord.Net.Rest/Entities/Channels/ThreadHelper.cs View File

@@ -19,6 +19,9 @@ namespace Discord.Rest
if (autoArchiveDuration == ThreadArchiveDuration.ThreeDays && !channel.Guild.Features.Contains("THREE_DAY_THREAD_ARCHIVE")) if (autoArchiveDuration == ThreadArchiveDuration.ThreeDays && !channel.Guild.Features.Contains("THREE_DAY_THREAD_ARCHIVE"))
throw new ArgumentException($"The guild {channel.Guild.Name} does not have the THREE_DAY_THREAD_ARCHIVE feature!"); throw new ArgumentException($"The guild {channel.Guild.Name} does not have the THREE_DAY_THREAD_ARCHIVE feature!");


if (type == ThreadType.PrivateThread && !channel.Guild.Features.Contains("PRIVATE_THREADS"))
throw new ArgumentException($"The guild {channel.Guild.Name} does not have the PRIVATE_THREADS feature!");

var args = new StartThreadParams() var args = new StartThreadParams()
{ {
Name = name, Name = name,


+ 108
- 0
src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs View File

@@ -83,6 +83,8 @@ namespace Discord.Rest
public int? ApproximateMemberCount { get; private set; } public int? ApproximateMemberCount { get; private set; }
/// <inheritdoc /> /// <inheritdoc />
public int? ApproximatePresenceCount { get; private set; } public int? ApproximatePresenceCount { get; private set; }
/// <inheritdoc />
public NsfwLevel NsfwLevel { get; private set; }


/// <inheritdoc /> /// <inheritdoc />
public CultureInfo PreferredCulture { get; private set; } public CultureInfo PreferredCulture { get; private set; }
@@ -151,6 +153,7 @@ namespace Discord.Rest
SystemChannelFlags = model.SystemChannelFlags; SystemChannelFlags = model.SystemChannelFlags;
Description = model.Description; Description = model.Description;
PremiumSubscriptionCount = model.PremiumSubscriptionCount.GetValueOrDefault(); PremiumSubscriptionCount = model.PremiumSubscriptionCount.GetValueOrDefault();
NsfwLevel = model.NsfwLevel;
if (model.MaxPresences.IsSpecified) if (model.MaxPresences.IsSpecified)
MaxPresences = model.MaxPresences.Value ?? 25000; MaxPresences = model.MaxPresences.Value ?? 25000;
if (model.MaxMembers.IsSpecified) if (model.MaxMembers.IsSpecified)
@@ -391,6 +394,35 @@ namespace Discord.Rest
return channels.OfType<RestTextChannel>().ToImmutableArray(); return channels.OfType<RestTextChannel>().ToImmutableArray();
} }


/// <summary>
/// Gets a thread channel in this guild.
/// </summary>
/// <param name="id">The snowflake identifier for the thread channel.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous get operation. The task result contains the thread channel associated
/// with the specified <paramref name="id"/>; <see langword="null"/> if none is found.
/// </returns>
public async Task<RestThreadChannel> GetThreadChannelAsync(ulong id, RequestOptions options = null)
{
var channel = await GuildHelper.GetChannelAsync(this, Discord, id, options).ConfigureAwait(false);
return channel as RestThreadChannel;
}

/// <summary>
/// Gets a collection of all thread in this guild.
/// </summary>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous get operation. The task result contains a read-only collection of
/// threads found within this guild.
/// </returns>
public async Task<IReadOnlyCollection<RestThreadChannel>> GetThreadChannelsAsync(RequestOptions options = null)
{
var channels = await GuildHelper.GetChannelsAsync(this, Discord, options).ConfigureAwait(false);
return channels.OfType<RestThreadChannel>().ToImmutableArray();
}

/// <summary> /// <summary>
/// Gets a voice channel in this guild. /// Gets a voice channel in this guild.
/// </summary> /// </summary>
@@ -419,6 +451,35 @@ namespace Discord.Rest
var channels = await GuildHelper.GetChannelsAsync(this, Discord, options).ConfigureAwait(false); var channels = await GuildHelper.GetChannelsAsync(this, Discord, options).ConfigureAwait(false);
return channels.OfType<RestVoiceChannel>().ToImmutableArray(); return channels.OfType<RestVoiceChannel>().ToImmutableArray();
} }
/// <summary>
/// Gets a stage channel in this guild
/// </summary>
/// <param name="id">The snowflake identifier for the stage channel.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous get operation. The task result contains the stage channel associated
/// with the specified <paramref name="id"/>; <see langword="null" /> if none is found.
/// </returns>
public async Task<RestStageChannel> GetStageChannelAsync(ulong id, RequestOptions options = null)
{
var channel = await GuildHelper.GetChannelAsync(this, Discord, id, options).ConfigureAwait(false);
return channel as RestStageChannel;
}

/// <summary>
/// Gets a collection of all stage channels in this guild.
/// </summary>
/// <param name="mode">The <see cref="CacheMode"/> that determines whether the object should be fetched from cache.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous get operation. The task result contains a read-only collection of
/// stage channels found within this guild.
/// </returns>
public async Task<IReadOnlyCollection<RestStageChannel>> GetStageChannelsAsync(RequestOptions options = null)
{
var channels = await GuildHelper.GetChannelsAsync(this, Discord, options).ConfigureAwait(false);
return channels.OfType<RestStageChannel>().ToImmutableArray();
}


/// <summary> /// <summary>
/// Gets a collection of all category channels in this guild. /// Gets a collection of all category channels in this guild.
@@ -808,6 +869,18 @@ namespace Discord.Rest
public Task<IReadOnlyCollection<RestWebhook>> GetWebhooksAsync(RequestOptions options = null) public Task<IReadOnlyCollection<RestWebhook>> GetWebhooksAsync(RequestOptions options = null)
=> GuildHelper.GetWebhooksAsync(this, Discord, options); => GuildHelper.GetWebhooksAsync(this, Discord, options);


//Interactions
/// <summary>
/// Gets this guilds slash commands commands
/// </summary>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous get operation. The task result contains a read-only collection
/// of application commands found within the guild.
/// </returns>
public async Task<IReadOnlyCollection<RestApplicationCommand>> GetApplicationCommandsAsync (RequestOptions options = null)
=> await ClientHelper.GetGuildApplicationCommands(Discord, Id, options).ConfigureAwait(false);

/// <summary> /// <summary>
/// Returns the name of the guild. /// Returns the name of the guild.
/// </summary> /// </summary>
@@ -888,6 +961,22 @@ namespace Discord.Rest
return null; return null;
} }
/// <inheritdoc /> /// <inheritdoc />
async Task<IThreadChannel> IGuild.GetThreadChannelAsync(ulong id, CacheMode mode, RequestOptions options)
{
if (mode == CacheMode.AllowDownload)
return await GetThreadChannelAsync(id, options).ConfigureAwait(false);
else
return null;
}
/// <inheritdoc />
async Task<IReadOnlyCollection<IThreadChannel>> IGuild.GetThreadChannelsAsync(CacheMode mode, RequestOptions options)
{
if (mode == CacheMode.AllowDownload)
return await GetThreadChannelsAsync(options).ConfigureAwait(false);
else
return null;
}
/// <inheritdoc />
async Task<IReadOnlyCollection<IVoiceChannel>> IGuild.GetVoiceChannelsAsync(CacheMode mode, RequestOptions options) async Task<IReadOnlyCollection<IVoiceChannel>> IGuild.GetVoiceChannelsAsync(CacheMode mode, RequestOptions options)
{ {
if (mode == CacheMode.AllowDownload) if (mode == CacheMode.AllowDownload)
@@ -904,6 +993,22 @@ namespace Discord.Rest
return null; return null;
} }
/// <inheritdoc /> /// <inheritdoc />
async Task<IStageChannel> IGuild.GetStageChannelAsync(ulong id, CacheMode mode, RequestOptions options )
{
if (mode == CacheMode.AllowDownload)
return await GetStageChannelAsync(id, options).ConfigureAwait(false);
else
return null;
}
/// <inheritdoc />
async Task<IReadOnlyCollection<IStageChannel>> IGuild.GetStageChannelsAsync(CacheMode mode, RequestOptions options)
{
if (mode == CacheMode.AllowDownload)
return await GetStageChannelsAsync(options).ConfigureAwait(false);
else
return null;
}
/// <inheritdoc />
async Task<IVoiceChannel> IGuild.GetVoiceChannelAsync(ulong id, CacheMode mode, RequestOptions options) async Task<IVoiceChannel> IGuild.GetVoiceChannelAsync(ulong id, CacheMode mode, RequestOptions options)
{ {
if (mode == CacheMode.AllowDownload) if (mode == CacheMode.AllowDownload)
@@ -1061,5 +1166,8 @@ namespace Discord.Rest
/// <inheritdoc /> /// <inheritdoc />
async Task<IReadOnlyCollection<IWebhook>> IGuild.GetWebhooksAsync(RequestOptions options) async Task<IReadOnlyCollection<IWebhook>> IGuild.GetWebhooksAsync(RequestOptions options)
=> await GetWebhooksAsync(options).ConfigureAwait(false); => await GetWebhooksAsync(options).ConfigureAwait(false);
/// <inheritdoc />
async Task<IReadOnlyCollection<IApplicationCommand>> IGuild.GetApplicationCommandsAsync (RequestOptions options)
=> await GetApplicationCommandsAsync(options).ConfigureAwait(false);
} }
} }

+ 45
- 4
src/Discord.Net.Rest/Entities/Interactions/InteractionHelper.cs View File

@@ -296,15 +296,33 @@ namespace Discord.Rest
var args = new MessageProperties(); var args = new MessageProperties();
func(args); func(args);


var embed = args.Embed;
var embeds = args.Embeds;

bool hasText = args.Content.IsSpecified ? !string.IsNullOrEmpty(args.Content.Value) : !string.IsNullOrEmpty(message.Content); bool hasText = args.Content.IsSpecified ? !string.IsNullOrEmpty(args.Content.Value) : !string.IsNullOrEmpty(message.Content);
bool hasEmbed = args.Embeds.IsSpecified ? args.Embeds.Value != null : message.Embeds.Any();
if (!hasText && !hasEmbed)
bool hasEmbeds = (embed.IsSpecified && embed.Value != null) || (embeds.IsSpecified && embeds.Value?.Length > 0) || message.Embeds.Any();

if (!hasText && !hasEmbeds)
Preconditions.NotNullOrEmpty(args.Content.IsSpecified ? args.Content.Value : string.Empty, nameof(args.Content)); Preconditions.NotNullOrEmpty(args.Content.IsSpecified ? args.Content.Value : string.Empty, nameof(args.Content));


var apiEmbeds = embed.IsSpecified || embeds.IsSpecified ? new List<API.Embed>() : null;

if (embed.IsSpecified && embed.Value != null)
{
apiEmbeds.Add(embed.Value.ToModel());
}

if (embeds.IsSpecified && embeds.Value != null)
{
apiEmbeds.AddRange(embeds.Value.Select(x => x.ToModel()));
}

Preconditions.AtMost(apiEmbeds?.Count ?? 0, 10, nameof(args.Embeds), "A max of 10 embeds are allowed.");

var apiArgs = new API.Rest.ModifyInteractionResponseParams var apiArgs = new API.Rest.ModifyInteractionResponseParams
{ {
Content = args.Content, Content = args.Content,
Embeds = args.Embeds.IsSpecified ? args.Embeds.Value.Select(x => x.ToModel()).ToArray() : Optional.Create<API.Embed[]>(),
Embeds = apiEmbeds?.ToArray() ?? Optional<API.Embed[]>.Unspecified,
AllowedMentions = args.AllowedMentions.IsSpecified ? args.AllowedMentions.Value.ToModel() : Optional<API.AllowedMentions>.Unspecified, AllowedMentions = args.AllowedMentions.IsSpecified ? args.AllowedMentions.Value.ToModel() : Optional<API.AllowedMentions>.Unspecified,
Components = args.Components.IsSpecified ? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() : Optional<API.ActionRowComponent[]>.Unspecified, Components = args.Components.IsSpecified ? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() : Optional<API.ActionRowComponent[]>.Unspecified,
}; };
@@ -321,10 +339,33 @@ namespace Discord.Rest
var args = new MessageProperties(); var args = new MessageProperties();
func(args); func(args);


var embed = args.Embed;
var embeds = args.Embeds;

bool hasText = !string.IsNullOrEmpty(args.Content.GetValueOrDefault());
bool hasEmbeds = (embed.IsSpecified && embed.Value != null) || (embeds.IsSpecified && embeds.Value?.Length > 0);

if (!hasText && !hasEmbeds)
Preconditions.NotNullOrEmpty(args.Content.IsSpecified ? args.Content.Value : string.Empty, nameof(args.Content));

var apiEmbeds = embed.IsSpecified || embeds.IsSpecified ? new List<API.Embed>() : null;

if (embed.IsSpecified && embed.Value != null)
{
apiEmbeds.Add(embed.Value.ToModel());
}

if (embeds.IsSpecified && embeds.Value != null)
{
apiEmbeds.AddRange(embeds.Value.Select(x => x.ToModel()));
}

Preconditions.AtMost(apiEmbeds?.Count ?? 0, 10, nameof(args.Embeds), "A max of 10 embeds are allowed.");

var apiArgs = new ModifyInteractionResponseParams var apiArgs = new ModifyInteractionResponseParams
{ {
Content = args.Content, Content = args.Content,
Embeds = args.Embeds.IsSpecified ? args.Embeds.Value?.Select(x => x.ToModel()).ToArray() : Optional<API.Embed[]>.Unspecified,
Embeds = apiEmbeds?.ToArray() ?? Optional<API.Embed[]>.Unspecified,
AllowedMentions = args.AllowedMentions.IsSpecified ? args.AllowedMentions.Value?.ToModel() : Optional<API.AllowedMentions>.Unspecified, AllowedMentions = args.AllowedMentions.IsSpecified ? args.AllowedMentions.Value?.ToModel() : Optional<API.AllowedMentions>.Unspecified,
Components = args.Components.IsSpecified ? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() : Optional<API.ActionRowComponent[]>.Unspecified, Components = args.Components.IsSpecified ? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() : Optional<API.ActionRowComponent[]>.Unspecified,
Flags = args.Flags Flags = args.Flags


+ 45
- 5
src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs View File

@@ -1,3 +1,4 @@
using Discord.API;
using Discord.API.Rest; using Discord.API.Rest;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@@ -33,9 +34,13 @@ namespace Discord.Rest
if (msg.Author.Id != client.CurrentUser.Id && (args.Content.IsSpecified || args.Embeds.IsSpecified || args.AllowedMentions.IsSpecified)) if (msg.Author.Id != client.CurrentUser.Id && (args.Content.IsSpecified || args.Embeds.IsSpecified || args.AllowedMentions.IsSpecified))
throw new InvalidOperationException("Only the author of a message may modify the message content, embed, or allowed mentions."); throw new InvalidOperationException("Only the author of a message may modify the message content, embed, or allowed mentions.");


var embed = args.Embed;
var embeds = args.Embeds;

bool hasText = args.Content.IsSpecified ? !string.IsNullOrEmpty(args.Content.Value) : !string.IsNullOrEmpty(msg.Content); bool hasText = args.Content.IsSpecified ? !string.IsNullOrEmpty(args.Content.Value) : !string.IsNullOrEmpty(msg.Content);
bool hasEmbed = args.Embeds.IsSpecified ? args.Embeds.Value != null : msg.Embeds.Any();
if (!hasText && !hasEmbed)
bool hasEmbeds = (embed.IsSpecified && embed.Value != null) || (embeds.IsSpecified && embeds.Value?.Length > 0) || msg.Embeds.Any();

if (!hasText && !hasEmbeds)
Preconditions.NotNullOrEmpty(args.Content.IsSpecified ? args.Content.Value : string.Empty, nameof(args.Content)); Preconditions.NotNullOrEmpty(args.Content.IsSpecified ? args.Content.Value : string.Empty, nameof(args.Content));


if (args.AllowedMentions.IsSpecified) if (args.AllowedMentions.IsSpecified)
@@ -61,10 +66,24 @@ namespace Discord.Rest
} }
} }


var apiEmbeds = embed.IsSpecified || embeds.IsSpecified ? new List<API.Embed>() : null;

if (embed.IsSpecified && embed.Value != null)
{
apiEmbeds.Add(embed.Value.ToModel());
}

if (embeds.IsSpecified && embeds.Value != null)
{
apiEmbeds.AddRange(embeds.Value.Select(x => x.ToModel()));
}

Preconditions.AtMost(apiEmbeds?.Count ?? 0, 10, nameof(args.Embeds), "A max of 10 embeds are allowed.");

var apiArgs = new ModifyMessageParams var apiArgs = new ModifyMessageParams
{ {
Content = args.Content, Content = args.Content,
Embeds = args.Embeds.IsSpecified ? args.Embeds.Value.Select(x => x.ToModel()).ToArray() : Optional<API.Embed[]>.Unspecified,
Embeds = apiEmbeds?.ToArray() ?? Optional<API.Embed[]>.Unspecified,
Components = args.Components.IsSpecified ? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() : Optional<API.ActionRowComponent[]>.Unspecified, Components = args.Components.IsSpecified ? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() : Optional<API.ActionRowComponent[]>.Unspecified,
Flags = args.Flags, Flags = args.Flags,
AllowedMentions = args.AllowedMentions.IsSpecified ? args.AllowedMentions.Value.ToModel() : Optional<API.AllowedMentions>.Unspecified, AllowedMentions = args.AllowedMentions.IsSpecified ? args.AllowedMentions.Value.ToModel() : Optional<API.AllowedMentions>.Unspecified,
@@ -78,7 +97,13 @@ namespace Discord.Rest
var args = new MessageProperties(); var args = new MessageProperties();
func(args); func(args);


if (args.Content.IsSpecified && string.IsNullOrEmpty(args.Content.Value) && args.Embeds.IsSpecified && args.Embeds.Value == null)
var embed = args.Embed;
var embeds = args.Embeds;

bool hasText = args.Content.IsSpecified && string.IsNullOrEmpty(args.Content.Value);
bool hasEmbeds = (embed.IsSpecified && embed.Value != null) || (embeds.IsSpecified && embeds.Value?.Length > 0);

if (!hasText && !hasEmbeds)
Preconditions.NotNullOrEmpty(args.Content.IsSpecified ? args.Content.Value : string.Empty, nameof(args.Content)); Preconditions.NotNullOrEmpty(args.Content.IsSpecified ? args.Content.Value : string.Empty, nameof(args.Content));


if (args.AllowedMentions.IsSpecified) if (args.AllowedMentions.IsSpecified)
@@ -105,12 +130,27 @@ namespace Discord.Rest
} }
} }


var apiEmbeds = embed.IsSpecified || embeds.IsSpecified ? new List<API.Embed>() : null;

if (embed.IsSpecified && embed.Value != null)
{
apiEmbeds.Add(embed.Value.ToModel());
}

if (embeds.IsSpecified && embeds.Value != null)
{
apiEmbeds.AddRange(embeds.Value.Select(x => x.ToModel()));
}

Preconditions.AtMost(apiEmbeds?.Count ?? 0, 10, nameof(args.Embeds), "A max of 10 embeds are allowed.");

var apiArgs = new API.Rest.ModifyMessageParams var apiArgs = new API.Rest.ModifyMessageParams
{ {
Content = args.Content, Content = args.Content,
Embeds = args.Embeds.IsSpecified ? args.Embeds.Value.Select(x => x.ToModel()).ToArray() : Optional.Create<API.Embed[]>(),
Embeds = apiEmbeds?.ToArray() ?? Optional<API.Embed[]>.Unspecified,
Flags = args.Flags.IsSpecified ? args.Flags.Value : Optional.Create<MessageFlags?>(), Flags = args.Flags.IsSpecified ? args.Flags.Value : Optional.Create<MessageFlags?>(),
AllowedMentions = args.AllowedMentions.IsSpecified ? args.AllowedMentions.Value.ToModel() : Optional.Create<API.AllowedMentions>(), AllowedMentions = args.AllowedMentions.IsSpecified ? args.AllowedMentions.Value.ToModel() : Optional.Create<API.AllowedMentions>(),
Components = args.Components.IsSpecified ? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() : Optional<API.ActionRowComponent[]>.Unspecified,
}; };
return await client.ApiClient.ModifyMessageAsync(channelId, msgId, apiArgs, options).ConfigureAwait(false); return await client.ApiClient.ModifyMessageAsync(channelId, msgId, apiArgs, options).ConfigureAwait(false);
} }


+ 3
- 0
src/Discord.Net.Rest/Entities/Users/RestGroupUser.cs View File

@@ -1,3 +1,4 @@
using System;
using System.Diagnostics; using System.Diagnostics;
using Model = Discord.API.User; using Model = Discord.API.User;


@@ -37,5 +38,7 @@ namespace Discord.Rest
string IVoiceState.VoiceSessionId => null; string IVoiceState.VoiceSessionId => null;
/// <inheritdoc /> /// <inheritdoc />
bool IVoiceState.IsStreaming => false; bool IVoiceState.IsStreaming => false;
/// <inheritdoc />
DateTimeOffset? IVoiceState.RequestToSpeakTimestamp => null;
} }
} }

+ 2
- 0
src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs View File

@@ -169,5 +169,7 @@ namespace Discord.Rest
string IVoiceState.VoiceSessionId => null; string IVoiceState.VoiceSessionId => null;
/// <inheritdoc /> /// <inheritdoc />
bool IVoiceState.IsStreaming => false; bool IVoiceState.IsStreaming => false;
/// <inheritdoc />
DateTimeOffset? IVoiceState.RequestToSpeakTimestamp => null;
} }
} }

+ 2
- 2
src/Discord.Net.Rest/Entities/Users/RestThreadUser.cs View File

@@ -9,7 +9,7 @@ using Model = Discord.API.ThreadMember;
namespace Discord.Rest namespace Discord.Rest
{ {
/// <summary> /// <summary>
/// Represents a thread user recieved over the REST api.
/// Represents a thread user received over the REST api.
/// </summary> /// </summary>
public class RestThreadUser : RestEntity<ulong> public class RestThreadUser : RestEntity<ulong>
{ {
@@ -51,7 +51,7 @@ namespace Discord.Rest
/// Gets the guild user for this thread user. /// Gets the guild user for this thread user.
/// </summary> /// </summary>
/// <returns> /// <returns>
/// A task representing the asyncronous get operation. The task returns a
/// A task representing the asynchronous get operation. The task returns a
/// <see cref="IGuildUser"/> that represents the current thread user. /// <see cref="IGuildUser"/> that represents the current thread user.
/// </returns> /// </returns>
public Task<IGuildUser> GetGuildUser() public Task<IGuildUser> GetGuildUser()


+ 12
- 0
src/Discord.Net.Rest/Entities/Users/RestUser.cs View File

@@ -22,6 +22,10 @@ namespace Discord.Rest
/// <inheritdoc /> /// <inheritdoc />
public string AvatarId { get; private set; } public string AvatarId { get; private set; }
/// <inheritdoc /> /// <inheritdoc />
public string BannerId { get; private set; }
/// <inheritdoc />
public Color? AccentColor { get; private set; }
/// <inheritdoc />
public UserProperties? PublicFlags { get; private set; } public UserProperties? PublicFlags { get; private set; }


/// <inheritdoc /> /// <inheritdoc />
@@ -61,6 +65,10 @@ namespace Discord.Rest
{ {
if (model.Avatar.IsSpecified) if (model.Avatar.IsSpecified)
AvatarId = model.Avatar.Value; AvatarId = model.Avatar.Value;
if (model.Banner.IsSpecified)
BannerId = model.Banner.Value;
if (model.AccentColor.IsSpecified)
AccentColor = model.AccentColor.Value;
if (model.Discriminator.IsSpecified) if (model.Discriminator.IsSpecified)
DiscriminatorValue = ushort.Parse(model.Discriminator.Value, NumberStyles.None, CultureInfo.InvariantCulture); DiscriminatorValue = ushort.Parse(model.Discriminator.Value, NumberStyles.None, CultureInfo.InvariantCulture);
if (model.Bot.IsSpecified) if (model.Bot.IsSpecified)
@@ -92,6 +100,10 @@ namespace Discord.Rest
public string GetAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128) public string GetAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128)
=> CDN.GetUserAvatarUrl(Id, AvatarId, size, format); => CDN.GetUserAvatarUrl(Id, AvatarId, size, format);


/// <inheritdoc />
public string GetBannerUrl(ImageFormat format = ImageFormat.Auto, ushort size = 256)
=> CDN.GetUserBannerUrl(Id, BannerId, size, format);

/// <inheritdoc /> /// <inheritdoc />
public string GetDefaultAvatarUrl() public string GetDefaultAvatarUrl()
=> CDN.GetDefaultUserAvatarUrl(DiscriminatorValue); => CDN.GetDefaultUserAvatarUrl(DiscriminatorValue);


+ 2
- 0
src/Discord.Net.Rest/Entities/Users/RestWebhookUser.cs View File

@@ -107,5 +107,7 @@ namespace Discord.Rest
string IVoiceState.VoiceSessionId => null; string IVoiceState.VoiceSessionId => null;
/// <inheritdoc /> /// <inheritdoc />
bool IVoiceState.IsStreaming => false; bool IVoiceState.IsStreaming => false;
/// <inheritdoc />
DateTimeOffset? IVoiceState.RequestToSpeakTimestamp => null;
} }
} }

+ 4
- 0
src/Discord.Net.Rest/Entities/Webhooks/RestWebhook.cs View File

@@ -24,6 +24,8 @@ namespace Discord.Rest
public ulong? GuildId { get; private set; } public ulong? GuildId { get; private set; }
/// <inheritdoc /> /// <inheritdoc />
public IUser Creator { get; private set; } public IUser Creator { get; private set; }
/// <inheritdoc />
public ulong? ApplicationId { get; private set; }


/// <inheritdoc /> /// <inheritdoc />
public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id);
@@ -66,6 +68,8 @@ namespace Discord.Rest
GuildId = model.GuildId.Value; GuildId = model.GuildId.Value;
if (model.Name.IsSpecified) if (model.Name.IsSpecified)
Name = model.Name.Value; Name = model.Name.Value;

ApplicationId = model.ApplicationId;
} }


/// <inheritdoc /> /// <inheritdoc />


+ 1
- 0
src/Discord.Net.Rest/Extensions/EntityExtensions.cs View File

@@ -68,6 +68,7 @@ namespace Discord.Rest
model.Video = entity.Video.Value.ToModel(); model.Video = entity.Video.Value.ToModel();
return model; return model;
} }

public static API.AllowedMentions ToModel(this AllowedMentions entity) public static API.AllowedMentions ToModel(this AllowedMentions entity)
{ {
return new API.AllowedMentions() return new API.AllowedMentions()


+ 1
- 1
src/Discord.Net.Rest/Net/Converters/ArrayConverter.cs View File

@@ -1,4 +1,4 @@
using Newtonsoft.Json;
using Newtonsoft.Json;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;




+ 2
- 2
src/Discord.Net.WebSocket/API/Gateway/GatewayOpCode.cs View File

@@ -1,4 +1,4 @@
#pragma warning disable CS1591
#pragma warning disable CS1591
namespace Discord.API.Gateway namespace Discord.API.Gateway
{ {
internal enum GatewayOpCode : byte internal enum GatewayOpCode : byte
@@ -10,7 +10,7 @@ namespace Discord.API.Gateway
/// <summary> C→S - Used to associate a connection with a token and specify configuration. </summary> /// <summary> C→S - Used to associate a connection with a token and specify configuration. </summary>
Identify = 2, Identify = 2,
/// <summary> C→S - Used to update client's status and current game id. </summary> /// <summary> C→S - Used to update client's status and current game id. </summary>
StatusUpdate = 3,
PresenceUpdate = 3,
/// <summary> C→S - Used to join a particular voice channel. </summary> /// <summary> C→S - Used to join a particular voice channel. </summary>
VoiceStateUpdate = 4, VoiceStateUpdate = 4,
/// <summary> C→S - Used to ensure the guild's voice server is alive. </summary> /// <summary> C→S - Used to ensure the guild's voice server is alive. </summary>


+ 5
- 1
src/Discord.Net.WebSocket/API/Gateway/GuildMemberUpdateEvent.cs View File

@@ -1,10 +1,14 @@
#pragma warning disable CS1591
#pragma warning disable CS1591
using Newtonsoft.Json; using Newtonsoft.Json;
using System;


namespace Discord.API.Gateway namespace Discord.API.Gateway
{ {
internal class GuildMemberUpdateEvent : GuildMember internal class GuildMemberUpdateEvent : GuildMember
{ {
[JsonProperty("joined_at")]
public new DateTimeOffset? JoinedAt { get; set; }

[JsonProperty("guild_id")] [JsonProperty("guild_id")]
public ulong GuildId { get; set; } public ulong GuildId { get; set; }
} }


+ 1
- 1
src/Discord.Net.WebSocket/API/Gateway/IdentifyParams.cs View File

@@ -16,7 +16,7 @@ namespace Discord.API.Gateway
[JsonProperty("shard")] [JsonProperty("shard")]
public Optional<int[]> ShardingParams { get; set; } public Optional<int[]> ShardingParams { get; set; }
[JsonProperty("presence")] [JsonProperty("presence")]
public Optional<StatusUpdateParams> Presence { get; set; }
public Optional<PresenceUpdateParams> Presence { get; set; }
[JsonProperty("intents")] [JsonProperty("intents")]
public Optional<int> Intents { get; set; } public Optional<int> Intents { get; set; }
} }


+ 5
- 4
src/Discord.Net.WebSocket/API/Gateway/StatusUpdateParams.cs View File

@@ -4,15 +4,16 @@ using Newtonsoft.Json;
namespace Discord.API.Gateway namespace Discord.API.Gateway
{ {
[JsonObject(MemberSerialization = MemberSerialization.OptIn)] [JsonObject(MemberSerialization = MemberSerialization.OptIn)]
internal class StatusUpdateParams
internal class PresenceUpdateParams

{ {
[JsonProperty("status")] [JsonProperty("status")]
public UserStatus Status { get; set; } public UserStatus Status { get; set; }
[JsonProperty("since"), Int53]
[JsonProperty("since", NullValueHandling = NullValueHandling.Include), Int53]
public long? IdleSince { get; set; } public long? IdleSince { get; set; }
[JsonProperty("afk")] [JsonProperty("afk")]
public bool IsAFK { get; set; } public bool IsAFK { get; set; }
[JsonProperty("game")]
public Game Game { get; set; }
[JsonProperty("activities")]
public object[] Activities { get; set; } // TODO, change to interface later
} }
} }

+ 60
- 1
src/Discord.Net.WebSocket/BaseSocketClient.Events.cs View File

@@ -542,7 +542,7 @@ namespace Discord.WebSocket
internal readonly AsyncEvent<Func<SocketApplicationCommand, Task>> _applicationCommandDeleted = new AsyncEvent<Func<SocketApplicationCommand, Task>>(); internal readonly AsyncEvent<Func<SocketApplicationCommand, Task>> _applicationCommandDeleted = new AsyncEvent<Func<SocketApplicationCommand, Task>>();


/// <summary> /// <summary>
/// Fired when a thread is created within a guild.
/// Fired when a thread is created within a guild, or when the current user is added to a thread.
/// </summary> /// </summary>
public event Func<SocketThreadChannel, Task> ThreadCreated public event Func<SocketThreadChannel, Task> ThreadCreated
{ {
@@ -592,5 +592,64 @@ namespace Discord.WebSocket
} }
internal readonly AsyncEvent<Func<SocketThreadUser, Task>> _threadMemberLeft = new AsyncEvent<Func<SocketThreadUser, Task>>(); internal readonly AsyncEvent<Func<SocketThreadUser, Task>> _threadMemberLeft = new AsyncEvent<Func<SocketThreadUser, Task>>();


/// <summary>
/// Fired when a stage is started.
/// </summary>
public event Func<SocketStageChannel, Task> StageStarted
{
add { _stageStarted.Add(value); }
remove { _stageStarted.Remove(value); }
}
internal readonly AsyncEvent<Func<SocketStageChannel, Task>> _stageStarted = new AsyncEvent<Func<SocketStageChannel, Task>>();

/// <summary>
/// Fired when a stage ends.
/// </summary>
public event Func<SocketStageChannel, Task> StageEnded
{
add { _stageEnded.Add(value); }
remove { _stageEnded.Remove(value); }
}
internal readonly AsyncEvent<Func<SocketStageChannel, Task>> _stageEnded = new AsyncEvent<Func<SocketStageChannel, Task>>();

/// <summary>
/// Fired when a stage is updated.
/// </summary>
public event Func<SocketStageChannel, SocketStageChannel, Task> StageUpdated
{
add { _stageUpdated.Add(value); }
remove { _stageUpdated.Remove(value); }
}
internal readonly AsyncEvent<Func<SocketStageChannel, SocketStageChannel, Task>> _stageUpdated = new AsyncEvent<Func<SocketStageChannel, SocketStageChannel, Task>>();

/// <summary>
/// Fired when a user requests to speak within a stage channel.
/// </summary>
public event Func<SocketStageChannel, SocketGuildUser, Task> RequestToSpeak
{
add { _requestToSpeak.Add(value); }
remove { _requestToSpeak.Remove(value); }
}
internal readonly AsyncEvent<Func<SocketStageChannel, SocketGuildUser, Task>> _requestToSpeak = new AsyncEvent<Func<SocketStageChannel, SocketGuildUser, Task>>();

/// <summary>
/// Fired when a speaker is added in a stage channel.
/// </summary>
public event Func<SocketStageChannel, SocketGuildUser, Task> SpeakerAdded
{
add { _speakerAdded.Add(value); }
remove { _speakerAdded.Remove(value); }
}
internal readonly AsyncEvent<Func<SocketStageChannel, SocketGuildUser, Task>> _speakerAdded = new AsyncEvent<Func<SocketStageChannel, SocketGuildUser, Task>>();

/// <summary>
/// Fired when a speaker is removed from a stage channel.
/// </summary>
public event Func<SocketStageChannel, SocketGuildUser, Task> SpeakerRemoved
{
add { _speakerRemoved.Add(value); }
remove { _speakerRemoved.Remove(value); }
}
internal readonly AsyncEvent<Func<SocketStageChannel, SocketGuildUser, Task>> _speakerRemoved = new AsyncEvent<Func<SocketStageChannel, SocketGuildUser, Task>>();
} }
} }

+ 5
- 0
src/Discord.Net.WebSocket/ConnectionManager.cs View File

@@ -185,6 +185,11 @@ namespace Discord
_readyPromise.TrySetException(ex); _readyPromise.TrySetException(ex);
_connectionPromise.TrySetException(ex); _connectionPromise.TrySetException(ex);
_connectionCancelToken?.Cancel(); _connectionCancelToken?.Cancel();

_ = Task.Run(async () =>
{
await _logger.ErrorAsync($"Failed to start the connection: {ex}", ex);
});
} }
public void CriticalError(Exception ex) public void CriticalError(Exception ex)
{ {


+ 8
- 2
src/Discord.Net.WebSocket/Discord.Net.WebSocket.csproj View File

@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="../../Discord.Net.targets" /> <Import Project="../../Discord.Net.targets" />
<Import Project="../../StyleAnalyzer.targets" /> <Import Project="../../StyleAnalyzer.targets" />
<PropertyGroup> <PropertyGroup>
@@ -8,7 +8,7 @@
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">net461;netstandard2.0;netstandard2.1</TargetFrameworks> <TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">net461;netstandard2.0;netstandard2.1</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' != 'Windows_NT' ">netstandard2.0;netstandard2.1</TargetFrameworks> <TargetFrameworks Condition=" '$(OS)' != 'Windows_NT' ">netstandard2.0;netstandard2.1</TargetFrameworks>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Version>2.4.9</Version>
<Version>3.0.1-pre</Version>
<RepositoryUrl>https://github.com/Discord-Net-Labs/Discord.Net-Labs</RepositoryUrl> <RepositoryUrl>https://github.com/Discord-Net-Labs/Discord.Net-Labs</RepositoryUrl>
<PackageProjectUrl>https://github.com/Discord-Net-Labs/Discord.Net-Labs</PackageProjectUrl> <PackageProjectUrl>https://github.com/Discord-Net-Labs/Discord.Net-Labs</PackageProjectUrl>
<PackageIcon>Temporary.png</PackageIcon> <PackageIcon>Temporary.png</PackageIcon>
@@ -16,7 +16,13 @@
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>
<DocumentationFile>..\Discord.Net.WebSocket\Discord.Net.WebSocket.xml</DocumentationFile> <DocumentationFile>..\Discord.Net.WebSocket\Discord.Net.WebSocket.xml</DocumentationFile>
<AssemblyVersion>3.0.1</AssemblyVersion>
<FileVersion>3.0.1</FileVersion>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DefineConstants>TRACE;</DefineConstants>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Discord.Net.Core\Discord.Net.Core.csproj" /> <ProjectReference Include="..\Discord.Net.Core\Discord.Net.Core.csproj" />
<ProjectReference Include="..\Discord.Net.Rest\Discord.Net.Rest.csproj" /> <ProjectReference Include="..\Discord.Net.Rest\Discord.Net.Rest.csproj" />


+ 219
- 20
src/Discord.Net.WebSocket/Discord.Net.WebSocket.xml View File

@@ -13,7 +13,7 @@
<member name="F:Discord.API.Gateway.GatewayOpCode.Identify"> <member name="F:Discord.API.Gateway.GatewayOpCode.Identify">
<summary> C→S - Used to associate a connection with a token and specify configuration. </summary> <summary> C→S - Used to associate a connection with a token and specify configuration. </summary>
</member> </member>
<member name="F:Discord.API.Gateway.GatewayOpCode.StatusUpdate">
<member name="F:Discord.API.Gateway.GatewayOpCode.PresenceUpdate">
<summary> C→S - Used to update client's status and current game id. </summary> <summary> C→S - Used to update client's status and current game id. </summary>
</member> </member>
<member name="F:Discord.API.Gateway.GatewayOpCode.VoiceStateUpdate"> <member name="F:Discord.API.Gateway.GatewayOpCode.VoiceStateUpdate">
@@ -758,7 +758,7 @@
</member> </member>
<member name="E:Discord.WebSocket.BaseSocketClient.ThreadCreated"> <member name="E:Discord.WebSocket.BaseSocketClient.ThreadCreated">
<summary> <summary>
Fired when a thread is created within a guild.
Fired when a thread is created within a guild, or when the current user is added to a thread.
</summary> </summary>
</member> </member>
<member name="E:Discord.WebSocket.BaseSocketClient.ThreadUpdated"> <member name="E:Discord.WebSocket.BaseSocketClient.ThreadUpdated">
@@ -781,6 +781,36 @@
Fired when a user leaves a thread Fired when a user leaves a thread
</summary> </summary>
</member> </member>
<member name="E:Discord.WebSocket.BaseSocketClient.StageStarted">
<summary>
Fired when a stage is started.
</summary>
</member>
<member name="E:Discord.WebSocket.BaseSocketClient.StageEnded">
<summary>
Fired when a stage ends.
</summary>
</member>
<member name="E:Discord.WebSocket.BaseSocketClient.StageUpdated">
<summary>
Fired when a stage is updated.
</summary>
</member>
<member name="E:Discord.WebSocket.BaseSocketClient.RequestToSpeak">
<summary>
Fired when a user requests to speak within a stage channel.
</summary>
</member>
<member name="E:Discord.WebSocket.BaseSocketClient.SpeakerAdded">
<summary>
Fired when a speaker is added in a stage channel.
</summary>
</member>
<member name="E:Discord.WebSocket.BaseSocketClient.SpeakerRemoved">
<summary>
Fired when a speaker is removed from a stage channel.
</summary>
</member>
<member name="P:Discord.WebSocket.DiscordShardedClient.Latency"> <member name="P:Discord.WebSocket.DiscordShardedClient.Latency">
<inheritdoc /> <inheritdoc />
</member> </member>
@@ -2142,6 +2172,57 @@
</note> </note>
</remarks> </remarks>
</member> </member>
<member name="T:Discord.WebSocket.SocketStageChannel">
<summary>
Represents a stage channel recieved over the gateway.
</summary>
</member>
<member name="P:Discord.WebSocket.SocketStageChannel.Topic">
<inheritdoc/>
</member>
<member name="P:Discord.WebSocket.SocketStageChannel.PrivacyLevel">
<inheritdoc/>
</member>
<member name="P:Discord.WebSocket.SocketStageChannel.DiscoverableDisabled">
<inheritdoc/>
</member>
<member name="P:Discord.WebSocket.SocketStageChannel.Live">
<inheritdoc/>
</member>
<member name="P:Discord.WebSocket.SocketStageChannel.IsSpeaker">
<summary>
Returns <see langword="true"/> if the current user is a speaker within the stage, otherwise <see langword="false"/>.
</summary>
</member>
<member name="P:Discord.WebSocket.SocketStageChannel.Speakers">
<summary>
Gets a collection of users who are speakers within the stage.
</summary>
</member>
<member name="M:Discord.WebSocket.SocketStageChannel.StartStageAsync(System.String,Discord.StagePrivacyLevel,Discord.RequestOptions)">
<inheritdoc/>
</member>
<member name="M:Discord.WebSocket.SocketStageChannel.ModifyInstanceAsync(System.Action{Discord.StageInstanceProperties},Discord.RequestOptions)">
<inheritdoc/>
</member>
<member name="M:Discord.WebSocket.SocketStageChannel.StopStageAsync(Discord.RequestOptions)">
<inheritdoc/>
</member>
<member name="M:Discord.WebSocket.SocketStageChannel.RequestToSpeak(Discord.RequestOptions)">
<inheritdoc/>
</member>
<member name="M:Discord.WebSocket.SocketStageChannel.BecomeSpeakerAsync(Discord.RequestOptions)">
<inheritdoc/>
</member>
<member name="M:Discord.WebSocket.SocketStageChannel.StopSpeakingAsync(Discord.RequestOptions)">
<inheritdoc/>
</member>
<member name="M:Discord.WebSocket.SocketStageChannel.MoveToSpeaker(Discord.IGuildUser,Discord.RequestOptions)">
<inheritdoc/>
</member>
<member name="M:Discord.WebSocket.SocketStageChannel.RemoveFromSpeaker(Discord.IGuildUser,Discord.RequestOptions)">
<inheritdoc/>
</member>
<member name="T:Discord.WebSocket.SocketTextChannel"> <member name="T:Discord.WebSocket.SocketTextChannel">
<summary> <summary>
Represents a WebSocket-based channel in a guild that can send and receive messages. Represents a WebSocket-based channel in a guild that can send and receive messages.
@@ -2787,6 +2868,9 @@
<member name="P:Discord.WebSocket.SocketGuild.MaxVideoChannelUsers"> <member name="P:Discord.WebSocket.SocketGuild.MaxVideoChannelUsers">
<inheritdoc /> <inheritdoc />
</member> </member>
<member name="P:Discord.WebSocket.SocketGuild.NsfwLevel">
<inheritdoc />
</member>
<member name="P:Discord.WebSocket.SocketGuild.PreferredCulture"> <member name="P:Discord.WebSocket.SocketGuild.PreferredCulture">
<inheritdoc /> <inheritdoc />
</member> </member>
@@ -2898,6 +2982,14 @@
A read-only collection of voice channels found within this guild. A read-only collection of voice channels found within this guild.
</returns> </returns>
</member> </member>
<member name="P:Discord.WebSocket.SocketGuild.StageChannels">
<summary>
Gets a collection of all stage channels in this guild.
</summary>
<returns>
A read-only collection of stage channels found within this guild.
</returns>
</member>
<member name="P:Discord.WebSocket.SocketGuild.CategoryChannels"> <member name="P:Discord.WebSocket.SocketGuild.CategoryChannels">
<summary> <summary>
Gets a collection of all category channels in this guild. Gets a collection of all category channels in this guild.
@@ -3055,6 +3147,15 @@
A text channel associated with the specified <paramref name="id" />; <see langword="null"/> if none is found. A text channel associated with the specified <paramref name="id" />; <see langword="null"/> if none is found.
</returns> </returns>
</member> </member>
<member name="M:Discord.WebSocket.SocketGuild.GetThreadChannel(System.UInt64)">
<summary>
Gets a thread in this guild.
</summary>
<param name="id">The snowflake identifier for the thread.</param>
<returns>
A thread channel associated with the specified <paramref name="id" />; <see langword="null"/> if none is found.
</returns>
</member>
<member name="M:Discord.WebSocket.SocketGuild.GetVoiceChannel(System.UInt64)"> <member name="M:Discord.WebSocket.SocketGuild.GetVoiceChannel(System.UInt64)">
<summary> <summary>
Gets a voice channel in this guild. Gets a voice channel in this guild.
@@ -3064,6 +3165,15 @@
A voice channel associated with the specified <paramref name="id" />; <see langword="null"/> if none is found. A voice channel associated with the specified <paramref name="id" />; <see langword="null"/> if none is found.
</returns> </returns>
</member> </member>
<member name="M:Discord.WebSocket.SocketGuild.GetStageChannel(System.UInt64)">
<summary>
Gets a stage channel in this guild.
</summary>
<param name="id">The snowflake identifier for the stage channel.</param>
<returns>
A stage channel associated with the specified <paramref name="id" />; <see langword="null"/> if none is found.
</returns>
</member>
<member name="M:Discord.WebSocket.SocketGuild.GetCategoryChannel(System.UInt64)"> <member name="M:Discord.WebSocket.SocketGuild.GetCategoryChannel(System.UInt64)">
<summary> <summary>
Gets a category channel in this guild. Gets a category channel in this guild.
@@ -3390,6 +3500,12 @@
<member name="M:Discord.WebSocket.SocketGuild.Discord#IGuild#GetTextChannelAsync(System.UInt64,Discord.CacheMode,Discord.RequestOptions)"> <member name="M:Discord.WebSocket.SocketGuild.Discord#IGuild#GetTextChannelAsync(System.UInt64,Discord.CacheMode,Discord.RequestOptions)">
<inheritdoc /> <inheritdoc />
</member> </member>
<member name="M:Discord.WebSocket.SocketGuild.Discord#IGuild#GetThreadChannelAsync(System.UInt64,Discord.CacheMode,Discord.RequestOptions)">
<inheritdoc />
</member>
<member name="M:Discord.WebSocket.SocketGuild.Discord#IGuild#GetThreadChannelsAsync(Discord.CacheMode,Discord.RequestOptions)">
<inheritdoc />
</member>
<member name="M:Discord.WebSocket.SocketGuild.Discord#IGuild#GetVoiceChannelsAsync(Discord.CacheMode,Discord.RequestOptions)"> <member name="M:Discord.WebSocket.SocketGuild.Discord#IGuild#GetVoiceChannelsAsync(Discord.CacheMode,Discord.RequestOptions)">
<inheritdoc /> <inheritdoc />
</member> </member>
@@ -3399,6 +3515,12 @@
<member name="M:Discord.WebSocket.SocketGuild.Discord#IGuild#GetVoiceChannelAsync(System.UInt64,Discord.CacheMode,Discord.RequestOptions)"> <member name="M:Discord.WebSocket.SocketGuild.Discord#IGuild#GetVoiceChannelAsync(System.UInt64,Discord.CacheMode,Discord.RequestOptions)">
<inheritdoc /> <inheritdoc />
</member> </member>
<member name="M:Discord.WebSocket.SocketGuild.Discord#IGuild#GetStageChannelAsync(System.UInt64,Discord.CacheMode,Discord.RequestOptions)">
<inheritdoc />
</member>
<member name="M:Discord.WebSocket.SocketGuild.Discord#IGuild#GetStageChannelsAsync(Discord.CacheMode,Discord.RequestOptions)">
<inheritdoc />
</member>
<member name="M:Discord.WebSocket.SocketGuild.Discord#IGuild#GetAFKChannelAsync(Discord.CacheMode,Discord.RequestOptions)"> <member name="M:Discord.WebSocket.SocketGuild.Discord#IGuild#GetAFKChannelAsync(Discord.CacheMode,Discord.RequestOptions)">
<inheritdoc /> <inheritdoc />
</member> </member>
@@ -3477,6 +3599,9 @@
<member name="M:Discord.WebSocket.SocketGuild.Discord#IGuild#GetWebhooksAsync(Discord.RequestOptions)"> <member name="M:Discord.WebSocket.SocketGuild.Discord#IGuild#GetWebhooksAsync(Discord.RequestOptions)">
<inheritdoc /> <inheritdoc />
</member> </member>
<member name="M:Discord.WebSocket.SocketGuild.Discord#IGuild#GetApplicationCommandsAsync(Discord.RequestOptions)">
<inheritdoc />
</member>
<member name="T:Discord.WebSocket.SocketMessageComponent"> <member name="T:Discord.WebSocket.SocketMessageComponent">
<summary> <summary>
Represents a Websocket-based interaction type for Message Components. Represents a Websocket-based interaction type for Message Components.
@@ -3492,7 +3617,7 @@
The message that contained the trigger for this interaction. The message that contained the trigger for this interaction.
</summary> </summary>
</member> </member>
<member name="M:Discord.WebSocket.SocketMessageComponent.RespondAsync(System.String,Discord.Embed[],System.Boolean,System.Boolean,Discord.AllowedMentions,Discord.RequestOptions,Discord.MessageComponent)">
<member name="M:Discord.WebSocket.SocketMessageComponent.RespondAsync(System.String,Discord.Embed[],System.Boolean,System.Boolean,Discord.AllowedMentions,Discord.RequestOptions,Discord.MessageComponent,Discord.Embed)">
<inheritdoc/> <inheritdoc/>
</member> </member>
<member name="M:Discord.WebSocket.SocketMessageComponent.UpdateAsync(System.Action{Discord.MessageProperties},Discord.RequestOptions)"> <member name="M:Discord.WebSocket.SocketMessageComponent.UpdateAsync(System.Action{Discord.MessageProperties},Discord.RequestOptions)">
@@ -3503,18 +3628,22 @@
<param name="options">The request options for this async request.</param> <param name="options">The request options for this async request.</param>
<returns>A task that represents the asynchronous operation of updating the message.</returns> <returns>A task that represents the asynchronous operation of updating the message.</returns>
</member> </member>
<member name="M:Discord.WebSocket.SocketMessageComponent.FollowupAsync(System.String,Discord.Embed[],System.Boolean,System.Boolean,Discord.AllowedMentions,Discord.RequestOptions,Discord.MessageComponent)">
<member name="M:Discord.WebSocket.SocketMessageComponent.FollowupAsync(System.String,Discord.Embed[],System.Boolean,System.Boolean,Discord.AllowedMentions,Discord.RequestOptions,Discord.MessageComponent,Discord.Embed)">
<inheritdoc/> <inheritdoc/>
</member> </member>
<member name="M:Discord.WebSocket.SocketMessageComponent.DeferAsync(Discord.RequestOptions)">
<member name="M:Discord.WebSocket.SocketMessageComponent.DeferLoadingAsync(System.Boolean,Discord.RequestOptions)">
<summary> <summary>
Acknowledges this interaction with the <see cref="F:Discord.InteractionResponseType.DeferredUpdateMessage"/>.
Defers an interaction and responds with type 5 (<see cref="F:Discord.InteractionResponseType.DeferredChannelMessageWithSource"/>)
</summary> </summary>
<param name="ephemeral"><see langword="true"/> to send this message ephemerally, otherwise <see langword="false"/>.</param>
<param name="options">The request options for this async request.</param> <param name="options">The request options for this async request.</param>
<returns> <returns>
A task that represents the asynchronous operation of acknowledging the interaction. A task that represents the asynchronous operation of acknowledging the interaction.
</returns> </returns>
</member> </member>
<member name="M:Discord.WebSocket.SocketMessageComponent.DeferAsync(System.Boolean,Discord.RequestOptions)">
<inheritdoc/>
</member>
<member name="T:Discord.WebSocket.SocketMessageComponentData"> <member name="T:Discord.WebSocket.SocketMessageComponentData">
<summary> <summary>
Represents the data sent with a <see cref="F:Discord.InteractionType.MessageComponent"/>. Represents the data sent with a <see cref="F:Discord.InteractionType.MessageComponent"/>.
@@ -3619,19 +3748,14 @@
The data associated with this interaction. The data associated with this interaction.
</summary> </summary>
</member> </member>
<member name="M:Discord.WebSocket.SocketSlashCommand.RespondAsync(System.String,Discord.Embed[],System.Boolean,System.Boolean,Discord.AllowedMentions,Discord.RequestOptions,Discord.MessageComponent)">
<member name="M:Discord.WebSocket.SocketSlashCommand.RespondAsync(System.String,Discord.Embed[],System.Boolean,System.Boolean,Discord.AllowedMentions,Discord.RequestOptions,Discord.MessageComponent,Discord.Embed)">
<inheritdoc/> <inheritdoc/>
</member> </member>
<member name="M:Discord.WebSocket.SocketSlashCommand.FollowupAsync(System.String,Discord.Embed[],System.Boolean,System.Boolean,Discord.AllowedMentions,Discord.RequestOptions,Discord.MessageComponent)">
<member name="M:Discord.WebSocket.SocketSlashCommand.FollowupAsync(System.String,Discord.Embed[],System.Boolean,System.Boolean,Discord.AllowedMentions,Discord.RequestOptions,Discord.MessageComponent,Discord.Embed)">
<inheritdoc/> <inheritdoc/>
</member> </member>
<member name="M:Discord.WebSocket.SocketSlashCommand.DeferAsync(Discord.RequestOptions)">
<summary>
Acknowledges this interaction with the <see cref="F:Discord.InteractionResponseType.DeferredChannelMessageWithSource"/>.
</summary>
<returns>
A task that represents the asynchronous operation of acknowledging the interaction.
</returns>
<member name="M:Discord.WebSocket.SocketSlashCommand.DeferAsync(System.Boolean,Discord.RequestOptions)">
<inheritdoc/>
</member> </member>
<member name="T:Discord.WebSocket.SocketSlashCommandData"> <member name="T:Discord.WebSocket.SocketSlashCommandData">
<summary> <summary>
@@ -3708,12 +3832,12 @@
<see langword="true"/> if the token is valid for replying to, otherwise <see langword="false"/>. <see langword="true"/> if the token is valid for replying to, otherwise <see langword="false"/>.
</summary> </summary>
</member> </member>
<member name="M:Discord.WebSocket.SocketInteraction.RespondAsync(System.String,Discord.Embed[],System.Boolean,System.Boolean,Discord.AllowedMentions,Discord.RequestOptions,Discord.MessageComponent)">
<member name="M:Discord.WebSocket.SocketInteraction.RespondAsync(System.String,Discord.Embed[],System.Boolean,System.Boolean,Discord.AllowedMentions,Discord.RequestOptions,Discord.MessageComponent,Discord.Embed)">
<summary> <summary>
Responds to an Interaction with type <see cref="F:Discord.InteractionResponseType.ChannelMessageWithSource"/>. Responds to an Interaction with type <see cref="F:Discord.InteractionResponseType.ChannelMessageWithSource"/>.
<para> <para>
If you have <see cref="P:Discord.WebSocket.DiscordSocketConfig.AlwaysAcknowledgeInteractions"/> set to <see langword="true"/>, You should use If you have <see cref="P:Discord.WebSocket.DiscordSocketConfig.AlwaysAcknowledgeInteractions"/> set to <see langword="true"/>, You should use
<see cref="!:FollowupAsync(Discord.Embed[],string,bool,bool,Discord.AllowedMentions,Discord.RequestOptions,Discord.MessageComponent)"/> instead.
<see cref="M:Discord.WebSocket.SocketInteraction.FollowupAsync(System.String,Discord.Embed[],System.Boolean,System.Boolean,Discord.AllowedMentions,Discord.RequestOptions,Discord.MessageComponent,Discord.Embed)"/> instead.
</para> </para>
</summary> </summary>
<param name="text">The text of the message to be sent.</param> <param name="text">The text of the message to be sent.</param>
@@ -3723,10 +3847,11 @@
<param name="allowedMentions">The allowed mentions for this response.</param> <param name="allowedMentions">The allowed mentions for this response.</param>
<param name="options">The request options for this response.</param> <param name="options">The request options for this response.</param>
<param name="component">A <see cref="T:Discord.MessageComponent"/> to be sent with this response</param> <param name="component">A <see cref="T:Discord.MessageComponent"/> to be sent with this response</param>
<param name="embed">A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored.</param>
<exception cref="T:System.ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="F:Discord.DiscordConfig.MaxMessageSize"/>.</exception> <exception cref="T:System.ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="F:Discord.DiscordConfig.MaxMessageSize"/>.</exception>
<exception cref="T:System.InvalidOperationException">The parameters provided were invalid or the token was invalid.</exception> <exception cref="T:System.InvalidOperationException">The parameters provided were invalid or the token was invalid.</exception>
</member> </member>
<member name="M:Discord.WebSocket.SocketInteraction.FollowupAsync(System.String,Discord.Embed[],System.Boolean,System.Boolean,Discord.AllowedMentions,Discord.RequestOptions,Discord.MessageComponent)">
<member name="M:Discord.WebSocket.SocketInteraction.FollowupAsync(System.String,Discord.Embed[],System.Boolean,System.Boolean,Discord.AllowedMentions,Discord.RequestOptions,Discord.MessageComponent,Discord.Embed)">
<summary> <summary>
Sends a followup message for this interaction. Sends a followup message for this interaction.
</summary> </summary>
@@ -3737,6 +3862,7 @@
<param name="allowedMentions">The allowed mentions for this response.</param> <param name="allowedMentions">The allowed mentions for this response.</param>
<param name="options">The request options for this response.</param> <param name="options">The request options for this response.</param>
<param name="component">A <see cref="T:Discord.MessageComponent"/> to be sent with this response</param> <param name="component">A <see cref="T:Discord.MessageComponent"/> to be sent with this response</param>
<param name="embed">A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored.</param>
<returns> <returns>
The sent message. The sent message.
</returns> </returns>
@@ -3764,14 +3890,25 @@
A task that represents the asynchronous operation of acknowledging the interaction. A task that represents the asynchronous operation of acknowledging the interaction.
</returns> </returns>
</member> </member>
<member name="M:Discord.WebSocket.SocketInteraction.DeferAsync(Discord.RequestOptions)">
<member name="M:Discord.WebSocket.SocketInteraction.DeferAsync(System.Boolean,Discord.RequestOptions)">
<summary> <summary>
Acknowledges this interaction. Acknowledges this interaction.
</summary> </summary>
<param name="ephemeral"><see langword="true"/> to send this message ephemerally, otherwise <see langword="false"/>.</param>
<param name="options">The request options for this async request.</param>
<returns> <returns>
A task that represents the asynchronous operation of acknowledging the interaction. A task that represents the asynchronous operation of acknowledging the interaction.
</returns> </returns>
</member> </member>
<member name="M:Discord.WebSocket.SocketInteraction.Discord#IDiscordInteraction#FollowupAsync(System.String,Discord.Embed[],System.Boolean,System.Boolean,Discord.AllowedMentions,Discord.RequestOptions,Discord.MessageComponent,Discord.Embed)">
<inheritdoc/>
</member>
<member name="M:Discord.WebSocket.SocketInteraction.Discord#IDiscordInteraction#GetOriginalResponseAsync(Discord.RequestOptions)">
<inheritdoc/>
</member>
<member name="M:Discord.WebSocket.SocketInteraction.Discord#IDiscordInteraction#ModifyOriginalResponseAsync(System.Action{Discord.MessageProperties},Discord.RequestOptions)">
<inheritdoc/>
</member>
<member name="T:Discord.WebSocket.SocketInvite"> <member name="T:Discord.WebSocket.SocketInvite">
<summary> <summary>
Represents a WebSocket-based invite to a guild. Represents a WebSocket-based invite to a guild.
@@ -4308,6 +4445,12 @@
<member name="P:Discord.WebSocket.SocketGroupUser.AvatarId"> <member name="P:Discord.WebSocket.SocketGroupUser.AvatarId">
<inheritdoc /> <inheritdoc />
</member> </member>
<member name="P:Discord.WebSocket.SocketGroupUser.BannerId">
<inheritdoc />
</member>
<member name="P:Discord.WebSocket.SocketGroupUser.AccentColor">
<inheritdoc />
</member>
<member name="P:Discord.WebSocket.SocketGroupUser.Presence"> <member name="P:Discord.WebSocket.SocketGroupUser.Presence">
<inheritdoc /> <inheritdoc />
</member> </member>
@@ -4338,6 +4481,9 @@
<member name="P:Discord.WebSocket.SocketGroupUser.Discord#IVoiceState#IsStreaming"> <member name="P:Discord.WebSocket.SocketGroupUser.Discord#IVoiceState#IsStreaming">
<inheritdoc /> <inheritdoc />
</member> </member>
<member name="P:Discord.WebSocket.SocketGroupUser.Discord#IVoiceState#RequestToSpeakTimestamp">
<inheritdoc />
</member>
<member name="T:Discord.WebSocket.SocketGuildUser"> <member name="T:Discord.WebSocket.SocketGuildUser">
<summary> <summary>
Represents a WebSocket-based guild user. Represents a WebSocket-based guild user.
@@ -4363,6 +4509,12 @@
<member name="P:Discord.WebSocket.SocketGuildUser.AvatarId"> <member name="P:Discord.WebSocket.SocketGuildUser.AvatarId">
<inheritdoc /> <inheritdoc />
</member> </member>
<member name="P:Discord.WebSocket.SocketGuildUser.BannerId">
<inheritdoc />
</member>
<member name="P:Discord.WebSocket.SocketGuildUser.AccentColor">
<inheritdoc />
</member>
<member name="P:Discord.WebSocket.SocketGuildUser.GuildPermissions"> <member name="P:Discord.WebSocket.SocketGuildUser.GuildPermissions">
<inheritdoc /> <inheritdoc />
</member> </member>
@@ -4387,6 +4539,9 @@
<member name="P:Discord.WebSocket.SocketGuildUser.IsStreaming"> <member name="P:Discord.WebSocket.SocketGuildUser.IsStreaming">
<inheritdoc /> <inheritdoc />
</member> </member>
<member name="P:Discord.WebSocket.SocketGuildUser.RequestToSpeakTimestamp">
<inheritdoc />
</member>
<member name="P:Discord.WebSocket.SocketGuildUser.IsPending"> <member name="P:Discord.WebSocket.SocketGuildUser.IsPending">
<inheritdoc /> <inheritdoc />
</member> </member>
@@ -4423,7 +4578,7 @@
Returns the position of the user within the role hierarchy. Returns the position of the user within the role hierarchy.
</summary> </summary>
<remarks> <remarks>
The returned value equal to the position of the highest role the user has, or
The returned value equal to the position of the highest role the user has, or
<see cref="F:System.Int32.MaxValue"/> if user is the server owner. <see cref="F:System.Int32.MaxValue"/> if user is the server owner.
</remarks> </remarks>
</member> </member>
@@ -4545,6 +4700,12 @@
<member name="P:Discord.WebSocket.SocketSelfUser.AvatarId"> <member name="P:Discord.WebSocket.SocketSelfUser.AvatarId">
<inheritdoc /> <inheritdoc />
</member> </member>
<member name="P:Discord.WebSocket.SocketSelfUser.BannerId">
<inheritdoc />
</member>
<member name="P:Discord.WebSocket.SocketSelfUser.AccentColor">
<inheritdoc />
</member>
<member name="P:Discord.WebSocket.SocketSelfUser.Presence"> <member name="P:Discord.WebSocket.SocketSelfUser.Presence">
<inheritdoc /> <inheritdoc />
</member> </member>
@@ -4598,6 +4759,12 @@
<member name="P:Discord.WebSocket.SocketThreadUser.AvatarId"> <member name="P:Discord.WebSocket.SocketThreadUser.AvatarId">
<inheritdoc/> <inheritdoc/>
</member> </member>
<member name="P:Discord.WebSocket.SocketThreadUser.BannerId">
<inheritdoc/>
</member>
<member name="P:Discord.WebSocket.SocketThreadUser.AccentColor">
<inheritdoc/>
</member>
<member name="P:Discord.WebSocket.SocketThreadUser.DiscriminatorValue"> <member name="P:Discord.WebSocket.SocketThreadUser.DiscriminatorValue">
<inheritdoc/> <inheritdoc/>
</member> </member>
@@ -4634,6 +4801,9 @@
<member name="P:Discord.WebSocket.SocketThreadUser.IsStreaming"> <member name="P:Discord.WebSocket.SocketThreadUser.IsStreaming">
<inheritdoc/> <inheritdoc/>
</member> </member>
<member name="P:Discord.WebSocket.SocketThreadUser.RequestToSpeakTimestamp">
<inheritdoc/>
</member>
<member name="M:Discord.WebSocket.SocketThreadUser.GetPermissions(Discord.IGuildChannel)"> <member name="M:Discord.WebSocket.SocketThreadUser.GetPermissions(Discord.IGuildChannel)">
<inheritdoc/> <inheritdoc/>
</member> </member>
@@ -4702,6 +4872,12 @@
<member name="P:Discord.WebSocket.SocketUnknownUser.AvatarId"> <member name="P:Discord.WebSocket.SocketUnknownUser.AvatarId">
<inheritdoc /> <inheritdoc />
</member> </member>
<member name="P:Discord.WebSocket.SocketUnknownUser.BannerId">
<inheritdoc />
</member>
<member name="P:Discord.WebSocket.SocketUnknownUser.AccentColor">
<inheritdoc />
</member>
<member name="P:Discord.WebSocket.SocketUnknownUser.IsBot"> <member name="P:Discord.WebSocket.SocketUnknownUser.IsBot">
<inheritdoc /> <inheritdoc />
</member> </member>
@@ -4732,6 +4908,12 @@
<member name="P:Discord.WebSocket.SocketUser.AvatarId"> <member name="P:Discord.WebSocket.SocketUser.AvatarId">
<inheritdoc /> <inheritdoc />
</member> </member>
<member name="P:Discord.WebSocket.SocketUser.BannerId">
<inheritdoc />
</member>
<member name="P:Discord.WebSocket.SocketUser.AccentColor">
<inheritdoc />
</member>
<member name="P:Discord.WebSocket.SocketUser.IsWebhook"> <member name="P:Discord.WebSocket.SocketUser.IsWebhook">
<inheritdoc /> <inheritdoc />
</member> </member>
@@ -4770,6 +4952,9 @@
<member name="M:Discord.WebSocket.SocketUser.GetAvatarUrl(Discord.ImageFormat,System.UInt16)"> <member name="M:Discord.WebSocket.SocketUser.GetAvatarUrl(Discord.ImageFormat,System.UInt16)">
<inheritdoc /> <inheritdoc />
</member> </member>
<member name="M:Discord.WebSocket.SocketUser.GetBannerUrl(Discord.ImageFormat,System.UInt16)">
<inheritdoc />
</member>
<member name="M:Discord.WebSocket.SocketUser.GetDefaultAvatarUrl"> <member name="M:Discord.WebSocket.SocketUser.GetDefaultAvatarUrl">
<inheritdoc /> <inheritdoc />
</member> </member>
@@ -4799,6 +4984,9 @@
<member name="P:Discord.WebSocket.SocketVoiceState.VoiceSessionId"> <member name="P:Discord.WebSocket.SocketVoiceState.VoiceSessionId">
<inheritdoc /> <inheritdoc />
</member> </member>
<member name="P:Discord.WebSocket.SocketVoiceState.RequestToSpeakTimestamp">
<inheritdoc/>
</member>
<member name="P:Discord.WebSocket.SocketVoiceState.IsMuted"> <member name="P:Discord.WebSocket.SocketVoiceState.IsMuted">
<inheritdoc /> <inheritdoc />
</member> </member>
@@ -4848,6 +5036,14 @@
<member name="P:Discord.WebSocket.SocketWebhookUser.AvatarId"> <member name="P:Discord.WebSocket.SocketWebhookUser.AvatarId">
<inheritdoc /> <inheritdoc />
</member> </member>
<member name="P:Discord.WebSocket.SocketWebhookUser.BannerId">
<inheritdoc />
<exception cref="T:System.NotSupportedException">Webhook users does not support banners.</exception>
</member>
<member name="P:Discord.WebSocket.SocketWebhookUser.AccentColor">
<inheritdoc />
<exception cref="T:System.NotSupportedException">Webhook users does not support accent colors.</exception>
</member>
<member name="P:Discord.WebSocket.SocketWebhookUser.IsBot"> <member name="P:Discord.WebSocket.SocketWebhookUser.IsBot">
<inheritdoc /> <inheritdoc />
</member> </member>
@@ -4948,6 +5144,9 @@
<member name="P:Discord.WebSocket.SocketWebhookUser.Discord#IVoiceState#IsStreaming"> <member name="P:Discord.WebSocket.SocketWebhookUser.Discord#IVoiceState#IsStreaming">
<inheritdoc /> <inheritdoc />
</member> </member>
<member name="P:Discord.WebSocket.SocketWebhookUser.Discord#IVoiceState#RequestToSpeakTimestamp">
<inheritdoc />
</member>
<member name="T:Discord.WebSocket.SocketVoiceServer"> <member name="T:Discord.WebSocket.SocketVoiceServer">
<summary> <summary>
Represents a WebSocket-based voice server. Represents a WebSocket-based voice server.


+ 2
- 2
src/Discord.Net.WebSocket/DiscordShardedClient.Events.cs View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Threading.Tasks; using System.Threading.Tasks;


namespace Discord.WebSocket namespace Discord.WebSocket
@@ -35,4 +35,4 @@ namespace Discord.WebSocket
} }
private readonly AsyncEvent<Func<int, int, DiscordSocketClient, Task>> _shardLatencyUpdatedEvent = new AsyncEvent<Func<int, int, DiscordSocketClient, Task>>(); private readonly AsyncEvent<Func<int, int, DiscordSocketClient, Task>> _shardLatencyUpdatedEvent = new AsyncEvent<Func<int, int, DiscordSocketClient, Task>>();
} }
}
}

+ 24
- 1
src/Discord.Net.WebSocket/DiscordShardedClient.cs View File

@@ -30,7 +30,16 @@ namespace Discord.WebSocket
/// <inheritdoc /> /// <inheritdoc />
public override IActivity Activity { get => _shards[0].Activity; protected set { } } public override IActivity Activity { get => _shards[0].Activity; protected set { } }


internal new DiscordSocketApiClient ApiClient => base.ApiClient as DiscordSocketApiClient;
internal new DiscordSocketApiClient ApiClient
{
get
{
if (base.ApiClient.CurrentUserId == null)
base.ApiClient.CurrentUserId = CurrentUser?.Id;

return base.ApiClient;
}
}
/// <inheritdoc /> /// <inheritdoc />
public override IReadOnlyCollection<SocketGuild> Guilds => GetGuilds().ToReadOnlyCollection(GetGuildCount); public override IReadOnlyCollection<SocketGuild> Guilds => GetGuilds().ToReadOnlyCollection(GetGuildCount);
/// <inheritdoc /> /// <inheritdoc />
@@ -379,6 +388,20 @@ namespace Discord.WebSocket
client.InviteDeleted += (channel, invite) => _inviteDeletedEvent.InvokeAsync(channel, invite); client.InviteDeleted += (channel, invite) => _inviteDeletedEvent.InvokeAsync(channel, invite);


client.InteractionCreated += (interaction) => _interactionCreatedEvent.InvokeAsync(interaction); client.InteractionCreated += (interaction) => _interactionCreatedEvent.InvokeAsync(interaction);

client.ThreadUpdated += (thread1, thread2) => _threadUpdated.InvokeAsync(thread1, thread2);
client.ThreadCreated += (thread) => _threadCreated.InvokeAsync(thread);
client.ThreadDeleted += (thread) => _threadDeleted.InvokeAsync(thread);

client.ThreadMemberJoined += (user) => _threadMemberJoined.InvokeAsync(user);
client.ThreadMemberLeft += (user) => _threadMemberLeft.InvokeAsync(user);
client.StageEnded += (stage) => _stageEnded.InvokeAsync(stage);
client.StageStarted += (stage) => _stageStarted.InvokeAsync(stage);
client.StageUpdated += (stage1, stage2) => _stageUpdated.InvokeAsync(stage1, stage2);

client.RequestToSpeak += (stage, user) => _requestToSpeak.InvokeAsync(stage, user);
client.SpeakerAdded += (stage, user) => _speakerAdded.InvokeAsync(stage, user);
client.SpeakerRemoved += (stage, user) => _speakerRemoved.InvokeAsync(stage, user);
} }


//IDiscordClient //IDiscordClient


+ 35
- 7
src/Discord.Net.WebSocket/DiscordSocketApiClient.cs View File

@@ -75,8 +75,15 @@ namespace Discord.API
using (var jsonReader = new JsonTextReader(reader)) using (var jsonReader = new JsonTextReader(reader))
{ {
var msg = _serializer.Deserialize<SocketFrame>(jsonReader); var msg = _serializer.Deserialize<SocketFrame>(jsonReader);

if (msg != null) if (msg != null)
{
#if DEBUG_PACKETS
Console.WriteLine($"<- {(GatewayOpCode)msg.Operation} [{msg.Type ?? "none"}] : {(msg.Payload as Newtonsoft.Json.Linq.JToken)?.ToString().Length}");
#endif

await _receivedGatewayEvent.InvokeAsync((GatewayOpCode)msg.Operation, msg.Sequence, msg.Type, msg.Payload).ConfigureAwait(false); await _receivedGatewayEvent.InvokeAsync((GatewayOpCode)msg.Operation, msg.Sequence, msg.Type, msg.Payload).ConfigureAwait(false);
}
} }
} }
}; };
@@ -87,11 +94,21 @@ namespace Discord.API
{ {
var msg = _serializer.Deserialize<SocketFrame>(jsonReader); var msg = _serializer.Deserialize<SocketFrame>(jsonReader);
if (msg != null) if (msg != null)
{
#if DEBUG_PACKETS
Console.WriteLine($"<- {(GatewayOpCode)msg.Operation} [{msg.Type ?? "none"}] : {(msg.Payload as Newtonsoft.Json.Linq.JToken)?.ToString().Length}");
#endif

await _receivedGatewayEvent.InvokeAsync((GatewayOpCode)msg.Operation, msg.Sequence, msg.Type, msg.Payload).ConfigureAwait(false); await _receivedGatewayEvent.InvokeAsync((GatewayOpCode)msg.Operation, msg.Sequence, msg.Type, msg.Payload).ConfigureAwait(false);
}
} }
}; };
WebSocketClient.Closed += async ex => WebSocketClient.Closed += async ex =>
{ {
#if DEBUG_PACKETS
Console.WriteLine(ex);
#endif

await DisconnectAsync().ConfigureAwait(false); await DisconnectAsync().ConfigureAwait(false);
await _disconnectedEvent.InvokeAsync(ex).ConfigureAwait(false); await _disconnectedEvent.InvokeAsync(ex).ConfigureAwait(false);
}; };
@@ -153,6 +170,11 @@ namespace Discord.API
var gatewayResponse = await GetGatewayAsync().ConfigureAwait(false); var gatewayResponse = await GetGatewayAsync().ConfigureAwait(false);
_gatewayUrl = $"{gatewayResponse.Url}?v={DiscordConfig.APIVersion}&encoding={DiscordSocketConfig.GatewayEncoding}&compress=zlib-stream"; _gatewayUrl = $"{gatewayResponse.Url}?v={DiscordConfig.APIVersion}&encoding={DiscordSocketConfig.GatewayEncoding}&compress=zlib-stream";
} }

#if DEBUG_PACKETS
Console.WriteLine("Connecting to gateway: " + _gatewayUrl);
#endif

await WebSocketClient.ConnectAsync(_gatewayUrl).ConfigureAwait(false); await WebSocketClient.ConnectAsync(_gatewayUrl).ConfigureAwait(false);


ConnectionState = ConnectionState.Connected; ConnectionState = ConnectionState.Connected;
@@ -213,6 +235,10 @@ namespace Discord.API
options.BucketId = GatewayBucket.Get(GatewayBucketType.Unbucketed).Id; options.BucketId = GatewayBucket.Get(GatewayBucketType.Unbucketed).Id;
await RequestQueue.SendAsync(new WebSocketRequest(WebSocketClient, bytes, true, opCode == GatewayOpCode.Heartbeat, options)).ConfigureAwait(false); await RequestQueue.SendAsync(new WebSocketRequest(WebSocketClient, bytes, true, opCode == GatewayOpCode.Heartbeat, options)).ConfigureAwait(false);
await _sentGatewayMessageEvent.InvokeAsync(opCode).ConfigureAwait(false); await _sentGatewayMessageEvent.InvokeAsync(opCode).ConfigureAwait(false);

#if DEBUG_PACKETS
Console.WriteLine($"-> {opCode}:\n{SerializeJson(payload)}");
#endif
} }


public async Task SendIdentifyAsync(int largeThreshold = 100, int shardID = 0, int totalShards = 1, GatewayIntents gatewayIntents = GatewayIntents.AllUnprivileged, (UserStatus, bool, long?, GameModel)? presence = null, RequestOptions options = null) public async Task SendIdentifyAsync(int largeThreshold = 100, int shardID = 0, int totalShards = 1, GatewayIntents gatewayIntents = GatewayIntents.AllUnprivileged, (UserStatus, bool, long?, GameModel)? presence = null, RequestOptions options = null)
@@ -220,7 +246,9 @@ namespace Discord.API
options = RequestOptions.CreateOrClone(options); options = RequestOptions.CreateOrClone(options);
var props = new Dictionary<string, string> var props = new Dictionary<string, string>
{ {
["$device"] = "Discord.Net"
["$device"] = "Discord.Net Labs",
["$os"] = Environment.OSVersion.Platform.ToString(),
[$"browser"] = "Discord.Net Labs"
}; };
var msg = new IdentifyParams() var msg = new IdentifyParams()
{ {
@@ -237,12 +265,12 @@ namespace Discord.API


if (presence.HasValue) if (presence.HasValue)
{ {
msg.Presence = new StatusUpdateParams
msg.Presence = new PresenceUpdateParams
{ {
Status = presence.Value.Item1, Status = presence.Value.Item1,
IsAFK = presence.Value.Item2, IsAFK = presence.Value.Item2,
IdleSince = presence.Value.Item3, IdleSince = presence.Value.Item3,
Game = presence.Value.Item4,
Activities = new object[] { presence.Value.Item4 }
}; };
} }


@@ -264,18 +292,18 @@ namespace Discord.API
options = RequestOptions.CreateOrClone(options); options = RequestOptions.CreateOrClone(options);
await SendGatewayAsync(GatewayOpCode.Heartbeat, lastSeq, options: options).ConfigureAwait(false); await SendGatewayAsync(GatewayOpCode.Heartbeat, lastSeq, options: options).ConfigureAwait(false);
} }
public async Task SendStatusUpdateAsync(UserStatus status, bool isAFK, long? since, GameModel game, RequestOptions options = null)
public async Task SendPresenceUpdateAsync(UserStatus status, bool isAFK, long? since, GameModel game, RequestOptions options = null)
{ {
options = RequestOptions.CreateOrClone(options); options = RequestOptions.CreateOrClone(options);
var args = new StatusUpdateParams
var args = new PresenceUpdateParams
{ {
Status = status, Status = status,
IdleSince = since, IdleSince = since,
IsAFK = isAFK, IsAFK = isAFK,
Game = game
Activities = new object[] { game }
}; };
options.BucketId = GatewayBucket.Get(GatewayBucketType.PresenceUpdate).Id; options.BucketId = GatewayBucket.Get(GatewayBucketType.PresenceUpdate).Id;
await SendGatewayAsync(GatewayOpCode.StatusUpdate, args, options: options).ConfigureAwait(false);
await SendGatewayAsync(GatewayOpCode.PresenceUpdate, args, options: options).ConfigureAwait(false);
} }
public async Task SendRequestMembersAsync(IEnumerable<ulong> guildIds, RequestOptions options = null) public async Task SendRequestMembersAsync(IEnumerable<ulong> guildIds, RequestOptions options = null)
{ {


+ 67
- 2
src/Discord.Net.WebSocket/DiscordSocketClient.cs View File

@@ -495,7 +495,7 @@ namespace Discord.WebSocket


var presence = BuildCurrentStatus() ?? (UserStatus.Online, false, null, null); var presence = BuildCurrentStatus() ?? (UserStatus.Online, false, null, null);


await ApiClient.SendStatusUpdateAsync(
await ApiClient.SendPresenceUpdateAsync(
status: presence.Item1, status: presence.Item1,
isAFK: presence.Item2, isAFK: presence.Item2,
since: presence.Item3, since: presence.Item3,
@@ -1770,6 +1770,29 @@ namespace Discord.WebSocket
} }
} }


if (user is SocketGuildUser guildUser && data.ChannelId.HasValue)
{
SocketStageChannel stage = guildUser.Guild.GetStageChannel(data.ChannelId.Value);

if (stage != null && before.VoiceChannel != null && after.VoiceChannel != null)
{
if (!before.RequestToSpeakTimestamp.HasValue && after.RequestToSpeakTimestamp.HasValue)
{
await TimedInvokeAsync(_requestToSpeak, nameof(RequestToSpeak), stage, guildUser);
return;
}
if(before.IsSuppressed && !after.IsSuppressed)
{
await TimedInvokeAsync(_speakerAdded, nameof(SpeakerAdded), stage, guildUser);
return;
}
if(!before.IsSuppressed && after.IsSuppressed)
{
await TimedInvokeAsync(_speakerRemoved, nameof(SpeakerRemoved), stage, guildUser);
}
}
}

await TimedInvokeAsync(_userVoiceStateUpdatedEvent, nameof(UserVoiceStateUpdated), user, before, after).ConfigureAwait(false); await TimedInvokeAsync(_userVoiceStateUpdatedEvent, nameof(UserVoiceStateUpdated), user, before, after).ConfigureAwait(false);
} }
break; break;
@@ -2001,8 +2024,9 @@ namespace Discord.WebSocket
threadChannel = (SocketThreadChannel)guild.AddChannel(this.State, data); threadChannel = (SocketThreadChannel)guild.AddChannel(this.State, data);
if (data.ThreadMember.IsSpecified) if (data.ThreadMember.IsSpecified)
threadChannel.AddOrUpdateThreadMember(data.ThreadMember.Value, guild.CurrentUser); threadChannel.AddOrUpdateThreadMember(data.ThreadMember.Value, guild.CurrentUser);
await TimedInvokeAsync(_threadCreated, nameof(ThreadCreated), threadChannel).ConfigureAwait(false);
} }

await TimedInvokeAsync(_threadCreated, nameof(ThreadCreated), threadChannel).ConfigureAwait(false);
} }


break; break;
@@ -2180,6 +2204,47 @@ namespace Discord.WebSocket


break; break;


case "STAGE_INSTANCE_CREATE" or "STAGE_INSTANCE_UPDATE" or "STAGE_INSTANCE_DELETE":
{
await _gatewayLogger.DebugAsync($"Received Dispatch ({type})").ConfigureAwait(false);

var data = (payload as JToken).ToObject<StageInstance>(_serializer);

var guild = State.GetGuild(data.GuildId);

if(guild == null)
{
await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false);
return;
}

var stageChannel = guild.GetStageChannel(data.ChannelId);

if(stageChannel == null)
{
await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false);
return;
}

SocketStageChannel before = type == "STAGE_INSTANCE_UPDATE" ? stageChannel.Clone() : null;

stageChannel.Update(data, type == "STAGE_INSTANCE_CREATE" ? true : type == "STAGE_INSTANCE_DELETE" ? false : false);

switch (type)
{
case "STAGE_INSTANCE_CREATE":
await TimedInvokeAsync(_stageStarted, nameof(StageStarted), stageChannel);
return;
case "STAGE_INSTANCE_DELETE":
await TimedInvokeAsync(_stageEnded, nameof(StageEnded), stageChannel);
return;
case "STAGE_INSTANCE_UPDATE":
await TimedInvokeAsync(_stageUpdated, nameof(StageUpdated), before, stageChannel);
return;
}
}
break;

//Ignored (User only) //Ignored (User only)
case "CHANNEL_PINS_ACK": case "CHANNEL_PINS_ACK":
await _gatewayLogger.DebugAsync("Ignored Dispatch (CHANNEL_PINS_ACK)").ConfigureAwait(false); await _gatewayLogger.DebugAsync("Ignored Dispatch (CHANNEL_PINS_ACK)").ConfigureAwait(false);


+ 2
- 0
src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs View File

@@ -58,6 +58,8 @@ namespace Discord.WebSocket
return SocketCategoryChannel.Create(guild, state, model); return SocketCategoryChannel.Create(guild, state, model);
case ChannelType.PrivateThread or ChannelType.PublicThread or ChannelType.NewsThread: case ChannelType.PrivateThread or ChannelType.PublicThread or ChannelType.NewsThread:
return SocketThreadChannel.Create(guild, state, model); return SocketThreadChannel.Create(guild, state, model);
case ChannelType.Stage:
return SocketStageChannel.Create(guild, state, model);
default: default:
return new SocketGuildChannel(guild.Discord, model.Id, guild); return new SocketGuildChannel(guild.Discord, model.Id, guild);
} }


+ 168
- 0
src/Discord.Net.WebSocket/Entities/Channels/SocketStageChannel.cs View File

@@ -0,0 +1,168 @@
using Discord.Rest;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Model = Discord.API.Channel;
using StageInstance = Discord.API.StageInstance;

namespace Discord.WebSocket
{
/// <summary>
/// Represents a stage channel recieved over the gateway.
/// </summary>
public class SocketStageChannel : SocketVoiceChannel, IStageChannel
{
/// <inheritdoc/>
public string Topic { get; private set; }

/// <inheritdoc/>
public StagePrivacyLevel? PrivacyLevel { get; private set; }

/// <inheritdoc/>
public bool? DiscoverableDisabled { get; private set; }

/// <inheritdoc/>
public bool Live { get; private set; } = false;

/// <summary>
/// Returns <see langword="true"/> if the current user is a speaker within the stage, otherwise <see langword="false"/>.
/// </summary>
public bool IsSpeaker
=> !Guild.CurrentUser.IsSuppressed;

/// <summary>
/// Gets a collection of users who are speakers within the stage.
/// </summary>
public IReadOnlyCollection<SocketGuildUser> Speakers
=> this.Users.Where(x => !x.IsSuppressed).ToImmutableArray();

internal new SocketStageChannel Clone() => MemberwiseClone() as SocketStageChannel;


internal SocketStageChannel(DiscordSocketClient discord, ulong id, SocketGuild guild)
: base(discord, id, guild)
{

}

internal new static SocketStageChannel Create(SocketGuild guild, ClientState state, Model model)
{
var entity = new SocketStageChannel(guild.Discord, model.Id, guild);
entity.Update(state, model);
return entity;
}

internal override void Update(ClientState state, Model model)
{
base.Update(state, model);
}

internal void Update(StageInstance model, bool isLive = false)
{
this.Live = isLive;
if (isLive)
{
this.Topic = model.Topic;
this.PrivacyLevel = model.PrivacyLevel;
this.DiscoverableDisabled = model.DiscoverableDisabled;
}
else
{
this.Topic = null;
this.PrivacyLevel = null;
this.DiscoverableDisabled = null;
}
}

/// <inheritdoc/>
public async Task StartStageAsync(string topic, StagePrivacyLevel privacyLevel = StagePrivacyLevel.GuildOnly, RequestOptions options = null)
{
var args = new API.Rest.CreateStageInstanceParams()
{
ChannelId = this.Id,
Topic = topic,
PrivacyLevel = privacyLevel,
};

var model = await Discord.ApiClient.CreateStageInstanceAsync(args, options).ConfigureAwait(false);

this.Update(model, true);
}

/// <inheritdoc/>
public async Task ModifyInstanceAsync(Action<StageInstanceProperties> func, RequestOptions options = null)
{
var model = await ChannelHelper.ModifyAsync(this, Discord, func, options);

this.Update(model, true);
}

/// <inheritdoc/>
public async Task StopStageAsync(RequestOptions options = null)
{
await Discord.ApiClient.DeleteStageInstanceAsync(this.Id, options);

Update(null, false);
}

/// <inheritdoc/>
public Task RequestToSpeak(RequestOptions options = null)
{
var args = new API.Rest.ModifyVoiceStateParams()
{
ChannelId = this.Id,
RequestToSpeakTimestamp = DateTimeOffset.UtcNow
};
return Discord.ApiClient.ModifyMyVoiceState(this.Guild.Id, args, options);
}

/// <inheritdoc/>
public Task BecomeSpeakerAsync(RequestOptions options = null)
{
var args = new API.Rest.ModifyVoiceStateParams()
{
ChannelId = this.Id,
Suppressed = false
};
return Discord.ApiClient.ModifyMyVoiceState(this.Guild.Id, args, options);
}

/// <inheritdoc/>
public Task StopSpeakingAsync(RequestOptions options = null)
{
var args = new API.Rest.ModifyVoiceStateParams()
{
ChannelId = this.Id,
Suppressed = true
};
return Discord.ApiClient.ModifyMyVoiceState(this.Guild.Id, args, options);
}

/// <inheritdoc/>
public Task MoveToSpeaker(IGuildUser user, RequestOptions options = null)
{
var args = new API.Rest.ModifyVoiceStateParams()
{
ChannelId = this.Id,
Suppressed = false
};

return Discord.ApiClient.ModifyUserVoiceState(this.Guild.Id, user.Id, args);
}

/// <inheritdoc/>
public Task RemoveFromSpeaker(IGuildUser user, RequestOptions options = null)
{
var args = new API.Rest.ModifyVoiceStateParams()
{
ChannelId = this.Id,
Suppressed = true
};

return Discord.ApiClient.ModifyUserVoiceState(this.Guild.Id, user.Id, args);
}
}
}

+ 2
- 2
src/Discord.Net.WebSocket/Entities/Channels/SocketThreadChannel.cs View File

@@ -281,14 +281,14 @@ namespace Discord.WebSocket
/// <b>This method is not supported in threads.</b> /// <b>This method is not supported in threads.</b>
/// </remarks> /// </remarks>
public override OverwritePermissions? GetPermissionOverwrite(IRole role) public override OverwritePermissions? GetPermissionOverwrite(IRole role)
=> throw new NotImplementedException();
=> ParentChannel.GetPermissionOverwrite(role);


/// <inheritdoc/> /// <inheritdoc/>
/// <remarks> /// <remarks>
/// <b>This method is not supported in threads.</b> /// <b>This method is not supported in threads.</b>
/// </remarks> /// </remarks>
public override OverwritePermissions? GetPermissionOverwrite(IUser user) public override OverwritePermissions? GetPermissionOverwrite(IUser user)
=> throw new NotImplementedException();
=> ParentChannel.GetPermissionOverwrite(user);


/// <inheritdoc/> /// <inheritdoc/>
/// <remarks> /// <remarks>


+ 47
- 0
src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs View File

@@ -118,6 +118,8 @@ namespace Discord.WebSocket
public int? MaxMembers { get; private set; } public int? MaxMembers { get; private set; }
/// <inheritdoc /> /// <inheritdoc />
public int? MaxVideoChannelUsers { get; private set; } public int? MaxVideoChannelUsers { get; private set; }
/// <inheritdoc />
public NsfwLevel NsfwLevel { get; private set; }


/// <inheritdoc /> /// <inheritdoc />
public CultureInfo PreferredCulture { get; private set; } public CultureInfo PreferredCulture { get; private set; }
@@ -269,6 +271,14 @@ namespace Discord.WebSocket
public IReadOnlyCollection<SocketVoiceChannel> VoiceChannels public IReadOnlyCollection<SocketVoiceChannel> VoiceChannels
=> Channels.OfType<SocketVoiceChannel>().ToImmutableArray(); => Channels.OfType<SocketVoiceChannel>().ToImmutableArray();
/// <summary> /// <summary>
/// Gets a collection of all stage channels in this guild.
/// </summary>
/// <returns>
/// A read-only collection of stage channels found within this guild.
/// </returns>
public IReadOnlyCollection<SocketStageChannel> StageChannels
=> Channels.OfType<SocketStageChannel>().ToImmutableArray();
/// <summary>
/// Gets a collection of all category channels in this guild. /// Gets a collection of all category channels in this guild.
/// </summary> /// </summary>
/// <returns> /// <returns>
@@ -464,6 +474,7 @@ namespace Discord.WebSocket
SystemChannelFlags = model.SystemChannelFlags; SystemChannelFlags = model.SystemChannelFlags;
Description = model.Description; Description = model.Description;
PremiumSubscriptionCount = model.PremiumSubscriptionCount.GetValueOrDefault(); PremiumSubscriptionCount = model.PremiumSubscriptionCount.GetValueOrDefault();
NsfwLevel = model.NsfwLevel;
if (model.MaxPresences.IsSpecified) if (model.MaxPresences.IsSpecified)
MaxPresences = model.MaxPresences.Value ?? 25000; MaxPresences = model.MaxPresences.Value ?? 25000;
if (model.MaxMembers.IsSpecified) if (model.MaxMembers.IsSpecified)
@@ -630,6 +641,16 @@ namespace Discord.WebSocket
public SocketTextChannel GetTextChannel(ulong id) public SocketTextChannel GetTextChannel(ulong id)
=> GetChannel(id) as SocketTextChannel; => GetChannel(id) as SocketTextChannel;
/// <summary> /// <summary>
/// Gets a thread in this guild.
/// </summary>
/// <param name="id">The snowflake identifier for the thread.</param>
/// <returns>
/// A thread channel associated with the specified <paramref name="id" />; <see langword="null"/> if none is found.
/// </returns>
public SocketThreadChannel GetThreadChannel(ulong id)
=> GetChannel(id) as SocketThreadChannel;

/// <summary>
/// Gets a voice channel in this guild. /// Gets a voice channel in this guild.
/// </summary> /// </summary>
/// <param name="id">The snowflake identifier for the voice channel.</param> /// <param name="id">The snowflake identifier for the voice channel.</param>
@@ -639,6 +660,15 @@ namespace Discord.WebSocket
public SocketVoiceChannel GetVoiceChannel(ulong id) public SocketVoiceChannel GetVoiceChannel(ulong id)
=> GetChannel(id) as SocketVoiceChannel; => GetChannel(id) as SocketVoiceChannel;
/// <summary> /// <summary>
/// Gets a stage channel in this guild.
/// </summary>
/// <param name="id">The snowflake identifier for the stage channel.</param>
/// <returns>
/// A stage channel associated with the specified <paramref name="id" />; <see langword="null"/> if none is found.
/// </returns>
public SocketStageChannel GetStageChannel(ulong id)
=> GetChannel(id) as SocketStageChannel;
/// <summary>
/// Gets a category channel in this guild. /// Gets a category channel in this guild.
/// </summary> /// </summary>
/// <param name="id">The snowflake identifier for the category channel.</param> /// <param name="id">The snowflake identifier for the category channel.</param>
@@ -1326,6 +1356,12 @@ namespace Discord.WebSocket
Task<ITextChannel> IGuild.GetTextChannelAsync(ulong id, CacheMode mode, RequestOptions options) Task<ITextChannel> IGuild.GetTextChannelAsync(ulong id, CacheMode mode, RequestOptions options)
=> Task.FromResult<ITextChannel>(GetTextChannel(id)); => Task.FromResult<ITextChannel>(GetTextChannel(id));
/// <inheritdoc /> /// <inheritdoc />
Task<IThreadChannel> IGuild.GetThreadChannelAsync(ulong id, CacheMode mode, RequestOptions options)
=> Task.FromResult<IThreadChannel>(GetThreadChannel(id));
/// <inheritdoc />
Task<IReadOnlyCollection<IThreadChannel>> IGuild.GetThreadChannelsAsync(CacheMode mode, RequestOptions options)
=> Task.FromResult<IReadOnlyCollection<IThreadChannel>>(ThreadChannels);
/// <inheritdoc />
Task<IReadOnlyCollection<IVoiceChannel>> IGuild.GetVoiceChannelsAsync(CacheMode mode, RequestOptions options) Task<IReadOnlyCollection<IVoiceChannel>> IGuild.GetVoiceChannelsAsync(CacheMode mode, RequestOptions options)
=> Task.FromResult<IReadOnlyCollection<IVoiceChannel>>(VoiceChannels); => Task.FromResult<IReadOnlyCollection<IVoiceChannel>>(VoiceChannels);
/// <inheritdoc /> /// <inheritdoc />
@@ -1335,6 +1371,12 @@ namespace Discord.WebSocket
Task<IVoiceChannel> IGuild.GetVoiceChannelAsync(ulong id, CacheMode mode, RequestOptions options) Task<IVoiceChannel> IGuild.GetVoiceChannelAsync(ulong id, CacheMode mode, RequestOptions options)
=> Task.FromResult<IVoiceChannel>(GetVoiceChannel(id)); => Task.FromResult<IVoiceChannel>(GetVoiceChannel(id));
/// <inheritdoc /> /// <inheritdoc />
Task<IStageChannel> IGuild.GetStageChannelAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null)
=> Task.FromResult<IStageChannel>(GetStageChannel(id));
/// <inheritdoc />
Task<IReadOnlyCollection<IStageChannel>> IGuild.GetStageChannelsAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null)
=> Task.FromResult<IReadOnlyCollection<IStageChannel>>(StageChannels);
/// <inheritdoc />
Task<IVoiceChannel> IGuild.GetAFKChannelAsync(CacheMode mode, RequestOptions options) Task<IVoiceChannel> IGuild.GetAFKChannelAsync(CacheMode mode, RequestOptions options)
=> Task.FromResult<IVoiceChannel>(AFKChannel); => Task.FromResult<IVoiceChannel>(AFKChannel);
/// <inheritdoc /> /// <inheritdoc />
@@ -1436,6 +1478,9 @@ namespace Discord.WebSocket
/// <inheritdoc /> /// <inheritdoc />
async Task<IReadOnlyCollection<IWebhook>> IGuild.GetWebhooksAsync(RequestOptions options) async Task<IReadOnlyCollection<IWebhook>> IGuild.GetWebhooksAsync(RequestOptions options)
=> await GetWebhooksAsync(options).ConfigureAwait(false); => await GetWebhooksAsync(options).ConfigureAwait(false);
/// <inheritdoc />
async Task<IReadOnlyCollection<IApplicationCommand>> IGuild.GetApplicationCommandsAsync (RequestOptions options)
=> await GetApplicationCommandsAsync(options).ConfigureAwait(false);


void IDisposable.Dispose() void IDisposable.Dispose()
{ {
@@ -1443,5 +1488,7 @@ namespace Discord.WebSocket
_audioLock?.Dispose(); _audioLock?.Dispose();
_audioClient?.Dispose(); _audioClient?.Dispose();
} }

} }
} }

+ 49
- 12
src/Discord.Net.WebSocket/Entities/Interaction/Message Components/SocketMessageComponent.cs View File

@@ -4,12 +4,13 @@ using System.Threading.Tasks;
using Model = Discord.API.Interaction; using Model = Discord.API.Interaction;
using DataModel = Discord.API.MessageComponentInteractionData; using DataModel = Discord.API.MessageComponentInteractionData;
using Discord.Rest; using Discord.Rest;
using System.Collections.Generic;


namespace Discord.WebSocket namespace Discord.WebSocket
{ {
/// <summary>
/// Represents a Websocket-based interaction type for Message Components.
/// </summary>
/// <summary>
/// Represents a Websocket-based interaction type for Message Components.
/// </summary>
public class SocketMessageComponent : SocketInteraction public class SocketMessageComponent : SocketInteraction
{ {
/// <summary> /// <summary>
@@ -123,7 +124,7 @@ namespace Discord.WebSocket
}; };


if (ephemeral) if (ephemeral)
response.Data.Value.Flags = 64;
response.Data.Value.Flags = MessageFlags.Ephemeral;


await InteractionHelper.SendInteractionResponse(this.Discord, response, this.Id, Token, options); await InteractionHelper.SendInteractionResponse(this.Discord, response, this.Id, Token, options);
} }
@@ -149,8 +150,28 @@ namespace Discord.WebSocket
Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions), "A max of 100 user Ids are allowed."); Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions), "A max of 100 user Ids are allowed.");
} }


if (args.Embeds.IsSpecified)
Preconditions.AtMost(args.Embeds.Value?.Length ?? 0, 10, nameof(args.Embeds), "A max of 10 embeds are allowed.");
var embed = args.Embed;
var embeds = args.Embeds;

bool hasText = args.Content.IsSpecified ? !string.IsNullOrEmpty(args.Content.Value) : !string.IsNullOrEmpty(Message.Content);
bool hasEmbeds = (embed.IsSpecified && embed.Value != null) || (embeds.IsSpecified && embeds.Value?.Length > 0) || Message.Embeds.Any();

if (!hasText && !hasEmbeds)
Preconditions.NotNullOrEmpty(args.Content.IsSpecified ? args.Content.Value : string.Empty, nameof(args.Content));

var apiEmbeds = embed.IsSpecified || embeds.IsSpecified ? new List<API.Embed>() : null;

if (embed.IsSpecified && embed.Value != null)
{
apiEmbeds.Add(embed.Value.ToModel());
}

if (embeds.IsSpecified && embeds.Value != null)
{
apiEmbeds.AddRange(embeds.Value.Select(x => x.ToModel()));
}

Preconditions.AtMost(apiEmbeds?.Count ?? 0, 10, nameof(args.Embeds), "A max of 10 embeds are allowed.");


// check that user flag and user Id list are exclusive, same with role flag and role Id list // check that user flag and user Id list are exclusive, same with role flag and role Id list
if (args.AllowedMentions.IsSpecified && args.AllowedMentions.Value != null && args.AllowedMentions.Value.AllowedTypes.HasValue) if (args.AllowedMentions.IsSpecified && args.AllowedMentions.Value != null && args.AllowedMentions.Value.AllowedTypes.HasValue)
@@ -176,11 +197,11 @@ namespace Discord.WebSocket
{ {
Content = args.Content, Content = args.Content,
AllowedMentions = args.AllowedMentions.IsSpecified ? args.AllowedMentions.Value?.ToModel() : Optional<API.AllowedMentions>.Unspecified, AllowedMentions = args.AllowedMentions.IsSpecified ? args.AllowedMentions.Value?.ToModel() : Optional<API.AllowedMentions>.Unspecified,
Embeds = args.Embeds.IsSpecified ? args.Embeds.Value?.Select(x => x.ToModel()).ToArray() : Optional<API.Embed[]>.Unspecified,
Embeds = apiEmbeds?.ToArray() ?? Optional<API.Embed[]>.Unspecified,
Components = args.Components.IsSpecified Components = args.Components.IsSpecified
? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray()
: Optional<API.ActionRowComponent[]>.Unspecified, : Optional<API.ActionRowComponent[]>.Unspecified,
Flags = args.Flags.IsSpecified ? (int?)args.Flags.Value ?? Optional<int>.Unspecified : Optional<int>.Unspecified
Flags = args.Flags.IsSpecified ? args.Flags.Value ?? Optional<MessageFlags>.Unspecified : Optional<MessageFlags>.Unspecified
} }
}; };


@@ -213,27 +234,43 @@ namespace Discord.WebSocket
AllowedMentions = allowedMentions?.ToModel() ?? Optional<API.AllowedMentions>.Unspecified, AllowedMentions = allowedMentions?.ToModel() ?? Optional<API.AllowedMentions>.Unspecified,
IsTTS = isTTS, IsTTS = isTTS,
Embeds = embeds?.Select(x => x.ToModel()).ToArray() ?? Optional<API.Embed[]>.Unspecified, Embeds = embeds?.Select(x => x.ToModel()).ToArray() ?? Optional<API.Embed[]>.Unspecified,
Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified
Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified,
}; };


if (ephemeral) if (ephemeral)
args.Flags = 64;
args.Flags = MessageFlags.Ephemeral;


return await InteractionHelper.SendFollowupAsync(Discord.Rest, args, Token, Channel, options); return await InteractionHelper.SendFollowupAsync(Discord.Rest, args, Token, Channel, options);
} }


/// <summary> /// <summary>
/// Acknowledges this interaction with the <see cref="InteractionResponseType.DeferredUpdateMessage"/>.
/// Defers an interaction and responds with type 5 (<see cref="InteractionResponseType.DeferredChannelMessageWithSource"/>)
/// </summary> /// </summary>
/// <param name="ephemeral"><see langword="true"/> to send this message ephemerally, otherwise <see langword="false"/>.</param>
/// <param name="options">The request options for this async request.</param> /// <param name="options">The request options for this async request.</param>
/// <returns> /// <returns>
/// A task that represents the asynchronous operation of acknowledging the interaction. /// A task that represents the asynchronous operation of acknowledging the interaction.
/// </returns> /// </returns>
public override Task DeferAsync(RequestOptions options = null)
public Task DeferLoadingAsync(bool ephemeral = false, RequestOptions options = null)
{
var response = new API.InteractionResponse()
{
Type = InteractionResponseType.DeferredChannelMessageWithSource,
Data = ephemeral ? new API.InteractionCallbackData() { Flags = MessageFlags.Ephemeral } : Optional<API.InteractionCallbackData>.Unspecified

};

return Discord.Rest.ApiClient.CreateInteractionResponse(response, this.Id, this.Token, options);
}

/// <inheritdoc/>
public override Task DeferAsync(bool ephemeral = false, RequestOptions options = null)
{ {
var response = new API.InteractionResponse() var response = new API.InteractionResponse()
{ {
Type = InteractionResponseType.DeferredUpdateMessage, Type = InteractionResponseType.DeferredUpdateMessage,
Data = ephemeral ? new API.InteractionCallbackData() { Flags = MessageFlags.Ephemeral } : Optional<API.InteractionCallbackData>.Unspecified

}; };


return Discord.Rest.ApiClient.CreateInteractionResponse(response, this.Id, this.Token, options); return Discord.Rest.ApiClient.CreateInteractionResponse(response, this.Id, this.Token, options);


+ 5
- 9
src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketSlashCommand.cs View File

@@ -106,7 +106,7 @@ namespace Discord.WebSocket
}; };


if (ephemeral) if (ephemeral)
response.Data.Value.Flags = 64;
response.Data.Value.Flags = MessageFlags.Ephemeral;


await InteractionHelper.SendInteractionResponse(this.Discord, response, this.Id, Token, options); await InteractionHelper.SendInteractionResponse(this.Discord, response, this.Id, Token, options);
} }
@@ -141,22 +141,18 @@ namespace Discord.WebSocket
}; };


if (ephemeral) if (ephemeral)
args.Flags = 64;
args.Flags = MessageFlags.Ephemeral;


return await InteractionHelper.SendFollowupAsync(Discord.Rest, args, Token, Channel, options); return await InteractionHelper.SendFollowupAsync(Discord.Rest, args, Token, Channel, options);
} }


/// <summary>
/// Acknowledges this interaction with the <see cref="InteractionResponseType.DeferredChannelMessageWithSource"/>.
/// </summary>
/// <returns>
/// A task that represents the asynchronous operation of acknowledging the interaction.
/// </returns>
public override Task DeferAsync(RequestOptions options = null)
/// <inheritdoc/>
public override Task DeferAsync(bool ephemeral = false, RequestOptions options = null)
{ {
var response = new API.InteractionResponse var response = new API.InteractionResponse
{ {
Type = InteractionResponseType.DeferredChannelMessageWithSource, Type = InteractionResponseType.DeferredChannelMessageWithSource,
Data = ephemeral ? new API.InteractionCallbackData() { Flags = MessageFlags.Ephemeral } : Optional<API.InteractionCallbackData>.Unspecified
}; };


return Discord.Rest.ApiClient.CreateInteractionResponse(response, this.Id, this.Token, options); return Discord.Rest.ApiClient.CreateInteractionResponse(response, this.Id, this.Token, options);


+ 3
- 3
src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketSlashCommandDataOption.cs View File

@@ -86,9 +86,9 @@ namespace Discord.WebSocket
break; break;
case ApplicationCommandOptionType.Integer: case ApplicationCommandOptionType.Integer:
{ {
if (model.Value.Value is int val)
if (model.Value.Value is long val)
this.Value = val; this.Value = val;
else if (int.TryParse(model.Value.Value.ToString(), out int res))
else if (long.TryParse(model.Value.Value.ToString(), out long res))
this.Value = res; this.Value = res;
} }
break; break;
@@ -109,7 +109,7 @@ namespace Discord.WebSocket
} }
break; break;
} }
} }


this.Options = model.Options.IsSpecified this.Options = model.Options.IsSpecified


+ 19
- 2
src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs View File

@@ -156,20 +156,37 @@ namespace Discord.WebSocket
/// A task that represents the asynchronous operation of acknowledging the interaction. /// A task that represents the asynchronous operation of acknowledging the interaction.
/// </returns> /// </returns>
[Obsolete("This method deprecated, please use DeferAsync instead")] [Obsolete("This method deprecated, please use DeferAsync instead")]
public Task AcknowledgeAsync(RequestOptions options = null) => DeferAsync(options);
public Task AcknowledgeAsync(RequestOptions options = null) => DeferAsync(options: options);


/// <summary> /// <summary>
/// Acknowledges this interaction. /// Acknowledges this interaction.
/// </summary> /// </summary>
/// <param name="ephemeral"><see langword="true"/> to send this message ephemerally, otherwise <see langword="false"/>.</param>
/// <param name="options">The request options for this async request.</param>
/// <returns> /// <returns>
/// A task that represents the asynchronous operation of acknowledging the interaction. /// A task that represents the asynchronous operation of acknowledging the interaction.
/// </returns> /// </returns>
public abstract Task DeferAsync(RequestOptions options = null);
public abstract Task DeferAsync(bool ephemeral = false, RequestOptions options = null);


private bool CheckToken() private bool CheckToken()
{ {
// Tokens last for 15 minutes according to https://discord.com/developers/docs/interactions/slash-commands#responding-to-an-interaction // Tokens last for 15 minutes according to https://discord.com/developers/docs/interactions/slash-commands#responding-to-an-interaction
return (DateTime.UtcNow - this.CreatedAt.UtcDateTime).TotalMinutes <= 15d; return (DateTime.UtcNow - this.CreatedAt.UtcDateTime).TotalMinutes <= 15d;
} }

// IDiscordInteraction

/// <inheritdoc/>
async Task<IUserMessage> IDiscordInteraction.FollowupAsync (string text, Embed[] embeds, bool isTTS, bool ephemeral, AllowedMentions allowedMentions,
RequestOptions options, MessageComponent component, Embed embed)
=> await FollowupAsync(text, embeds, isTTS, ephemeral, allowedMentions, options, component, embed).ConfigureAwait(false);

/// <inheritdoc/>
async Task<IUserMessage> IDiscordInteraction.GetOriginalResponseAsync (RequestOptions options)
=> await GetOriginalResponseAsync(options).ConfigureAwait(false);

/// <inheritdoc/>
async Task<IUserMessage> IDiscordInteraction.ModifyOriginalResponseAsync (Action<MessageProperties> func, RequestOptions options)
=> await ModifyOriginalResponseAsync(func, options).ConfigureAwait(false);
} }
} }

+ 4
- 1
src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs View File

@@ -122,7 +122,10 @@ namespace Discord.WebSocket
} }
internal static SocketMessage Create(DiscordSocketClient discord, ClientState state, SocketUser author, ISocketMessageChannel channel, Model model) internal static SocketMessage Create(DiscordSocketClient discord, ClientState state, SocketUser author, ISocketMessageChannel channel, Model model)
{ {
if (model.Type == MessageType.Default || model.Type == MessageType.Reply)
if (model.Type == MessageType.Default ||
model.Type == MessageType.Reply ||
model.Type == MessageType.ApplicationCommand ||
model.Type == MessageType.ThreadStarterMessage)
return SocketUserMessage.Create(discord, state, author, channel, model); return SocketUserMessage.Create(discord, state, author, channel, model);
else else
return SocketSystemMessage.Create(discord, state, author, channel, model); return SocketSystemMessage.Create(discord, state, author, channel, model);


+ 3
- 1
src/Discord.Net.WebSocket/Entities/Users/SocketGlobalUser.cs View File

@@ -12,6 +12,8 @@ namespace Discord.WebSocket
public override string Username { get; internal set; } public override string Username { get; internal set; }
public override ushort DiscriminatorValue { get; internal set; } public override ushort DiscriminatorValue { get; internal set; }
public override string AvatarId { get; internal set; } public override string AvatarId { get; internal set; }
public override string BannerId { get; internal set; }
public override Color? AccentColor { get; internal set; }
internal override SocketPresence Presence { get; set; } internal override SocketPresence Presence { get; set; }


public override bool IsWebhook => false; public override bool IsWebhook => false;
@@ -47,7 +49,7 @@ namespace Discord.WebSocket
discord.RemoveUser(Id); discord.RemoveUser(Id);
} }
} }
internal void Update(ClientState state, PresenceModel model) internal void Update(ClientState state, PresenceModel model)
{ {
Presence = SocketPresence.Create(model); Presence = SocketPresence.Create(model);


+ 7
- 0
src/Discord.Net.WebSocket/Entities/Users/SocketGroupUser.cs View File

@@ -1,3 +1,4 @@
using System;
using System.Diagnostics; using System.Diagnostics;
using Model = Discord.API.User; using Model = Discord.API.User;


@@ -28,6 +29,10 @@ namespace Discord.WebSocket
/// <inheritdoc /> /// <inheritdoc />
public override string AvatarId { get { return GlobalUser.AvatarId; } internal set { GlobalUser.AvatarId = value; } } public override string AvatarId { get { return GlobalUser.AvatarId; } internal set { GlobalUser.AvatarId = value; } }
/// <inheritdoc /> /// <inheritdoc />
public override string BannerId { get { return GlobalUser.BannerId; } internal set { GlobalUser.BannerId = value; } }
/// <inheritdoc />
public override Color? AccentColor { get { return GlobalUser.AccentColor; } internal set { GlobalUser.AccentColor = value; } }
/// <inheritdoc />
internal override SocketPresence Presence { get { return GlobalUser.Presence; } set { GlobalUser.Presence = value; } } internal override SocketPresence Presence { get { return GlobalUser.Presence; } set { GlobalUser.Presence = value; } }


/// <inheritdoc /> /// <inheritdoc />
@@ -66,5 +71,7 @@ namespace Discord.WebSocket
string IVoiceState.VoiceSessionId => null; string IVoiceState.VoiceSessionId => null;
/// <inheritdoc /> /// <inheritdoc />
bool IVoiceState.IsStreaming => false; bool IVoiceState.IsStreaming => false;
/// <inheritdoc />
DateTimeOffset? IVoiceState.RequestToSpeakTimestamp => null;
} }
} }

+ 10
- 1
src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs View File

@@ -38,6 +38,11 @@ namespace Discord.WebSocket
public override ushort DiscriminatorValue { get { return GlobalUser.DiscriminatorValue; } internal set { GlobalUser.DiscriminatorValue = value; } } public override ushort DiscriminatorValue { get { return GlobalUser.DiscriminatorValue; } internal set { GlobalUser.DiscriminatorValue = value; } }
/// <inheritdoc /> /// <inheritdoc />
public override string AvatarId { get { return GlobalUser.AvatarId; } internal set { GlobalUser.AvatarId = value; } } public override string AvatarId { get { return GlobalUser.AvatarId; } internal set { GlobalUser.AvatarId = value; } }
/// <inheritdoc />
public override string BannerId { get { return GlobalUser.BannerId; } internal set { GlobalUser.BannerId = value; } }
/// <inheritdoc />
public override Color? AccentColor { get { return GlobalUser.AccentColor; } internal set { GlobalUser.AccentColor = value; } }

/// <inheritdoc /> /// <inheritdoc />
public GuildPermissions GuildPermissions => new GuildPermissions(Permissions.ResolveGuild(Guild, this)); public GuildPermissions GuildPermissions => new GuildPermissions(Permissions.ResolveGuild(Guild, this));
internal override SocketPresence Presence { get; set; } internal override SocketPresence Presence { get; set; }
@@ -57,7 +62,11 @@ namespace Discord.WebSocket
/// <inheritdoc /> /// <inheritdoc />
public bool IsStreaming => VoiceState?.IsStreaming ?? false; public bool IsStreaming => VoiceState?.IsStreaming ?? false;
/// <inheritdoc /> /// <inheritdoc />
public DateTimeOffset? RequestToSpeakTimestamp => VoiceState?.RequestToSpeakTimestamp ?? null;
/// <inheritdoc />
public bool? IsPending { get; private set; } public bool? IsPending { get; private set; }

/// <inheritdoc /> /// <inheritdoc />
public DateTimeOffset? JoinedAt => DateTimeUtils.FromTicks(_joinedAtTicks); public DateTimeOffset? JoinedAt => DateTimeUtils.FromTicks(_joinedAtTicks);
/// <summary> /// <summary>
@@ -87,7 +96,7 @@ namespace Discord.WebSocket
/// Returns the position of the user within the role hierarchy. /// Returns the position of the user within the role hierarchy.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// The returned value equal to the position of the highest role the user has, or
/// The returned value equal to the position of the highest role the user has, or
/// <see cref="int.MaxValue"/> if user is the server owner. /// <see cref="int.MaxValue"/> if user is the server owner.
/// </remarks> /// </remarks>
public int Hierarchy public int Hierarchy


+ 4
- 0
src/Discord.Net.WebSocket/Entities/Users/SocketSelfUser.cs View File

@@ -29,6 +29,10 @@ namespace Discord.WebSocket
/// <inheritdoc /> /// <inheritdoc />
public override string AvatarId { get { return GlobalUser.AvatarId; } internal set { GlobalUser.AvatarId = value; } } public override string AvatarId { get { return GlobalUser.AvatarId; } internal set { GlobalUser.AvatarId = value; } }
/// <inheritdoc /> /// <inheritdoc />
public override string BannerId { get { return GlobalUser.BannerId; } internal set { GlobalUser.BannerId = value; } }
/// <inheritdoc />
public override Color? AccentColor { get { return GlobalUser.AccentColor; } internal set { GlobalUser.AccentColor = value; } }
/// <inheritdoc />
internal override SocketPresence Presence { get { return GlobalUser.Presence; } set { GlobalUser.Presence = value; } } internal override SocketPresence Presence { get { return GlobalUser.Presence; } set { GlobalUser.Presence = value; } }
/// <inheritdoc /> /// <inheritdoc />
public UserProperties Flags { get; internal set; } public UserProperties Flags { get; internal set; }


+ 19
- 1
src/Discord.Net.WebSocket/Entities/Users/SocketThreadUser.cs View File

@@ -36,7 +36,7 @@ namespace Discord.WebSocket


/// <inheritdoc/> /// <inheritdoc/>
public string Nickname public string Nickname
=> GuildUser.Nickname;
=> GuildUser.Nickname;


/// <inheritdoc/> /// <inheritdoc/>
public DateTimeOffset? PremiumSince public DateTimeOffset? PremiumSince
@@ -53,6 +53,20 @@ namespace Discord.WebSocket
internal set => GuildUser.AvatarId = value; internal set => GuildUser.AvatarId = value;
} }


/// <inheritdoc/>
public override string BannerId
{
get => GuildUser.BannerId;
internal set => GuildUser.BannerId = value;
}

/// <inheritdoc/>
public override Color? AccentColor
{
get => GuildUser.AccentColor;
internal set => GuildUser.AccentColor = value;
}

/// <inheritdoc/> /// <inheritdoc/>
public override ushort DiscriminatorValue public override ushort DiscriminatorValue
{ {
@@ -110,6 +124,10 @@ namespace Discord.WebSocket
public bool IsStreaming public bool IsStreaming
=> GuildUser.IsStreaming; => GuildUser.IsStreaming;


/// <inheritdoc/>
public DateTimeOffset? RequestToSpeakTimestamp
=> GuildUser.RequestToSpeakTimestamp;

private SocketGuildUser GuildUser { get; set; } private SocketGuildUser GuildUser { get; set; }


internal SocketThreadUser(SocketGuild guild, SocketThreadChannel thread, SocketGuildUser member) internal SocketThreadUser(SocketGuild guild, SocketThreadChannel thread, SocketGuildUser member)


+ 8
- 1
src/Discord.Net.WebSocket/Entities/Users/SocketUnknownUser.cs View File

@@ -19,9 +19,16 @@ namespace Discord.WebSocket
public override ushort DiscriminatorValue { get; internal set; } public override ushort DiscriminatorValue { get; internal set; }
/// <inheritdoc /> /// <inheritdoc />
public override string AvatarId { get; internal set; } public override string AvatarId { get; internal set; }

/// <inheritdoc />
public override string BannerId { get; internal set; }

/// <inheritdoc />
public override Color? AccentColor { get; internal set; }

/// <inheritdoc /> /// <inheritdoc />
public override bool IsBot { get; internal set; } public override bool IsBot { get; internal set; }
/// <inheritdoc /> /// <inheritdoc />
public override bool IsWebhook => false; public override bool IsWebhook => false;
/// <inheritdoc /> /// <inheritdoc />


+ 18
- 0
src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs View File

@@ -25,6 +25,10 @@ namespace Discord.WebSocket
/// <inheritdoc /> /// <inheritdoc />
public abstract string AvatarId { get; internal set; } public abstract string AvatarId { get; internal set; }
/// <inheritdoc /> /// <inheritdoc />
public abstract string BannerId { get; internal set; }
/// <inheritdoc />
public abstract Color? AccentColor { get; internal set; }
/// <inheritdoc />
public abstract bool IsWebhook { get; } public abstract bool IsWebhook { get; }
/// <inheritdoc /> /// <inheritdoc />
public UserProperties? PublicFlags { get; private set; } public UserProperties? PublicFlags { get; private set; }
@@ -64,6 +68,16 @@ namespace Discord.WebSocket
AvatarId = model.Avatar.Value; AvatarId = model.Avatar.Value;
hasChanges = true; hasChanges = true;
} }
if (model.Banner.IsSpecified && model.Banner.Value != BannerId)
{
BannerId = model.Banner.Value;
hasChanges = true;
}
if (model.AccentColor.IsSpecified && model.AccentColor.Value != AccentColor?.RawValue)
{
AccentColor = model.AccentColor.Value;
hasChanges = true;
}
if (model.Discriminator.IsSpecified) if (model.Discriminator.IsSpecified)
{ {
var newVal = ushort.Parse(model.Discriminator.Value, NumberStyles.None, CultureInfo.InvariantCulture); var newVal = ushort.Parse(model.Discriminator.Value, NumberStyles.None, CultureInfo.InvariantCulture);
@@ -99,6 +113,10 @@ namespace Discord.WebSocket
public string GetAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128) public string GetAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128)
=> CDN.GetUserAvatarUrl(Id, AvatarId, size, format); => CDN.GetUserAvatarUrl(Id, AvatarId, size, format);


/// <inheritdoc />
public string GetBannerUrl(ImageFormat format = ImageFormat.Auto, ushort size = 256)
=> CDN.GetUserBannerUrl(Id, BannerId, size, format);

/// <inheritdoc /> /// <inheritdoc />
public string GetDefaultAvatarUrl() public string GetDefaultAvatarUrl()
=> CDN.GetDefaultUserAvatarUrl(DiscriminatorValue); => CDN.GetDefaultUserAvatarUrl(DiscriminatorValue);


+ 7
- 3
src/Discord.Net.WebSocket/Entities/Users/SocketVoiceState.cs View File

@@ -13,7 +13,7 @@ namespace Discord.WebSocket
/// <summary> /// <summary>
/// Initializes a default <see cref="SocketVoiceState"/> with everything set to <c>null</c> or <c>false</c>. /// Initializes a default <see cref="SocketVoiceState"/> with everything set to <c>null</c> or <c>false</c>.
/// </summary> /// </summary>
public static readonly SocketVoiceState Default = new SocketVoiceState(null, null, false, false, false, false, false, false);
public static readonly SocketVoiceState Default = new SocketVoiceState(null, null, null, false, false, false, false, false, false);


[Flags] [Flags]
private enum Flags : byte private enum Flags : byte
@@ -35,6 +35,8 @@ namespace Discord.WebSocket
public SocketVoiceChannel VoiceChannel { get; } public SocketVoiceChannel VoiceChannel { get; }
/// <inheritdoc /> /// <inheritdoc />
public string VoiceSessionId { get; } public string VoiceSessionId { get; }
/// <inheritdoc/>
public DateTimeOffset? RequestToSpeakTimestamp { get; private set; }


/// <inheritdoc /> /// <inheritdoc />
public bool IsMuted => (_voiceStates & Flags.Muted) != 0; public bool IsMuted => (_voiceStates & Flags.Muted) != 0;
@@ -48,11 +50,13 @@ namespace Discord.WebSocket
public bool IsSelfDeafened => (_voiceStates & Flags.SelfDeafened) != 0; public bool IsSelfDeafened => (_voiceStates & Flags.SelfDeafened) != 0;
/// <inheritdoc /> /// <inheritdoc />
public bool IsStreaming => (_voiceStates & Flags.SelfStream) != 0; public bool IsStreaming => (_voiceStates & Flags.SelfStream) != 0;


internal SocketVoiceState(SocketVoiceChannel voiceChannel, string sessionId, bool isSelfMuted, bool isSelfDeafened, bool isMuted, bool isDeafened, bool isSuppressed, bool isStream)
internal SocketVoiceState(SocketVoiceChannel voiceChannel, DateTimeOffset? requestToSpeak, string sessionId, bool isSelfMuted, bool isSelfDeafened, bool isMuted, bool isDeafened, bool isSuppressed, bool isStream)
{ {
VoiceChannel = voiceChannel; VoiceChannel = voiceChannel;
VoiceSessionId = sessionId; VoiceSessionId = sessionId;
RequestToSpeakTimestamp = requestToSpeak;


Flags voiceStates = Flags.Normal; Flags voiceStates = Flags.Normal;
if (isSelfMuted) if (isSelfMuted)
@@ -71,7 +75,7 @@ namespace Discord.WebSocket
} }
internal static SocketVoiceState Create(SocketVoiceChannel voiceChannel, Model model) internal static SocketVoiceState Create(SocketVoiceChannel voiceChannel, Model model)
{ {
return new SocketVoiceState(voiceChannel, model.SessionId, model.SelfMute, model.SelfDeaf, model.Mute, model.Deaf, model.Suppress, model.SelfStream);
return new SocketVoiceState(voiceChannel, model.RequestToSpeakTimestamp.IsSpecified ? model.RequestToSpeakTimestamp.Value : null, model.SessionId, model.SelfMute, model.SelfDeaf, model.Mute, model.Deaf, model.Suppress, model.SelfStream);
} }


/// <summary> /// <summary>


+ 19
- 0
src/Discord.Net.WebSocket/Entities/Users/SocketWebhookUser.cs View File

@@ -24,6 +24,23 @@ namespace Discord.WebSocket
public override ushort DiscriminatorValue { get; internal set; } public override ushort DiscriminatorValue { get; internal set; }
/// <inheritdoc /> /// <inheritdoc />
public override string AvatarId { get; internal set; } public override string AvatarId { get; internal set; }

/// <inheritdoc />
/// <exception cref="NotSupportedException">Webhook users does not support banners.</exception>
public override string BannerId
{
get => throw new NotSupportedException("Webhook users does not support banners.");
internal set => throw new NotSupportedException("Webhook users does not support banners.");
}

/// <inheritdoc />
/// <exception cref="NotSupportedException">Webhook users does not support accent colors.</exception>
public override Color? AccentColor
{
get => throw new NotSupportedException("Webhook users does not support accent colors.");
internal set => throw new NotSupportedException("Webhook users does not support accent colors.");
}

/// <inheritdoc /> /// <inheritdoc />
public override bool IsBot { get; internal set; } public override bool IsBot { get; internal set; }


@@ -138,5 +155,7 @@ namespace Discord.WebSocket
string IVoiceState.VoiceSessionId => null; string IVoiceState.VoiceSessionId => null;
/// <inheritdoc /> /// <inheritdoc />
bool IVoiceState.IsStreaming => false; bool IVoiceState.IsStreaming => false;
/// <inheritdoc />
DateTimeOffset? IVoiceState.RequestToSpeakTimestamp => null;
} }
} }

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

@@ -6,7 +6,7 @@
<RootNamespace>Discord.Webhook</RootNamespace> <RootNamespace>Discord.Webhook</RootNamespace>
<Description>A core Discord.Net Labs library containing the Webhook client and models.</Description> <Description>A core Discord.Net Labs library containing the Webhook client and models.</Description>
<TargetFrameworks>netstandard2.0;netstandard2.1</TargetFrameworks> <TargetFrameworks>netstandard2.0;netstandard2.1</TargetFrameworks>
<Version>2.3.4</Version>
<Version>3.0.0-pre</Version>
<PackageId>Discord.Net.Labs.Webhook</PackageId> <PackageId>Discord.Net.Labs.Webhook</PackageId>
<PackageProjectUrl>https://github.com/Discord-Net-Labs/Discord.Net-Labs</PackageProjectUrl> <PackageProjectUrl>https://github.com/Discord-Net-Labs/Discord.Net-Labs</PackageProjectUrl>
<RepositoryUrl>https://github.com/Discord-Net-Labs/Discord.Net-Labs</RepositoryUrl> <RepositoryUrl>https://github.com/Discord-Net-Labs/Discord.Net-Labs</RepositoryUrl>


+ 6
- 1
src/Discord.Net.Webhook/Discord.Net.Webhook.xml View File

@@ -31,7 +31,7 @@
<exception cref="T:System.ArgumentException">Thrown if the <paramref name="webhookUrl"/> is an invalid format.</exception> <exception cref="T:System.ArgumentException">Thrown if the <paramref name="webhookUrl"/> is an invalid format.</exception>
<exception cref="T:System.ArgumentNullException">Thrown if the <paramref name="webhookUrl"/> is null or whitespace.</exception> <exception cref="T:System.ArgumentNullException">Thrown if the <paramref name="webhookUrl"/> is null or whitespace.</exception>
</member> </member>
<member name="M:Discord.Webhook.DiscordWebhookClient.SendMessageAsync(System.String,System.Boolean,System.Collections.Generic.IEnumerable{Discord.Embed},System.String,System.String,Discord.RequestOptions,Discord.AllowedMentions)">
<member name="M:Discord.Webhook.DiscordWebhookClient.SendMessageAsync(System.String,System.Boolean,System.Collections.Generic.IEnumerable{Discord.Embed},System.String,System.String,Discord.RequestOptions,Discord.AllowedMentions,Discord.MessageComponent)">
<summary> Sends a message to the channel for this webhook. </summary> <summary> Sends a message to the channel for this webhook. </summary>
<returns> Returns the ID of the created message. </returns> <returns> Returns the ID of the created message. </returns>
</member> </member>
@@ -99,6 +99,11 @@
Gets or sets the allowed mentions of the message. Gets or sets the allowed mentions of the message.
</summary> </summary>
</member> </member>
<member name="P:Discord.Webhook.WebhookMessageProperties.Components">
<summary>
Gets or sets the components that the message should display.
</summary>
</member>
<member name="M:Discord.Webhook.WebhookClientHelper.GetWebhookAsync(Discord.Webhook.DiscordWebhookClient,System.UInt64)"> <member name="M:Discord.Webhook.WebhookClientHelper.GetWebhookAsync(Discord.Webhook.DiscordWebhookClient,System.UInt64)">
<exception cref="T:System.InvalidOperationException">Could not find a webhook with the supplied credentials.</exception> <exception cref="T:System.InvalidOperationException">Could not find a webhook with the supplied credentials.</exception>
</member> </member>


+ 2
- 2
src/Discord.Net.Webhook/DiscordWebhookClient.cs View File

@@ -88,8 +88,8 @@ namespace Discord.Webhook
/// <summary> Sends a message to the channel for this webhook. </summary> /// <summary> Sends a message to the channel for this webhook. </summary>
/// <returns> Returns the ID of the created message. </returns> /// <returns> Returns the ID of the created message. </returns>
public Task<ulong> SendMessageAsync(string text = null, bool isTTS = false, IEnumerable<Embed> embeds = null, public Task<ulong> SendMessageAsync(string text = null, bool isTTS = false, IEnumerable<Embed> embeds = null,
string username = null, string avatarUrl = null, RequestOptions options = null, AllowedMentions allowedMentions = null)
=> WebhookClientHelper.SendMessageAsync(this, text, isTTS, embeds, username, avatarUrl, allowedMentions, options);
string username = null, string avatarUrl = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageComponent component = null)
=> WebhookClientHelper.SendMessageAsync(this, text, isTTS, embeds, username, avatarUrl, allowedMentions, options, component);


/// <summary> /// <summary>
/// Modifies a message posted using this webhook. /// Modifies a message posted using this webhook.


+ 4
- 0
src/Discord.Net.Webhook/Entities/Messages/WebhookMessageProperties.cs View File

@@ -22,5 +22,9 @@ namespace Discord.Webhook
/// Gets or sets the allowed mentions of the message. /// Gets or sets the allowed mentions of the message.
/// </summary> /// </summary>
public Optional<AllowedMentions> AllowedMentions { get; set; } public Optional<AllowedMentions> AllowedMentions { get; set; }
/// <summary>
/// Gets or sets the components that the message should display.
/// </summary>
public Optional<MessageComponent> Components { get; set; }
} }
} }

+ 3
- 0
src/Discord.Net.Webhook/Entities/Webhooks/RestInternalWebhook.cs View File

@@ -17,6 +17,7 @@ namespace Discord.Webhook
public string Name { get; private set; } public string Name { get; private set; }
public string AvatarId { get; private set; } public string AvatarId { get; private set; }
public ulong? GuildId { get; private set; } public ulong? GuildId { get; private set; }
public ulong? ApplicationId { get; private set; }


public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id);


@@ -44,6 +45,8 @@ namespace Discord.Webhook
GuildId = model.GuildId.Value; GuildId = model.GuildId.Value;
if (model.Name.IsSpecified) if (model.Name.IsSpecified)
Name = model.Name.Value; Name = model.Name.Value;

ApplicationId = model.ApplicationId;
} }


public string GetAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128) public string GetAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128)


+ 5
- 2
src/Discord.Net.Webhook/WebhookClientHelper.cs View File

@@ -21,7 +21,7 @@ namespace Discord.Webhook
return RestInternalWebhook.Create(client, model); return RestInternalWebhook.Create(client, model);
} }
public static async Task<ulong> SendMessageAsync(DiscordWebhookClient client, public static async Task<ulong> SendMessageAsync(DiscordWebhookClient client,
string text, bool isTTS, IEnumerable<Embed> embeds, string username, string avatarUrl, AllowedMentions allowedMentions, RequestOptions options)
string text, bool isTTS, IEnumerable<Embed> embeds, string username, string avatarUrl, AllowedMentions allowedMentions, RequestOptions options, MessageComponent component)
{ {
var args = new CreateWebhookMessageParams var args = new CreateWebhookMessageParams
{ {
@@ -37,6 +37,8 @@ namespace Discord.Webhook
args.AvatarUrl = avatarUrl; args.AvatarUrl = avatarUrl;
if (allowedMentions != null) if (allowedMentions != null)
args.AllowedMentions = allowedMentions.ToModel(); args.AllowedMentions = allowedMentions.ToModel();
if (component != null)
args.Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray();


var model = await client.ApiClient.CreateWebhookMessageAsync(client.Webhook.Id, args, options: options).ConfigureAwait(false); var model = await client.ApiClient.CreateWebhookMessageAsync(client.Webhook.Id, args, options: options).ConfigureAwait(false);
return model.Id; return model.Id;
@@ -83,7 +85,8 @@ namespace Discord.Webhook
: Optional.Create<API.Embed[]>(), : Optional.Create<API.Embed[]>(),
AllowedMentions = args.AllowedMentions.IsSpecified AllowedMentions = args.AllowedMentions.IsSpecified
? args.AllowedMentions.Value.ToModel() ? args.AllowedMentions.Value.ToModel()
: Optional.Create<API.AllowedMentions>()
: Optional.Create<API.AllowedMentions>(),
Components = args.Components.IsSpecified ? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() : Optional<API.ActionRowComponent[]>.Unspecified,
}; };


await client.ApiClient.ModifyWebhookMessageAsync(client.Webhook.Id, messageId, apiArgs, options) await client.ApiClient.ModifyWebhookMessageAsync(client.Webhook.Id, messageId, apiArgs, options)


+ 10
- 10
src/Discord.Net/Discord.Net.nuspec View File

@@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata> <metadata>
<id>Discord.Net.Labs</id> <id>Discord.Net.Labs</id>
<version>2.4.9$suffix$</version>
<version>3.0.2-pre$suffix$</version>
<title>Discord.Net Labs</title> <title>Discord.Net Labs</title>
<authors>Discord.Net Contributors</authors> <authors>Discord.Net Contributors</authors>
<owners>quinchs</owners> <owners>quinchs</owners>
@@ -14,23 +14,23 @@
<iconUrl>https://avatars.githubusercontent.com/u/84047264</iconUrl> <iconUrl>https://avatars.githubusercontent.com/u/84047264</iconUrl>
<dependencies> <dependencies>
<group targetFramework="net461"> <group targetFramework="net461">
<dependency id="Discord.Net.Labs.Core" version="2.4.6$suffix$" />
<dependency id="Discord.Net.Labs.Rest" version="2.4.6$suffix$" />
<dependency id="Discord.Net.Labs.WebSocket" version="2.4.8$suffix$" />
<dependency id="Discord.Net.Labs.Core" version="3.0.1-pre$suffix$" />
<dependency id="Discord.Net.Labs.Rest" version="3.0.1-pre$suffix$" />
<dependency id="Discord.Net.Labs.WebSocket" version="3.0.1-pre$suffix$" />
<dependency id="Discord.Net.Labs.Commands" version="2.3.5$suffix$" /> <dependency id="Discord.Net.Labs.Commands" version="2.3.5$suffix$" />
<dependency id="Discord.Net.Labs.Webhook" version="2.3.4$suffix$" /> <dependency id="Discord.Net.Labs.Webhook" version="2.3.4$suffix$" />
</group> </group>
<group targetFramework="netstandard2.0"> <group targetFramework="netstandard2.0">
<dependency id="Discord.Net.Labs.Core" version="2.4.6$suffix$" />
<dependency id="Discord.Net.Labs.Rest" version="2.4.6$suffix$" />
<dependency id="Discord.Net.Labs.WebSocket" version="2.4.8$suffix$" />
<dependency id="Discord.Net.Labs.Core" version="3.0.1-pre$suffix$" />
<dependency id="Discord.Net.Labs.Rest" version="3.0.1-pre$suffix$" />
<dependency id="Discord.Net.Labs.WebSocket" version="3.0.1-pre$suffix$" />
<dependency id="Discord.Net.Labs.Commands" version="2.3.5$suffix$" /> <dependency id="Discord.Net.Labs.Commands" version="2.3.5$suffix$" />
<dependency id="Discord.Net.Labs.Webhook" version="2.3.4$suffix$" /> <dependency id="Discord.Net.Labs.Webhook" version="2.3.4$suffix$" />
</group> </group>
<group targetFramework="netstandard2.1"> <group targetFramework="netstandard2.1">
<dependency id="Discord.Net.Labs.Core" version="2.4.6$suffix$" />
<dependency id="Discord.Net.Labs.Rest" version="2.4.6$suffix$" />
<dependency id="Discord.Net.Labs.WebSocket" version="2.4.8$suffix$" />
<dependency id="Discord.Net.Labs.Core" version="3.0.1-pre$suffix$" />
<dependency id="Discord.Net.Labs.Rest" version="3.0.1-pre$suffix$" />
<dependency id="Discord.Net.Labs.WebSocket" version="3.0.1-pre$suffix$" />
<dependency id="Discord.Net.Labs.Commands" version="2.3.5$suffix$" /> <dependency id="Discord.Net.Labs.Commands" version="2.3.5$suffix$" />
<dependency id="Discord.Net.Labs.Webhook" version="2.3.4$suffix$" /> <dependency id="Discord.Net.Labs.Webhook" version="2.3.4$suffix$" />
</group> </group>


+ 14
- 2
test/Discord.Net.Tests.Unit/GuildPermissionsTests.cs View File

@@ -91,7 +91,13 @@ namespace Discord
AssertFlag(() => new GuildPermissions(manageNicknames: true), GuildPermission.ManageNicknames); AssertFlag(() => new GuildPermissions(manageNicknames: true), GuildPermission.ManageNicknames);
AssertFlag(() => new GuildPermissions(manageRoles: true), GuildPermission.ManageRoles); AssertFlag(() => new GuildPermissions(manageRoles: true), GuildPermission.ManageRoles);
AssertFlag(() => new GuildPermissions(manageWebhooks: true), GuildPermission.ManageWebhooks); AssertFlag(() => new GuildPermissions(manageWebhooks: true), GuildPermission.ManageWebhooks);
AssertFlag(() => new GuildPermissions(manageEmojis: true), GuildPermission.ManageEmojis);
AssertFlag(() => new GuildPermissions(manageEmojisAndStickers: true), GuildPermission.ManageEmojisAndStickers);
AssertFlag(() => new GuildPermissions(useSlashCommands: true), GuildPermission.UseSlashCommands);
AssertFlag(() => new GuildPermissions(requestToSpeak: true), GuildPermission.RequestToSpeak);
AssertFlag(() => new GuildPermissions(manageThreads: true), GuildPermission.ManageThreads);
AssertFlag(() => new GuildPermissions(usePublicThreads: true), GuildPermission.UsePublicThreads);
AssertFlag(() => new GuildPermissions(usePrivateThreads: true), GuildPermission.UsePrivateThreads);
AssertFlag(() => new GuildPermissions(useExternalStickers: true), GuildPermission.UseExternalStickers);
} }


/// <summary> /// <summary>
@@ -161,7 +167,13 @@ namespace Discord
AssertUtil(GuildPermission.ManageNicknames, x => x.ManageNicknames, (p, enable) => p.Modify(manageNicknames: enable)); AssertUtil(GuildPermission.ManageNicknames, x => x.ManageNicknames, (p, enable) => p.Modify(manageNicknames: enable));
AssertUtil(GuildPermission.ManageRoles, x => x.ManageRoles, (p, enable) => p.Modify(manageRoles: enable)); AssertUtil(GuildPermission.ManageRoles, x => x.ManageRoles, (p, enable) => p.Modify(manageRoles: enable));
AssertUtil(GuildPermission.ManageWebhooks, x => x.ManageWebhooks, (p, enable) => p.Modify(manageWebhooks: enable)); AssertUtil(GuildPermission.ManageWebhooks, x => x.ManageWebhooks, (p, enable) => p.Modify(manageWebhooks: enable));
AssertUtil(GuildPermission.ManageEmojis, x => x.ManageEmojisAndStickers, (p, enable) => p.Modify(manageEmojis: enable));
AssertUtil(GuildPermission.ManageEmojisAndStickers, x => x.ManageEmojisAndStickers, (p, enable) => p.Modify(manageEmojisAndStickers: enable));
AssertUtil(GuildPermission.UseSlashCommands, x => x.UseSlashCommands, (p, enable) => p.Modify(useSlashCommands: enable));
AssertUtil(GuildPermission.RequestToSpeak, x => x.RequestToSpeak, (p, enable) => p.Modify(requestToSpeak: enable));
AssertUtil(GuildPermission.ManageThreads, x => x.ManageThreads, (p, enable) => p.Modify(manageThreads: enable));
AssertUtil(GuildPermission.UsePublicThreads, x => x.UsePublicThreads, (p, enable) => p.Modify(usePublicThreads: enable));
AssertUtil(GuildPermission.UsePrivateThreads, x => x.UsePrivateThreads, (p, enable) => p.Modify(usePrivateThreads: enable));
AssertUtil(GuildPermission.UseExternalStickers, x => x.UseExternalStickers, (p, enable) => p.Modify(useExternalStickers: enable));
} }
} }
} }

Loading…
Cancel
Save