commitpull/1923/head73740e4169
Author: quin lynch <lynchquin@gmail.com> Date: Wed Aug 4 23:17:04 2021 -0300 add sharded events commitdac6ba3603
Merge: 22e3a529 39b7e715 Author: quin lynch <lynchquin@gmail.com> Date: Wed Aug 4 22:16:12 2021 -0300 Merge branch 'feature/stage-channels' of https://github.com/Discord-Net-Labs/Discord.Net-Labs into feature/stage-channels commit22e3a529e0
Author: quin lynch <lynchquin@gmail.com> Date: Wed Aug 4 22:15:03 2021 -0300 Threads pre 2 commit39b7e715f3
Author: Quin Lynch <49576606+quinchs@users.noreply.github.com> Date: Tue Aug 3 20:43:21 2021 -0300 Update README.md commit56536f6448
Author: Quin Lynch <49576606+quinchs@users.noreply.github.com> Date: Tue Aug 3 20:28:27 2021 -0300 Update README.md commitd98e79452c
Author: quin lynch <lynchquin@gmail.com> Date: Sat Jul 31 21:22:56 2021 -0300 Request to speak stuff commit950fe80cec
Author: quin lynch <lynchquin@gmail.com> Date: Sat Jul 31 20:59:11 2021 -0300 Rest stage channels commit360b9436bb
Author: quin lynch <lynchquin@gmail.com> Date: Sat Jul 31 20:29:38 2021 -0300 Stage channel socket side commit928edaa03b
Author: quin lynch <lynchquin@gmail.com> Date: Sat Jul 31 19:19:40 2021 -0300 Add models commitec58a9b7e8
Author: quin lynch <lynchquin@gmail.com> Date: Sat Jul 31 18:59:09 2021 -0300 initial stage channel events
@@ -1914,6 +1914,70 @@ | |||
A read-only collection of users that can access this channel. | |||
</returns> | |||
</member> | |||
<member name="T:Discord.IStageChannel"> | |||
<summary> | |||
Represents a generic Stage Channel. | |||
</summary> | |||
</member> | |||
<member name="P:Discord.IStageChannel.Topic"> | |||
<summary> | |||
Gets the topic of the Stage instance. | |||
</summary> | |||
<remarks> | |||
If the stage isn't live then this property will be set to <see langword="null"/>. | |||
</remarks> | |||
</member> | |||
<member name="P:Discord.IStageChannel.PrivacyLevel"> | |||
<summary> | |||
The <see cref="T:Discord.StagePrivacyLevel"/> of the current stage. | |||
</summary> | |||
<remarks> | |||
If the stage isn't live then this property will be set to <see langword="null"/>. | |||
</remarks> | |||
</member> | |||
<member name="P:Discord.IStageChannel.DiscoverableDisabled"> | |||
<summary> | |||
<see langword="true"/> if stage discovery is disabled, otherwise <see langword="false"/>. | |||
</summary> | |||
</member> | |||
<member name="P:Discord.IStageChannel.Live"> | |||
<summary> | |||
<see langword="true"/> when the stage is live, otherwise <see langword="false"/>. | |||
</summary> | |||
<remarks> | |||
If the stage isn't live then this property will be set to <see langword="null"/>. | |||
</remarks> | |||
</member> | |||
<member name="M:Discord.IStageChannel.StartStageAsync(System.String,Discord.StagePrivacyLevel,Discord.RequestOptions)"> | |||
<summary> | |||
Starts the stage, creating a stage instance. | |||
</summary> | |||
<param name="topic">The topic for the stage/</param> | |||
<param name="privacyLevel">The privacy level of the stage</param> | |||
<param name="options">The options to be used when sending the request.</param> | |||
<returns> | |||
A task that represents the asynchronous start operation. | |||
</returns> | |||
</member> | |||
<member name="M:Discord.IStageChannel.ModifyInstanceAsync(System.Action{Discord.StageInstanceProperties},Discord.RequestOptions)"> | |||
<summary> | |||
Modifies the current stage instance. | |||
</summary> | |||
<param name="func">The properties to modify the stage instance with.</param> | |||
<param name="options">The options to be used when sending the request.</param> | |||
<returns> | |||
A task that represents the asynchronous modify operation. | |||
</returns> | |||
</member> | |||
<member name="M:Discord.IStageChannel.StopStageAsync(Discord.RequestOptions)"> | |||
<summary> | |||
Stops the stage, deleting the stage instance. | |||
</summary> | |||
<param name="options">The options to be used when sending the request.</param> | |||
<returns> | |||
A task that represents the asynchronous stop operation. | |||
</returns> | |||
</member> | |||
<member name="T:Discord.ITextChannel"> | |||
<summary> | |||
Represents a generic channel in a guild that can send and receive messages. | |||
@@ -2202,6 +2266,21 @@ | |||
<param name="id"> Sets the ID of the channel to apply this position to. </param> | |||
<param name="position"> Sets the new zero-based position of this channel. </param> | |||
</member> | |||
<member name="T:Discord.StageInstanceProperties"> | |||
<summary> | |||
Represents properties to use when modifying a stage instance. | |||
</summary> | |||
</member> | |||
<member name="P:Discord.StageInstanceProperties.Topic"> | |||
<summary> | |||
Gets or sets the topic of the stage. | |||
</summary> | |||
</member> | |||
<member name="P:Discord.StageInstanceProperties.PrivacyLevel"> | |||
<summary> | |||
Gets or sets the privacy level of the stage. | |||
</summary> | |||
</member> | |||
<member name="T:Discord.TextChannelProperties"> | |||
<summary> | |||
Provides properties that are used to modify an <see cref="T:Discord.ITextChannel"/> with the specified changes. | |||
@@ -3344,6 +3423,29 @@ | |||
with the specified <paramref name="id"/>; <see langword="null" /> if none is found. | |||
</returns> | |||
</member> | |||
<member name="M:Discord.IGuild.GetStageChannelAsync(System.UInt64,Discord.CacheMode,Discord.RequestOptions)"> | |||
<summary> | |||
Gets a stage channel in this guild | |||
</summary> | |||
<param name="id">The snowflake identifier for the stage channel.</param> | |||
<param name="mode">The <see cref="T:Discord.CacheMode"/> that determines whether the object should be fetched from cache.</param> | |||
<param name="options">The options to be used when sending the request.</param> | |||
<returns> | |||
A task that represents the asynchronous get operation. The task result contains the stage channel associated | |||
with the specified <paramref name="id"/>; <see langword="null" /> if none is found. | |||
</returns> | |||
</member> | |||
<member name="M:Discord.IGuild.GetStageChannelsAsync(Discord.CacheMode,Discord.RequestOptions)"> | |||
<summary> | |||
Gets a collection of all stage channels in this guild. | |||
</summary> | |||
<param name="mode">The <see cref="T:Discord.CacheMode"/> that determines whether the object should be fetched from cache.</param> | |||
<param name="options">The options to be used when sending the request.</param> | |||
<returns> | |||
A task that represents the asynchronous get operation. The task result contains a read-only collection of | |||
stage channels found within this guild. | |||
</returns> | |||
</member> | |||
<member name="M:Discord.IGuild.GetAFKChannelAsync(Discord.CacheMode,Discord.RequestOptions)"> | |||
<summary> | |||
Gets the AFK voice channel in this guild. | |||
@@ -9780,6 +9882,11 @@ | |||
<c>true</c> if the user is streaming; otherwise <c>false</c>. | |||
</returns> | |||
</member> | |||
<member name="P:Discord.IVoiceState.RequestToSpeakTimestamp"> | |||
<summary> | |||
Gets the time on which the user requested to speak. | |||
</summary> | |||
</member> | |||
<member name="T:Discord.IWebhookUser"> | |||
<summary> Represents a Webhook Discord user. </summary> | |||
</member> | |||
@@ -0,0 +1,75 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Text; | |||
using System.Threading.Tasks; | |||
namespace Discord | |||
{ | |||
/// <summary> | |||
/// Represents a generic Stage Channel. | |||
/// </summary> | |||
public interface IStageChannel : IVoiceChannel | |||
{ | |||
/// <summary> | |||
/// Gets the topic of the Stage instance. | |||
/// </summary> | |||
/// <remarks> | |||
/// If the stage isn't live then this property will be set to <see langword="null"/>. | |||
/// </remarks> | |||
string Topic { get; } | |||
/// <summary> | |||
/// The <see cref="StagePrivacyLevel"/> of the current stage. | |||
/// </summary> | |||
/// <remarks> | |||
/// If the stage isn't live then this property will be set to <see langword="null"/>. | |||
/// </remarks> | |||
StagePrivacyLevel? PrivacyLevel { get; } | |||
/// <summary> | |||
/// <see langword="true"/> if stage discovery is disabled, otherwise <see langword="false"/>. | |||
/// </summary> | |||
bool? DiscoverableDisabled { get; } | |||
/// <summary> | |||
/// <see langword="true"/> when the stage is live, otherwise <see langword="false"/>. | |||
/// </summary> | |||
/// <remarks> | |||
/// If the stage isn't live then this property will be set to <see langword="null"/>. | |||
/// </remarks> | |||
bool Live { get; } | |||
/// <summary> | |||
/// Starts the stage, creating a stage instance. | |||
/// </summary> | |||
/// <param name="topic">The topic for the stage/</param> | |||
/// <param name="privacyLevel">The privacy level of the stage</param> | |||
/// <param name="options">The options to be used when sending the request.</param> | |||
/// <returns> | |||
/// A task that represents the asynchronous start operation. | |||
/// </returns> | |||
Task StartStageAsync(string topic, StagePrivacyLevel privacyLevel = StagePrivacyLevel.GuildOnly, RequestOptions options = null); | |||
/// <summary> | |||
/// Modifies the current stage instance. | |||
/// </summary> | |||
/// <param name="func">The properties to modify the stage instance with.</param> | |||
/// <param name="options">The options to be used when sending the request.</param> | |||
/// <returns> | |||
/// A task that represents the asynchronous modify operation. | |||
/// </returns> | |||
Task ModifyInstanceAsync(Action<StageInstanceProperties> func, RequestOptions options = null); | |||
/// <summary> | |||
/// Stops the stage, deleting the stage instance. | |||
/// </summary> | |||
/// <param name="options">The options to be used when sending the request.</param> | |||
/// <returns> | |||
/// A task that represents the asynchronous stop operation. | |||
/// </returns> | |||
Task StopStageAsync(RequestOptions options = null); | |||
Task RequestToSpeak(RequestOptions options = null); | |||
} | |||
} |
@@ -0,0 +1,24 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Text; | |||
using System.Threading.Tasks; | |||
namespace Discord | |||
{ | |||
/// <summary> | |||
/// Represents properties to use when modifying a stage instance. | |||
/// </summary> | |||
public class StageInstanceProperties | |||
{ | |||
/// <summary> | |||
/// Gets or sets the topic of the stage. | |||
/// </summary> | |||
public Optional<string> Topic { get; set; } | |||
/// <summary> | |||
/// Gets or sets the privacy level of the stage. | |||
/// </summary> | |||
public Optional<StagePrivacyLevel> PrivacyLevel { get; set; } | |||
} | |||
} |
@@ -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, | |||
} | |||
} |
@@ -530,6 +530,27 @@ namespace Discord | |||
/// </returns> | |||
Task<IVoiceChannel> GetVoiceChannelAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | |||
/// <summary> | |||
/// Gets a stage channel in this guild | |||
/// </summary> | |||
/// <param name="id">The snowflake identifier for the stage channel.</param> | |||
/// <param name="mode">The <see cref="CacheMode"/> that determines whether the object should be fetched from cache.</param> | |||
/// <param name="options">The options to be used when sending the request.</param> | |||
/// <returns> | |||
/// A task that represents the asynchronous get operation. The task result contains the stage channel associated | |||
/// with the specified <paramref name="id"/>; <see langword="null" /> if none is found. | |||
/// </returns> | |||
Task<IStageChannel> GetStageChannelAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | |||
/// <summary> | |||
/// Gets a collection of all stage channels in this guild. | |||
/// </summary> | |||
/// <param name="mode">The <see cref="CacheMode"/> that determines whether the object should be fetched from cache.</param> | |||
/// <param name="options">The options to be used when sending the request.</param> | |||
/// <returns> | |||
/// A task that represents the asynchronous get operation. The task result contains a read-only collection of | |||
/// stage channels found within this guild. | |||
/// </returns> | |||
Task<IReadOnlyCollection<IStageChannel>> GetStageChannelsAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | |||
/// <summary> | |||
/// Gets the AFK voice channel in this guild. | |||
/// </summary> | |||
/// <param name="mode">The <see cref="CacheMode"/> that determines whether the object should be fetched from cache.</param> | |||
@@ -1,3 +1,5 @@ | |||
using System; | |||
namespace Discord | |||
{ | |||
/// <summary> | |||
@@ -62,5 +64,9 @@ namespace Discord | |||
/// <c>true</c> if the user is streaming; otherwise <c>false</c>. | |||
/// </returns> | |||
bool IsStreaming { get; } | |||
/// <summary> | |||
/// Gets the time on which the user requested to speak. | |||
/// </summary> | |||
DateTimeOffset? RequestToSpeakTimestamp { get; } | |||
} | |||
} |
@@ -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; } | |||
} | |||
} |
@@ -1,5 +1,6 @@ | |||
#pragma warning disable CS1591 | |||
using Newtonsoft.Json; | |||
using System; | |||
namespace Discord.API | |||
{ | |||
@@ -28,5 +29,7 @@ namespace Discord.API | |||
public bool Suppress { get; set; } | |||
[JsonProperty("self_stream")] | |||
public bool SelfStream { get; set; } | |||
[JsonProperty("request_to_speak_timestamp")] | |||
public Optional<DateTimeOffset?> RequestToSpeakTimestamp { get; set; } | |||
} | |||
} |
@@ -0,0 +1,21 @@ | |||
using Newtonsoft.Json; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Text; | |||
using System.Threading.Tasks; | |||
namespace Discord.API.Rest | |||
{ | |||
internal class CreateStageInstanceParams | |||
{ | |||
[JsonProperty("channel_id")] | |||
public ulong ChannelId { get; set; } | |||
[JsonProperty("topic")] | |||
public string Topic { get; set; } | |||
[JsonProperty("privacy_level")] | |||
public Optional<StagePrivacyLevel> PrivacyLevel { get; set; } | |||
} | |||
} |
@@ -0,0 +1,19 @@ | |||
using Newtonsoft.Json; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Text; | |||
using System.Threading.Tasks; | |||
namespace Discord.API.Rest | |||
{ | |||
internal class ModifyStageInstanceParams | |||
{ | |||
[JsonProperty("topic")] | |||
public Optional<string> Topic { get; set; } | |||
[JsonProperty("privacy_level")] | |||
public Optional<StagePrivacyLevel> PrivacyLevel { get; set; } | |||
} | |||
} |
@@ -0,0 +1,17 @@ | |||
using Newtonsoft.Json; | |||
using System; | |||
namespace Discord.API.Rest | |||
{ | |||
internal class ModifyVoiceStateParams | |||
{ | |||
[JsonProperty("channel_id")] | |||
public ulong ChannelId { get; set; } | |||
[JsonProperty("suppress")] | |||
public Optional<bool> Suppressed { get; set; } | |||
[JsonProperty("request_to_speak_timestamp")] | |||
public Optional<DateTimeOffset> RequestToSpeakTimestamp { get; set; } | |||
} | |||
} |
@@ -2281,6 +2281,38 @@ | |||
Represents a REST-based news channel in a guild that has the same properties as a <see cref="T:Discord.Rest.RestTextChannel"/>. | |||
</summary> | |||
</member> | |||
<member name="T:Discord.Rest.RestStageChannel"> | |||
<summary> | |||
Represents a REST-based stage channel in a guild. | |||
</summary> | |||
</member> | |||
<member name="P:Discord.Rest.RestStageChannel.Topic"> | |||
<inheritdoc/> | |||
</member> | |||
<member name="P:Discord.Rest.RestStageChannel.PrivacyLevel"> | |||
<inheritdoc/> | |||
</member> | |||
<member name="P:Discord.Rest.RestStageChannel.DiscoverableDisabled"> | |||
<inheritdoc/> | |||
</member> | |||
<member name="P:Discord.Rest.RestStageChannel.Live"> | |||
<inheritdoc/> | |||
</member> | |||
<member name="M:Discord.Rest.RestStageChannel.ModifyInstanceAsync(System.Action{Discord.StageInstanceProperties},Discord.RequestOptions)"> | |||
<inheritdoc/> | |||
</member> | |||
<member name="M:Discord.Rest.RestStageChannel.StartStageAsync(System.String,Discord.StagePrivacyLevel,Discord.RequestOptions)"> | |||
<inheritdoc/> | |||
</member> | |||
<member name="M:Discord.Rest.RestStageChannel.StopStageAsync(Discord.RequestOptions)"> | |||
<inheritdoc/> | |||
</member> | |||
<member name="M:Discord.Rest.RestStageChannel.UpdateAsync(Discord.RequestOptions)"> | |||
<inheritdoc/> | |||
</member> | |||
<member name="M:Discord.Rest.RestStageChannel.RequestToSpeak(Discord.RequestOptions)"> | |||
<inheritdoc/> | |||
</member> | |||
<member name="T:Discord.Rest.RestTextChannel"> | |||
<summary> | |||
Represents a REST-based channel in a guild that can send and receive messages. | |||
@@ -3130,6 +3162,28 @@ | |||
voice channels found within this guild. | |||
</returns> | |||
</member> | |||
<member name="M:Discord.Rest.RestGuild.GetStageChannelAsync(System.UInt64,Discord.RequestOptions)"> | |||
<summary> | |||
Gets a stage channel in this guild | |||
</summary> | |||
<param name="id">The snowflake identifier for the stage channel.</param> | |||
<param name="options">The options to be used when sending the request.</param> | |||
<returns> | |||
A task that represents the asynchronous get operation. The task result contains the stage channel associated | |||
with the specified <paramref name="id"/>; <see langword="null" /> if none is found. | |||
</returns> | |||
</member> | |||
<member name="M:Discord.Rest.RestGuild.GetStageChannelsAsync(Discord.RequestOptions)"> | |||
<summary> | |||
Gets a collection of all stage channels in this guild. | |||
</summary> | |||
<param name="mode">The <see cref="T:Discord.CacheMode"/> that determines whether the object should be fetched from cache.</param> | |||
<param name="options">The options to be used when sending the request.</param> | |||
<returns> | |||
A task that represents the asynchronous get operation. The task result contains a read-only collection of | |||
stage channels found within this guild. | |||
</returns> | |||
</member> | |||
<member name="M:Discord.Rest.RestGuild.GetCategoryChannelsAsync(Discord.RequestOptions)"> | |||
<summary> | |||
Gets a collection of all category channels in this guild. | |||
@@ -3496,6 +3550,12 @@ | |||
<member name="M:Discord.Rest.RestGuild.Discord#IGuild#GetCategoriesAsync(Discord.CacheMode,Discord.RequestOptions)"> | |||
<inheritdoc /> | |||
</member> | |||
<member name="M:Discord.Rest.RestGuild.Discord#IGuild#GetStageChannelAsync(System.UInt64,Discord.CacheMode,Discord.RequestOptions)"> | |||
<inheritdoc /> | |||
</member> | |||
<member name="M:Discord.Rest.RestGuild.Discord#IGuild#GetStageChannelsAsync(Discord.CacheMode,Discord.RequestOptions)"> | |||
<inheritdoc /> | |||
</member> | |||
<member name="M:Discord.Rest.RestGuild.Discord#IGuild#GetVoiceChannelAsync(System.UInt64,Discord.CacheMode,Discord.RequestOptions)"> | |||
<inheritdoc /> | |||
</member> | |||
@@ -4365,6 +4425,9 @@ | |||
<member name="P:Discord.Rest.RestGroupUser.Discord#IVoiceState#IsStreaming"> | |||
<inheritdoc /> | |||
</member> | |||
<member name="P:Discord.Rest.RestGroupUser.Discord#IVoiceState#RequestToSpeakTimestamp"> | |||
<inheritdoc /> | |||
</member> | |||
<member name="T:Discord.Rest.RestGuildUser"> | |||
<summary> | |||
Represents a REST-based guild user. | |||
@@ -4456,6 +4519,9 @@ | |||
<member name="P:Discord.Rest.RestGuildUser.Discord#IVoiceState#IsStreaming"> | |||
<inheritdoc /> | |||
</member> | |||
<member name="P:Discord.Rest.RestGuildUser.Discord#IVoiceState#RequestToSpeakTimestamp"> | |||
<inheritdoc /> | |||
</member> | |||
<member name="T:Discord.Rest.RestSelfUser"> | |||
<summary> | |||
Represents the logged-in REST-based user. | |||
@@ -4679,6 +4745,9 @@ | |||
<member name="P:Discord.Rest.RestWebhookUser.Discord#IVoiceState#IsStreaming"> | |||
<inheritdoc /> | |||
</member> | |||
<member name="P:Discord.Rest.RestWebhookUser.Discord#IVoiceState#RequestToSpeakTimestamp"> | |||
<inheritdoc /> | |||
</member> | |||
<member name="P:Discord.Rest.RestWebhook.Token"> | |||
<inheritdoc /> | |||
</member> | |||
@@ -577,6 +577,70 @@ namespace Discord.API | |||
return await SendAsync<ChannelThreads>("GET", () => $"channels/{channelId}/users/@me/threads/archived/private{query}", bucket, options: options); | |||
} | |||
// stage | |||
public async Task<StageInstance> CreateStageInstanceAsync(CreateStageInstanceParams args, RequestOptions options = null) | |||
{ | |||
options = RequestOptions.CreateOrClone(options); | |||
var bucket = new BucketIds(); | |||
return await SendJsonAsync<StageInstance>("POST", () => $"stage-instances", args, bucket, options: options).ConfigureAwait(false); | |||
} | |||
public async Task<StageInstance> ModifyStageInstanceAsync(ulong channelId, ModifyStageInstanceParams args, RequestOptions options = null) | |||
{ | |||
Preconditions.NotEqual(channelId, 0, nameof(channelId)); | |||
options = RequestOptions.CreateOrClone(options); | |||
var bucket = new BucketIds(channelId: channelId); | |||
return await SendJsonAsync<StageInstance>("PATCH", () => $"stage-instances/{channelId}", args, bucket, options: options).ConfigureAwait(false); | |||
} | |||
public async Task DeleteStageInstanceAsync(ulong channelId, RequestOptions options = null) | |||
{ | |||
Preconditions.NotEqual(channelId, 0, nameof(channelId)); | |||
options = RequestOptions.CreateOrClone(options); | |||
try | |||
{ | |||
await SendAsync("DELETE", $"stage-instances/{channelId}", options: options).ConfigureAwait(false); | |||
} | |||
catch (HttpException httpEx) when (httpEx.HttpCode == HttpStatusCode.NotFound) { } | |||
} | |||
public async Task<StageInstance> GetStageInstanceAsync(ulong channelId, RequestOptions options = null) | |||
{ | |||
Preconditions.NotEqual(channelId, 0, nameof(channelId)); | |||
options = RequestOptions.CreateOrClone(options); | |||
var bucket = new BucketIds(channelId: channelId); | |||
try | |||
{ | |||
return await SendAsync<StageInstance>("POST", () => $"stage-instances/{channelId}", bucket, options: options).ConfigureAwait(false); | |||
} | |||
catch(HttpException httpEx) when (httpEx.HttpCode == HttpStatusCode.NotFound) | |||
{ | |||
return null; | |||
} | |||
} | |||
public async Task ModifyMyVoiceState(ulong guildId, ModifyVoiceStateParams args, RequestOptions options = null) | |||
{ | |||
Preconditions.NotEqual(guildId, 0, nameof(guildId)); | |||
options = RequestOptions.CreateOrClone(options); | |||
var bucket = new BucketIds(); | |||
await SendJsonAsync("PATCH", () => $"guilds/{guildId}/voice-states/@me", args, bucket, options: options).ConfigureAwait(false); | |||
} | |||
// roles | |||
public async Task AddRoleAsync(ulong guildId, ulong userId, ulong roleId, RequestOptions options = null) | |||
{ | |||
@@ -6,6 +6,7 @@ using System.IO; | |||
using System.Linq; | |||
using System.Threading.Tasks; | |||
using Model = Discord.API.Channel; | |||
using StageInstance = Discord.API.StageInstance; | |||
namespace Discord.Rest | |||
{ | |||
@@ -92,6 +93,21 @@ namespace Discord.Rest | |||
return await client.ApiClient.ModifyGuildChannelAsync(channel.Id, apiArgs, options).ConfigureAwait(false); | |||
} | |||
public static async Task<StageInstance> ModifyAsync(IStageChannel channel, BaseDiscordClient client, | |||
Action<StageInstanceProperties> func, RequestOptions options = null) | |||
{ | |||
var args = new StageInstanceProperties(); | |||
func(args); | |||
var apiArgs = new ModifyStageInstanceParams() | |||
{ | |||
PrivacyLevel = args.PrivacyLevel, | |||
Topic = args.Topic | |||
}; | |||
return await client.ApiClient.ModifyStageInstanceAsync(channel.Id, apiArgs, options); | |||
} | |||
//Invites | |||
public static async Task<IReadOnlyCollection<RestInviteMetadata>> GetInvitesAsync(IGuildChannel channel, BaseDiscordClient client, | |||
RequestOptions options) | |||
@@ -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: | |||
@@ -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 | |||
{ | |||
/// <summary> | |||
/// Represents a REST-based stage channel in a guild. | |||
/// </summary> | |||
public class RestStageChannel : RestVoiceChannel, IStageChannel | |||
{ | |||
/// <inheritdoc/> | |||
public string Topic { get; private set; } | |||
/// <inheritdoc/> | |||
public StagePrivacyLevel? PrivacyLevel { get; private set; } | |||
/// <inheritdoc/> | |||
public bool? DiscoverableDisabled { get; private set; } | |||
/// <inheritdoc/> | |||
public bool Live { get; private set; } | |||
internal RestStageChannel(BaseDiscordClient discord, IGuild guild, ulong id) | |||
: base(discord, guild, id) | |||
{ | |||
} | |||
internal static new RestStageChannel Create(BaseDiscordClient discord, IGuild guild, Model model) | |||
{ | |||
var entity = new RestStageChannel(discord, guild, model.Id); | |||
entity.Update(model); | |||
return entity; | |||
} | |||
internal void Update(StageInstance model, bool isLive = false) | |||
{ | |||
this.Live = isLive; | |||
if(isLive) | |||
{ | |||
this.Topic = model.Topic; | |||
this.PrivacyLevel = model.PrivacyLevel; | |||
this.DiscoverableDisabled = model.DiscoverableDisabled; | |||
} | |||
else | |||
{ | |||
this.Topic = null; | |||
this.PrivacyLevel = null; | |||
this.DiscoverableDisabled = null; | |||
} | |||
} | |||
/// <inheritdoc/> | |||
public async Task ModifyInstanceAsync(Action<StageInstanceProperties> func, RequestOptions options = null) | |||
{ | |||
var model = await ChannelHelper.ModifyAsync(this, Discord, func, options); | |||
Update(model, true); | |||
} | |||
/// <inheritdoc/> | |||
public async Task StartStageAsync(string topic, StagePrivacyLevel privacyLevel = StagePrivacyLevel.GuildOnly, RequestOptions options = null) | |||
{ | |||
var args = new API.Rest.CreateStageInstanceParams() | |||
{ | |||
ChannelId = this.Id, | |||
PrivacyLevel = privacyLevel, | |||
Topic = topic | |||
}; | |||
var model = await Discord.ApiClient.CreateStageInstanceAsync(args, options); | |||
Update(model, true); | |||
} | |||
/// <inheritdoc/> | |||
public async Task StopStageAsync(RequestOptions options = null) | |||
{ | |||
await Discord.ApiClient.DeleteStageInstanceAsync(this.Id, options); | |||
Update(null, false); | |||
} | |||
/// <inheritdoc/> | |||
public override async Task UpdateAsync(RequestOptions options = null) | |||
{ | |||
await base.UpdateAsync(options); | |||
var model = await Discord.ApiClient.GetStageInstanceAsync(this.Id, options); | |||
Update(model, model != null); | |||
} | |||
/// <inheritdoc/> | |||
public Task RequestToSpeak(RequestOptions options = null) | |||
{ | |||
var args = new API.Rest.ModifyVoiceStateParams() | |||
{ | |||
ChannelId = this.Id, | |||
RequestToSpeakTimestamp = DateTimeOffset.UtcNow | |||
}; | |||
return Discord.ApiClient.ModifyMyVoiceState(this.Guild.Id, args, options); | |||
} | |||
} | |||
} |
@@ -451,6 +451,35 @@ namespace Discord.Rest | |||
var channels = await GuildHelper.GetChannelsAsync(this, Discord, options).ConfigureAwait(false); | |||
return channels.OfType<RestVoiceChannel>().ToImmutableArray(); | |||
} | |||
/// <summary> | |||
/// Gets a stage channel in this guild | |||
/// </summary> | |||
/// <param name="id">The snowflake identifier for the stage channel.</param> | |||
/// <param name="options">The options to be used when sending the request.</param> | |||
/// <returns> | |||
/// A task that represents the asynchronous get operation. The task result contains the stage channel associated | |||
/// with the specified <paramref name="id"/>; <see langword="null" /> if none is found. | |||
/// </returns> | |||
public async Task<RestStageChannel> GetStageChannelAsync(ulong id, RequestOptions options = null) | |||
{ | |||
var channel = await GuildHelper.GetChannelAsync(this, Discord, id, options).ConfigureAwait(false); | |||
return channel as RestStageChannel; | |||
} | |||
/// <summary> | |||
/// Gets a collection of all stage channels in this guild. | |||
/// </summary> | |||
/// <param name="mode">The <see cref="CacheMode"/> that determines whether the object should be fetched from cache.</param> | |||
/// <param name="options">The options to be used when sending the request.</param> | |||
/// <returns> | |||
/// A task that represents the asynchronous get operation. The task result contains a read-only collection of | |||
/// stage channels found within this guild. | |||
/// </returns> | |||
public async Task<IReadOnlyCollection<RestStageChannel>> GetStageChannelsAsync(RequestOptions options = null) | |||
{ | |||
var channels = await GuildHelper.GetChannelsAsync(this, Discord, options).ConfigureAwait(false); | |||
return channels.OfType<RestStageChannel>().ToImmutableArray(); | |||
} | |||
/// <summary> | |||
/// Gets a collection of all category channels in this guild. | |||
@@ -952,6 +981,22 @@ namespace Discord.Rest | |||
return null; | |||
} | |||
/// <inheritdoc /> | |||
async Task<IStageChannel> IGuild.GetStageChannelAsync(ulong id, CacheMode mode, RequestOptions options ) | |||
{ | |||
if (mode == CacheMode.AllowDownload) | |||
return await GetStageChannelAsync(id, options).ConfigureAwait(false); | |||
else | |||
return null; | |||
} | |||
/// <inheritdoc /> | |||
async Task<IReadOnlyCollection<IStageChannel>> IGuild.GetStageChannelsAsync(CacheMode mode, RequestOptions options) | |||
{ | |||
if (mode == CacheMode.AllowDownload) | |||
return await GetStageChannelsAsync(options).ConfigureAwait(false); | |||
else | |||
return null; | |||
} | |||
/// <inheritdoc /> | |||
async Task<IVoiceChannel> IGuild.GetVoiceChannelAsync(ulong id, CacheMode mode, RequestOptions options) | |||
{ | |||
if (mode == CacheMode.AllowDownload) | |||
@@ -1109,5 +1154,6 @@ namespace Discord.Rest | |||
/// <inheritdoc /> | |||
async Task<IReadOnlyCollection<IWebhook>> IGuild.GetWebhooksAsync(RequestOptions options) | |||
=> await GetWebhooksAsync(options).ConfigureAwait(false); | |||
} | |||
} |
@@ -1,3 +1,4 @@ | |||
using System; | |||
using System.Diagnostics; | |||
using Model = Discord.API.User; | |||
@@ -37,5 +38,7 @@ namespace Discord.Rest | |||
string IVoiceState.VoiceSessionId => null; | |||
/// <inheritdoc /> | |||
bool IVoiceState.IsStreaming => false; | |||
/// <inheritdoc /> | |||
DateTimeOffset? IVoiceState.RequestToSpeakTimestamp => null; | |||
} | |||
} |
@@ -169,5 +169,7 @@ namespace Discord.Rest | |||
string IVoiceState.VoiceSessionId => null; | |||
/// <inheritdoc /> | |||
bool IVoiceState.IsStreaming => false; | |||
/// <inheritdoc /> | |||
DateTimeOffset? IVoiceState.RequestToSpeakTimestamp => null; | |||
} | |||
} |
@@ -107,5 +107,7 @@ namespace Discord.Rest | |||
string IVoiceState.VoiceSessionId => null; | |||
/// <inheritdoc /> | |||
bool IVoiceState.IsStreaming => false; | |||
/// <inheritdoc /> | |||
DateTimeOffset? IVoiceState.RequestToSpeakTimestamp => null; | |||
} | |||
} |
@@ -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; } | |||
} | |||
@@ -592,5 +592,64 @@ namespace Discord.WebSocket | |||
} | |||
internal readonly AsyncEvent<Func<SocketThreadUser, Task>> _threadMemberLeft = new AsyncEvent<Func<SocketThreadUser, Task>>(); | |||
/// <summary> | |||
/// Fired when a stage is started. | |||
/// </summary> | |||
public event Func<SocketStageChannel, Task> StageStarted | |||
{ | |||
add { _stageStarted.Add(value); } | |||
remove { _stageStarted.Remove(value); } | |||
} | |||
internal readonly AsyncEvent<Func<SocketStageChannel, Task>> _stageStarted = new AsyncEvent<Func<SocketStageChannel, Task>>(); | |||
/// <summary> | |||
/// Fired when a stage ends. | |||
/// </summary> | |||
public event Func<SocketStageChannel, Task> StageEnded | |||
{ | |||
add { _stageEnded.Add(value); } | |||
remove { _stageEnded.Remove(value); } | |||
} | |||
internal readonly AsyncEvent<Func<SocketStageChannel, Task>> _stageEnded = new AsyncEvent<Func<SocketStageChannel, Task>>(); | |||
/// <summary> | |||
/// Fired when a stage is updated. | |||
/// </summary> | |||
public event Func<SocketStageChannel, SocketStageChannel, Task> StageUpdated | |||
{ | |||
add { _stageUpdated.Add(value); } | |||
remove { _stageUpdated.Remove(value); } | |||
} | |||
internal readonly AsyncEvent<Func<SocketStageChannel, SocketStageChannel, Task>> _stageUpdated = new AsyncEvent<Func<SocketStageChannel, SocketStageChannel, Task>>(); | |||
/// <summary> | |||
/// Fired when a user requests to speak within a stage channel. | |||
/// </summary> | |||
public event Func<SocketStageChannel, SocketGuildUser, Task> RequestToSpeak | |||
{ | |||
add { _requestToSpeak.Add(value); } | |||
remove { _requestToSpeak.Remove(value); } | |||
} | |||
internal readonly AsyncEvent<Func<SocketStageChannel, SocketGuildUser, Task>> _requestToSpeak = new AsyncEvent<Func<SocketStageChannel, SocketGuildUser, Task>>(); | |||
/// <summary> | |||
/// Fired when a speaker is added in a stage channel. | |||
/// </summary> | |||
public event Func<SocketStageChannel, SocketGuildUser, Task> SpeakerAdded | |||
{ | |||
add { _speakerAdded.Add(value); } | |||
remove { _speakerAdded.Remove(value); } | |||
} | |||
internal readonly AsyncEvent<Func<SocketStageChannel, SocketGuildUser, Task>> _speakerAdded = new AsyncEvent<Func<SocketStageChannel, SocketGuildUser, Task>>(); | |||
/// <summary> | |||
/// Fired when a speaker is removed from a stage channel. | |||
/// </summary> | |||
public event Func<SocketStageChannel, SocketGuildUser, Task> SpeakerRemoved | |||
{ | |||
add { _speakerRemoved.Add(value); } | |||
remove { _speakerRemoved.Remove(value); } | |||
} | |||
internal readonly AsyncEvent<Func<SocketStageChannel, SocketGuildUser, Task>> _speakerRemoved = new AsyncEvent<Func<SocketStageChannel, SocketGuildUser, Task>>(); | |||
} | |||
} |
@@ -1,4 +1,4 @@ | |||
<Project Sdk="Microsoft.NET.Sdk"> | |||
<Project Sdk="Microsoft.NET.Sdk"> | |||
<Import Project="../../Discord.Net.targets" /> | |||
<Import Project="../../StyleAnalyzer.targets" /> | |||
<PropertyGroup> | |||
@@ -17,6 +17,9 @@ | |||
<PropertyGroup> | |||
<DocumentationFile>..\Discord.Net.WebSocket\Discord.Net.WebSocket.xml</DocumentationFile> | |||
</PropertyGroup> | |||
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net461|AnyCPU'"> | |||
<DefineConstants>DEBUG;TRACE;DEBUG_LIMITS</DefineConstants> | |||
</PropertyGroup> | |||
<ItemGroup> | |||
<ProjectReference Include="..\Discord.Net.Core\Discord.Net.Core.csproj" /> | |||
<ProjectReference Include="..\Discord.Net.Rest\Discord.Net.Rest.csproj" /> | |||
@@ -781,6 +781,36 @@ | |||
Fired when a user leaves a thread | |||
</summary> | |||
</member> | |||
<member name="E:Discord.WebSocket.BaseSocketClient.StageStarted"> | |||
<summary> | |||
Fired when a stage is started. | |||
</summary> | |||
</member> | |||
<member name="E:Discord.WebSocket.BaseSocketClient.StageEnded"> | |||
<summary> | |||
Fired when a stage ends. | |||
</summary> | |||
</member> | |||
<member name="E:Discord.WebSocket.BaseSocketClient.StageUpdated"> | |||
<summary> | |||
Fired when a stage is updated. | |||
</summary> | |||
</member> | |||
<member name="E:Discord.WebSocket.BaseSocketClient.RequestToSpeak"> | |||
<summary> | |||
Fired when a user requests to speak within a stage channel. | |||
</summary> | |||
</member> | |||
<member name="E:Discord.WebSocket.BaseSocketClient.SpeakerAdded"> | |||
<summary> | |||
Fired when a speaker is added in a stage channel. | |||
</summary> | |||
</member> | |||
<member name="E:Discord.WebSocket.BaseSocketClient.SpeakerRemoved"> | |||
<summary> | |||
Fired when a speaker is removed from a stage channel. | |||
</summary> | |||
</member> | |||
<member name="P:Discord.WebSocket.DiscordShardedClient.Latency"> | |||
<inheritdoc /> | |||
</member> | |||
@@ -2142,6 +2172,40 @@ | |||
</note> | |||
</remarks> | |||
</member> | |||
<member name="T:Discord.WebSocket.SocketStageChannel"> | |||
<summary> | |||
Represents a stage channel recieved over the gateway. | |||
</summary> | |||
</member> | |||
<member name="P:Discord.WebSocket.SocketStageChannel.Topic"> | |||
<inheritdoc/> | |||
</member> | |||
<member name="P:Discord.WebSocket.SocketStageChannel.PrivacyLevel"> | |||
<inheritdoc/> | |||
</member> | |||
<member name="P:Discord.WebSocket.SocketStageChannel.DiscoverableDisabled"> | |||
<inheritdoc/> | |||
</member> | |||
<member name="P:Discord.WebSocket.SocketStageChannel.Live"> | |||
<inheritdoc/> | |||
</member> | |||
<member name="P:Discord.WebSocket.SocketStageChannel.Speakers"> | |||
<summary> | |||
Gets a collection of users who are speakers within the stage. | |||
</summary> | |||
</member> | |||
<member name="M:Discord.WebSocket.SocketStageChannel.StartStageAsync(System.String,Discord.StagePrivacyLevel,Discord.RequestOptions)"> | |||
<inheritdoc/> | |||
</member> | |||
<member name="M:Discord.WebSocket.SocketStageChannel.ModifyInstanceAsync(System.Action{Discord.StageInstanceProperties},Discord.RequestOptions)"> | |||
<inheritdoc/> | |||
</member> | |||
<member name="M:Discord.WebSocket.SocketStageChannel.StopStageAsync(Discord.RequestOptions)"> | |||
<inheritdoc/> | |||
</member> | |||
<member name="M:Discord.WebSocket.SocketStageChannel.RequestToSpeak(Discord.RequestOptions)"> | |||
<inheritdoc/> | |||
</member> | |||
<member name="T:Discord.WebSocket.SocketTextChannel"> | |||
<summary> | |||
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. | |||
</returns> | |||
</member> | |||
<member name="P:Discord.WebSocket.SocketGuild.StageChannels"> | |||
<summary> | |||
Gets a collection of all stage channels in this guild. | |||
</summary> | |||
<returns> | |||
A read-only collection of stage channels found within this guild. | |||
</returns> | |||
</member> | |||
<member name="P:Discord.WebSocket.SocketGuild.CategoryChannels"> | |||
<summary> | |||
Gets a collection of all category channels in this guild. | |||
@@ -3076,6 +3148,15 @@ | |||
A voice channel associated with the specified <paramref name="id" />; <see langword="null"/> if none is found. | |||
</returns> | |||
</member> | |||
<member name="M:Discord.WebSocket.SocketGuild.GetStageChannel(System.UInt64)"> | |||
<summary> | |||
Gets a stage channel in this guild. | |||
</summary> | |||
<param name="id">The snowflake identifier for the stage channel.</param> | |||
<returns> | |||
A stage channel associated with the specified <paramref name="id" />; <see langword="null"/> if none is found. | |||
</returns> | |||
</member> | |||
<member name="M:Discord.WebSocket.SocketGuild.GetCategoryChannel(System.UInt64)"> | |||
<summary> | |||
Gets a category channel in this guild. | |||
@@ -3417,6 +3498,12 @@ | |||
<member name="M:Discord.WebSocket.SocketGuild.Discord#IGuild#GetVoiceChannelAsync(System.UInt64,Discord.CacheMode,Discord.RequestOptions)"> | |||
<inheritdoc /> | |||
</member> | |||
<member name="M:Discord.WebSocket.SocketGuild.Discord#IGuild#GetStageChannelAsync(System.UInt64,Discord.CacheMode,Discord.RequestOptions)"> | |||
<inheritdoc /> | |||
</member> | |||
<member name="M:Discord.WebSocket.SocketGuild.Discord#IGuild#GetStageChannelsAsync(Discord.CacheMode,Discord.RequestOptions)"> | |||
<inheritdoc /> | |||
</member> | |||
<member name="M:Discord.WebSocket.SocketGuild.Discord#IGuild#GetAFKChannelAsync(Discord.CacheMode,Discord.RequestOptions)"> | |||
<inheritdoc /> | |||
</member> | |||
@@ -4358,6 +4445,9 @@ | |||
<member name="P:Discord.WebSocket.SocketGroupUser.Discord#IVoiceState#IsStreaming"> | |||
<inheritdoc /> | |||
</member> | |||
<member name="P:Discord.WebSocket.SocketGroupUser.Discord#IVoiceState#RequestToSpeakTimestamp"> | |||
<inheritdoc /> | |||
</member> | |||
<member name="T:Discord.WebSocket.SocketGuildUser"> | |||
<summary> | |||
Represents a WebSocket-based guild user. | |||
@@ -4407,6 +4497,9 @@ | |||
<member name="P:Discord.WebSocket.SocketGuildUser.IsStreaming"> | |||
<inheritdoc /> | |||
</member> | |||
<member name="P:Discord.WebSocket.SocketGuildUser.RequestToSpeakTimestamp"> | |||
<inheritdoc /> | |||
</member> | |||
<member name="P:Discord.WebSocket.SocketGuildUser.IsPending"> | |||
<inheritdoc /> | |||
</member> | |||
@@ -4654,6 +4747,9 @@ | |||
<member name="P:Discord.WebSocket.SocketThreadUser.IsStreaming"> | |||
<inheritdoc/> | |||
</member> | |||
<member name="P:Discord.WebSocket.SocketThreadUser.RequestToSpeakTimestamp"> | |||
<inheritdoc/> | |||
</member> | |||
<member name="M:Discord.WebSocket.SocketThreadUser.GetPermissions(Discord.IGuildChannel)"> | |||
<inheritdoc/> | |||
</member> | |||
@@ -4819,6 +4915,9 @@ | |||
<member name="P:Discord.WebSocket.SocketVoiceState.VoiceSessionId"> | |||
<inheritdoc /> | |||
</member> | |||
<member name="P:Discord.WebSocket.SocketVoiceState.RequestToSpeakTimestamp"> | |||
<inheritdoc/> | |||
</member> | |||
<member name="P:Discord.WebSocket.SocketVoiceState.IsMuted"> | |||
<inheritdoc /> | |||
</member> | |||
@@ -4968,6 +5067,9 @@ | |||
<member name="P:Discord.WebSocket.SocketWebhookUser.Discord#IVoiceState#IsStreaming"> | |||
<inheritdoc /> | |||
</member> | |||
<member name="P:Discord.WebSocket.SocketWebhookUser.Discord#IVoiceState#RequestToSpeakTimestamp"> | |||
<inheritdoc /> | |||
</member> | |||
<member name="T:Discord.WebSocket.SocketVoiceServer"> | |||
<summary> | |||
Represents a WebSocket-based voice server. | |||
@@ -1,4 +1,4 @@ | |||
using System; | |||
using System; | |||
using System.Threading.Tasks; | |||
namespace Discord.WebSocket | |||
@@ -35,4 +35,4 @@ namespace Discord.WebSocket | |||
} | |||
private readonly AsyncEvent<Func<int, int, DiscordSocketClient, Task>> _shardLatencyUpdatedEvent = new AsyncEvent<Func<int, int, DiscordSocketClient, Task>>(); | |||
} | |||
} | |||
} |
@@ -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 | |||
@@ -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<StageInstance>(_serializer); | |||
var guild = State.GetGuild(data.GuildId); | |||
if(guild == null) | |||
{ | |||
await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false); | |||
return; | |||
} | |||
var stageChannel = guild.GetStageChannel(data.ChannelId); | |||
if(stageChannel == null) | |||
{ | |||
await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); | |||
return; | |||
} | |||
SocketStageChannel before = type == "STAGE_INSTANCE_UPDATE" ? stageChannel.Clone() : null; | |||
stageChannel.Update(data, type == "STAGE_INSTANCE_CREATE" ? true : type == "STAGE_INSTANCE_DELETE" ? false : false); | |||
switch (type) | |||
{ | |||
case "STAGE_INSTANCE_CREATE": | |||
await TimedInvokeAsync(_stageStarted, nameof(StageStarted), stageChannel); | |||
return; | |||
case "STAGE_INSTANCE_DELETE": | |||
await TimedInvokeAsync(_stageEnded, nameof(StageEnded), stageChannel); | |||
return; | |||
case "STAGE_INSTANCE_UPDATE": | |||
await TimedInvokeAsync(_stageUpdated, nameof(StageUpdated), before, stageChannel); | |||
return; | |||
} | |||
} | |||
break; | |||
//Ignored (User only) | |||
case "CHANNEL_PINS_ACK": | |||
await _gatewayLogger.DebugAsync("Ignored Dispatch (CHANNEL_PINS_ACK)").ConfigureAwait(false); | |||
@@ -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); | |||
} | |||
@@ -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 | |||
{ | |||
/// <summary> | |||
/// Represents a stage channel recieved over the gateway. | |||
/// </summary> | |||
public class SocketStageChannel : SocketVoiceChannel, IStageChannel | |||
{ | |||
/// <inheritdoc/> | |||
public string Topic { get; private set; } | |||
/// <inheritdoc/> | |||
public StagePrivacyLevel? PrivacyLevel { get; private set; } | |||
/// <inheritdoc/> | |||
public bool? DiscoverableDisabled { get; private set; } | |||
/// <inheritdoc/> | |||
public bool Live { get; private set; } = false; | |||
/// <summary> | |||
/// Gets a collection of users who are speakers within the stage. | |||
/// </summary> | |||
public IReadOnlyCollection<SocketGuildUser> Speakers | |||
=> this.Users.Where(x => !x.IsSuppressed).ToImmutableArray(); | |||
internal new SocketStageChannel Clone() => MemberwiseClone() as SocketStageChannel; | |||
internal SocketStageChannel(DiscordSocketClient discord, ulong id, SocketGuild guild) | |||
: base(discord, id, guild) | |||
{ | |||
} | |||
internal new static SocketStageChannel Create(SocketGuild guild, ClientState state, Model model) | |||
{ | |||
var entity = new SocketStageChannel(guild.Discord, model.Id, guild); | |||
entity.Update(state, model); | |||
return entity; | |||
} | |||
internal override void Update(ClientState state, Model model) | |||
{ | |||
base.Update(state, model); | |||
} | |||
internal void Update(StageInstance model, bool isLive = false) | |||
{ | |||
this.Live = isLive; | |||
if (isLive) | |||
{ | |||
this.Topic = model.Topic; | |||
this.PrivacyLevel = model.PrivacyLevel; | |||
this.DiscoverableDisabled = model.DiscoverableDisabled; | |||
} | |||
else | |||
{ | |||
this.Topic = null; | |||
this.PrivacyLevel = null; | |||
this.DiscoverableDisabled = null; | |||
} | |||
} | |||
/// <inheritdoc/> | |||
public async Task StartStageAsync(string topic, StagePrivacyLevel privacyLevel = StagePrivacyLevel.GuildOnly, RequestOptions options = null) | |||
{ | |||
var args = new API.Rest.CreateStageInstanceParams() | |||
{ | |||
ChannelId = this.Id, | |||
Topic = topic, | |||
PrivacyLevel = privacyLevel, | |||
}; | |||
var model = await Discord.ApiClient.CreateStageInstanceAsync(args, options).ConfigureAwait(false); | |||
this.Update(model, true); | |||
} | |||
/// <inheritdoc/> | |||
public async Task ModifyInstanceAsync(Action<StageInstanceProperties> func, RequestOptions options = null) | |||
{ | |||
var model = await ChannelHelper.ModifyAsync(this, Discord, func, options); | |||
this.Update(model, true); | |||
} | |||
/// <inheritdoc/> | |||
public async Task StopStageAsync(RequestOptions options = null) | |||
{ | |||
await Discord.ApiClient.DeleteStageInstanceAsync(this.Id, options); | |||
Update(null, false); | |||
} | |||
/// <inheritdoc/> | |||
public Task RequestToSpeak(RequestOptions options = null) | |||
{ | |||
var args = new API.Rest.ModifyVoiceStateParams() | |||
{ | |||
ChannelId = this.Id, | |||
RequestToSpeakTimestamp = DateTimeOffset.UtcNow | |||
}; | |||
return Discord.ApiClient.ModifyMyVoiceState(this.Guild.Id, args, options); | |||
} | |||
} | |||
} |
@@ -271,6 +271,14 @@ namespace Discord.WebSocket | |||
public IReadOnlyCollection<SocketVoiceChannel> VoiceChannels | |||
=> Channels.OfType<SocketVoiceChannel>().ToImmutableArray(); | |||
/// <summary> | |||
/// Gets a collection of all stage channels in this guild. | |||
/// </summary> | |||
/// <returns> | |||
/// A read-only collection of stage channels found within this guild. | |||
/// </returns> | |||
public IReadOnlyCollection<SocketStageChannel> StageChannels | |||
=> Channels.OfType<SocketStageChannel>().ToImmutableArray(); | |||
/// <summary> | |||
/// Gets a collection of all category channels in this guild. | |||
/// </summary> | |||
/// <returns> | |||
@@ -652,6 +660,15 @@ namespace Discord.WebSocket | |||
public SocketVoiceChannel GetVoiceChannel(ulong id) | |||
=> GetChannel(id) as SocketVoiceChannel; | |||
/// <summary> | |||
/// Gets a stage channel in this guild. | |||
/// </summary> | |||
/// <param name="id">The snowflake identifier for the stage channel.</param> | |||
/// <returns> | |||
/// A stage channel associated with the specified <paramref name="id" />; <see langword="null"/> if none is found. | |||
/// </returns> | |||
public SocketStageChannel GetStageChannel(ulong id) | |||
=> GetChannel(id) as SocketStageChannel; | |||
/// <summary> | |||
/// Gets a category channel in this guild. | |||
/// </summary> | |||
/// <param name="id">The snowflake identifier for the category channel.</param> | |||
@@ -1354,6 +1371,12 @@ namespace Discord.WebSocket | |||
Task<IVoiceChannel> IGuild.GetVoiceChannelAsync(ulong id, CacheMode mode, RequestOptions options) | |||
=> Task.FromResult<IVoiceChannel>(GetVoiceChannel(id)); | |||
/// <inheritdoc /> | |||
Task<IStageChannel> IGuild.GetStageChannelAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null) | |||
=> Task.FromResult<IStageChannel>(GetStageChannel(id)); | |||
/// <inheritdoc /> | |||
Task<IReadOnlyCollection<IStageChannel>> IGuild.GetStageChannelsAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null) | |||
=> Task.FromResult<IReadOnlyCollection<IStageChannel>>(StageChannels); | |||
/// <inheritdoc /> | |||
Task<IVoiceChannel> IGuild.GetAFKChannelAsync(CacheMode mode, RequestOptions options) | |||
=> Task.FromResult<IVoiceChannel>(AFKChannel); | |||
/// <inheritdoc /> | |||
@@ -1462,5 +1485,7 @@ namespace Discord.WebSocket | |||
_audioLock?.Dispose(); | |||
_audioClient?.Dispose(); | |||
} | |||
} | |||
} |
@@ -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; | |||
/// <inheritdoc /> | |||
bool IVoiceState.IsStreaming => false; | |||
/// <inheritdoc /> | |||
DateTimeOffset? IVoiceState.RequestToSpeakTimestamp => null; | |||
} | |||
} |
@@ -57,7 +57,11 @@ namespace Discord.WebSocket | |||
/// <inheritdoc /> | |||
public bool IsStreaming => VoiceState?.IsStreaming ?? false; | |||
/// <inheritdoc /> | |||
public DateTimeOffset? RequestToSpeakTimestamp => VoiceState?.RequestToSpeakTimestamp ?? null; | |||
/// <inheritdoc /> | |||
public bool? IsPending { get; private set; } | |||
/// <inheritdoc /> | |||
public DateTimeOffset? JoinedAt => DateTimeUtils.FromTicks(_joinedAtTicks); | |||
/// <summary> | |||
@@ -110,6 +110,10 @@ namespace Discord.WebSocket | |||
public bool IsStreaming | |||
=> GuildUser.IsStreaming; | |||
/// <inheritdoc/> | |||
public DateTimeOffset? RequestToSpeakTimestamp | |||
=> GuildUser.RequestToSpeakTimestamp; | |||
private SocketGuildUser GuildUser { get; set; } | |||
internal SocketThreadUser(SocketGuild guild, SocketThreadChannel thread, SocketGuildUser member) | |||
@@ -13,7 +13,7 @@ namespace Discord.WebSocket | |||
/// <summary> | |||
/// Initializes a default <see cref="SocketVoiceState"/> with everything set to <c>null</c> or <c>false</c>. | |||
/// </summary> | |||
public static readonly SocketVoiceState Default = new SocketVoiceState(null, null, false, false, false, false, false, false); | |||
public static readonly SocketVoiceState Default = new SocketVoiceState(null, null, null, false, false, false, false, false, false); | |||
[Flags] | |||
private enum Flags : byte | |||
@@ -35,6 +35,8 @@ namespace Discord.WebSocket | |||
public SocketVoiceChannel VoiceChannel { get; } | |||
/// <inheritdoc /> | |||
public string VoiceSessionId { get; } | |||
/// <inheritdoc/> | |||
public DateTimeOffset? RequestToSpeakTimestamp { get; private set; } | |||
/// <inheritdoc /> | |||
public bool IsMuted => (_voiceStates & Flags.Muted) != 0; | |||
@@ -48,11 +50,13 @@ namespace Discord.WebSocket | |||
public bool IsSelfDeafened => (_voiceStates & Flags.SelfDeafened) != 0; | |||
/// <inheritdoc /> | |||
public bool IsStreaming => (_voiceStates & Flags.SelfStream) != 0; | |||
internal SocketVoiceState(SocketVoiceChannel voiceChannel, string sessionId, bool isSelfMuted, bool isSelfDeafened, bool isMuted, bool isDeafened, bool isSuppressed, bool isStream) | |||
internal SocketVoiceState(SocketVoiceChannel voiceChannel, DateTimeOffset? requestToSpeak, string sessionId, bool isSelfMuted, bool isSelfDeafened, bool isMuted, bool isDeafened, bool isSuppressed, bool isStream) | |||
{ | |||
VoiceChannel = voiceChannel; | |||
VoiceSessionId = sessionId; | |||
RequestToSpeakTimestamp = requestToSpeak; | |||
Flags voiceStates = Flags.Normal; | |||
if (isSelfMuted) | |||
@@ -71,7 +75,7 @@ namespace Discord.WebSocket | |||
} | |||
internal static SocketVoiceState Create(SocketVoiceChannel voiceChannel, Model model) | |||
{ | |||
return new SocketVoiceState(voiceChannel, model.SessionId, model.SelfMute, model.SelfDeaf, model.Mute, model.Deaf, model.Suppress, model.SelfStream); | |||
return new SocketVoiceState(voiceChannel, model.RequestToSpeakTimestamp.IsSpecified ? model.RequestToSpeakTimestamp.Value : null, model.SessionId, model.SelfMute, model.SelfDeaf, model.Mute, model.Deaf, model.Suppress, model.SelfStream); | |||
} | |||
/// <summary> | |||
@@ -138,5 +138,7 @@ namespace Discord.WebSocket | |||
string IVoiceState.VoiceSessionId => null; | |||
/// <inheritdoc /> | |||
bool IVoiceState.IsStreaming => false; | |||
/// <inheritdoc /> | |||
DateTimeOffset? IVoiceState.RequestToSpeakTimestamp => null; | |||
} | |||
} |