diff --git a/src/Discord.Net.Core/Discord.Net.Core.xml b/src/Discord.Net.Core/Discord.Net.Core.xml
index 72820de90..ca9e9696f 100644
--- a/src/Discord.Net.Core/Discord.Net.Core.xml
+++ b/src/Discord.Net.Core/Discord.Net.Core.xml
@@ -1914,6 +1914,70 @@
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.
+
+
Represents a generic channel in a guild that can send and receive messages.
@@ -2202,6 +2266,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.
@@ -3344,6 +3423,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.
@@ -9780,6 +9882,11 @@
true if the user is streaming; otherwise false.
+
+
+ Gets the time on which the user requested to speak.
+
+
Represents a Webhook Discord user.
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..6f517d100
--- /dev/null
+++ b/src/Discord.Net.Core/Entities/Channels/IStageChannel.cs
@@ -0,0 +1,75 @@
+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);
+
+ Task RequestToSpeak(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/Guilds/IGuild.cs b/src/Discord.Net.Core/Entities/Guilds/IGuild.cs
index e0be51cf5..ad2e0317d 100644
--- a/src/Discord.Net.Core/Entities/Guilds/IGuild.cs
+++ b/src/Discord.Net.Core/Entities/Guilds/IGuild.cs
@@ -530,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.
diff --git a/src/Discord.Net.Core/Entities/Users/IVoiceState.cs b/src/Discord.Net.Core/Entities/Users/IVoiceState.cs
index a9b347003..c9a22761f 100644
--- a/src/Discord.Net.Core/Entities/Users/IVoiceState.cs
+++ b/src/Discord.Net.Core/Entities/Users/IVoiceState.cs
@@ -1,3 +1,5 @@
+using System;
+
namespace Discord
{
///
@@ -62,5 +64,9 @@ namespace Discord
/// true if the user is streaming; otherwise false.
///
bool IsStreaming { get; }
+ ///
+ /// Gets the time on which the user requested to speak.
+ ///
+ DateTimeOffset? RequestToSpeakTimestamp { get; }
}
}
diff --git a/src/Discord.Net.Rest/API/Common/StageInstance.cs b/src/Discord.Net.Rest/API/Common/StageInstance.cs
new file mode 100644
index 000000000..4cb5f5823
--- /dev/null
+++ b/src/Discord.Net.Rest/API/Common/StageInstance.cs
@@ -0,0 +1,30 @@
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Discord.API
+{
+ internal class StageInstance
+ {
+ [JsonProperty("id")]
+ public ulong Id { get; set; }
+
+ [JsonProperty("guild_id")]
+ public ulong GuildId { get; set; }
+
+ [JsonProperty("channel_id")]
+ public ulong ChannelId { get; set; }
+
+ [JsonProperty("topic")]
+ public string Topic { get; set; }
+
+ [JsonProperty("privacy_level")]
+ public StagePrivacyLevel PrivacyLevel { get; set; }
+
+ [JsonProperty("discoverable_disabled")]
+ public bool DiscoverableDisabled { get; set; }
+ }
+}
diff --git a/src/Discord.Net.Rest/API/Common/VoiceState.cs b/src/Discord.Net.Rest/API/Common/VoiceState.cs
index c7a571ed0..27aacb6a4 100644
--- a/src/Discord.Net.Rest/API/Common/VoiceState.cs
+++ b/src/Discord.Net.Rest/API/Common/VoiceState.cs
@@ -1,5 +1,6 @@
#pragma warning disable CS1591
using Newtonsoft.Json;
+using System;
namespace Discord.API
{
@@ -28,5 +29,7 @@ namespace Discord.API
public bool Suppress { get; set; }
[JsonProperty("self_stream")]
public bool SelfStream { get; set; }
+ [JsonProperty("request_to_speak_timestamp")]
+ public Optional RequestToSpeakTimestamp { get; set; }
}
}
diff --git a/src/Discord.Net.Rest/API/Rest/CreateStageInstanceParams.cs b/src/Discord.Net.Rest/API/Rest/CreateStageInstanceParams.cs
new file mode 100644
index 000000000..294f9e1a5
--- /dev/null
+++ b/src/Discord.Net.Rest/API/Rest/CreateStageInstanceParams.cs
@@ -0,0 +1,21 @@
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Discord.API.Rest
+{
+ internal class CreateStageInstanceParams
+ {
+ [JsonProperty("channel_id")]
+ public ulong ChannelId { get; set; }
+
+ [JsonProperty("topic")]
+ public string Topic { get; set; }
+
+ [JsonProperty("privacy_level")]
+ public Optional PrivacyLevel { get; set; }
+ }
+}
diff --git a/src/Discord.Net.Rest/API/Rest/ModifyStageInstanceParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyStageInstanceParams.cs
new file mode 100644
index 000000000..df73954de
--- /dev/null
+++ b/src/Discord.Net.Rest/API/Rest/ModifyStageInstanceParams.cs
@@ -0,0 +1,19 @@
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Discord.API.Rest
+{
+ internal class ModifyStageInstanceParams
+ {
+
+ [JsonProperty("topic")]
+ public Optional Topic { get; set; }
+
+ [JsonProperty("privacy_level")]
+ public Optional PrivacyLevel { get; set; }
+ }
+}
diff --git a/src/Discord.Net.Rest/API/Rest/ModifyVoiceStateParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyVoiceStateParams.cs
new file mode 100644
index 000000000..1ff0f3e08
--- /dev/null
+++ b/src/Discord.Net.Rest/API/Rest/ModifyVoiceStateParams.cs
@@ -0,0 +1,17 @@
+using Newtonsoft.Json;
+using System;
+
+namespace Discord.API.Rest
+{
+ internal class ModifyVoiceStateParams
+ {
+ [JsonProperty("channel_id")]
+ public ulong ChannelId { get; set; }
+
+ [JsonProperty("suppress")]
+ public Optional Suppressed { get; set; }
+
+ [JsonProperty("request_to_speak_timestamp")]
+ public Optional RequestToSpeakTimestamp { get; set; }
+ }
+}
diff --git a/src/Discord.Net.Rest/Discord.Net.Rest.xml b/src/Discord.Net.Rest/Discord.Net.Rest.xml
index 3aaa2c6a2..c8865f786 100644
--- a/src/Discord.Net.Rest/Discord.Net.Rest.xml
+++ b/src/Discord.Net.Rest/Discord.Net.Rest.xml
@@ -2281,6 +2281,38 @@
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.
@@ -3130,6 +3162,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.
@@ -3496,6 +3550,12 @@
+
+
+
+
+
+
@@ -4365,6 +4425,9 @@
+
+
+
Represents a REST-based guild user.
@@ -4456,6 +4519,9 @@
+
+
+
Represents the logged-in REST-based user.
@@ -4679,6 +4745,9 @@
+
+
+
diff --git a/src/Discord.Net.Rest/DiscordRestApiClient.cs b/src/Discord.Net.Rest/DiscordRestApiClient.cs
index d34a6a4b9..99c2dadad 100644
--- a/src/Discord.Net.Rest/DiscordRestApiClient.cs
+++ b/src/Discord.Net.Rest/DiscordRestApiClient.cs
@@ -577,6 +577,70 @@ 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);
+ }
+
// 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 27d6cd1a6..f0150aeb2 100644
--- a/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs
+++ b/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs
@@ -40,6 +40,8 @@ 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:
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..883a080f1
--- /dev/null
+++ b/src/Discord.Net.Rest/Entities/Channels/RestStageChannel.cs
@@ -0,0 +1,109 @@
+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);
+ }
+ }
+}
diff --git a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs
index a184f1290..2fab63347 100644
--- a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs
+++ b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs
@@ -451,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.
@@ -952,6 +981,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)
@@ -1109,5 +1154,6 @@ namespace Discord.Rest
///
async Task> IGuild.GetWebhooksAsync(RequestOptions options)
=> await GetWebhooksAsync(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/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.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/BaseSocketClient.Events.cs b/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs
index 32f79f3d1..4982655d7 100644
--- a/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs
+++ b/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs
@@ -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/Discord.Net.WebSocket.csproj b/src/Discord.Net.WebSocket/Discord.Net.WebSocket.csproj
index 3f1f63db3..35aabb010 100644
--- a/src/Discord.Net.WebSocket/Discord.Net.WebSocket.csproj
+++ b/src/Discord.Net.WebSocket/Discord.Net.WebSocket.csproj
@@ -1,4 +1,4 @@
-
+
@@ -17,6 +17,9 @@
..\Discord.Net.WebSocket\Discord.Net.WebSocket.xml
+
+ DEBUG;TRACE;DEBUG_LIMITS
+
diff --git a/src/Discord.Net.WebSocket/Discord.Net.WebSocket.xml b/src/Discord.Net.WebSocket/Discord.Net.WebSocket.xml
index 01d919d2b..93873217f 100644
--- a/src/Discord.Net.WebSocket/Discord.Net.WebSocket.xml
+++ b/src/Discord.Net.WebSocket/Discord.Net.WebSocket.xml
@@ -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,40 @@
+
+
+ Represents a stage channel recieved over the gateway.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 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.
@@ -2901,6 +2965,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.
@@ -3076,6 +3148,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.
@@ -3417,6 +3498,12 @@
+
+
+
+
+
+
@@ -4358,6 +4445,9 @@
+
+
+
Represents a WebSocket-based guild user.
@@ -4407,6 +4497,9 @@
+
+
+
@@ -4654,6 +4747,9 @@
+
+
+
@@ -4819,6 +4915,9 @@
+
+
+
@@ -4968,6 +5067,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 18272eef2..6847d8580 100644
--- a/src/Discord.Net.WebSocket/DiscordShardedClient.cs
+++ b/src/Discord.Net.WebSocket/DiscordShardedClient.cs
@@ -386,6 +386,13 @@ namespace Discord.WebSocket
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/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs
index 14a9bbbe8..3b283a004 100644
--- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs
+++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs
@@ -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;
@@ -2181,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..15e49901e
--- /dev/null
+++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketStageChannel.cs
@@ -0,0 +1,116 @@
+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;
+
+ ///
+ /// 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);
+ }
+ }
+}
diff --git a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs
index 33c266beb..c32bb3f49 100644
--- a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs
+++ b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs
@@ -271,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.
///
///
@@ -652,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.
@@ -1354,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);
///
@@ -1462,5 +1485,7 @@ namespace Discord.WebSocket
_audioLock?.Dispose();
_audioClient?.Dispose();
}
+
+
}
}
diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketGroupUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketGroupUser.cs
index 8545e492b..805a88110 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;
@@ -66,5 +67,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..f79fc7afe 100644
--- a/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs
+++ b/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs
@@ -57,7 +57,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);
///
diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketThreadUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketThreadUser.cs
index df9194d5b..d1237d598 100644
--- a/src/Discord.Net.WebSocket/Entities/Users/SocketThreadUser.cs
+++ b/src/Discord.Net.WebSocket/Entities/Users/SocketThreadUser.cs
@@ -110,6 +110,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/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..2b0ecbb19 100644
--- a/src/Discord.Net.WebSocket/Entities/Users/SocketWebhookUser.cs
+++ b/src/Discord.Net.WebSocket/Entities/Users/SocketWebhookUser.cs
@@ -138,5 +138,7 @@ namespace Discord.WebSocket
string IVoiceState.VoiceSessionId => null;
///
bool IVoiceState.IsStreaming => false;
+ ///
+ DateTimeOffset? IVoiceState.RequestToSpeakTimestamp => null;
}
}