diff --git a/docs/guides/emoji/emoji.md b/docs/guides/emoji/emoji.md
index 60a84409c..dbf654bbf 100644
--- a/docs/guides/emoji/emoji.md
+++ b/docs/guides/emoji/emoji.md
@@ -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.
\ No newline at end of file
+see the documentation of @Discord.IEmote.
diff --git a/docs/guides/slash-commands/03-responding-to-slash-commands.md b/docs/guides/slash-commands/03-responding-to-slash-commands.md
index 8ab7307d4..ad3c7145b 100644
--- a/docs/guides/slash-commands/03-responding-to-slash-commands.md
+++ b/docs/guides/slash-commands/03-responding-to-slash-commands.md
@@ -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.
diff --git a/src/Discord.Net.Core/CDN.cs b/src/Discord.Net.Core/CDN.cs
index e1e8e5e1a..b1879eebc 100644
--- a/src/Discord.Net.Core/CDN.cs
+++ b/src/Discord.Net.Core/CDN.cs
@@ -46,6 +46,24 @@ namespace Discord
string extension = FormatToExtension(format, avatarId);
return $"{DiscordConfig.CDNUrl}avatars/{userId}/{avatarId}.{extension}?size={size}";
}
+
+ ///
+ /// Returns a user banner URL.
+ ///
+ /// The user snowflake identifier.
+ /// The banner identifier.
+ /// The size of the image to return in horizontal pixels. This can be any power of two between 16 and 2048.
+ /// The format to return.
+ ///
+ /// A URL pointing to the user's banner in the specified size.
+ ///
+ 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}";
+ }
///
/// Returns the default user avatar URL.
///
diff --git a/src/Discord.Net.Core/Discord.Net.Core.csproj b/src/Discord.Net.Core/Discord.Net.Core.csproj
index 73a2654d2..309c45fb6 100644
--- a/src/Discord.Net.Core/Discord.Net.Core.csproj
+++ b/src/Discord.Net.Core/Discord.Net.Core.csproj
@@ -8,12 +8,12 @@
net461;netstandard2.0;netstandard2.1
netstandard2.0;netstandard2.1
Discord.Net.Labs.Core
- 2.4.6
+ 3.0.1-pre
Discord.Net.Labs.Core
https://github.com/Discord-Net-Labs/Discord.Net-Labs
Temporary.png
- 2.3.8
- 2.3.8
+ 3.3.1
+ 3.0.1
false
diff --git a/src/Discord.Net.Core/Discord.Net.Core.xml b/src/Discord.Net.Core/Discord.Net.Core.xml
index 1ab7ab78d..94b0c5cf6 100644
--- a/src/Discord.Net.Core/Discord.Net.Core.xml
+++ b/src/Discord.Net.Core/Discord.Net.Core.xml
@@ -100,6 +100,18 @@
A URL pointing to the user's avatar in the specified size.
+
+
+ Returns a user banner URL.
+
+ The user snowflake identifier.
+ The banner identifier.
+ The size of the image to return in horizontal pixels. This can be any power of two between 16 and 2048.
+ The format to return.
+
+ A URL pointing to the user's banner in the specified size.
+
+
Returns the default user avatar URL.
@@ -1914,6 +1926,117 @@
A read-only collection of users that can access this channel.
+
+
+ Represents a generic Stage Channel.
+
+
+
+
+ Gets the topic of the Stage instance.
+
+
+ If the stage isn't live then this property will be set to .
+
+
+
+
+ The of the current stage.
+
+
+ If the stage isn't live then this property will be set to .
+
+
+
+
+ if stage discovery is disabled, otherwise .
+
+
+
+
+ when the stage is live, otherwise .
+
+
+ If the stage isn't live then this property will be set to .
+
+
+
+
+ Starts the stage, creating a stage instance.
+
+ The topic for the stage/
+ The privacy level of the stage
+ The options to be used when sending the request.
+
+ A task that represents the asynchronous start operation.
+
+
+
+
+ Modifies the current stage instance.
+
+ The properties to modify the stage instance with.
+ The options to be used when sending the request.
+
+ A task that represents the asynchronous modify operation.
+
+
+
+
+ Stops the stage, deleting the stage instance.
+
+ The options to be used when sending the request.
+
+ A task that represents the asynchronous stop operation.
+
+
+
+
+ Indicates that the bot would like to speak within a stage channel.
+
+ The options to be used when sending the request.
+
+ A task that represents the asynchronous request to speak operation.
+
+
+
+
+ Makes the current user become a speaker within a stage.
+
+ The options to be used when sending the request.
+
+ A task that represents the asynchronous speaker modify operation.
+
+
+
+
+ Makes the current user a listener.
+
+ The options to be used when sending the request.
+
+ A task that represents the asynchronous stop operation.
+
+
+
+
+ Makes a user a speaker within a stage.
+
+ The user to make the speaker.
+ The options to be used when sending the request.
+
+ A task that represents the asynchronous move operation.
+
+
+
+
+ Removes a user from speaking.
+
+ The user to remove from speaking.
+ The options to be used when sending the request.
+
+ A task that represents the asynchronous remove operation.
+
+
Represents a generic channel in a guild that can send and receive messages.
@@ -2202,6 +2325,21 @@
Sets the ID of the channel to apply this position to.
Sets the new zero-based position of this channel.
+
+
+ Represents properties to use when modifying a stage instance.
+
+
+
+
+ Gets or sets the topic of the stage.
+
+
+
+
+ Gets or sets the privacy level of the stage.
+
+
Provides properties that are used to modify an with the specified changes.
@@ -3114,6 +3252,14 @@
language tag format.
+
+
+ Gets the NSFW level of this guild.
+
+
+ The NSFW level of this guild.
+
+
Gets the preferred culture of this guild.
@@ -3336,6 +3482,29 @@
with the specified ; if none is found.
+
+
+ Gets a stage channel in this guild
+
+ The snowflake identifier for the stage channel.
+ The that determines whether the object should be fetched from cache.
+ The options to be used when sending the request.
+
+ A task that represents the asynchronous get operation. The task result contains the stage channel associated
+ with the specified ; if none is found.
+
+
+
+
+ Gets a collection of all stage channels in this guild.
+
+ The that determines whether the object should be fetched from cache.
+ The options to be used when sending the request.
+
+ A task that represents the asynchronous get operation. The task result contains a read-only collection of
+ stage channels found within this guild.
+
+
Gets the AFK voice channel in this guild.
@@ -3402,6 +3571,28 @@
admins and moderators of Community guilds receive notices from Discord; if none is set.
+
+
+ Gets a thread channel within this guild.
+
+ The id of the thread channel.
+ The that determines whether the object should be fetched from cache.
+ The options to be used when sending the request.
+
+ A task that represents the asynchronous get operation. The task result contains the thread channel.
+
+
+
+
+ Gets a collection of all thread channels in this guild.
+
+ The that determines whether the object should be fetched from cache.
+ The options to be used when sending the request.
+
+ A task that represents the asynchronous get operation. The task result contains a read-only collection of
+ thread channels found within this guild.
+
+
Creates a new text channel in this guild.
@@ -3723,6 +3914,16 @@
A task that represents the asynchronous removal operation.
+
+
+ Gets this guilds slash commands commands
+
+ The options to be used when sending the request.
+
+ A task that represents the asynchronous get operation. The task result contains a read-only collection
+ of application commands found within the guild.
+
+
Holds information for a guild integration feature.
@@ -3891,6 +4092,26 @@
Users must have MFA enabled on their account to perform administrative actions.
+
+
+ Default or unset.
+
+
+
+
+ Guild has extremely suggestive or mature content that would only be suitable for users 18 or over.
+
+
+
+
+ Guild has no content that could be deemed NSFW; in other words, SFW.
+
+
+
+
+ Guild has mildly NSFW content that may not be suitable for users under 18.
+
+
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.
-
-
- Deletes this command
-
- The options to be used when sending the request.
- A task that represents the asynchronous delete operation.
-
Represents data of an Interaction Command, see .
@@ -4433,6 +4647,58 @@
read-only property, always 1.
+
+
+ Responds to an Interaction with type .
+
+ The text of the message to be sent.
+ A array of embeds to send with this response. Max 10
+ if the message should be read out by a text-to-speech reader, otherwise .
+ if the response should be hidden to everyone besides the invoker of the command, otherwise .
+ The allowed mentions for this response.
+ The request options for this response.
+ A to be sent with this response
+ A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored.
+
+
+
+ Sends a followup message for this interaction.
+
+ The text of the message to be sent
+ A array of embeds to send with this response. Max 10
+ if the message should be read out by a text-to-speech reader, otherwise .
+ if the response should be hidden to everyone besides the invoker of the command, otherwise .
+ The allowed mentions for this response.
+ The request options for this response.
+ A to be sent with this response
+ A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored.
+
+ The sent message.
+
+
+
+
+ Gets the original response for this interaction.
+
+ The request options for this async request.
+ A that represents the initial response.
+
+
+
+ Edits original response for this interaction.
+
+ A delegate containing the properties to modify the message with.
+ The request options for this async request.
+ A that represents the initial response.
+
+
+
+ Acknowledges this interaction.
+
+
+ A task that represents the asynchronous operation of acknowledging the interaction.
+
+
Represents an interface used to specify classes that they are a vaild dataype of a class.
@@ -4583,7 +4849,7 @@
Represents a builder for creating a .
-
+
The max length of a .
@@ -4706,11 +4972,16 @@
Represents a class used to build 's.
+
+
+ The max length of a .
+
+
Gets or sets the label of the current button.
- length exceeds .
+ length exceeds .
@@ -5030,16 +5301,26 @@
Represents a class used to build 's.
+
+
+ The maximum length of a .
+
+
The maximum length of a .
+
+
+ The maximum length of a .
+
+
Gets or sets the label of the current select menu.
- length exceeds
+ length exceeds
@@ -7398,6 +7679,14 @@
This must be less than the constant defined by .
+
+
+ Gets or sets a single embed for this message.
+
+
+ This property will be added to the array, in the future please use the array rather than this property.
+
+
Gets or sets the embeds of the message.
@@ -7554,10 +7843,38 @@
The message for when a news channel subscription is added to a text channel.
+
+
+ The message for when a guild is disqualified from discovery.
+
+
+
+
+ The message for when a guild is requalified for discovery.
+
+
+
+
+ The message for when the initial warning is sent for the initial grace period discovery.
+
+
+
+
+ The message for when the final warning is sent for the initial grace period discovery.
+
+
+
+
+ The message for when a thread is created.
+
+
The message is an inline reply.
+
+ Only available in API v8
+
@@ -7567,6 +7884,19 @@
Only available in API v8
+
+
+ The message that starts a thread.
+
+
+ Only available in API v9
+
+
+
+
+ The message for a invite reminder
+
+
A metadata containing reaction information.
@@ -8397,7 +8727,25 @@
If true, a user may edit the webhooks for this guild.
- If true, a user may edit the emojis for this guild.
+ If true, a user may edit the emojis and stickers for this guild.
+
+
+ If true, a user may use slash commands in this guild.
+
+
+ If true, a user may request to speak in stage channels.
+
+
+ If true, a user may manage threads in this guild.
+
+
+ If true, a user may create public threads in this guild.
+
+
+ If true, a user may create private threads in this guild.
+
+
+ If true, a user may use external stickers in this guild.
Creates a new with the provided packed value.
@@ -8405,10 +8753,10 @@
Creates a new with the provided packed value after converting to ulong.
-
+
Creates a new structure with the provided permissions.
-
+
Creates a new from this one, changing the provided non-null permissions.
@@ -8595,6 +8943,9 @@
Represents a color used in Discord.
+
+ Gets the max decimal value of color.
+
Gets the default user color value.
@@ -8699,20 +9050,21 @@
Initializes a struct with the given raw value.
- The following will create a color that has a hex value of
+ The following will create a color that has a hex value of
#607D8B.
Color darkGrey = new Color(0x607D8B);
The raw value of the color (e.g. 0x607D8B).
+ Value exceeds .
Initializes a struct with the given RGB bytes.
- The following will create a color that has a value of
+ The following will create a color that has a value of
#607D8B.
Color darkGrey = new Color((byte)0b_01100000, (byte)0b_01111101, (byte)0b_10001011);
@@ -8721,13 +9073,14 @@
The byte that represents the red color.
The byte that represents the green color.
The byte that represents the blue color.
+ Value exceeds .
Initializes a struct with the given RGB value.
- The following will create a color that has a value of
+ The following will create a color that has a value of
#607D8B.
Color darkGrey = new Color(96, 125, 139);
@@ -8743,7 +9096,7 @@
Initializes a struct with the given RGB float value.
- The following will create a color that has a value of
+ The following will create a color that has a value of
#607c8c.
Color darkGrey = new Color(0.38f, 0.49f, 0.55f);
@@ -9511,19 +9864,33 @@
Gets the identifier of this user's avatar.
+
+
+ Gets the identifier of this user's banner.
+
+
+
+
+ Gets the user's banner color.
+
+
+ A struct representing the accent color of this user's banner.
+
+
Gets the avatar URL for this user.
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 null. If you wish to
+ (i.e. their avatar identifier is not set), this method will return null. If you wish to
retrieve the default avatar for this user, consider using (see
example).
- 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.
+ 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.
@@ -9534,6 +9901,17 @@
A string representing the user's avatar URL; null if the user does not have an avatar in place.
+
+
+ Gets the banner URL for this user.
+
+ The format to return.
+ The size of the image to return in. This can be any power of two between 16 and 2048.
+
+
+ A string representing the user's avatar URL; null if the user does not have an banner in place.
+
+
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.
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
- with a 403 as its
+ still be created by Discord. However, attempting to send a message will yield a
+ with a 403 as its
. There are currently no official workarounds by
Discord.
@@ -9689,6 +10067,11 @@
true if the user is streaming; otherwise false.
+
+
+ Gets the time on which the user requested to speak.
+
+
Represents a Webhook Discord user.
@@ -9891,6 +10274,11 @@
Gets the user that created this webhook.
+
+
+ Gets the ID of the application owning this webhook.
+
+
Modifies this webhook.
diff --git a/src/Discord.Net.Core/Entities/Channels/IStageChannel.cs b/src/Discord.Net.Core/Entities/Channels/IStageChannel.cs
new file mode 100644
index 000000000..e3b51b614
--- /dev/null
+++ b/src/Discord.Net.Core/Entities/Channels/IStageChannel.cs
@@ -0,0 +1,120 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Discord
+{
+ ///
+ /// Represents a generic Stage Channel.
+ ///
+ public interface IStageChannel : IVoiceChannel
+ {
+ ///
+ /// Gets the topic of the Stage instance.
+ ///
+ ///
+ /// If the stage isn't live then this property will be set to .
+ ///
+ string Topic { get; }
+
+ ///
+ /// The of the current stage.
+ ///
+ ///
+ /// If the stage isn't live then this property will be set to .
+ ///
+ StagePrivacyLevel? PrivacyLevel { get; }
+
+ ///
+ /// if stage discovery is disabled, otherwise .
+ ///
+ bool? DiscoverableDisabled { get; }
+
+ ///
+ /// when the stage is live, otherwise .
+ ///
+ ///
+ /// If the stage isn't live then this property will be set to .
+ ///
+ bool Live { get; }
+
+ ///
+ /// Starts the stage, creating a stage instance.
+ ///
+ /// The topic for the stage/
+ /// The privacy level of the stage
+ /// The options to be used when sending the request.
+ ///
+ /// A task that represents the asynchronous start operation.
+ ///
+ Task StartStageAsync(string topic, StagePrivacyLevel privacyLevel = StagePrivacyLevel.GuildOnly, RequestOptions options = null);
+
+ ///
+ /// Modifies the current stage instance.
+ ///
+ /// The properties to modify the stage instance with.
+ /// The options to be used when sending the request.
+ ///
+ /// A task that represents the asynchronous modify operation.
+ ///
+ Task ModifyInstanceAsync(Action func, RequestOptions options = null);
+
+ ///
+ /// Stops the stage, deleting the stage instance.
+ ///
+ /// The options to be used when sending the request.
+ ///
+ /// A task that represents the asynchronous stop operation.
+ ///
+ Task StopStageAsync(RequestOptions options = null);
+
+ ///
+ /// Indicates that the bot would like to speak within a stage channel.
+ ///
+ /// The options to be used when sending the request.
+ ///
+ /// A task that represents the asynchronous request to speak operation.
+ ///
+ Task RequestToSpeak(RequestOptions options = null);
+
+ ///
+ /// Makes the current user become a speaker within a stage.
+ ///
+ /// The options to be used when sending the request.
+ ///
+ /// A task that represents the asynchronous speaker modify operation.
+ ///
+ Task BecomeSpeakerAsync(RequestOptions options = null);
+
+ ///
+ /// Makes the current user a listener.
+ ///
+ /// The options to be used when sending the request.
+ ///
+ /// A task that represents the asynchronous stop operation.
+ ///
+ Task StopSpeakingAsync(RequestOptions options = null);
+
+ ///
+ /// Makes a user a speaker within a stage.
+ ///
+ /// The user to make the speaker.
+ /// The options to be used when sending the request.
+ ///
+ /// A task that represents the asynchronous move operation.
+ ///
+ Task MoveToSpeaker(IGuildUser user, RequestOptions options = null);
+
+ ///
+ /// Removes a user from speaking.
+ ///
+ /// The user to remove from speaking.
+ /// The options to be used when sending the request.
+ ///
+ /// A task that represents the asynchronous remove operation.
+ ///
+ Task RemoveFromSpeaker(IGuildUser user, RequestOptions options = null);
+ }
+}
diff --git a/src/Discord.Net.Core/Entities/Channels/StageInstanceProperties.cs b/src/Discord.Net.Core/Entities/Channels/StageInstanceProperties.cs
new file mode 100644
index 000000000..ad539adc3
--- /dev/null
+++ b/src/Discord.Net.Core/Entities/Channels/StageInstanceProperties.cs
@@ -0,0 +1,24 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Discord
+{
+ ///
+ /// Represents properties to use when modifying a stage instance.
+ ///
+ public class StageInstanceProperties
+ {
+ ///
+ /// Gets or sets the topic of the stage.
+ ///
+ public Optional Topic { get; set; }
+
+ ///
+ /// Gets or sets the privacy level of the stage.
+ ///
+ public Optional PrivacyLevel { get; set; }
+ }
+}
diff --git a/src/Discord.Net.Core/Entities/Channels/StagePrivacyLevel.cs b/src/Discord.Net.Core/Entities/Channels/StagePrivacyLevel.cs
new file mode 100644
index 000000000..6a51ab4ac
--- /dev/null
+++ b/src/Discord.Net.Core/Entities/Channels/StagePrivacyLevel.cs
@@ -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,
+ }
+}
diff --git a/src/Discord.Net.Core/Entities/Emotes/Emoji.cs b/src/Discord.Net.Core/Entities/Emotes/Emoji.cs
index 58392d1be..60861522c 100644
--- a/src/Discord.Net.Core/Entities/Emotes/Emoji.cs
+++ b/src/Discord.Net.Core/Entities/Emotes/Emoji.cs
@@ -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 _unicodesAndNames;
- private static IReadOnlyDictionary UnicodesAndNames
+ private static IReadOnlyCollection _unicodes;
+ private static IReadOnlyCollection Unicodes
{
get
{
- _unicodesAndNames ??= NamesAndUnicodes.ToDictionary(kvp => kvp.Value, kvp => kvp.Key);
+ _unicodes ??= NamesAndUnicodes.Select(kvp => kvp.Value).ToImmutableHashSet();
+ return _unicodes;
+ }
+ }
+
+ private static IReadOnlyDictionary> _unicodesAndNames;
+ private static IReadOnlyDictionary> UnicodesAndNames
+ {
+ get
+ {
+ _unicodesAndNames ??=
+ NamesAndUnicodes
+ .GroupBy(kvp => kvp.Value)
+ .ToImmutableDictionary(
+ grouping => grouping.Key,
+ grouping => grouping.Select(kvp => kvp.Key)
+ .ToList()
+ .AsReadOnly()
+ );
return _unicodesAndNames;
}
}
diff --git a/src/Discord.Net.Core/Entities/Guilds/IGuild.cs b/src/Discord.Net.Core/Entities/Guilds/IGuild.cs
index b8fd858df..414b6fe73 100644
--- a/src/Discord.Net.Core/Entities/Guilds/IGuild.cs
+++ b/src/Discord.Net.Core/Entities/Guilds/IGuild.cs
@@ -316,6 +316,14 @@ namespace Discord
///
string PreferredLocale { get; }
+ ///
+ /// Gets the NSFW level of this guild.
+ ///
+ ///
+ /// The NSFW level of this guild.
+ ///
+ NsfwLevel NsfwLevel { get; }
+
///
/// Gets the preferred culture of this guild.
///
@@ -522,6 +530,27 @@ namespace Discord
///
Task GetVoiceChannelAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null);
///
+ /// Gets a stage channel in this guild
+ ///
+ /// The snowflake identifier for the stage channel.
+ /// The that determines whether the object should be fetched from cache.
+ /// The options to be used when sending the request.
+ ///
+ /// A task that represents the asynchronous get operation. The task result contains the stage channel associated
+ /// with the specified ; if none is found.
+ ///
+ Task GetStageChannelAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null);
+ ///
+ /// Gets a collection of all stage channels in this guild.
+ ///
+ /// The that determines whether the object should be fetched from cache.
+ /// The options to be used when sending the request.
+ ///
+ /// A task that represents the asynchronous get operation. The task result contains a read-only collection of
+ /// stage channels found within this guild.
+ ///
+ Task> GetStageChannelsAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null);
+ ///
/// Gets the AFK voice channel in this guild.
///
/// The that determines whether the object should be fetched from cache.
@@ -581,6 +610,26 @@ namespace Discord
/// admins and moderators of Community guilds receive notices from Discord; if none is set.
///
Task GetPublicUpdatesChannelAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null);
+ ///
+ /// Gets a thread channel within this guild.
+ ///
+ /// The id of the thread channel.
+ /// The that determines whether the object should be fetched from cache.
+ /// The options to be used when sending the request.
+ ///
+ /// A task that represents the asynchronous get operation. The task result contains the thread channel.
+ ///
+ Task GetThreadChannelAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null);
+ ///
+ /// Gets a collection of all thread channels in this guild.
+ ///
+ /// The that determines whether the object should be fetched from cache.
+ /// The options to be used when sending the request.
+ ///
+ /// A task that represents the asynchronous get operation. The task result contains a read-only collection of
+ /// thread channels found within this guild.
+ ///
+ Task> GetThreadChannelsAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null);
///
/// Creates a new text channel in this guild.
@@ -892,5 +941,15 @@ namespace Discord
/// A task that represents the asynchronous removal operation.
///
Task DeleteEmoteAsync(GuildEmote emote, RequestOptions options = null);
+
+ ///
+ /// Gets this guilds slash commands commands
+ ///
+ /// The options to be used when sending the request.
+ ///
+ /// A task that represents the asynchronous get operation. The task result contains a read-only collection
+ /// of application commands found within the guild.
+ ///
+ Task> GetApplicationCommandsAsync (RequestOptions options = null);
}
}
diff --git a/src/Discord.Net.Core/Entities/Guilds/NsfwLevel.cs b/src/Discord.Net.Core/Entities/Guilds/NsfwLevel.cs
new file mode 100644
index 000000000..e3ac345d9
--- /dev/null
+++ b/src/Discord.Net.Core/Entities/Guilds/NsfwLevel.cs
@@ -0,0 +1,22 @@
+namespace Discord
+{
+ public enum NsfwLevel
+ {
+ ///
+ /// Default or unset.
+ ///
+ Default = 0,
+ ///
+ /// Guild has extremely suggestive or mature content that would only be suitable for users 18 or over.
+ ///
+ Explicit = 1,
+ ///
+ /// Guild has no content that could be deemed NSFW; in other words, SFW.
+ ///
+ Safe = 2,
+ ///
+ /// Guild has mildly NSFW content that may not be suitable for users under 18.
+ ///
+ AgeRestricted = 3
+ }
+}
\ No newline at end of file
diff --git a/src/Discord.Net.Core/Entities/Interactions/IApplicationCommand.cs b/src/Discord.Net.Core/Entities/Interactions/IApplicationCommand.cs
index eb61c539f..a1a33acea 100644
--- a/src/Discord.Net.Core/Entities/Interactions/IApplicationCommand.cs
+++ b/src/Discord.Net.Core/Entities/Interactions/IApplicationCommand.cs
@@ -9,7 +9,7 @@ namespace Discord
///
/// The base command model that belongs to an application. see
///
- public interface IApplicationCommand : ISnowflakeEntity
+ public interface IApplicationCommand : ISnowflakeEntity, IDeletable
{
///
/// 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.
///
IReadOnlyCollection Options { get; }
-
- ///
- /// Deletes this command
- ///
- /// The options to be used when sending the request.
- /// A task that represents the asynchronous delete operation.
- Task DeleteAsync(RequestOptions options = null);
}
}
diff --git a/src/Discord.Net.Core/Entities/Interactions/IDiscordInteraction.cs b/src/Discord.Net.Core/Entities/Interactions/IDiscordInteraction.cs
index 466bf3e91..ae015c2a6 100644
--- a/src/Discord.Net.Core/Entities/Interactions/IDiscordInteraction.cs
+++ b/src/Discord.Net.Core/Entities/Interactions/IDiscordInteraction.cs
@@ -39,5 +39,59 @@ namespace Discord
/// read-only property, always 1.
///
int Version { get; }
+
+ ///
+ /// Responds to an Interaction with type .
+ ///
+ /// The text of the message to be sent.
+ /// A array of embeds to send with this response. Max 10
+ /// if the message should be read out by a text-to-speech reader, otherwise .
+ /// if the response should be hidden to everyone besides the invoker of the command, otherwise .
+ /// The allowed mentions for this response.
+ /// The request options for this response.
+ /// A to be sent with this response
+ /// A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored.
+ 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);
+
+ ///
+ /// Sends a followup message for this interaction.
+ ///
+ /// The text of the message to be sent
+ /// A array of embeds to send with this response. Max 10
+ /// if the message should be read out by a text-to-speech reader, otherwise .
+ /// if the response should be hidden to everyone besides the invoker of the command, otherwise .
+ /// The allowed mentions for this response.
+ /// The request options for this response.
+ /// A to be sent with this response
+ /// A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored.
+ ///
+ /// The sent message.
+ ///
+ Task 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);
+
+ ///
+ /// Gets the original response for this interaction.
+ ///
+ /// The request options for this async request.
+ /// A that represents the initial response.
+ Task GetOriginalResponseAsync (RequestOptions options = null);
+
+ ///
+ /// Edits original response for this interaction.
+ ///
+ /// A delegate containing the properties to modify the message with.
+ /// The request options for this async request.
+ /// A that represents the initial response.
+ Task ModifyOriginalResponseAsync (Action func, RequestOptions options = null);
+
+ ///
+ /// Acknowledges this interaction.
+ ///
+ ///
+ /// A task that represents the asynchronous operation of acknowledging the interaction.
+ ///
+ Task DeferAsync (bool ephemeral = false, RequestOptions options = null);
}
}
diff --git a/src/Discord.Net.Core/Entities/Interactions/Message Components/ComponentBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/Message Components/ComponentBuilder.cs
index bb2f80a81..085c62cef 100644
--- a/src/Discord.Net.Core/Entities/Interactions/Message Components/ComponentBuilder.cs
+++ b/src/Discord.Net.Core/Entities/Interactions/Message Components/ComponentBuilder.cs
@@ -13,7 +13,7 @@ namespace Discord
///
/// The max length of a .
///
- public const int MaxLabelLength = 80;
+ public const int MaxButtonLabelLength = 80;
///
/// The max length of a .
@@ -307,17 +307,22 @@ namespace Discord
///
public class ButtonBuilder
{
+ ///
+ /// The max length of a .
+ ///
+ public const int MaxLabelLength = 80;
+
///
/// Gets or sets the label of the current button.
///
- /// length exceeds .
+ /// length exceeds .
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
///
public class SelectMenuOptionBuilder
{
+ ///
+ /// The maximum length of a .
+ ///
+ public const int MaxLabelLength = 100;
+
///
/// The maximum length of a .
///
- public const int MaxDescriptionLength = 50;
+ public const int MaxDescriptionLength = 100;
+
+ ///
+ /// The maximum length of a .
+ ///
+ public const int MaxSelectLabelLength = 100;
///
/// Gets or sets the label of the current select menu.
///
- /// length exceeds
+ /// length exceeds
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;
}
diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs b/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs
index 2ab2699a6..5b92e02a5 100644
--- a/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs
+++ b/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs
@@ -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)
{
diff --git a/src/Discord.Net.Core/Entities/Messages/MessageProperties.cs b/src/Discord.Net.Core/Entities/Messages/MessageProperties.cs
index f4971b69e..abd09d856 100644
--- a/src/Discord.Net.Core/Entities/Messages/MessageProperties.cs
+++ b/src/Discord.Net.Core/Entities/Messages/MessageProperties.cs
@@ -18,6 +18,14 @@ namespace Discord
///
public Optional Content { get; set; }
+ ///
+ /// Gets or sets a single embed for this message.
+ ///
+ ///
+ /// This property will be added to the array, in the future please use the array rather than this property.
+ ///
+ public Optional
..\Discord.Net.Rest\Discord.Net.Rest.xml
diff --git a/src/Discord.Net.Rest/Discord.Net.Rest.xml b/src/Discord.Net.Rest/Discord.Net.Rest.xml
index eda180f01..8aa36360f 100644
--- a/src/Discord.Net.Rest/Discord.Net.Rest.xml
+++ b/src/Discord.Net.Rest/Discord.Net.Rest.xml
@@ -2281,6 +2281,50 @@
Represents a REST-based news channel in a guild that has the same properties as a .
+
+
+ Represents a REST-based stage channel in a guild.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Represents a REST-based channel in a guild that can send and receive messages.
@@ -2896,6 +2940,9 @@
+
+
+
@@ -3085,6 +3132,27 @@
message channels found within this guild.
+
+
+ Gets a thread channel in this guild.
+
+ The snowflake identifier for the thread channel.
+ The options to be used when sending the request.
+
+ A task that represents the asynchronous get operation. The task result contains the thread channel associated
+ with the specified ; if none is found.
+
+
+
+
+ Gets a collection of all thread in this guild.
+
+ The options to be used when sending the request.
+
+ A task that represents the asynchronous get operation. The task result contains a read-only collection of
+ threads found within this guild.
+
+
Gets a voice channel in this guild.
@@ -3106,6 +3174,28 @@
voice channels found within this guild.
+
+
+ Gets a stage channel in this guild
+
+ The snowflake identifier for the stage channel.
+ The options to be used when sending the request.
+
+ A task that represents the asynchronous get operation. The task result contains the stage channel associated
+ with the specified ; if none is found.
+
+
+
+
+ Gets a collection of all stage channels in this guild.
+
+ The that determines whether the object should be fetched from cache.
+ The options to be used when sending the request.
+
+ A task that represents the asynchronous get operation. The task result contains a read-only collection of
+ stage channels found within this guild.
+
+
Gets a collection of all category channels in this guild.
@@ -3403,6 +3493,16 @@
of webhooks found within the guild.
+
+
+ Gets this guilds slash commands commands
+
+ The options to be used when sending the request.
+
+ A task that represents the asynchronous get operation. The task result contains a read-only collection
+ of application commands found within the guild.
+
+
Returns the name of the guild.
@@ -3460,12 +3560,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
@@ -3548,6 +3660,9 @@
+
+
+
@@ -4335,6 +4450,9 @@
+
+
+
Represents a REST-based guild user.
@@ -4426,6 +4544,9 @@
+
+
+
Represents the logged-in REST-based user.
@@ -4462,7 +4583,7 @@
- Represents a thread user recieved over the REST api.
+ Represents a thread user received over the REST api.
@@ -4485,7 +4606,7 @@
Gets the guild user for this thread user.
- A task representing the asyncronous get operation. The task returns a
+ A task representing the asynchronous get operation. The task returns a
that represents the current thread user.
@@ -4506,6 +4627,12 @@
+
+
+
+
+
+
@@ -4548,6 +4675,9 @@
+
+
+
@@ -4649,6 +4779,9 @@
+
+
+
@@ -4667,6 +4800,9 @@
+
+
+
diff --git a/src/Discord.Net.Rest/DiscordRestApiClient.cs b/src/Discord.Net.Rest/DiscordRestApiClient.cs
index d34a6a4b9..5535f4e5c 100644
--- a/src/Discord.Net.Rest/DiscordRestApiClient.cs
+++ b/src/Discord.Net.Rest/DiscordRestApiClient.cs
@@ -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("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 ListThreadMembersAsync(ulong channelId, RequestOptions options = null)
@@ -506,7 +511,7 @@ namespace Discord.API
var bucket = new BucketIds(channelId: channelId);
- return await SendAsync("GET", $"channels/{channelId}/threads/active");
+ return await SendAsync("GET", () => $"channels/{channelId}/threads/active", bucket);
}
public async Task GetPublicArchivedThreadsAsync(ulong channelId, DateTimeOffset? before = null, int? limit = null, RequestOptions options = null)
@@ -577,6 +582,82 @@ namespace Discord.API
return await SendAsync("GET", () => $"channels/{channelId}/users/@me/threads/archived/private{query}", bucket, options: options);
}
+ // stage
+ public async Task CreateStageInstanceAsync(CreateStageInstanceParams args, RequestOptions options = null)
+ {
+
+ options = RequestOptions.CreateOrClone(options);
+
+ var bucket = new BucketIds();
+
+ return await SendJsonAsync("POST", () => $"stage-instances", args, bucket, options: options).ConfigureAwait(false);
+ }
+
+ public async Task 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("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 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("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)
{
diff --git a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs
index f69c010f2..98715bfce 100644
--- a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs
+++ b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs
@@ -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 ModifyAsync(IStageChannel channel, BaseDiscordClient client,
+ Action 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> GetInvitesAsync(IGuildChannel channel, BaseDiscordClient client,
RequestOptions options)
diff --git a/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs
index fdfee39ea..f0150aeb2 100644
--- a/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs
+++ b/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs
@@ -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);
}
diff --git a/src/Discord.Net.Rest/Entities/Channels/RestStageChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestStageChannel.cs
new file mode 100644
index 000000000..c5bad43da
--- /dev/null
+++ b/src/Discord.Net.Rest/Entities/Channels/RestStageChannel.cs
@@ -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
+{
+ ///
+ /// Represents a REST-based stage channel in a guild.
+ ///
+ public class RestStageChannel : RestVoiceChannel, IStageChannel
+ {
+ ///
+ public string Topic { get; private set; }
+
+ ///
+ public StagePrivacyLevel? PrivacyLevel { get; private set; }
+
+ ///
+ public bool? DiscoverableDisabled { get; private set; }
+
+ ///
+ 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;
+ }
+ }
+
+ ///
+ public async Task ModifyInstanceAsync(Action func, RequestOptions options = null)
+ {
+ var model = await ChannelHelper.ModifyAsync(this, Discord, func, options);
+
+ Update(model, true);
+ }
+
+ ///
+ 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);
+ }
+
+ ///
+ public async Task StopStageAsync(RequestOptions options = null)
+ {
+ await Discord.ApiClient.DeleteStageInstanceAsync(this.Id, options);
+
+ Update(null, false);
+ }
+
+ ///
+ 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);
+ }
+
+ ///
+ 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);
+ }
+
+ ///
+ 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);
+ }
+
+ ///
+ 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);
+ }
+
+ ///
+ 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);
+ }
+
+ ///
+ 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);
+ }
+ }
+}
diff --git a/src/Discord.Net.Rest/Entities/Channels/ThreadHelper.cs b/src/Discord.Net.Rest/Entities/Channels/ThreadHelper.cs
index 958210d85..f1bf238bc 100644
--- a/src/Discord.Net.Rest/Entities/Channels/ThreadHelper.cs
+++ b/src/Discord.Net.Rest/Entities/Channels/ThreadHelper.cs
@@ -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,
diff --git a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs
index 37491909c..126a211c8 100644
--- a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs
+++ b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs
@@ -83,6 +83,8 @@ namespace Discord.Rest
public int? ApproximateMemberCount { get; private set; }
///
public int? ApproximatePresenceCount { get; private set; }
+ ///
+ public NsfwLevel NsfwLevel { get; private set; }
///
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().ToImmutableArray();
}
+ ///
+ /// Gets a thread channel in this guild.
+ ///
+ /// The snowflake identifier for the thread channel.
+ /// The options to be used when sending the request.
+ ///
+ /// A task that represents the asynchronous get operation. The task result contains the thread channel associated
+ /// with the specified ; if none is found.
+ ///
+ public async Task GetThreadChannelAsync(ulong id, RequestOptions options = null)
+ {
+ var channel = await GuildHelper.GetChannelAsync(this, Discord, id, options).ConfigureAwait(false);
+ return channel as RestThreadChannel;
+ }
+
+ ///
+ /// Gets a collection of all thread in this guild.
+ ///
+ /// The options to be used when sending the request.
+ ///
+ /// A task that represents the asynchronous get operation. The task result contains a read-only collection of
+ /// threads found within this guild.
+ ///
+ public async Task> GetThreadChannelsAsync(RequestOptions options = null)
+ {
+ var channels = await GuildHelper.GetChannelsAsync(this, Discord, options).ConfigureAwait(false);
+ return channels.OfType().ToImmutableArray();
+ }
+
///
/// Gets a voice channel in this guild.
///
@@ -419,6 +451,35 @@ namespace Discord.Rest
var channels = await GuildHelper.GetChannelsAsync(this, Discord, options).ConfigureAwait(false);
return channels.OfType().ToImmutableArray();
}
+ ///
+ /// Gets a stage channel in this guild
+ ///
+ /// The snowflake identifier for the stage channel.
+ /// The options to be used when sending the request.
+ ///
+ /// A task that represents the asynchronous get operation. The task result contains the stage channel associated
+ /// with the specified ; if none is found.
+ ///
+ public async Task GetStageChannelAsync(ulong id, RequestOptions options = null)
+ {
+ var channel = await GuildHelper.GetChannelAsync(this, Discord, id, options).ConfigureAwait(false);
+ return channel as RestStageChannel;
+ }
+
+ ///
+ /// Gets a collection of all stage channels in this guild.
+ ///
+ /// The that determines whether the object should be fetched from cache.
+ /// The options to be used when sending the request.
+ ///
+ /// A task that represents the asynchronous get operation. The task result contains a read-only collection of
+ /// stage channels found within this guild.
+ ///
+ public async Task> GetStageChannelsAsync(RequestOptions options = null)
+ {
+ var channels = await GuildHelper.GetChannelsAsync(this, Discord, options).ConfigureAwait(false);
+ return channels.OfType().ToImmutableArray();
+ }
///
/// Gets a collection of all category channels in this guild.
@@ -808,6 +869,18 @@ namespace Discord.Rest
public Task> GetWebhooksAsync(RequestOptions options = null)
=> GuildHelper.GetWebhooksAsync(this, Discord, options);
+ //Interactions
+ ///
+ /// Gets this guilds slash commands commands
+ ///
+ /// The options to be used when sending the request.
+ ///
+ /// A task that represents the asynchronous get operation. The task result contains a read-only collection
+ /// of application commands found within the guild.
+ ///
+ public async Task> GetApplicationCommandsAsync (RequestOptions options = null)
+ => await ClientHelper.GetGuildApplicationCommands(Discord, Id, options).ConfigureAwait(false);
+
///
/// Returns the name of the guild.
///
@@ -888,6 +961,22 @@ namespace Discord.Rest
return null;
}
///
+ async Task IGuild.GetThreadChannelAsync(ulong id, CacheMode mode, RequestOptions options)
+ {
+ if (mode == CacheMode.AllowDownload)
+ return await GetThreadChannelAsync(id, options).ConfigureAwait(false);
+ else
+ return null;
+ }
+ ///
+ async Task> IGuild.GetThreadChannelsAsync(CacheMode mode, RequestOptions options)
+ {
+ if (mode == CacheMode.AllowDownload)
+ return await GetThreadChannelsAsync(options).ConfigureAwait(false);
+ else
+ return null;
+ }
+ ///
async Task> IGuild.GetVoiceChannelsAsync(CacheMode mode, RequestOptions options)
{
if (mode == CacheMode.AllowDownload)
@@ -904,6 +993,22 @@ namespace Discord.Rest
return null;
}
///
+ async Task IGuild.GetStageChannelAsync(ulong id, CacheMode mode, RequestOptions options )
+ {
+ if (mode == CacheMode.AllowDownload)
+ return await GetStageChannelAsync(id, options).ConfigureAwait(false);
+ else
+ return null;
+ }
+ ///
+ async Task> IGuild.GetStageChannelsAsync(CacheMode mode, RequestOptions options)
+ {
+ if (mode == CacheMode.AllowDownload)
+ return await GetStageChannelsAsync(options).ConfigureAwait(false);
+ else
+ return null;
+ }
+ ///
async Task IGuild.GetVoiceChannelAsync(ulong id, CacheMode mode, RequestOptions options)
{
if (mode == CacheMode.AllowDownload)
@@ -1061,5 +1166,8 @@ namespace Discord.Rest
///
async Task> IGuild.GetWebhooksAsync(RequestOptions options)
=> await GetWebhooksAsync(options).ConfigureAwait(false);
+ ///
+ async Task> IGuild.GetApplicationCommandsAsync (RequestOptions options)
+ => await GetApplicationCommandsAsync(options).ConfigureAwait(false);
}
}
diff --git a/src/Discord.Net.Rest/Entities/Interactions/InteractionHelper.cs b/src/Discord.Net.Rest/Entities/Interactions/InteractionHelper.cs
index e4df0d75e..7ed48a04d 100644
--- a/src/Discord.Net.Rest/Entities/Interactions/InteractionHelper.cs
+++ b/src/Discord.Net.Rest/Entities/Interactions/InteractionHelper.cs
@@ -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() : 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(),
+ Embeds = apiEmbeds?.ToArray() ?? Optional.Unspecified,
AllowedMentions = args.AllowedMentions.IsSpecified ? args.AllowedMentions.Value.ToModel() : Optional.Unspecified,
Components = args.Components.IsSpecified ? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() : Optional.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() : 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.Unspecified,
+ Embeds = apiEmbeds?.ToArray() ?? Optional.Unspecified,
AllowedMentions = args.AllowedMentions.IsSpecified ? args.AllowedMentions.Value?.ToModel() : Optional.Unspecified,
Components = args.Components.IsSpecified ? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() : Optional.Unspecified,
Flags = args.Flags
diff --git a/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs b/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs
index 5812a7316..9546025b0 100644
--- a/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs
+++ b/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs
@@ -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() : 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.Unspecified,
+ Embeds = apiEmbeds?.ToArray() ?? Optional.Unspecified,
Components = args.Components.IsSpecified ? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() : Optional.Unspecified,
Flags = args.Flags,
AllowedMentions = args.AllowedMentions.IsSpecified ? args.AllowedMentions.Value.ToModel() : Optional.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() : 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(),
+ Embeds = apiEmbeds?.ToArray() ?? Optional.Unspecified,
Flags = args.Flags.IsSpecified ? args.Flags.Value : Optional.Create(),
AllowedMentions = args.AllowedMentions.IsSpecified ? args.AllowedMentions.Value.ToModel() : Optional.Create(),
+ Components = args.Components.IsSpecified ? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() : Optional.Unspecified,
};
return await client.ApiClient.ModifyMessageAsync(channelId, msgId, apiArgs, options).ConfigureAwait(false);
}
diff --git a/src/Discord.Net.Rest/Entities/Users/RestGroupUser.cs b/src/Discord.Net.Rest/Entities/Users/RestGroupUser.cs
index 55e9843eb..63b89035b 100644
--- a/src/Discord.Net.Rest/Entities/Users/RestGroupUser.cs
+++ b/src/Discord.Net.Rest/Entities/Users/RestGroupUser.cs
@@ -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;
///
bool IVoiceState.IsStreaming => false;
+ ///
+ DateTimeOffset? IVoiceState.RequestToSpeakTimestamp => null;
}
}
diff --git a/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs b/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs
index 6e6bbe09c..d094be618 100644
--- a/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs
+++ b/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs
@@ -169,5 +169,7 @@ namespace Discord.Rest
string IVoiceState.VoiceSessionId => null;
///
bool IVoiceState.IsStreaming => false;
+ ///
+ DateTimeOffset? IVoiceState.RequestToSpeakTimestamp => null;
}
}
diff --git a/src/Discord.Net.Rest/Entities/Users/RestThreadUser.cs b/src/Discord.Net.Rest/Entities/Users/RestThreadUser.cs
index d74591e75..bb981bfdb 100644
--- a/src/Discord.Net.Rest/Entities/Users/RestThreadUser.cs
+++ b/src/Discord.Net.Rest/Entities/Users/RestThreadUser.cs
@@ -9,7 +9,7 @@ using Model = Discord.API.ThreadMember;
namespace Discord.Rest
{
///
- /// Represents a thread user recieved over the REST api.
+ /// Represents a thread user received over the REST api.
///
public class RestThreadUser : RestEntity
{
@@ -51,7 +51,7 @@ namespace Discord.Rest
/// Gets the guild user for this thread user.
///
///
- /// A task representing the asyncronous get operation. The task returns a
+ /// A task representing the asynchronous get operation. The task returns a
/// that represents the current thread user.
///
public Task GetGuildUser()
diff --git a/src/Discord.Net.Rest/Entities/Users/RestUser.cs b/src/Discord.Net.Rest/Entities/Users/RestUser.cs
index 7bc1447fe..618804fef 100644
--- a/src/Discord.Net.Rest/Entities/Users/RestUser.cs
+++ b/src/Discord.Net.Rest/Entities/Users/RestUser.cs
@@ -22,6 +22,10 @@ namespace Discord.Rest
///
public string AvatarId { get; private set; }
///
+ public string BannerId { get; private set; }
+ ///
+ public Color? AccentColor { get; private set; }
+ ///
public UserProperties? PublicFlags { get; private set; }
///
@@ -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);
+ ///
+ public string GetBannerUrl(ImageFormat format = ImageFormat.Auto, ushort size = 256)
+ => CDN.GetUserBannerUrl(Id, BannerId, size, format);
+
///
public string GetDefaultAvatarUrl()
=> CDN.GetDefaultUserAvatarUrl(DiscriminatorValue);
diff --git a/src/Discord.Net.Rest/Entities/Users/RestWebhookUser.cs b/src/Discord.Net.Rest/Entities/Users/RestWebhookUser.cs
index 2131fec93..9297f9af9 100644
--- a/src/Discord.Net.Rest/Entities/Users/RestWebhookUser.cs
+++ b/src/Discord.Net.Rest/Entities/Users/RestWebhookUser.cs
@@ -107,5 +107,7 @@ namespace Discord.Rest
string IVoiceState.VoiceSessionId => null;
///
bool IVoiceState.IsStreaming => false;
+ ///
+ DateTimeOffset? IVoiceState.RequestToSpeakTimestamp => null;
}
}
diff --git a/src/Discord.Net.Rest/Entities/Webhooks/RestWebhook.cs b/src/Discord.Net.Rest/Entities/Webhooks/RestWebhook.cs
index 9baddf003..5ae09fde0 100644
--- a/src/Discord.Net.Rest/Entities/Webhooks/RestWebhook.cs
+++ b/src/Discord.Net.Rest/Entities/Webhooks/RestWebhook.cs
@@ -24,6 +24,8 @@ namespace Discord.Rest
public ulong? GuildId { get; private set; }
///
public IUser Creator { get; private set; }
+ ///
+ public ulong? ApplicationId { get; private set; }
///
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;
}
///
diff --git a/src/Discord.Net.Rest/Extensions/EntityExtensions.cs b/src/Discord.Net.Rest/Extensions/EntityExtensions.cs
index f8676c783..0c2e0f8c2 100644
--- a/src/Discord.Net.Rest/Extensions/EntityExtensions.cs
+++ b/src/Discord.Net.Rest/Extensions/EntityExtensions.cs
@@ -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()
diff --git a/src/Discord.Net.Rest/Net/Converters/ArrayConverter.cs b/src/Discord.Net.Rest/Net/Converters/ArrayConverter.cs
index 3cededb7b..ce2e9b1f7 100644
--- a/src/Discord.Net.Rest/Net/Converters/ArrayConverter.cs
+++ b/src/Discord.Net.Rest/Net/Converters/ArrayConverter.cs
@@ -1,4 +1,4 @@
-using Newtonsoft.Json;
+using Newtonsoft.Json;
using System;
using System.Collections.Generic;
diff --git a/src/Discord.Net.WebSocket/API/Gateway/GatewayOpCode.cs b/src/Discord.Net.WebSocket/API/Gateway/GatewayOpCode.cs
index 13a2bb462..062aec224 100644
--- a/src/Discord.Net.WebSocket/API/Gateway/GatewayOpCode.cs
+++ b/src/Discord.Net.WebSocket/API/Gateway/GatewayOpCode.cs
@@ -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
/// C→S - Used to associate a connection with a token and specify configuration.
Identify = 2,
/// C→S - Used to update client's status and current game id.
- StatusUpdate = 3,
+ PresenceUpdate = 3,
/// C→S - Used to join a particular voice channel.
VoiceStateUpdate = 4,
/// C→S - Used to ensure the guild's voice server is alive.
diff --git a/src/Discord.Net.WebSocket/API/Gateway/GuildMemberUpdateEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/GuildMemberUpdateEvent.cs
index a234d6da5..744d2d502 100644
--- a/src/Discord.Net.WebSocket/API/Gateway/GuildMemberUpdateEvent.cs
+++ b/src/Discord.Net.WebSocket/API/Gateway/GuildMemberUpdateEvent.cs
@@ -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; }
}
diff --git a/src/Discord.Net.WebSocket/API/Gateway/IdentifyParams.cs b/src/Discord.Net.WebSocket/API/Gateway/IdentifyParams.cs
index bb54d4cdd..3b7ef35d6 100644
--- a/src/Discord.Net.WebSocket/API/Gateway/IdentifyParams.cs
+++ b/src/Discord.Net.WebSocket/API/Gateway/IdentifyParams.cs
@@ -16,7 +16,7 @@ namespace Discord.API.Gateway
[JsonProperty("shard")]
public Optional ShardingParams { get; set; }
[JsonProperty("presence")]
- public Optional Presence { get; set; }
+ public Optional Presence { get; set; }
[JsonProperty("intents")]
public Optional Intents { get; set; }
}
diff --git a/src/Discord.Net.WebSocket/API/Gateway/StatusUpdateParams.cs b/src/Discord.Net.WebSocket/API/Gateway/StatusUpdateParams.cs
index 5fec8b4bd..96897024c 100644
--- a/src/Discord.Net.WebSocket/API/Gateway/StatusUpdateParams.cs
+++ b/src/Discord.Net.WebSocket/API/Gateway/StatusUpdateParams.cs
@@ -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
}
}
diff --git a/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs b/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs
index d49afff25..4982655d7 100644
--- a/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs
+++ b/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs
@@ -542,7 +542,7 @@ namespace Discord.WebSocket
internal readonly AsyncEvent> _applicationCommandDeleted = new AsyncEvent>();
///
- /// 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.
///
public event Func ThreadCreated
{
@@ -592,5 +592,64 @@ namespace Discord.WebSocket
}
internal readonly AsyncEvent> _threadMemberLeft = new AsyncEvent>();
+ ///
+ /// Fired when a stage is started.
+ ///
+ public event Func StageStarted
+ {
+ add { _stageStarted.Add(value); }
+ remove { _stageStarted.Remove(value); }
+ }
+ internal readonly AsyncEvent> _stageStarted = new AsyncEvent>();
+
+ ///
+ /// Fired when a stage ends.
+ ///
+ public event Func StageEnded
+ {
+ add { _stageEnded.Add(value); }
+ remove { _stageEnded.Remove(value); }
+ }
+ internal readonly AsyncEvent> _stageEnded = new AsyncEvent>();
+
+ ///
+ /// Fired when a stage is updated.
+ ///
+ public event Func StageUpdated
+ {
+ add { _stageUpdated.Add(value); }
+ remove { _stageUpdated.Remove(value); }
+ }
+ internal readonly AsyncEvent> _stageUpdated = new AsyncEvent>();
+
+ ///
+ /// Fired when a user requests to speak within a stage channel.
+ ///
+ public event Func RequestToSpeak
+ {
+ add { _requestToSpeak.Add(value); }
+ remove { _requestToSpeak.Remove(value); }
+ }
+ internal readonly AsyncEvent> _requestToSpeak = new AsyncEvent>();
+
+ ///
+ /// Fired when a speaker is added in a stage channel.
+ ///
+ public event Func SpeakerAdded
+ {
+ add { _speakerAdded.Add(value); }
+ remove { _speakerAdded.Remove(value); }
+ }
+ internal readonly AsyncEvent> _speakerAdded = new AsyncEvent>();
+
+ ///
+ /// Fired when a speaker is removed from a stage channel.
+ ///
+ public event Func SpeakerRemoved
+ {
+ add { _speakerRemoved.Add(value); }
+ remove { _speakerRemoved.Remove(value); }
+ }
+ internal readonly AsyncEvent> _speakerRemoved = new AsyncEvent>();
}
}
diff --git a/src/Discord.Net.WebSocket/ConnectionManager.cs b/src/Discord.Net.WebSocket/ConnectionManager.cs
index e444f359f..9a19f90f4 100644
--- a/src/Discord.Net.WebSocket/ConnectionManager.cs
+++ b/src/Discord.Net.WebSocket/ConnectionManager.cs
@@ -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)
{
diff --git a/src/Discord.Net.WebSocket/Discord.Net.WebSocket.csproj b/src/Discord.Net.WebSocket/Discord.Net.WebSocket.csproj
index 3f1f63db3..167ccebb0 100644
--- a/src/Discord.Net.WebSocket/Discord.Net.WebSocket.csproj
+++ b/src/Discord.Net.WebSocket/Discord.Net.WebSocket.csproj
@@ -1,4 +1,4 @@
-
+
@@ -8,7 +8,7 @@
net461;netstandard2.0;netstandard2.1
netstandard2.0;netstandard2.1
true
- 2.4.9
+ 3.0.1-pre
https://github.com/Discord-Net-Labs/Discord.Net-Labs
https://github.com/Discord-Net-Labs/Discord.Net-Labs
Temporary.png
@@ -16,7 +16,13 @@
..\Discord.Net.WebSocket\Discord.Net.WebSocket.xml
+ 3.0.1
+ 3.0.1
+
+ TRACE;
+
+
diff --git a/src/Discord.Net.WebSocket/Discord.Net.WebSocket.xml b/src/Discord.Net.WebSocket/Discord.Net.WebSocket.xml
index b17f73024..61e22ffaa 100644
--- a/src/Discord.Net.WebSocket/Discord.Net.WebSocket.xml
+++ b/src/Discord.Net.WebSocket/Discord.Net.WebSocket.xml
@@ -13,7 +13,7 @@
C→S - Used to associate a connection with a token and specify configuration.
-
+
C→S - Used to update client's status and current game id.
@@ -758,7 +758,7 @@
- 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.
@@ -781,6 +781,36 @@
Fired when a user leaves a thread
+
+
+ Fired when a stage is started.
+
+
+
+
+ Fired when a stage ends.
+
+
+
+
+ Fired when a stage is updated.
+
+
+
+
+ Fired when a user requests to speak within a stage channel.
+
+
+
+
+ Fired when a speaker is added in a stage channel.
+
+
+
+
+ Fired when a speaker is removed from a stage channel.
+
+
@@ -2142,6 +2172,57 @@
+
+
+ Represents a stage channel recieved over the gateway.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Returns if the current user is a speaker within the stage, otherwise .
+
+
+
+
+ Gets a collection of users who are speakers within the stage.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Represents a WebSocket-based channel in a guild that can send and receive messages.
@@ -2787,6 +2868,9 @@
+
+
+
@@ -2898,6 +2982,14 @@
A read-only collection of voice channels found within this guild.
+
+
+ Gets a collection of all stage channels in this guild.
+
+
+ A read-only collection of stage channels found within this guild.
+
+
Gets a collection of all category channels in this guild.
@@ -3055,6 +3147,15 @@
A text channel associated with the specified ; if none is found.
+
+
+ Gets a thread in this guild.
+
+ The snowflake identifier for the thread.
+
+ A thread channel associated with the specified ; if none is found.
+
+
Gets a voice channel in this guild.
@@ -3064,6 +3165,15 @@
A voice channel associated with the specified ; if none is found.
+
+
+ Gets a stage channel in this guild.
+
+ The snowflake identifier for the stage channel.
+
+ A stage channel associated with the specified ; if none is found.
+
+
Gets a category channel in this guild.
@@ -3390,6 +3500,12 @@
+
+
+
+
+
+
@@ -3399,6 +3515,12 @@
+
+
+
+
+
+
@@ -3477,6 +3599,9 @@
+
+
+
Represents a Websocket-based interaction type for Message Components.
@@ -3492,7 +3617,7 @@
The message that contained the trigger for this interaction.
-
+
@@ -3503,18 +3628,22 @@
The request options for this async request.
A task that represents the asynchronous operation of updating the message.
-
+
-
+
- Acknowledges this interaction with the .
+ Defers an interaction and responds with type 5 ()
+ to send this message ephemerally, otherwise .
The request options for this async request.
A task that represents the asynchronous operation of acknowledging the interaction.
+
+
+
Represents the data sent with a .
@@ -3619,19 +3748,14 @@
The data associated with this interaction.
-
+
-
+
-
-
- Acknowledges this interaction with the .
-
-
- A task that represents the asynchronous operation of acknowledging the interaction.
-
+
+
@@ -3708,12 +3832,12 @@
if the token is valid for replying to, otherwise .
-
+
Responds to an Interaction with type .
If you have set to , You should use
- instead.
+ instead.
The text of the message to be sent.
@@ -3723,10 +3847,11 @@
The allowed mentions for this response.
The request options for this response.
A to be sent with this response
+ A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored.
Message content is too long, length must be less or equal to .
The parameters provided were invalid or the token was invalid.
-
+
Sends a followup message for this interaction.
@@ -3737,6 +3862,7 @@
The allowed mentions for this response.
The request options for this response.
A to be sent with this response
+ A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored.
The sent message.
@@ -3764,14 +3890,25 @@
A task that represents the asynchronous operation of acknowledging the interaction.
-
+
Acknowledges this interaction.
+ to send this message ephemerally, otherwise .
+ The request options for this async request.
A task that represents the asynchronous operation of acknowledging the interaction.
+
+
+
+
+
+
+
+
+
Represents a WebSocket-based invite to a guild.
@@ -4308,6 +4445,12 @@
+
+
+
+
+
+
@@ -4338,6 +4481,9 @@
+
+
+
Represents a WebSocket-based guild user.
@@ -4363,6 +4509,12 @@
+
+
+
+
+
+
@@ -4387,6 +4539,9 @@
+
+
+
@@ -4423,7 +4578,7 @@
Returns the position of the user within the role hierarchy.
- 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
if user is the server owner.
@@ -4545,6 +4700,12 @@
+
+
+
+
+
+
@@ -4598,6 +4759,12 @@
+
+
+
+
+
+
@@ -4634,6 +4801,9 @@
+
+
+
@@ -4702,6 +4872,12 @@
+
+
+
+
+
+
@@ -4732,6 +4908,12 @@
+
+
+
+
+
+
@@ -4770,6 +4952,9 @@
+
+
+
@@ -4799,6 +4984,9 @@
+
+
+
@@ -4848,6 +5036,14 @@
+
+
+ Webhook users does not support banners.
+
+
+
+ Webhook users does not support accent colors.
+
@@ -4948,6 +5144,9 @@
+
+
+
Represents a WebSocket-based voice server.
diff --git a/src/Discord.Net.WebSocket/DiscordShardedClient.Events.cs b/src/Discord.Net.WebSocket/DiscordShardedClient.Events.cs
index c9e679669..a9e6e311d 100644
--- a/src/Discord.Net.WebSocket/DiscordShardedClient.Events.cs
+++ b/src/Discord.Net.WebSocket/DiscordShardedClient.Events.cs
@@ -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> _shardLatencyUpdatedEvent = new AsyncEvent>();
}
-}
\ No newline at end of file
+}
diff --git a/src/Discord.Net.WebSocket/DiscordShardedClient.cs b/src/Discord.Net.WebSocket/DiscordShardedClient.cs
index 386f9f7e5..450145f1c 100644
--- a/src/Discord.Net.WebSocket/DiscordShardedClient.cs
+++ b/src/Discord.Net.WebSocket/DiscordShardedClient.cs
@@ -30,7 +30,16 @@ namespace Discord.WebSocket
///
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;
+ }
+ }
///
public override IReadOnlyCollection Guilds => GetGuilds().ToReadOnlyCollection(GetGuildCount);
///
@@ -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
diff --git a/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs b/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs
index 65fd23d3f..e19cedb33 100644
--- a/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs
+++ b/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs
@@ -75,8 +75,15 @@ namespace Discord.API
using (var jsonReader = new JsonTextReader(reader))
{
var msg = _serializer.Deserialize(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(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
{
- ["$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 guildIds, RequestOptions options = null)
{
diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs
index 12802247d..52a5d309d 100644
--- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs
+++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs
@@ -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(_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);
diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs
index 7187c9771..196f3c558 100644
--- a/src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs
+++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs
@@ -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);
}
diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketStageChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketStageChannel.cs
new file mode 100644
index 000000000..fb87a0591
--- /dev/null
+++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketStageChannel.cs
@@ -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
+{
+ ///
+ /// Represents a stage channel recieved over the gateway.
+ ///
+ public class SocketStageChannel : SocketVoiceChannel, IStageChannel
+ {
+ ///
+ public string Topic { get; private set; }
+
+ ///
+ public StagePrivacyLevel? PrivacyLevel { get; private set; }
+
+ ///
+ public bool? DiscoverableDisabled { get; private set; }
+
+ ///
+ public bool Live { get; private set; } = false;
+
+ ///
+ /// Returns if the current user is a speaker within the stage, otherwise .
+ ///
+ public bool IsSpeaker
+ => !Guild.CurrentUser.IsSuppressed;
+
+ ///
+ /// Gets a collection of users who are speakers within the stage.
+ ///
+ public IReadOnlyCollection 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;
+ }
+ }
+
+ ///
+ 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);
+ }
+
+ ///
+ public async Task ModifyInstanceAsync(Action func, RequestOptions options = null)
+ {
+ var model = await ChannelHelper.ModifyAsync(this, Discord, func, options);
+
+ this.Update(model, true);
+ }
+
+ ///
+ public async Task StopStageAsync(RequestOptions options = null)
+ {
+ await Discord.ApiClient.DeleteStageInstanceAsync(this.Id, options);
+
+ Update(null, false);
+ }
+
+ ///
+ 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);
+ }
+
+ ///
+ 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);
+ }
+
+ ///
+ 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);
+ }
+
+ ///
+ 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);
+ }
+
+ ///
+ 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);
+ }
+ }
+}
diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketThreadChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketThreadChannel.cs
index 8065a8b2b..33832096b 100644
--- a/src/Discord.Net.WebSocket/Entities/Channels/SocketThreadChannel.cs
+++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketThreadChannel.cs
@@ -281,14 +281,14 @@ namespace Discord.WebSocket
/// This method is not supported in threads.
///
public override OverwritePermissions? GetPermissionOverwrite(IRole role)
- => throw new NotImplementedException();
+ => ParentChannel.GetPermissionOverwrite(role);
///
///
/// This method is not supported in threads.
///
public override OverwritePermissions? GetPermissionOverwrite(IUser user)
- => throw new NotImplementedException();
+ => ParentChannel.GetPermissionOverwrite(user);
///
///
diff --git a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs
index f720db018..5c385fe01 100644
--- a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs
+++ b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs
@@ -118,6 +118,8 @@ namespace Discord.WebSocket
public int? MaxMembers { get; private set; }
///
public int? MaxVideoChannelUsers { get; private set; }
+ ///
+ public NsfwLevel NsfwLevel { get; private set; }
///
public CultureInfo PreferredCulture { get; private set; }
@@ -269,6 +271,14 @@ namespace Discord.WebSocket
public IReadOnlyCollection VoiceChannels
=> Channels.OfType().ToImmutableArray();
///
+ /// Gets a collection of all stage channels in this guild.
+ ///
+ ///
+ /// A read-only collection of stage channels found within this guild.
+ ///
+ public IReadOnlyCollection StageChannels
+ => Channels.OfType().ToImmutableArray();
+ ///
/// Gets a collection of all category channels in this guild.
///
///
@@ -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;
///
+ /// Gets a thread in this guild.
+ ///
+ /// The snowflake identifier for the thread.
+ ///
+ /// A thread channel associated with the specified ; if none is found.
+ ///
+ public SocketThreadChannel GetThreadChannel(ulong id)
+ => GetChannel(id) as SocketThreadChannel;
+
+ ///
/// Gets a voice channel in this guild.
///
/// The snowflake identifier for the voice channel.
@@ -639,6 +660,15 @@ namespace Discord.WebSocket
public SocketVoiceChannel GetVoiceChannel(ulong id)
=> GetChannel(id) as SocketVoiceChannel;
///
+ /// Gets a stage channel in this guild.
+ ///
+ /// The snowflake identifier for the stage channel.
+ ///
+ /// A stage channel associated with the specified ; if none is found.
+ ///
+ public SocketStageChannel GetStageChannel(ulong id)
+ => GetChannel(id) as SocketStageChannel;
+ ///
/// Gets a category channel in this guild.
///
/// The snowflake identifier for the category channel.
@@ -1326,6 +1356,12 @@ namespace Discord.WebSocket
Task IGuild.GetTextChannelAsync(ulong id, CacheMode mode, RequestOptions options)
=> Task.FromResult(GetTextChannel(id));
///
+ Task IGuild.GetThreadChannelAsync(ulong id, CacheMode mode, RequestOptions options)
+ => Task.FromResult(GetThreadChannel(id));
+ ///
+ Task> IGuild.GetThreadChannelsAsync(CacheMode mode, RequestOptions options)
+ => Task.FromResult>(ThreadChannels);
+ ///
Task> IGuild.GetVoiceChannelsAsync(CacheMode mode, RequestOptions options)
=> Task.FromResult>(VoiceChannels);
///
@@ -1335,6 +1371,12 @@ namespace Discord.WebSocket
Task IGuild.GetVoiceChannelAsync(ulong id, CacheMode mode, RequestOptions options)
=> Task.FromResult(GetVoiceChannel(id));
///
+ Task IGuild.GetStageChannelAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null)
+ => Task.FromResult(GetStageChannel(id));
+ ///
+ Task> IGuild.GetStageChannelsAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null)
+ => Task.FromResult>(StageChannels);
+ ///
Task IGuild.GetAFKChannelAsync(CacheMode mode, RequestOptions options)
=> Task.FromResult(AFKChannel);
///
@@ -1436,6 +1478,9 @@ namespace Discord.WebSocket
///
async Task> IGuild.GetWebhooksAsync(RequestOptions options)
=> await GetWebhooksAsync(options).ConfigureAwait(false);
+ ///
+ async Task> IGuild.GetApplicationCommandsAsync (RequestOptions options)
+ => await GetApplicationCommandsAsync(options).ConfigureAwait(false);
void IDisposable.Dispose()
{
@@ -1443,5 +1488,7 @@ namespace Discord.WebSocket
_audioLock?.Dispose();
_audioClient?.Dispose();
}
+
+
}
}
diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/Message Components/SocketMessageComponent.cs b/src/Discord.Net.WebSocket/Entities/Interaction/Message Components/SocketMessageComponent.cs
index 921f81de4..cc91b098f 100644
--- a/src/Discord.Net.WebSocket/Entities/Interaction/Message Components/SocketMessageComponent.cs
+++ b/src/Discord.Net.WebSocket/Entities/Interaction/Message Components/SocketMessageComponent.cs
@@ -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
{
- ///
- /// Represents a Websocket-based interaction type for Message Components.
- ///
+///
+/// Represents a Websocket-based interaction type for Message Components.
+///
public class SocketMessageComponent : SocketInteraction
{
///
@@ -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() : 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.Unspecified,
- Embeds = args.Embeds.IsSpecified ? args.Embeds.Value?.Select(x => x.ToModel()).ToArray() : Optional.Unspecified,
+ Embeds = apiEmbeds?.ToArray() ?? Optional.Unspecified,
Components = args.Components.IsSpecified
? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray()
: Optional.Unspecified,
- Flags = args.Flags.IsSpecified ? (int?)args.Flags.Value ?? Optional.Unspecified : Optional.Unspecified
+ Flags = args.Flags.IsSpecified ? args.Flags.Value ?? Optional.Unspecified : Optional.Unspecified
}
};
@@ -213,27 +234,43 @@ namespace Discord.WebSocket
AllowedMentions = allowedMentions?.ToModel() ?? Optional.Unspecified,
IsTTS = isTTS,
Embeds = embeds?.Select(x => x.ToModel()).ToArray() ?? Optional.Unspecified,
- Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional.Unspecified
+ Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional.Unspecified,
};
if (ephemeral)
- args.Flags = 64;
+ args.Flags = MessageFlags.Ephemeral;
return await InteractionHelper.SendFollowupAsync(Discord.Rest, args, Token, Channel, options);
}
///
- /// Acknowledges this interaction with the .
+ /// Defers an interaction and responds with type 5 ()
///
+ /// to send this message ephemerally, otherwise .
/// The request options for this async request.
///
/// A task that represents the asynchronous operation of acknowledging the interaction.
///
- 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.Unspecified
+
+ };
+
+ return Discord.Rest.ApiClient.CreateInteractionResponse(response, this.Id, this.Token, options);
+ }
+
+ ///
+ 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.Unspecified
+
};
return Discord.Rest.ApiClient.CreateInteractionResponse(response, this.Id, this.Token, options);
diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketSlashCommand.cs b/src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketSlashCommand.cs
index 245274613..bff5292c5 100644
--- a/src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketSlashCommand.cs
+++ b/src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketSlashCommand.cs
@@ -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);
}
- ///
- /// Acknowledges this interaction with the .
- ///
- ///
- /// A task that represents the asynchronous operation of acknowledging the interaction.
- ///
- public override Task DeferAsync(RequestOptions options = null)
+ ///
+ 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.Unspecified
};
return Discord.Rest.ApiClient.CreateInteractionResponse(response, this.Id, this.Token, options);
diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketSlashCommandDataOption.cs b/src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketSlashCommandDataOption.cs
index f33008cf3..f9c12257e 100644
--- a/src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketSlashCommandDataOption.cs
+++ b/src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketSlashCommandDataOption.cs
@@ -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
diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs b/src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs
index 4b2e3baec..e7f5873d6 100644
--- a/src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs
+++ b/src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs
@@ -156,20 +156,37 @@ namespace Discord.WebSocket
/// A task that represents the asynchronous operation of acknowledging the interaction.
///
[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);
///
/// Acknowledges this interaction.
///
+ /// to send this message ephemerally, otherwise .
+ /// The request options for this async request.
///
/// A task that represents the asynchronous operation of acknowledging the interaction.
///
- 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
+
+ ///
+ async Task 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);
+
+ ///
+ async Task IDiscordInteraction.GetOriginalResponseAsync (RequestOptions options)
+ => await GetOriginalResponseAsync(options).ConfigureAwait(false);
+
+ ///
+ async Task IDiscordInteraction.ModifyOriginalResponseAsync (Action func, RequestOptions options)
+ => await ModifyOriginalResponseAsync(func, options).ConfigureAwait(false);
}
}
diff --git a/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs
index 82c656486..1b62d14dd 100644
--- a/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs
+++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs
@@ -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);
diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketGlobalUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketGlobalUser.cs
index 15c5182fc..b1bce5934 100644
--- a/src/Discord.Net.WebSocket/Entities/Users/SocketGlobalUser.cs
+++ b/src/Discord.Net.WebSocket/Entities/Users/SocketGlobalUser.cs
@@ -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);
diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketGroupUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketGroupUser.cs
index 8545e492b..d99310540 100644
--- a/src/Discord.Net.WebSocket/Entities/Users/SocketGroupUser.cs
+++ b/src/Discord.Net.WebSocket/Entities/Users/SocketGroupUser.cs
@@ -1,3 +1,4 @@
+using System;
using System.Diagnostics;
using Model = Discord.API.User;
@@ -28,6 +29,10 @@ namespace Discord.WebSocket
///
public override string AvatarId { get { return GlobalUser.AvatarId; } internal set { GlobalUser.AvatarId = value; } }
///
+ public override string BannerId { get { return GlobalUser.BannerId; } internal set { GlobalUser.BannerId = value; } }
+ ///
+ public override Color? AccentColor { get { return GlobalUser.AccentColor; } internal set { GlobalUser.AccentColor = value; } }
+ ///
internal override SocketPresence Presence { get { return GlobalUser.Presence; } set { GlobalUser.Presence = value; } }
///
@@ -66,5 +71,7 @@ namespace Discord.WebSocket
string IVoiceState.VoiceSessionId => null;
///
bool IVoiceState.IsStreaming => false;
+ ///
+ DateTimeOffset? IVoiceState.RequestToSpeakTimestamp => null;
}
}
diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs
index 444c76ffa..ac8409a32 100644
--- a/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs
+++ b/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs
@@ -38,6 +38,11 @@ namespace Discord.WebSocket
public override ushort DiscriminatorValue { get { return GlobalUser.DiscriminatorValue; } internal set { GlobalUser.DiscriminatorValue = value; } }
///
public override string AvatarId { get { return GlobalUser.AvatarId; } internal set { GlobalUser.AvatarId = value; } }
+ ///
+ public override string BannerId { get { return GlobalUser.BannerId; } internal set { GlobalUser.BannerId = value; } }
+ ///
+ public override Color? AccentColor { get { return GlobalUser.AccentColor; } internal set { GlobalUser.AccentColor = value; } }
+
///
public GuildPermissions GuildPermissions => new GuildPermissions(Permissions.ResolveGuild(Guild, this));
internal override SocketPresence Presence { get; set; }
@@ -57,7 +62,11 @@ namespace Discord.WebSocket
///
public bool IsStreaming => VoiceState?.IsStreaming ?? false;
///
+ public DateTimeOffset? RequestToSpeakTimestamp => VoiceState?.RequestToSpeakTimestamp ?? null;
+ ///
public bool? IsPending { get; private set; }
+
+
///
public DateTimeOffset? JoinedAt => DateTimeUtils.FromTicks(_joinedAtTicks);
///
@@ -87,7 +96,7 @@ namespace Discord.WebSocket
/// Returns the position of the user within the role hierarchy.
///
///
- /// 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
/// if user is the server owner.
///
public int Hierarchy
diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketSelfUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketSelfUser.cs
index 7b11257a3..e821238ee 100644
--- a/src/Discord.Net.WebSocket/Entities/Users/SocketSelfUser.cs
+++ b/src/Discord.Net.WebSocket/Entities/Users/SocketSelfUser.cs
@@ -29,6 +29,10 @@ namespace Discord.WebSocket
///
public override string AvatarId { get { return GlobalUser.AvatarId; } internal set { GlobalUser.AvatarId = value; } }
///
+ public override string BannerId { get { return GlobalUser.BannerId; } internal set { GlobalUser.BannerId = value; } }
+ ///
+ public override Color? AccentColor { get { return GlobalUser.AccentColor; } internal set { GlobalUser.AccentColor = value; } }
+ ///
internal override SocketPresence Presence { get { return GlobalUser.Presence; } set { GlobalUser.Presence = value; } }
///
public UserProperties Flags { get; internal set; }
diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketThreadUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketThreadUser.cs
index df9194d5b..5fb1f56e5 100644
--- a/src/Discord.Net.WebSocket/Entities/Users/SocketThreadUser.cs
+++ b/src/Discord.Net.WebSocket/Entities/Users/SocketThreadUser.cs
@@ -36,7 +36,7 @@ namespace Discord.WebSocket
///
public string Nickname
- => GuildUser.Nickname;
+ => GuildUser.Nickname;
///
public DateTimeOffset? PremiumSince
@@ -53,6 +53,20 @@ namespace Discord.WebSocket
internal set => GuildUser.AvatarId = value;
}
+ ///
+ public override string BannerId
+ {
+ get => GuildUser.BannerId;
+ internal set => GuildUser.BannerId = value;
+ }
+
+ ///
+ public override Color? AccentColor
+ {
+ get => GuildUser.AccentColor;
+ internal set => GuildUser.AccentColor = value;
+ }
+
///
public override ushort DiscriminatorValue
{
@@ -110,6 +124,10 @@ namespace Discord.WebSocket
public bool IsStreaming
=> GuildUser.IsStreaming;
+ ///
+ public DateTimeOffset? RequestToSpeakTimestamp
+ => GuildUser.RequestToSpeakTimestamp;
+
private SocketGuildUser GuildUser { get; set; }
internal SocketThreadUser(SocketGuild guild, SocketThreadChannel thread, SocketGuildUser member)
diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketUnknownUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketUnknownUser.cs
index 840a1c30b..180e60a3b 100644
--- a/src/Discord.Net.WebSocket/Entities/Users/SocketUnknownUser.cs
+++ b/src/Discord.Net.WebSocket/Entities/Users/SocketUnknownUser.cs
@@ -19,9 +19,16 @@ namespace Discord.WebSocket
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; }
+
///
public override bool IsBot { get; internal set; }
-
+
///
public override bool IsWebhook => false;
///
diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs
index 025daf29a..c50fbee4f 100644
--- a/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs
+++ b/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs
@@ -25,6 +25,10 @@ namespace Discord.WebSocket
///
public abstract string AvatarId { get; internal set; }
///
+ public abstract string BannerId { get; internal set; }
+ ///
+ public abstract Color? AccentColor { get; internal set; }
+ ///
public abstract bool IsWebhook { get; }
///
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);
+ ///
+ public string GetBannerUrl(ImageFormat format = ImageFormat.Auto, ushort size = 256)
+ => CDN.GetUserBannerUrl(Id, BannerId, size, format);
+
///
public string GetDefaultAvatarUrl()
=> CDN.GetDefaultUserAvatarUrl(DiscriminatorValue);
diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketVoiceState.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketVoiceState.cs
index 5bf36e796..816a839fc 100644
--- a/src/Discord.Net.WebSocket/Entities/Users/SocketVoiceState.cs
+++ b/src/Discord.Net.WebSocket/Entities/Users/SocketVoiceState.cs
@@ -13,7 +13,7 @@ namespace Discord.WebSocket
///
/// Initializes a default with everything set to null or false.
///
- 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; }
///
public string VoiceSessionId { get; }
+ ///
+ public DateTimeOffset? RequestToSpeakTimestamp { get; private set; }
///
public bool IsMuted => (_voiceStates & Flags.Muted) != 0;
@@ -48,11 +50,13 @@ namespace Discord.WebSocket
public bool IsSelfDeafened => (_voiceStates & Flags.SelfDeafened) != 0;
///
public bool IsStreaming => (_voiceStates & Flags.SelfStream) != 0;
+
- internal SocketVoiceState(SocketVoiceChannel voiceChannel, string sessionId, bool isSelfMuted, bool isSelfDeafened, bool isMuted, bool isDeafened, bool isSuppressed, bool isStream)
+ internal SocketVoiceState(SocketVoiceChannel voiceChannel, DateTimeOffset? requestToSpeak, string sessionId, bool isSelfMuted, bool isSelfDeafened, bool isMuted, bool isDeafened, bool isSuppressed, bool isStream)
{
VoiceChannel = voiceChannel;
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);
}
///
diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketWebhookUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketWebhookUser.cs
index 404ab116d..f1269e649 100644
--- a/src/Discord.Net.WebSocket/Entities/Users/SocketWebhookUser.cs
+++ b/src/Discord.Net.WebSocket/Entities/Users/SocketWebhookUser.cs
@@ -24,6 +24,23 @@ namespace Discord.WebSocket
public override ushort DiscriminatorValue { get; internal set; }
///
public override string AvatarId { get; internal set; }
+
+ ///
+ /// Webhook users does not support banners.
+ 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.");
+ }
+
+ ///
+ /// Webhook users does not support accent colors.
+ 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.");
+ }
+
///
public override bool IsBot { get; internal set; }
@@ -138,5 +155,7 @@ namespace Discord.WebSocket
string IVoiceState.VoiceSessionId => null;
///
bool IVoiceState.IsStreaming => false;
+ ///
+ DateTimeOffset? IVoiceState.RequestToSpeakTimestamp => null;
}
}
diff --git a/src/Discord.Net.Webhook/Discord.Net.Webhook.csproj b/src/Discord.Net.Webhook/Discord.Net.Webhook.csproj
index 13e5cf111..f04dedf43 100644
--- a/src/Discord.Net.Webhook/Discord.Net.Webhook.csproj
+++ b/src/Discord.Net.Webhook/Discord.Net.Webhook.csproj
@@ -6,7 +6,7 @@
Discord.Webhook
A core Discord.Net Labs library containing the Webhook client and models.
netstandard2.0;netstandard2.1
- 2.3.4
+ 3.0.0-pre
Discord.Net.Labs.Webhook
https://github.com/Discord-Net-Labs/Discord.Net-Labs
https://github.com/Discord-Net-Labs/Discord.Net-Labs
diff --git a/src/Discord.Net.Webhook/Discord.Net.Webhook.xml b/src/Discord.Net.Webhook/Discord.Net.Webhook.xml
index f629c2c29..d1bafb9a3 100644
--- a/src/Discord.Net.Webhook/Discord.Net.Webhook.xml
+++ b/src/Discord.Net.Webhook/Discord.Net.Webhook.xml
@@ -31,7 +31,7 @@
Thrown if the is an invalid format.
Thrown if the is null or whitespace.
-
+
Sends a message to the channel for this webhook.
Returns the ID of the created message.
@@ -99,6 +99,11 @@
Gets or sets the allowed mentions of the message.
+
+
+ Gets or sets the components that the message should display.
+
+
Could not find a webhook with the supplied credentials.
diff --git a/src/Discord.Net.Webhook/DiscordWebhookClient.cs b/src/Discord.Net.Webhook/DiscordWebhookClient.cs
index 91d077411..d4affb08b 100644
--- a/src/Discord.Net.Webhook/DiscordWebhookClient.cs
+++ b/src/Discord.Net.Webhook/DiscordWebhookClient.cs
@@ -88,8 +88,8 @@ namespace Discord.Webhook
/// Sends a message to the channel for this webhook.
/// Returns the ID of the created message.
public Task SendMessageAsync(string text = null, bool isTTS = false, IEnumerable embeds = null,
- string username = null, string avatarUrl = null, RequestOptions options = null, AllowedMentions allowedMentions = null)
- => 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);
///
/// Modifies a message posted using this webhook.
diff --git a/src/Discord.Net.Webhook/Entities/Messages/WebhookMessageProperties.cs b/src/Discord.Net.Webhook/Entities/Messages/WebhookMessageProperties.cs
index dec7b6e3b..ca2ff10a0 100644
--- a/src/Discord.Net.Webhook/Entities/Messages/WebhookMessageProperties.cs
+++ b/src/Discord.Net.Webhook/Entities/Messages/WebhookMessageProperties.cs
@@ -22,5 +22,9 @@ namespace Discord.Webhook
/// Gets or sets the allowed mentions of the message.
///
public Optional AllowedMentions { get; set; }
+ ///
+ /// Gets or sets the components that the message should display.
+ ///
+ public Optional Components { get; set; }
}
}
diff --git a/src/Discord.Net.Webhook/Entities/Webhooks/RestInternalWebhook.cs b/src/Discord.Net.Webhook/Entities/Webhooks/RestInternalWebhook.cs
index bbb160fcd..2a5c4786e 100644
--- a/src/Discord.Net.Webhook/Entities/Webhooks/RestInternalWebhook.cs
+++ b/src/Discord.Net.Webhook/Entities/Webhooks/RestInternalWebhook.cs
@@ -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)
diff --git a/src/Discord.Net.Webhook/WebhookClientHelper.cs b/src/Discord.Net.Webhook/WebhookClientHelper.cs
index 528848f7f..6e3651323 100644
--- a/src/Discord.Net.Webhook/WebhookClientHelper.cs
+++ b/src/Discord.Net.Webhook/WebhookClientHelper.cs
@@ -21,7 +21,7 @@ namespace Discord.Webhook
return RestInternalWebhook.Create(client, model);
}
public static async Task SendMessageAsync(DiscordWebhookClient client,
- string text, bool isTTS, IEnumerable embeds, string username, string avatarUrl, AllowedMentions allowedMentions, RequestOptions options)
+ string text, bool isTTS, IEnumerable 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(),
AllowedMentions = args.AllowedMentions.IsSpecified
? args.AllowedMentions.Value.ToModel()
- : Optional.Create()
+ : Optional.Create(),
+ Components = args.Components.IsSpecified ? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() : Optional.Unspecified,
};
await client.ApiClient.ModifyWebhookMessageAsync(client.Webhook.Id, messageId, apiArgs, options)
diff --git a/src/Discord.Net/Discord.Net.nuspec b/src/Discord.Net/Discord.Net.nuspec
index 79f8893fa..eb3bd9b6d 100644
--- a/src/Discord.Net/Discord.Net.nuspec
+++ b/src/Discord.Net/Discord.Net.nuspec
@@ -2,7 +2,7 @@
Discord.Net.Labs
- 2.4.9$suffix$
+ 3.0.2-pre$suffix$
Discord.Net Labs
Discord.Net Contributors
quinchs
@@ -14,23 +14,23 @@
https://avatars.githubusercontent.com/u/84047264
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
diff --git a/test/Discord.Net.Tests.Unit/GuildPermissionsTests.cs b/test/Discord.Net.Tests.Unit/GuildPermissionsTests.cs
index 137dc5575..9be109c6e 100644
--- a/test/Discord.Net.Tests.Unit/GuildPermissionsTests.cs
+++ b/test/Discord.Net.Tests.Unit/GuildPermissionsTests.cs
@@ -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);
}
///
@@ -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));
}
}
}