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

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");`).

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

[!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

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

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

> 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.

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

@@ -46,6 +46,24 @@ namespace Discord
string extension = FormatToExtension(format, avatarId);
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>
/// Returns the default user avatar URL.
/// </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' ">netstandard2.0;netstandard2.1</TargetFrameworks>
<PackageId>Discord.Net.Labs.Core</PackageId>
<Version>2.4.6</Version>
<Version>3.0.1-pre</Version>
<Product>Discord.Net.Labs.Core</Product>
<RepositoryUrl>https://github.com/Discord-Net-Labs/Discord.Net-Labs</RepositoryUrl>
<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>
</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.
</returns>
</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)">
<summary>
Returns the default user avatar URL.
@@ -1914,6 +1926,117 @@
A read-only collection of users that can access this channel.
</returns>
</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">
<summary>
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="position"> Sets the new zero-based position of this channel. </param>
</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">
<summary>
Provides properties that are used to modify an <see cref="T:Discord.ITextChannel"/> with the specified changes.
@@ -3114,6 +3252,14 @@
language tag format.
</returns>
</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">
<summary>
Gets the preferred culture of this guild.
@@ -3336,6 +3482,29 @@
with the specified <paramref name="id"/>; <see langword="null" /> if none is found.
</returns>
</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)">
<summary>
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.
</returns>
</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)">
<summary>
Creates a new text channel in this guild.
@@ -3723,6 +3914,16 @@
A task that represents the asynchronous removal operation.
</returns>
</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">
<summary>
Holds information for a guild integration feature.
@@ -3891,6 +4092,26 @@
Users must have MFA enabled on their account to perform administrative actions.
</summary>
</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">
<summary>
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.
</summary>
</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">
<summary>
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.
</summary>
</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">
<summary>
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"/>.
</summary>
</member>
<member name="F:Discord.ComponentBuilder.MaxLabelLength">
<member name="F:Discord.ComponentBuilder.MaxButtonLabelLength">
<summary>
The max length of a <see cref="P:Discord.ButtonComponent.Label"/>.
</summary>
@@ -4706,11 +4972,16 @@
Represents a class used to build <see cref="T:Discord.ButtonComponent"/>'s.
</summary>
</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">
<summary>
Gets or sets the label of the current button.
</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 name="P:Discord.ButtonBuilder.CustomId">
<summary>
@@ -5030,16 +5301,26 @@
Represents a class used to build <see cref="T:Discord.SelectMenuOption"/>'s.
</summary>
</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">
<summary>
The maximum length of a <see cref="P:Discord.SelectMenuOption.Description"/>.
</summary>
</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">
<summary>
Gets or sets the label of the current select menu.
</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 name="P:Discord.SelectMenuOptionBuilder.Value">
<summary>
@@ -7398,6 +7679,14 @@
This must be less than the constant defined by <see cref="F:Discord.DiscordConfig.MaxMessageSize"/>.
</remarks>
</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">
<summary>
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.
</summary>
</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">
<summary>
The message is an inline reply.
</summary>
<remarks>
Only available in API v8
</remarks>
</member>
<member name="F:Discord.MessageType.ApplicationCommand">
<summary>
@@ -7567,6 +7884,19 @@
Only available in API v8
</remarks>
</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">
<summary>
A metadata containing reaction information.
@@ -8397,7 +8727,25 @@
<summary> If <c>true</c>, a user may edit the webhooks for this guild. </summary>
</member>
<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 name="M:Discord.GuildPermissions.#ctor(System.UInt64)">
<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)">
<summary> Creates a new <see cref="T:Discord.GuildPermissions"/> with the provided packed value after converting to ulong. </summary>
</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>
</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>
</member>
<member name="M:Discord.GuildPermissions.Has(Discord.GuildPermission)">
@@ -8595,6 +8943,9 @@
Represents a color used in Discord.
</summary>
</member>
<member name="F:Discord.Color.MaxDecimalValue">
<summary> Gets the max decimal value of color. </summary>
</member>
<member name="F:Discord.Color.Default">
<summary> Gets the default user color value. </summary>
</member>
@@ -8699,20 +9050,21 @@
Initializes a <see cref="T:Discord.Color"/> struct with the given raw value.
</summary>
<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>.
<code language="cs">
Color darkGrey = new Color(0x607D8B);
</code>
</example>
<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 name="M:Discord.Color.#ctor(System.Byte,System.Byte,System.Byte)">
<summary>
Initializes a <see cref="T:Discord.Color" /> struct with the given RGB bytes.
</summary>
<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>.
<code language="cs">
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="g">The byte that represents the green 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 name="M:Discord.Color.#ctor(System.Int32,System.Int32,System.Int32)">
<summary>
Initializes a <see cref="T:Discord.Color"/> struct with the given RGB value.
</summary>
<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>.
<code language="cs">
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.
</summary>
<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>.
<code language="cs">
Color darkGrey = new Color(0.38f, 0.49f, 0.55f);
@@ -9511,19 +9864,33 @@
Gets the identifier of this user's avatar.
</summary>
</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)">
<summary>
Gets the avatar URL for this user.
</summary>
<remarks>
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
example).
</remarks>
<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"
source="..\..\..\Discord.Net.Examples\Core\Entities\Users\IUser.Examples.cs"/>
</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.
</returns>
</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">
<summary>
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.
<note type="warning">
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
Discord.
</note>
@@ -9689,6 +10067,11 @@
<c>true</c> if the user is streaming; otherwise <c>false</c>.
</returns>
</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">
<summary> Represents a Webhook Discord user. </summary>
</member>
@@ -9891,6 +10274,11 @@
Gets the user that created this webhook.
</summary>
</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)">
<summary>
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.Collections.Generic;
using System.Collections.Immutable;
using System.Collections.ObjectModel;
using System.Linq;

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

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

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
{
_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;
}
}


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

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

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

/// <summary>
/// Gets the preferred culture of this guild.
/// </summary>
@@ -522,6 +530,27 @@ namespace Discord
/// </returns>
Task<IVoiceChannel> GetVoiceChannelAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null);
/// <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.
/// </summary>
/// <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.
/// </returns>
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>
/// Creates a new text channel in this guild.
@@ -892,5 +941,15 @@ namespace Discord
/// A task that represents the asynchronous removal operation.
/// </returns>
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>
/// The base command model that belongs to an application. see <see href="https://discord.com/developers/docs/interactions/slash-commands#applicationcommand"/>
/// </summary>
public interface IApplicationCommand : ISnowflakeEntity
public interface IApplicationCommand : ISnowflakeEntity, IDeletable
{
/// <summary>
/// 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.
/// </summary>
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.
/// </summary>
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>
/// The max length of a <see cref="ButtonComponent.Label"/>.
/// </summary>
public const int MaxLabelLength = 80;
public const int MaxButtonLabelLength = 80;

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

/// <summary>
/// Gets or sets the label of the current button.
/// </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
{
get => _label;
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;
}
@@ -539,8 +544,8 @@ namespace Discord
if (string.IsNullOrEmpty(this.Url))
throw new InvalidOperationException("Link buttons must have a link associated with them");
else
UrlValidation.Validate(this.Url);
}
UrlValidation.Validate(this.Url);
}
else if (string.IsNullOrEmpty(this.CustomId))
throw new InvalidOperationException("Non-link buttons must have a custom id associated with them");

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

/// <summary>
/// The maximum length of a <see cref="SelectMenuOption.Description"/>.
/// </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>
/// Gets or sets the label of the current select menu.
/// </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
{
get => _label;
set
{
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;
}


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

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


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

@@ -18,6 +18,14 @@ namespace Discord
/// </remarks>
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>
/// Gets or sets the embeds of the message.
/// </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);
/// <summary> If <c>true</c>, a user may edit the webhooks for this guild. </summary>
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);
/// <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>
public GuildPermissions(ulong rawValue) { RawValue = rawValue; }
@@ -121,7 +133,13 @@ namespace Discord
bool? manageNicknames = null,
bool? manageRoles = 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;

@@ -156,6 +174,12 @@ namespace Discord
Permissions.SetValue(ref value, manageRoles, GuildPermission.ManageRoles);
Permissions.SetValue(ref value, manageWebhooks, GuildPermission.ManageWebhooks);
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;
}
@@ -192,7 +216,13 @@ namespace Discord
bool manageNicknames = false,
bool manageRoles = 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,
createInstantInvite: createInstantInvite,
manageRoles: manageRoles,
@@ -224,7 +254,13 @@ namespace Discord
changeNickname: changeNickname,
manageNicknames: manageNicknames,
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>
@@ -259,11 +295,18 @@ namespace Discord
bool? manageNicknames = null,
bool? manageRoles = 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,
viewAuditLog, viewGuildInsights, viewChannel, sendMessages, sendTTSMessages, manageMessages, embedLinks, attachFiles,
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>
/// 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}")]
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>
public static readonly Color Default = new Color(0);
public static readonly Color Default = new(0);
/// <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>
public static readonly Color Teal = new Color(0x1ABC9C);
public static readonly Color Teal = new(0x1ABC9C);
/// <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>
public static readonly Color DarkTeal = new Color(0x11806A);
public static readonly Color DarkTeal = new(0x11806A);
/// <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>
public static readonly Color Green = new Color(0x2ECC71);
public static readonly Color Green = new(0x2ECC71);
/// <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>
public static readonly Color DarkGreen = new Color(0x1F8B4C);
public static readonly Color DarkGreen = new(0x1F8B4C);
/// <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>
public static readonly Color Blue = new Color(0x3498DB);
public static readonly Color Blue = new(0x3498DB);
/// <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>
public static readonly Color DarkBlue = new Color(0x206694);
public static readonly Color DarkBlue = new(0x206694);
/// <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>
public static readonly Color Purple = new Color(0x9B59B6);
public static readonly Color Purple = new(0x9B59B6);
/// <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>
public static readonly Color DarkPurple = new Color(0x71368A);
public static readonly Color DarkPurple = new(0x71368A);
/// <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>
public static readonly Color Magenta = new Color(0xE91E63);
public static readonly Color Magenta = new(0xE91E63);
/// <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>
public static readonly Color DarkMagenta = new Color(0xAD1457);
public static readonly Color DarkMagenta = new(0xAD1457);
/// <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>
public static readonly Color Gold = new Color(0xF1C40F);
public static readonly Color Gold = new(0xF1C40F);
/// <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>
public static readonly Color LightOrange = new Color(0xC27C0E);
public static readonly Color LightOrange = new(0xC27C0E);
/// <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>
public static readonly Color Orange = new Color(0xE67E22);
public static readonly Color Orange = new(0xE67E22);
/// <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>
public static readonly Color DarkOrange = new Color(0xA84300);
public static readonly Color DarkOrange = new(0xA84300);
/// <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>
public static readonly Color Red = new Color(0xE74C3C);
public static readonly Color Red = new(0xE74C3C);
/// <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>
public static readonly Color DarkRed = new Color(0x992D22);
public static readonly Color DarkRed = new(0x992D22);
/// <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>
public static readonly Color LightGrey = new Color(0x979C9F);
public static readonly Color LightGrey = new(0x979C9F);
/// <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>
public static readonly Color LighterGrey = new Color(0x95A5A6);
public static readonly Color LighterGrey = new(0x95A5A6);
/// <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>
public static readonly Color DarkGrey = new Color(0x607D8B);
public static readonly Color DarkGrey = new(0x607D8B);
/// <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>
public static readonly Color DarkerGrey = new Color(0x546E7A);
public static readonly Color DarkerGrey = new(0x546E7A);

/// <summary> Gets the encoded value for this color. </summary>
/// <remarks>
@@ -91,22 +93,27 @@ namespace Discord
/// Initializes a <see cref="Color"/> struct with the given raw value.
/// </summary>
/// <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>.
/// <code language="cs">
/// Color darkGrey = new Color(0x607D8B);
/// </code>
/// </example>
/// <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)
{
if (rawValue > MaxDecimalValue)
throw new ArgumentException($"{nameof(RawValue)} of color cannot be greater than {MaxDecimalValue}!", nameof(rawValue));

RawValue = rawValue;
}

/// <summary>
/// Initializes a <see cref="Color" /> struct with the given RGB bytes.
/// </summary>
/// <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>.
/// <code language="cs">
/// 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="g">The byte that represents the green 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)
{
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>
/// Initializes a <see cref="Color"/> struct with the given RGB value.
/// </summary>
/// <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>.
/// <code language="cs">
/// Color darkGrey = new Color(96, 125, 139);
@@ -145,16 +157,15 @@ namespace Discord
throw new ArgumentOutOfRangeException(nameof(g), "Value must be within [0,255].");
if (b < 0 || b > 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>
/// Initializes a <see cref="Color"/> struct with the given RGB float value.
/// </summary>
/// <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>.
/// <code language="cs">
/// 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].");
if (b < 0.0f || b > 1.0f)
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)
@@ -184,15 +194,22 @@ namespace Discord
public static bool operator !=(Color lhs, Color rhs)
=> 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)
=> (obj is Color c && RawValue == c.RawValue);
=> obj is Color c && RawValue == c.RawValue;

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>
/// 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>
string AvatarId { get; }
/// <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.
/// </summary>
/// <remarks>
/// 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
/// example).
/// </remarks>
/// <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"
/// source="..\..\..\Discord.Net.Examples\Core\Entities\Users\IUser.Examples.cs"/>
/// </example>
@@ -34,6 +46,16 @@ namespace Discord
/// </returns>
string GetAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128);
/// <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.
/// </summary>
/// <remarks>
@@ -93,8 +115,8 @@ namespace Discord
/// This method is used to obtain or create a channel used to send a direct message.
/// <note type="warning">
/// 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
/// Discord.
/// </note>


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

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

namespace Discord
{
/// <summary>
@@ -62,5 +64,9 @@ namespace Discord
/// <c>true</c> if the user is streaming; otherwise <c>false</c>.
/// </returns>
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;

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

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

/// <summary>
/// Modifies this webhook.
/// </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; }
[JsonProperty("created_at")]
public Optional<long> CreatedAt { get; set; }
//[JsonProperty("buttons")]
//public Optional<RichPresenceButton[]> Buttons { get; set; }

[OnError]
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; }
[JsonProperty("threads")]
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
[JsonProperty("flags")]
public Optional<int> Flags { get; set; }
public Optional<MessageFlags> Flags { get; set; }

[JsonProperty("components")]
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; }
[JsonProperty("avatar")]
public Optional<string> Avatar { get; set; }
[JsonProperty("banner")]
public Optional<string> Banner { get; set; }
[JsonProperty("accent_color")]
public Optional<uint?> AccentColor { get; set; }

//CurrentUser
[JsonProperty("verified")]


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

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

namespace Discord.API
{
@@ -28,5 +29,7 @@ namespace Discord.API
public bool Suppress { get; set; }
[JsonProperty("self_stream")]
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")]
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; }

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

[JsonProperty("components")]
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; }
[JsonProperty("allowed_mentions")]
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>
<PackageIcon>Temporary.png</PackageIcon>
<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>
<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>
<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"/>.
</summary>
</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">
<summary>
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">
<inheritdoc />
</member>
<member name="P:Discord.Rest.RestGuild.NsfwLevel">
<inheritdoc />
</member>
<member name="P:Discord.Rest.RestGuild.PreferredCulture">
<inheritdoc />
</member>
@@ -3085,6 +3132,27 @@
message channels found within this guild.
</returns>
</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)">
<summary>
Gets a voice channel in this guild.
@@ -3106,6 +3174,28 @@
voice channels found within this guild.
</returns>
</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)">
<summary>
Gets a collection of all category channels in this guild.
@@ -3403,6 +3493,16 @@
of webhooks found within the guild.
</returns>
</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">
<summary>
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)">
<inheritdoc />
</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)">
<inheritdoc />
</member>
<member name="M:Discord.Rest.RestGuild.Discord#IGuild#GetCategoriesAsync(Discord.CacheMode,Discord.RequestOptions)">
<inheritdoc />
</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)">
<inheritdoc />
</member>
@@ -3548,6 +3660,9 @@
<member name="M:Discord.Rest.RestGuild.Discord#IGuild#GetWebhooksAsync(Discord.RequestOptions)">
<inheritdoc />
</member>
<member name="M:Discord.Rest.RestGuild.Discord#IGuild#GetApplicationCommandsAsync(Discord.RequestOptions)">
<inheritdoc />
</member>
<member name="P:Discord.Rest.RestGuildIntegration.Name">
<inheritdoc />
</member>
@@ -4335,6 +4450,9 @@
<member name="P:Discord.Rest.RestGroupUser.Discord#IVoiceState#IsStreaming">
<inheritdoc />
</member>
<member name="P:Discord.Rest.RestGroupUser.Discord#IVoiceState#RequestToSpeakTimestamp">
<inheritdoc />
</member>
<member name="T:Discord.Rest.RestGuildUser">
<summary>
Represents a REST-based guild user.
@@ -4426,6 +4544,9 @@
<member name="P:Discord.Rest.RestGuildUser.Discord#IVoiceState#IsStreaming">
<inheritdoc />
</member>
<member name="P:Discord.Rest.RestGuildUser.Discord#IVoiceState#RequestToSpeakTimestamp">
<inheritdoc />
</member>
<member name="T:Discord.Rest.RestSelfUser">
<summary>
Represents the logged-in REST-based user.
@@ -4462,7 +4583,7 @@
</member>
<member name="T:Discord.Rest.RestThreadUser">
<summary>
Represents a thread user recieved over the REST api.
Represents a thread user received over the REST api.
</summary>
</member>
<member name="P:Discord.Rest.RestThreadUser.Thread">
@@ -4485,7 +4606,7 @@
Gets the guild user for this thread user.
</summary>
<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.
</returns>
</member>
@@ -4506,6 +4627,12 @@
<member name="P:Discord.Rest.RestUser.AvatarId">
<inheritdoc />
</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">
<inheritdoc />
</member>
@@ -4548,6 +4675,9 @@
<member name="M:Discord.Rest.RestUser.GetAvatarUrl(Discord.ImageFormat,System.UInt16)">
<inheritdoc />
</member>
<member name="M:Discord.Rest.RestUser.GetBannerUrl(Discord.ImageFormat,System.UInt16)">
<inheritdoc />
</member>
<member name="M:Discord.Rest.RestUser.GetDefaultAvatarUrl">
<inheritdoc />
</member>
@@ -4649,6 +4779,9 @@
<member name="P:Discord.Rest.RestWebhookUser.Discord#IVoiceState#IsStreaming">
<inheritdoc />
</member>
<member name="P:Discord.Rest.RestWebhookUser.Discord#IVoiceState#RequestToSpeakTimestamp">
<inheritdoc />
</member>
<member name="P:Discord.Rest.RestWebhook.Token">
<inheritdoc />
</member>
@@ -4667,6 +4800,9 @@
<member name="P:Discord.Rest.RestWebhook.Creator">
<inheritdoc />
</member>
<member name="P:Discord.Rest.RestWebhook.ApplicationId">
<inheritdoc />
</member>
<member name="P:Discord.Rest.RestWebhook.CreatedAt">
<inheritdoc />
</member>


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

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

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

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);
}
@@ -453,7 +453,9 @@ namespace Discord.API

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)
@@ -474,7 +476,9 @@ namespace Discord.API

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)
@@ -483,8 +487,9 @@ namespace Discord.API
Preconditions.NotEqual(userId, 0, nameof(channelId));

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)
@@ -506,7 +511,7 @@ namespace Discord.API

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)
@@ -577,6 +582,82 @@ namespace Discord.API
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
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.Threading.Tasks;
using Model = Discord.API.Channel;
using StageInstance = Discord.API.StageInstance;

namespace Discord.Rest
{
@@ -92,6 +93,21 @@ namespace Discord.Rest
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
public static async Task<IReadOnlyCollection<RestInviteMetadata>> GetInvitesAsync(IGuildChannel channel, BaseDiscordClient client,
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);
case ChannelType.Voice:
return RestVoiceChannel.Create(discord, guild, model);
case ChannelType.Stage:
return RestStageChannel.Create(discord, guild, model);
case ChannelType.Category:
return RestCategoryChannel.Create(discord, guild, model);
case ChannelType.PublicThread or ChannelType.PrivateThread or ChannelType.NewsThread:
return RestThreadChannel.Create(discord, guild, model);
default:
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"))
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()
{
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; }
/// <inheritdoc />
public int? ApproximatePresenceCount { get; private set; }
/// <inheritdoc />
public NsfwLevel NsfwLevel { get; private set; }

/// <inheritdoc />
public CultureInfo PreferredCulture { get; private set; }
@@ -151,6 +153,7 @@ namespace Discord.Rest
SystemChannelFlags = model.SystemChannelFlags;
Description = model.Description;
PremiumSubscriptionCount = model.PremiumSubscriptionCount.GetValueOrDefault();
NsfwLevel = model.NsfwLevel;
if (model.MaxPresences.IsSpecified)
MaxPresences = model.MaxPresences.Value ?? 25000;
if (model.MaxMembers.IsSpecified)
@@ -391,6 +394,35 @@ namespace Discord.Rest
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>
/// Gets a voice channel in this guild.
/// </summary>
@@ -419,6 +451,35 @@ namespace Discord.Rest
var channels = await GuildHelper.GetChannelsAsync(this, Discord, options).ConfigureAwait(false);
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>
/// 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)
=> 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>
/// Returns the name of the guild.
/// </summary>
@@ -888,6 +961,22 @@ namespace Discord.Rest
return null;
}
/// <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)
{
if (mode == CacheMode.AllowDownload)
@@ -904,6 +993,22 @@ namespace Discord.Rest
return null;
}
/// <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)
{
if (mode == CacheMode.AllowDownload)
@@ -1061,5 +1166,8 @@ namespace Discord.Rest
/// <inheritdoc />
async Task<IReadOnlyCollection<IWebhook>> IGuild.GetWebhooksAsync(RequestOptions options)
=> 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();
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 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));

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
{
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,
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();
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
{
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,
Components = args.Components.IsSpecified ? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() : Optional<API.ActionRowComponent[]>.Unspecified,
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 System;
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))
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 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));

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
{
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,
Flags = args.Flags,
AllowedMentions = args.AllowedMentions.IsSpecified ? args.AllowedMentions.Value.ToModel() : Optional<API.AllowedMentions>.Unspecified,
@@ -78,7 +97,13 @@ namespace Discord.Rest
var args = new MessageProperties();
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));

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
{
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?>(),
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);
}


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

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

@@ -37,5 +38,7 @@ namespace Discord.Rest
string IVoiceState.VoiceSessionId => null;
/// <inheritdoc />
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;
/// <inheritdoc />
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
{
/// <summary>
/// Represents a thread user recieved over the REST api.
/// Represents a thread user received over the REST api.
/// </summary>
public class RestThreadUser : RestEntity<ulong>
{
@@ -51,7 +51,7 @@ namespace Discord.Rest
/// Gets the guild user for this thread user.
/// </summary>
/// <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.
/// </returns>
public Task<IGuildUser> GetGuildUser()


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

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

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

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

/// <inheritdoc />
public string GetDefaultAvatarUrl()
=> 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;
/// <inheritdoc />
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; }
/// <inheritdoc />
public IUser Creator { get; private set; }
/// <inheritdoc />
public ulong? ApplicationId { get; private set; }

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

ApplicationId = model.ApplicationId;
}

/// <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();
return model;
}

public static API.AllowedMentions ToModel(this AllowedMentions entity)
{
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.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
{
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>
Identify = 2,
/// <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>
VoiceStateUpdate = 4,
/// <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 System;

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

[JsonProperty("guild_id")]
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")]
public Optional<int[]> ShardingParams { get; set; }
[JsonProperty("presence")]
public Optional<StatusUpdateParams> Presence { get; set; }
public Optional<PresenceUpdateParams> Presence { get; set; }
[JsonProperty("intents")]
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
{
[JsonObject(MemberSerialization = MemberSerialization.OptIn)]
internal class StatusUpdateParams
internal class PresenceUpdateParams

{
[JsonProperty("status")]
public UserStatus Status { get; set; }
[JsonProperty("since"), Int53]
[JsonProperty("since", NullValueHandling = NullValueHandling.Include), Int53]
public long? IdleSince { get; set; }
[JsonProperty("afk")]
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>>();

/// <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>
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>>();

/// <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);
_connectionPromise.TrySetException(ex);
_connectionCancelToken?.Cancel();

_ = Task.Run(async () =>
{
await _logger.ErrorAsync($"Failed to start the connection: {ex}", 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="../../StyleAnalyzer.targets" />
<PropertyGroup>
@@ -8,7 +8,7 @@
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">net461;netstandard2.0;netstandard2.1</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' != 'Windows_NT' ">netstandard2.0;netstandard2.1</TargetFrameworks>
<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>
<PackageProjectUrl>https://github.com/Discord-Net-Labs/Discord.Net-Labs</PackageProjectUrl>
<PackageIcon>Temporary.png</PackageIcon>
@@ -16,7 +16,13 @@
</PropertyGroup>
<PropertyGroup>
<DocumentationFile>..\Discord.Net.WebSocket\Discord.Net.WebSocket.xml</DocumentationFile>
<AssemblyVersion>3.0.1</AssemblyVersion>
<FileVersion>3.0.1</FileVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DefineConstants>TRACE;</DefineConstants>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Discord.Net.Core\Discord.Net.Core.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">
<summary> C→S - Used to associate a connection with a token and specify configuration. </summary>
</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>
</member>
<member name="F:Discord.API.Gateway.GatewayOpCode.VoiceStateUpdate">
@@ -758,7 +758,7 @@
</member>
<member name="E:Discord.WebSocket.BaseSocketClient.ThreadCreated">
<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>
</member>
<member name="E:Discord.WebSocket.BaseSocketClient.ThreadUpdated">
@@ -781,6 +781,36 @@
Fired when a user leaves a thread
</summary>
</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">
<inheritdoc />
</member>
@@ -2142,6 +2172,57 @@
</note>
</remarks>
</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">
<summary>
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">
<inheritdoc />
</member>
<member name="P:Discord.WebSocket.SocketGuild.NsfwLevel">
<inheritdoc />
</member>
<member name="P:Discord.WebSocket.SocketGuild.PreferredCulture">
<inheritdoc />
</member>
@@ -2898,6 +2982,14 @@
A read-only collection of voice channels found within this guild.
</returns>
</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">
<summary>
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.
</returns>
</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)">
<summary>
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.
</returns>
</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)">
<summary>
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)">
<inheritdoc />
</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)">
<inheritdoc />
</member>
@@ -3399,6 +3515,12 @@
<member name="M:Discord.WebSocket.SocketGuild.Discord#IGuild#GetVoiceChannelAsync(System.UInt64,Discord.CacheMode,Discord.RequestOptions)">
<inheritdoc />
</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)">
<inheritdoc />
</member>
@@ -3477,6 +3599,9 @@
<member name="M:Discord.WebSocket.SocketGuild.Discord#IGuild#GetWebhooksAsync(Discord.RequestOptions)">
<inheritdoc />
</member>
<member name="M:Discord.WebSocket.SocketGuild.Discord#IGuild#GetApplicationCommandsAsync(Discord.RequestOptions)">
<inheritdoc />
</member>
<member name="T:Discord.WebSocket.SocketMessageComponent">
<summary>
Represents a Websocket-based interaction type for Message Components.
@@ -3492,7 +3617,7 @@
The message that contained the trigger for this interaction.
</summary>
</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/>
</member>
<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>
<returns>A task that represents the asynchronous operation of updating the message.</returns>
</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/>
</member>
<member name="M:Discord.WebSocket.SocketMessageComponent.DeferAsync(Discord.RequestOptions)">
<member name="M:Discord.WebSocket.SocketMessageComponent.DeferLoadingAsync(System.Boolean,Discord.RequestOptions)">
<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>
<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>
A task that represents the asynchronous operation of acknowledging the interaction.
</returns>
</member>
<member name="M:Discord.WebSocket.SocketMessageComponent.DeferAsync(System.Boolean,Discord.RequestOptions)">
<inheritdoc/>
</member>
<member name="T:Discord.WebSocket.SocketMessageComponentData">
<summary>
Represents the data sent with a <see cref="F:Discord.InteractionType.MessageComponent"/>.
@@ -3619,19 +3748,14 @@
The data associated with this interaction.
</summary>
</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/>
</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/>
</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 name="T:Discord.WebSocket.SocketSlashCommandData">
<summary>
@@ -3708,12 +3832,12 @@
<see langword="true"/> if the token is valid for replying to, otherwise <see langword="false"/>.
</summary>
</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>
Responds to an Interaction with type <see cref="F:Discord.InteractionResponseType.ChannelMessageWithSource"/>.
<para>
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>
</summary>
<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="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>
<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>
</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>
Sends a followup message for this interaction.
</summary>
@@ -3737,6 +3862,7 @@
<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>
@@ -3764,14 +3890,25 @@
A task that represents the asynchronous operation of acknowledging the interaction.
</returns>
</member>
<member name="M:Discord.WebSocket.SocketInteraction.DeferAsync(Discord.RequestOptions)">
<member name="M:Discord.WebSocket.SocketInteraction.DeferAsync(System.Boolean,Discord.RequestOptions)">
<summary>
Acknowledges this interaction.
</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>
A task that represents the asynchronous operation of acknowledging the interaction.
</returns>
</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">
<summary>
Represents a WebSocket-based invite to a guild.
@@ -4308,6 +4445,12 @@
<member name="P:Discord.WebSocket.SocketGroupUser.AvatarId">
<inheritdoc />
</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">
<inheritdoc />
</member>
@@ -4338,6 +4481,9 @@
<member name="P:Discord.WebSocket.SocketGroupUser.Discord#IVoiceState#IsStreaming">
<inheritdoc />
</member>
<member name="P:Discord.WebSocket.SocketGroupUser.Discord#IVoiceState#RequestToSpeakTimestamp">
<inheritdoc />
</member>
<member name="T:Discord.WebSocket.SocketGuildUser">
<summary>
Represents a WebSocket-based guild user.
@@ -4363,6 +4509,12 @@
<member name="P:Discord.WebSocket.SocketGuildUser.AvatarId">
<inheritdoc />
</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">
<inheritdoc />
</member>
@@ -4387,6 +4539,9 @@
<member name="P:Discord.WebSocket.SocketGuildUser.IsStreaming">
<inheritdoc />
</member>
<member name="P:Discord.WebSocket.SocketGuildUser.RequestToSpeakTimestamp">
<inheritdoc />
</member>
<member name="P:Discord.WebSocket.SocketGuildUser.IsPending">
<inheritdoc />
</member>
@@ -4423,7 +4578,7 @@
Returns the position of the user within the role hierarchy.
</summary>
<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.
</remarks>
</member>
@@ -4545,6 +4700,12 @@
<member name="P:Discord.WebSocket.SocketSelfUser.AvatarId">
<inheritdoc />
</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">
<inheritdoc />
</member>
@@ -4598,6 +4759,12 @@
<member name="P:Discord.WebSocket.SocketThreadUser.AvatarId">
<inheritdoc/>
</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">
<inheritdoc/>
</member>
@@ -4634,6 +4801,9 @@
<member name="P:Discord.WebSocket.SocketThreadUser.IsStreaming">
<inheritdoc/>
</member>
<member name="P:Discord.WebSocket.SocketThreadUser.RequestToSpeakTimestamp">
<inheritdoc/>
</member>
<member name="M:Discord.WebSocket.SocketThreadUser.GetPermissions(Discord.IGuildChannel)">
<inheritdoc/>
</member>
@@ -4702,6 +4872,12 @@
<member name="P:Discord.WebSocket.SocketUnknownUser.AvatarId">
<inheritdoc />
</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">
<inheritdoc />
</member>
@@ -4732,6 +4908,12 @@
<member name="P:Discord.WebSocket.SocketUser.AvatarId">
<inheritdoc />
</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">
<inheritdoc />
</member>
@@ -4770,6 +4952,9 @@
<member name="M:Discord.WebSocket.SocketUser.GetAvatarUrl(Discord.ImageFormat,System.UInt16)">
<inheritdoc />
</member>
<member name="M:Discord.WebSocket.SocketUser.GetBannerUrl(Discord.ImageFormat,System.UInt16)">
<inheritdoc />
</member>
<member name="M:Discord.WebSocket.SocketUser.GetDefaultAvatarUrl">
<inheritdoc />
</member>
@@ -4799,6 +4984,9 @@
<member name="P:Discord.WebSocket.SocketVoiceState.VoiceSessionId">
<inheritdoc />
</member>
<member name="P:Discord.WebSocket.SocketVoiceState.RequestToSpeakTimestamp">
<inheritdoc/>
</member>
<member name="P:Discord.WebSocket.SocketVoiceState.IsMuted">
<inheritdoc />
</member>
@@ -4848,6 +5036,14 @@
<member name="P:Discord.WebSocket.SocketWebhookUser.AvatarId">
<inheritdoc />
</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">
<inheritdoc />
</member>
@@ -4948,6 +5144,9 @@
<member name="P:Discord.WebSocket.SocketWebhookUser.Discord#IVoiceState#IsStreaming">
<inheritdoc />
</member>
<member name="P:Discord.WebSocket.SocketWebhookUser.Discord#IVoiceState#RequestToSpeakTimestamp">
<inheritdoc />
</member>
<member name="T:Discord.WebSocket.SocketVoiceServer">
<summary>
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;

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>>();
}
}
}

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

@@ -30,7 +30,16 @@ namespace Discord.WebSocket
/// <inheritdoc />
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 />
public override IReadOnlyCollection<SocketGuild> Guilds => GetGuilds().ToReadOnlyCollection(GetGuildCount);
/// <inheritdoc />
@@ -379,6 +388,20 @@ namespace Discord.WebSocket
client.InviteDeleted += (channel, invite) => _inviteDeletedEvent.InvokeAsync(channel, invite);

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


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

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

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);
}
}
}
};
@@ -87,11 +94,21 @@ namespace Discord.API
{
var msg = _serializer.Deserialize<SocketFrame>(jsonReader);
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);
}
}
};
WebSocketClient.Closed += async ex =>
{
#if DEBUG_PACKETS
Console.WriteLine(ex);
#endif

await DisconnectAsync().ConfigureAwait(false);
await _disconnectedEvent.InvokeAsync(ex).ConfigureAwait(false);
};
@@ -153,6 +170,11 @@ namespace Discord.API
var gatewayResponse = await GetGatewayAsync().ConfigureAwait(false);
_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);

ConnectionState = ConnectionState.Connected;
@@ -213,6 +235,10 @@ namespace Discord.API
options.BucketId = GatewayBucket.Get(GatewayBucketType.Unbucketed).Id;
await RequestQueue.SendAsync(new WebSocketRequest(WebSocketClient, bytes, true, opCode == GatewayOpCode.Heartbeat, options)).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)
@@ -220,7 +246,9 @@ namespace Discord.API
options = RequestOptions.CreateOrClone(options);
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()
{
@@ -237,12 +265,12 @@ namespace Discord.API

if (presence.HasValue)
{
msg.Presence = new StatusUpdateParams
msg.Presence = new PresenceUpdateParams
{
Status = presence.Value.Item1,
IsAFK = presence.Value.Item2,
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);
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);
var args = new StatusUpdateParams
var args = new PresenceUpdateParams
{
Status = status,
IdleSince = since,
IsAFK = isAFK,
Game = game
Activities = new object[] { game }
};
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)
{


+ 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);

await ApiClient.SendStatusUpdateAsync(
await ApiClient.SendPresenceUpdateAsync(
status: presence.Item1,
isAFK: presence.Item2,
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);
}
break;
@@ -2001,8 +2024,9 @@ namespace Discord.WebSocket
threadChannel = (SocketThreadChannel)guild.AddChannel(this.State, data);
if (data.ThreadMember.IsSpecified)
threadChannel.AddOrUpdateThreadMember(data.ThreadMember.Value, guild.CurrentUser);
await TimedInvokeAsync(_threadCreated, nameof(ThreadCreated), threadChannel).ConfigureAwait(false);
}

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

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

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)
case "CHANNEL_PINS_ACK":
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);
case ChannelType.PrivateThread or ChannelType.PublicThread or ChannelType.NewsThread:
return SocketThreadChannel.Create(guild, state, model);
case ChannelType.Stage:
return SocketStageChannel.Create(guild, state, model);
default:
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>
/// </remarks>
public override OverwritePermissions? GetPermissionOverwrite(IRole role)
=> throw new NotImplementedException();
=> ParentChannel.GetPermissionOverwrite(role);

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

/// <inheritdoc/>
/// <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; }
/// <inheritdoc />
public int? MaxVideoChannelUsers { get; private set; }
/// <inheritdoc />
public NsfwLevel NsfwLevel { get; private set; }

/// <inheritdoc />
public CultureInfo PreferredCulture { get; private set; }
@@ -269,6 +271,14 @@ namespace Discord.WebSocket
public IReadOnlyCollection<SocketVoiceChannel> VoiceChannels
=> Channels.OfType<SocketVoiceChannel>().ToImmutableArray();
/// <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.
/// </summary>
/// <returns>
@@ -464,6 +474,7 @@ namespace Discord.WebSocket
SystemChannelFlags = model.SystemChannelFlags;
Description = model.Description;
PremiumSubscriptionCount = model.PremiumSubscriptionCount.GetValueOrDefault();
NsfwLevel = model.NsfwLevel;
if (model.MaxPresences.IsSpecified)
MaxPresences = model.MaxPresences.Value ?? 25000;
if (model.MaxMembers.IsSpecified)
@@ -630,6 +641,16 @@ namespace Discord.WebSocket
public SocketTextChannel GetTextChannel(ulong id)
=> GetChannel(id) as SocketTextChannel;
/// <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.
/// </summary>
/// <param name="id">The snowflake identifier for the voice channel.</param>
@@ -639,6 +660,15 @@ namespace Discord.WebSocket
public SocketVoiceChannel GetVoiceChannel(ulong id)
=> GetChannel(id) as SocketVoiceChannel;
/// <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.
/// </summary>
/// <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.FromResult<ITextChannel>(GetTextChannel(id));
/// <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.FromResult<IReadOnlyCollection<IVoiceChannel>>(VoiceChannels);
/// <inheritdoc />
@@ -1335,6 +1371,12 @@ namespace Discord.WebSocket
Task<IVoiceChannel> IGuild.GetVoiceChannelAsync(ulong id, CacheMode mode, RequestOptions options)
=> Task.FromResult<IVoiceChannel>(GetVoiceChannel(id));
/// <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.FromResult<IVoiceChannel>(AFKChannel);
/// <inheritdoc />
@@ -1436,6 +1478,9 @@ namespace Discord.WebSocket
/// <inheritdoc />
async Task<IReadOnlyCollection<IWebhook>> IGuild.GetWebhooksAsync(RequestOptions options)
=> await GetWebhooksAsync(options).ConfigureAwait(false);
/// <inheritdoc />
async Task<IReadOnlyCollection<IApplicationCommand>> IGuild.GetApplicationCommandsAsync (RequestOptions options)
=> await GetApplicationCommandsAsync(options).ConfigureAwait(false);

void IDisposable.Dispose()
{
@@ -1443,5 +1488,7 @@ namespace Discord.WebSocket
_audioLock?.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 DataModel = Discord.API.MessageComponentInteractionData;
using Discord.Rest;
using System.Collections.Generic;

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
{
/// <summary>
@@ -123,7 +124,7 @@ namespace Discord.WebSocket
};

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

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.");
}

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
if (args.AllowedMentions.IsSpecified && args.AllowedMentions.Value != null && args.AllowedMentions.Value.AllowedTypes.HasValue)
@@ -176,11 +197,11 @@ namespace Discord.WebSocket
{
Content = args.Content,
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
? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray()
: 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,
IsTTS = isTTS,
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)
args.Flags = 64;
args.Flags = MessageFlags.Ephemeral;

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

/// <summary>
/// Acknowledges this interaction with the <see cref="InteractionResponseType.DeferredUpdateMessage"/>.
/// Defers an interaction and responds with type 5 (<see cref="InteractionResponseType.DeferredChannelMessageWithSource"/>)
/// </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>
/// A task that represents the asynchronous operation of acknowledging the interaction.
/// </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()
{
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);


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

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

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

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

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

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
{
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);


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

@@ -86,9 +86,9 @@ namespace Discord.WebSocket
break;
case ApplicationCommandOptionType.Integer:
{
if (model.Value.Value is int val)
if (model.Value.Value is long 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;
}
break;
@@ -109,7 +109,7 @@ namespace Discord.WebSocket
}
break;
}
}

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.
/// </returns>
[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>
/// Acknowledges this interaction.
/// </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>
/// A task that represents the asynchronous operation of acknowledging the interaction.
/// </returns>
public abstract Task DeferAsync(RequestOptions options = null);
public abstract Task DeferAsync(bool ephemeral = false, RequestOptions options = null);

private bool CheckToken()
{
// 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;
}

// 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)
{
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);
else
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 ushort DiscriminatorValue { 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; }

public override bool IsWebhook => false;
@@ -47,7 +49,7 @@ namespace Discord.WebSocket
discord.RemoveUser(Id);
}
}
internal void Update(ClientState state, PresenceModel 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 Model = Discord.API.User;

@@ -28,6 +29,10 @@ namespace Discord.WebSocket
/// <inheritdoc />
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 />
internal override SocketPresence Presence { get { return GlobalUser.Presence; } set { GlobalUser.Presence = value; } }

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

/// <inheritdoc />
public DateTimeOffset? JoinedAt => DateTimeUtils.FromTicks(_joinedAtTicks);
/// <summary>
@@ -87,7 +96,7 @@ namespace Discord.WebSocket
/// Returns the position of the user within the role hierarchy.
/// </summary>
/// <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.
/// </remarks>
public int Hierarchy


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

@@ -29,6 +29,10 @@ namespace Discord.WebSocket
/// <inheritdoc />
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 />
internal override SocketPresence Presence { get { return GlobalUser.Presence; } set { GlobalUser.Presence = value; } }
/// <inheritdoc />
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/>
public string Nickname
=> GuildUser.Nickname;
=> GuildUser.Nickname;

/// <inheritdoc/>
public DateTimeOffset? PremiumSince
@@ -53,6 +53,20 @@ namespace Discord.WebSocket
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/>
public override ushort DiscriminatorValue
{
@@ -110,6 +124,10 @@ namespace Discord.WebSocket
public bool IsStreaming
=> GuildUser.IsStreaming;

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

private SocketGuildUser GuildUser { get; set; }

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; }
/// <inheritdoc />
public override string AvatarId { get; internal set; }

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

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

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


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

@@ -25,6 +25,10 @@ namespace Discord.WebSocket
/// <inheritdoc />
public abstract string AvatarId { get; internal set; }
/// <inheritdoc />
public abstract string BannerId { get; internal set; }
/// <inheritdoc />
public abstract Color? AccentColor { get; internal set; }
/// <inheritdoc />
public abstract bool IsWebhook { get; }
/// <inheritdoc />
public UserProperties? PublicFlags { get; private set; }
@@ -64,6 +68,16 @@ namespace Discord.WebSocket
AvatarId = model.Avatar.Value;
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)
{
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)
=> CDN.GetUserAvatarUrl(Id, AvatarId, size, format);

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

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


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

@@ -13,7 +13,7 @@ namespace Discord.WebSocket
/// <summary>
/// Initializes a default <see cref="SocketVoiceState"/> with everything set to <c>null</c> or <c>false</c>.
/// </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]
private enum Flags : byte
@@ -35,6 +35,8 @@ namespace Discord.WebSocket
public SocketVoiceChannel VoiceChannel { get; }
/// <inheritdoc />
public string VoiceSessionId { get; }
/// <inheritdoc/>
public DateTimeOffset? RequestToSpeakTimestamp { get; private set; }

/// <inheritdoc />
public bool IsMuted => (_voiceStates & Flags.Muted) != 0;
@@ -48,11 +50,13 @@ namespace Discord.WebSocket
public bool IsSelfDeafened => (_voiceStates & Flags.SelfDeafened) != 0;
/// <inheritdoc />
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;
VoiceSessionId = sessionId;
RequestToSpeakTimestamp = requestToSpeak;

Flags voiceStates = Flags.Normal;
if (isSelfMuted)
@@ -71,7 +75,7 @@ namespace Discord.WebSocket
}
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>


+ 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; }
/// <inheritdoc />
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 />
public override bool IsBot { get; internal set; }

@@ -138,5 +155,7 @@ namespace Discord.WebSocket
string IVoiceState.VoiceSessionId => null;
/// <inheritdoc />
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>
<Description>A core Discord.Net Labs library containing the Webhook client and models.</Description>
<TargetFrameworks>netstandard2.0;netstandard2.1</TargetFrameworks>
<Version>2.3.4</Version>
<Version>3.0.0-pre</Version>
<PackageId>Discord.Net.Labs.Webhook</PackageId>
<PackageProjectUrl>https://github.com/Discord-Net-Labs/Discord.Net-Labs</PackageProjectUrl>
<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.ArgumentNullException">Thrown if the <paramref name="webhookUrl"/> is null or whitespace.</exception>
</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>
<returns> Returns the ID of the created message. </returns>
</member>
@@ -99,6 +99,11 @@
Gets or sets the allowed mentions of the message.
</summary>
</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)">
<exception cref="T:System.InvalidOperationException">Could not find a webhook with the supplied credentials.</exception>
</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>
/// <returns> Returns the ID of the created message. </returns>
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>
/// 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.
/// </summary>
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 AvatarId { get; private set; }
public ulong? GuildId { get; private set; }
public ulong? ApplicationId { get; private set; }

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

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

ApplicationId = model.ApplicationId;
}

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);
}
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
{
@@ -37,6 +37,8 @@ namespace Discord.Webhook
args.AvatarUrl = avatarUrl;
if (allowedMentions != null)
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);
return model.Id;
@@ -83,7 +85,8 @@ namespace Discord.Webhook
: Optional.Create<API.Embed[]>(),
AllowedMentions = args.AllowedMentions.IsSpecified
? 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)


+ 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">
<metadata>
<id>Discord.Net.Labs</id>
<version>2.4.9$suffix$</version>
<version>3.0.2-pre$suffix$</version>
<title>Discord.Net Labs</title>
<authors>Discord.Net Contributors</authors>
<owners>quinchs</owners>
@@ -14,23 +14,23 @@
<iconUrl>https://avatars.githubusercontent.com/u/84047264</iconUrl>
<dependencies>
<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.Webhook" version="2.3.4$suffix$" />
</group>
<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.Webhook" version="2.3.4$suffix$" />
</group>
<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.Webhook" version="2.3.4$suffix$" />
</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(manageRoles: true), GuildPermission.ManageRoles);
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>
@@ -161,7 +167,13 @@ namespace Discord
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.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