[Feature] Voice channel status support (#2777)

* initial commit

* MOCKED CHANNELS AGRHHHHHHHH

* fix for sharded

* yup
This commit is contained in:
Mihail Gribkov
2023-11-18 23:42:14 +03:00
committed by GitHub
parent 8e4d449615
commit 8060dcf4ae
30 changed files with 323 additions and 17 deletions

View File

@@ -238,6 +238,11 @@ namespace Discord
/// </summary> /// </summary>
public const int MaxApplicationTagCount = 5; public const int MaxApplicationTagCount = 5;
/// <summary>
/// Returns the maximum length of a voice channel status.
/// </summary>
public const int MaxVoiceChannelStatusLength = 500;
/// <summary> /// <summary>
/// Returns the maximum number of entitlements that can be gotten per-batch. /// Returns the maximum number of entitlements that can be gotten per-batch.
/// </summary> /// </summary>

View File

@@ -249,6 +249,16 @@ namespace Discord
/// <summary> /// <summary>
/// Guild Onboarding was updated. /// Guild Onboarding was updated.
/// </summary> /// </summary>
OnboardingUpdated = 167 OnboardingUpdated = 167,
/// <summary>
/// A voice channel status was updated by a user.
/// </summary>
VoiceChannelStatusUpdated = 192,
/// <summary>
/// A voice channel status was deleted by a user.
/// </summary>
VoiceChannelStatusDeleted = 193
} }
} }

View File

@@ -41,5 +41,15 @@ namespace Discord
/// </returns> /// </returns>
/// <seealso cref="VoiceChannelProperties"/> /// <seealso cref="VoiceChannelProperties"/>
Task ModifyAsync(Action<VoiceChannelProperties> func, RequestOptions options = null); Task ModifyAsync(Action<VoiceChannelProperties> func, RequestOptions options = null);
/// <summary>
/// Sets the voice channel status in the current channel.
/// </summary>
/// <param name="status">The string to set as status.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous modification operation.
/// </returns>
Task SetStatusAsync(string status, RequestOptions options = null);
} }
} }

View File

@@ -182,5 +182,10 @@ namespace Discord
/// Allows sending voice messages. /// Allows sending voice messages.
/// </summary> /// </summary>
SendVoiceMessages = 1L << 46, SendVoiceMessages = 1L << 46,
/// <summary>
/// Allows setting voice channel status.
/// </summary>
SetVoiceChannelStatus = 1L << 48,
} }
} }

View File

@@ -23,7 +23,7 @@ namespace Discord
/// <summary> /// <summary>
/// Gets a <see cref="ChannelPermissions"/> that grants all permissions for voice channels. /// Gets a <see cref="ChannelPermissions"/> that grants all permissions for voice channels.
/// </summary> /// </summary>
public static readonly ChannelPermissions Voice = new(0b10001_001010_001010_110011_111101_111111_111101_010001); public static readonly ChannelPermissions Voice = new(0b1_010001_001010_001010_110011_111101_111111_111101_010001);
/// <summary> /// <summary>
/// Gets a <see cref="ChannelPermissions"/> that grants all permissions for stage channels. /// Gets a <see cref="ChannelPermissions"/> that grants all permissions for stage channels.
@@ -142,6 +142,9 @@ namespace Discord
public bool CreateEvents => Permissions.GetValue(RawValue, ChannelPermission.CreateEvents); public bool CreateEvents => Permissions.GetValue(RawValue, ChannelPermission.CreateEvents);
/// <summary> If <see langword="true"/>, a user can send voice messages in this channel.</summary> /// <summary> If <see langword="true"/>, a user can send voice messages in this channel.</summary>
public bool SendVoiceMessages => Permissions.GetValue(RawValue, ChannelPermission.SendVoiceMessages); public bool SendVoiceMessages => Permissions.GetValue(RawValue, ChannelPermission.SendVoiceMessages);
/// <summary> If <see langword="true"/>, a user can set the status of a voice channel.</summary>
public bool SetVoiceChannelStatus => Permissions.GetValue(RawValue, GuildPermission.SetVoiceChannelStatus);
/// <summary> Creates a new <see cref="ChannelPermissions"/> with the provided packed value.</summary> /// <summary> Creates a new <see cref="ChannelPermissions"/> with the provided packed value.</summary>
public ChannelPermissions(ulong rawValue) { RawValue = rawValue; } public ChannelPermissions(ulong rawValue) { RawValue = rawValue; }
@@ -179,7 +182,8 @@ namespace Discord
bool? startEmbeddedActivities = null, bool? startEmbeddedActivities = null,
bool? useSoundboard = null, bool? useSoundboard = null,
bool? createEvents = null, bool? createEvents = null,
bool? sendVoiceMessages = null) bool? sendVoiceMessages = null,
bool? setVoiceChannelStatus = null)
{ {
ulong value = initialValue; ulong value = initialValue;
@@ -216,6 +220,7 @@ namespace Discord
Permissions.SetValue(ref value, useSoundboard, ChannelPermission.UseSoundboard); Permissions.SetValue(ref value, useSoundboard, ChannelPermission.UseSoundboard);
Permissions.SetValue(ref value, createEvents, ChannelPermission.CreateEvents); Permissions.SetValue(ref value, createEvents, ChannelPermission.CreateEvents);
Permissions.SetValue(ref value, sendVoiceMessages, ChannelPermission.SendVoiceMessages); Permissions.SetValue(ref value, sendVoiceMessages, ChannelPermission.SendVoiceMessages);
Permissions.SetValue(ref value, setVoiceChannelStatus, ChannelPermission.SetVoiceChannelStatus);
RawValue = value; RawValue = value;
} }
@@ -254,12 +259,13 @@ namespace Discord
bool startEmbeddedActivities = false, bool startEmbeddedActivities = false,
bool useSoundboard = false, bool useSoundboard = false,
bool createEvents = false, bool createEvents = false,
bool sendVoiceMessages = false) bool sendVoiceMessages = false,
bool setVoiceChannelStatus = false)
: this(0, createInstantInvite, manageChannel, addReactions, viewChannel, sendMessages, sendTTSMessages, manageMessages, : this(0, createInstantInvite, manageChannel, addReactions, viewChannel, sendMessages, sendTTSMessages, manageMessages,
embedLinks, attachFiles, readMessageHistory, mentionEveryone, useExternalEmojis, connect, embedLinks, attachFiles, readMessageHistory, mentionEveryone, useExternalEmojis, connect,
speak, muteMembers, deafenMembers, moveMembers, useVoiceActivation, prioritySpeaker, stream, manageRoles, manageWebhooks, speak, muteMembers, deafenMembers, moveMembers, useVoiceActivation, prioritySpeaker, stream, manageRoles, manageWebhooks,
useApplicationCommands, requestToSpeak, manageThreads, createPublicThreads, createPrivateThreads, useExternalStickers, sendMessagesInThreads, useApplicationCommands, requestToSpeak, manageThreads, createPublicThreads, createPrivateThreads, useExternalStickers, sendMessagesInThreads,
startEmbeddedActivities, useSoundboard, createEvents, sendVoiceMessages) startEmbeddedActivities, useSoundboard, createEvents, sendVoiceMessages, setVoiceChannelStatus)
{ } { }
/// <summary> Creates a new <see cref="ChannelPermissions"/> from this one, changing the provided non-null permissions.</summary> /// <summary> Creates a new <see cref="ChannelPermissions"/> from this one, changing the provided non-null permissions.</summary>
@@ -296,7 +302,8 @@ namespace Discord
bool? startEmbeddedActivities = null, bool? startEmbeddedActivities = null,
bool? useSoundboard = null, bool? useSoundboard = null,
bool? createEvents = null, bool? createEvents = null,
bool? sendVoiceMessages = null) bool? sendVoiceMessages = null,
bool? setVoiceChannelStatus = null)
=> new ChannelPermissions(RawValue, => new ChannelPermissions(RawValue,
createInstantInvite, createInstantInvite,
manageChannel, manageChannel,
@@ -330,7 +337,8 @@ namespace Discord
startEmbeddedActivities, startEmbeddedActivities,
useSoundboard, useSoundboard,
createEvents, createEvents,
sendVoiceMessages); sendVoiceMessages,
setVoiceChannelStatus);
public bool Has(ChannelPermission permission) => Permissions.GetValue(RawValue, permission); public bool Has(ChannelPermission permission) => Permissions.GetValue(RawValue, permission);

View File

@@ -267,5 +267,10 @@ namespace Discord
/// Allows sending voice messages. /// Allows sending voice messages.
/// </summary> /// </summary>
SendVoiceMessages = 1L << 46, SendVoiceMessages = 1L << 46,
/// <summary>
/// Allows setting voice channel status.
/// </summary>
SetVoiceChannelStatus = 1L << 48,
} }
} }

View File

@@ -110,6 +110,8 @@ namespace Discord
public bool ViewMonetizationAnalytics => Permissions.GetValue(RawValue, GuildPermission.ViewMonetizationAnalytics); public bool ViewMonetizationAnalytics => Permissions.GetValue(RawValue, GuildPermission.ViewMonetizationAnalytics);
/// <summary> If <see langword="true"/>, a user can send voice messages in this channel.</summary> /// <summary> If <see langword="true"/>, a user can send voice messages in this channel.</summary>
public bool SendVoiceMessages => Permissions.GetValue(RawValue, GuildPermission.SendVoiceMessages); public bool SendVoiceMessages => Permissions.GetValue(RawValue, GuildPermission.SendVoiceMessages);
/// <summary> If <see langword="true"/>, a user can set the status of a voice channel.</summary>
public bool SetVoiceChannelStatus => Permissions.GetValue(RawValue, GuildPermission.SetVoiceChannelStatus);
/// <summary> Creates a new <see cref="GuildPermissions"/> with the provided packed value. </summary> /// <summary> Creates a new <see cref="GuildPermissions"/> with the provided packed value. </summary>
public GuildPermissions(ulong rawValue) { RawValue = rawValue; } public GuildPermissions(ulong rawValue) { RawValue = rawValue; }
@@ -161,7 +163,8 @@ namespace Discord
bool? moderateMembers = null, bool? moderateMembers = null,
bool? useSoundboard = null, bool? useSoundboard = null,
bool? viewMonetizationAnalytics = null, bool? viewMonetizationAnalytics = null,
bool? sendVoiceMessages = null) bool? sendVoiceMessages = null,
bool? setVoiceChannelStatus = null)
{ {
ulong value = initialValue; ulong value = initialValue;
@@ -209,6 +212,7 @@ namespace Discord
Permissions.SetValue(ref value, useSoundboard, GuildPermission.UseSoundboard); Permissions.SetValue(ref value, useSoundboard, GuildPermission.UseSoundboard);
Permissions.SetValue(ref value, viewMonetizationAnalytics, GuildPermission.ViewMonetizationAnalytics); Permissions.SetValue(ref value, viewMonetizationAnalytics, GuildPermission.ViewMonetizationAnalytics);
Permissions.SetValue(ref value, sendVoiceMessages, GuildPermission.SendVoiceMessages); Permissions.SetValue(ref value, sendVoiceMessages, GuildPermission.SendVoiceMessages);
Permissions.SetValue(ref value, setVoiceChannelStatus, GuildPermission.SetVoiceChannelStatus);
RawValue = value; RawValue = value;
} }
@@ -258,7 +262,8 @@ namespace Discord
bool moderateMembers = false, bool moderateMembers = false,
bool useSoundboard = false, bool useSoundboard = false,
bool viewMonetizationAnalytics = false, bool viewMonetizationAnalytics = false,
bool sendVoiceMessages = false) bool sendVoiceMessages = false,
bool setVoiceChannelStatus = false)
: this(0, : this(0,
createInstantInvite: createInstantInvite, createInstantInvite: createInstantInvite,
manageRoles: manageRoles, manageRoles: manageRoles,
@@ -303,7 +308,8 @@ namespace Discord
moderateMembers: moderateMembers, moderateMembers: moderateMembers,
useSoundboard: useSoundboard, useSoundboard: useSoundboard,
viewMonetizationAnalytics: viewMonetizationAnalytics, viewMonetizationAnalytics: viewMonetizationAnalytics,
sendVoiceMessages: sendVoiceMessages) sendVoiceMessages: sendVoiceMessages,
setVoiceChannelStatus: setVoiceChannelStatus)
{ } { }
/// <summary> Creates a new <see cref="GuildPermissions"/> from this one, changing the provided non-null permissions. </summary> /// <summary> Creates a new <see cref="GuildPermissions"/> from this one, changing the provided non-null permissions. </summary>
@@ -351,13 +357,14 @@ namespace Discord
bool? moderateMembers = null, bool? moderateMembers = null,
bool? useSoundboard = null, bool? useSoundboard = null,
bool? viewMonetizationAnalytics = null, bool? viewMonetizationAnalytics = null,
bool? sendVoiceMessages = null) bool? sendVoiceMessages = null,
bool? setVoiceChannelStatus = null)
=> new GuildPermissions(RawValue, createInstantInvite, kickMembers, banMembers, administrator, manageChannels, manageGuild, addReactions, => new GuildPermissions(RawValue, createInstantInvite, kickMembers, banMembers, administrator, manageChannels, manageGuild, addReactions,
viewAuditLog, viewGuildInsights, viewChannel, sendMessages, sendTTSMessages, manageMessages, embedLinks, attachFiles, viewAuditLog, viewGuildInsights, viewChannel, sendMessages, sendTTSMessages, manageMessages, embedLinks, attachFiles,
readMessageHistory, mentionEveryone, useExternalEmojis, connect, speak, muteMembers, deafenMembers, moveMembers, readMessageHistory, mentionEveryone, useExternalEmojis, connect, speak, muteMembers, deafenMembers, moveMembers,
useVoiceActivation, prioritySpeaker, stream, changeNickname, manageNicknames, manageRoles, manageWebhooks, manageEmojisAndStickers, useVoiceActivation, prioritySpeaker, stream, changeNickname, manageNicknames, manageRoles, manageWebhooks, manageEmojisAndStickers,
useApplicationCommands, requestToSpeak, manageEvents, manageThreads, createPublicThreads, createPrivateThreads, useExternalStickers, sendMessagesInThreads, useApplicationCommands, requestToSpeak, manageEvents, manageThreads, createPublicThreads, createPrivateThreads, useExternalStickers, sendMessagesInThreads,
startEmbeddedActivities, moderateMembers, useSoundboard, viewMonetizationAnalytics, sendVoiceMessages); startEmbeddedActivities, moderateMembers, useSoundboard, viewMonetizationAnalytics, sendVoiceMessages, setVoiceChannelStatus);
/// <summary> /// <summary>
/// Returns a value that indicates if a specific <see cref="GuildPermission"/> is enabled /// Returns a value that indicates if a specific <see cref="GuildPermission"/> is enabled

View File

@@ -36,4 +36,7 @@ internal class AuditLogOptions
[JsonProperty("auto_moderation_rule_trigger_type")] [JsonProperty("auto_moderation_rule_trigger_type")]
public AutoModTriggerType? AutoModRuleTriggerType { get; set; } public AutoModTriggerType? AutoModRuleTriggerType { get; set; }
[JsonProperty("status")]
public string Status { get; set; }
} }

View File

@@ -46,6 +46,9 @@ namespace Discord.API
[JsonProperty("video_quality_mode")] [JsonProperty("video_quality_mode")]
public Optional<VideoQualityMode> VideoQualityMode { get; set; } public Optional<VideoQualityMode> VideoQualityMode { get; set; }
[JsonProperty("status")]
public Optional<string> Status { get; set; }
//PrivateChannel //PrivateChannel
[JsonProperty("recipients")] [JsonProperty("recipients")]
public Optional<User[]> Recipients { get; set; } public Optional<User[]> Recipients { get; set; }

View File

@@ -0,0 +1,9 @@
using Newtonsoft.Json;
namespace Discord.API.Rest;
internal class ModifyVoiceStatusParams
{
[JsonProperty("status")]
public string Status { get; set; }
}

View File

@@ -467,6 +467,17 @@ namespace Discord.API
break; break;
} }
} }
public async Task ModifyVoiceChannelStatusAsync(ulong channelId, string status, RequestOptions options = null)
{
Preconditions.NotEqual(channelId, 0, nameof(channelId));
var payload = new ModifyVoiceStatusParams { Status = status };
var ids = new BucketIds();
await SendJsonAsync("PUT", () => $"channels/{channelId}/voice-status", payload, ids, options: options);
}
#endregion #endregion
#region Threads #region Threads

View File

@@ -86,6 +86,9 @@ internal static class AuditLogHelper
[ActionType.OnboardingQuestionCreated] = OnboardingPromptCreatedAuditLogData.Create, [ActionType.OnboardingQuestionCreated] = OnboardingPromptCreatedAuditLogData.Create,
[ActionType.OnboardingQuestionUpdated] = OnboardingPromptUpdatedAuditLogData.Create, [ActionType.OnboardingQuestionUpdated] = OnboardingPromptUpdatedAuditLogData.Create,
[ActionType.OnboardingUpdated] = OnboardingUpdatedAuditLogData.Create, [ActionType.OnboardingUpdated] = OnboardingUpdatedAuditLogData.Create,
[ActionType.VoiceChannelStatusUpdated] = VoiceChannelStatusUpdateAuditLogData.Create,
[ActionType.VoiceChannelStatusDeleted] = VoiceChannelStatusDeletedAuditLogData.Create
}; };
public static IAuditLogData CreateData(BaseDiscordClient discord, EntryModel entry, Model log = null) public static IAuditLogData CreateData(BaseDiscordClient discord, EntryModel entry, Model log = null)

View File

@@ -0,0 +1,25 @@
using EntryModel = Discord.API.AuditLogEntry;
using Model = Discord.API.AuditLog;
namespace Discord.Rest;
/// <summary>
/// Contains a piece of audit log data related to a voice channel status delete.
/// </summary>
public class VoiceChannelStatusDeletedAuditLogData : IAuditLogData
{
private VoiceChannelStatusDeletedAuditLogData(ulong channelId)
{
ChannelId = channelId;
}
internal static VoiceChannelStatusDeletedAuditLogData Create(BaseDiscordClient discord, EntryModel entry, Model log = null)
{
return new (entry.TargetId!.Value);
}
/// <summary>
/// Get the id of the channel status was removed in.
/// </summary>
public ulong ChannelId { get; }
}

View File

@@ -0,0 +1,31 @@
using EntryModel = Discord.API.AuditLogEntry;
using Model = Discord.API.AuditLog;
namespace Discord.Rest;
/// <summary>
/// Contains a piece of audit log data related to a voice channel status update.
/// </summary>
public class VoiceChannelStatusUpdateAuditLogData : IAuditLogData
{
private VoiceChannelStatusUpdateAuditLogData(string status, ulong channelId)
{
Status = status;
ChannelId = channelId;
}
internal static VoiceChannelStatusUpdateAuditLogData Create(BaseDiscordClient discord, EntryModel entry, Model log = null)
{
return new (entry.Options.Status, entry.TargetId!.Value);
}
/// <summary>
/// Gets the status that was set in the voice channel.
/// </summary>
public string Status { get; }
/// <summary>
/// Get the id of the channel status was updated in.
/// </summary>
public ulong ChannelId { get; }
}

View File

@@ -636,5 +636,16 @@ namespace Discord.Rest
await client.ApiClient.ModifyGuildChannelAsync(channel.Id, apiArgs, options).ConfigureAwait(false); await client.ApiClient.ModifyGuildChannelAsync(channel.Id, apiArgs, options).ConfigureAwait(false);
} }
#endregion #endregion
#region Voice
public static async Task ModifyVoiceChannelStatusAsync(IVoiceChannel channel, string status, BaseDiscordClient client, RequestOptions options)
{
Preconditions.AtMost(status.Length, DiscordConfig.MaxVoiceChannelStatusLength, $"Voice channel status length must be less than {DiscordConfig.MaxVoiceChannelStatusLength}.");
await client.ApiClient.ModifyVoiceChannelStatusAsync(channel.Id, status, options).ConfigureAwait(false);
}
#endregion
} }
} }

View File

@@ -150,5 +150,13 @@ namespace Discord.Rest
return Discord.ApiClient.ModifyUserVoiceState(Guild.Id, user.Id, args); return Discord.ApiClient.ModifyUserVoiceState(Guild.Id, user.Id, args);
} }
/// <inheritdoc />
/// <remarks>
/// Setting voice channel status is not supported in stage channels.
/// </remarks>
/// <exception cref="NotSupportedException">Setting voice channel status is not supported in stage channels.</exception>
public override Task SetStatusAsync(string status, RequestOptions options = null)
=> throw new NotSupportedException("Setting voice channel status is not supported in stage channels.");
} }
} }

View File

@@ -69,6 +69,10 @@ namespace Discord.Rest
public override Task<RestThreadChannel> CreateThreadAsync(string name, ThreadType type = ThreadType.PublicThread, ThreadArchiveDuration autoArchiveDuration = ThreadArchiveDuration.OneDay, IMessage message = null, bool? invitable = null, int? slowmode = null, RequestOptions options = null) public override Task<RestThreadChannel> CreateThreadAsync(string name, ThreadType type = ThreadType.PublicThread, ThreadArchiveDuration autoArchiveDuration = ThreadArchiveDuration.OneDay, IMessage message = null, bool? invitable = null, int? slowmode = null, RequestOptions options = null)
=> throw new InvalidOperationException("Cannot create a thread within a voice channel"); => throw new InvalidOperationException("Cannot create a thread within a voice channel");
/// <inheritdoc />
public virtual Task SetStatusAsync(string status, RequestOptions options = null)
=> ChannelHelper.ModifyVoiceChannelStatusAsync(this, status, Discord, options);
#endregion #endregion
private string DebuggerDisplay => $"{Name} ({Id}, Voice)"; private string DebuggerDisplay => $"{Name} ({Id}, Voice)";

View File

@@ -0,0 +1,15 @@
using Newtonsoft.Json;
namespace Discord.API.Gateway;
internal class VoiceChannelStatusUpdateEvent
{
[JsonProperty("id")]
public ulong Id { get; set; }
[JsonProperty("guild_id")]
public ulong GuildId { get; set; }
[JsonProperty("status")]
public string Status { get; set; }
}

View File

@@ -76,6 +76,22 @@ namespace Discord.WebSocket
remove { _channelUpdatedEvent.Remove(value); } remove { _channelUpdatedEvent.Remove(value); }
} }
internal readonly AsyncEvent<Func<SocketChannel, SocketChannel, Task>> _channelUpdatedEvent = new AsyncEvent<Func<SocketChannel, SocketChannel, Task>>(); internal readonly AsyncEvent<Func<SocketChannel, SocketChannel, Task>> _channelUpdatedEvent = new AsyncEvent<Func<SocketChannel, SocketChannel, Task>>();
/// <summary>
/// Fired when status of a voice channel is updated.
/// </summary>
/// <remarks>
/// The previous state of the channel is passed into the first parameter; the updated channel is passed into the second one.
/// </remarks>
public event Func<Cacheable<SocketVoiceChannel, ulong>, string, string, Task> VoiceChannelStatusUpdated
{
add { _voiceChannelStatusUpdated.Add(value); }
remove { _voiceChannelStatusUpdated.Remove(value); }
}
internal readonly AsyncEvent<Func<Cacheable<SocketVoiceChannel, ulong>, string, string, Task>> _voiceChannelStatusUpdated = new();
#endregion #endregion
#region Messages #region Messages

View File

@@ -519,6 +519,8 @@ namespace Discord.WebSocket
client.WebhooksUpdated += (arg1, arg2) => _webhooksUpdated.InvokeAsync(arg1, arg2); client.WebhooksUpdated += (arg1, arg2) => _webhooksUpdated.InvokeAsync(arg1, arg2);
client.AuditLogCreated += (arg1, arg2) => _auditLogCreated.InvokeAsync(arg1, arg2); client.AuditLogCreated += (arg1, arg2) => _auditLogCreated.InvokeAsync(arg1, arg2);
client.VoiceChannelStatusUpdated += (arg1, arg2, arg3) => _voiceChannelStatusUpdated.InvokeAsync(arg1, arg2, arg3);
client.EntitlementCreated += (arg1) => _entitlementCreated.InvokeAsync(arg1); client.EntitlementCreated += (arg1) => _entitlementCreated.InvokeAsync(arg1);
client.EntitlementUpdated += (arg1, arg2) => _entitlementUpdated.InvokeAsync(arg1, arg2); client.EntitlementUpdated += (arg1, arg2) => _entitlementUpdated.InvokeAsync(arg1, arg2);
client.EntitlementDeleted += (arg1) => _entitlementDeleted.InvokeAsync(arg1); client.EntitlementDeleted += (arg1) => _entitlementDeleted.InvokeAsync(arg1);

View File

@@ -2333,6 +2333,24 @@ namespace Discord.WebSocket
} }
break; break;
case "VOICE_CHANNEL_STATUS_UPDATE":
{
await _gatewayLogger.DebugAsync("Received Dispatch (VOICE_CHANNEL_STATUS_UPDATE)").ConfigureAwait(false);
var data = (payload as JToken).ToObject<VoiceChannelStatusUpdateEvent>(_serializer);
var guild = State.GetGuild(data.GuildId);
var channel = State.GetChannel(data.Id) as SocketVoiceChannel;
var channelCacheable = new Cacheable<SocketVoiceChannel, ulong>(channel, data.Id, channel is not null, () => null);
var before = (string)channel?.Status?.Clone();
var after = data.Status;
channel?.UpdateVoiceStatus(data.Status);
await TimedInvokeAsync(_voiceChannelStatusUpdated, nameof(VoiceChannelStatusUpdated), channelCacheable, before, after);
}
break;
#endregion #endregion
#region Invites #region Invites

View File

@@ -0,0 +1,25 @@
using EntryModel = Discord.API.AuditLogEntry;
using Model = Discord.API.AuditLog;
namespace Discord.WebSocket;
/// <summary>
/// Contains a piece of audit log data related to a voice channel status delete.
/// </summary>
public class SocketVoiceChannelStatusDeleteAuditLogData : ISocketAuditLogData
{
private SocketVoiceChannelStatusDeleteAuditLogData(ulong channelId)
{
ChannelId = channelId;
}
internal static SocketVoiceChannelStatusDeleteAuditLogData Create(DiscordSocketClient discord, EntryModel entry)
{
return new (entry.TargetId!.Value);
}
/// <summary>
/// Get the id of the channel status was removed in.
/// </summary>
public ulong ChannelId { get; }
}

View File

@@ -0,0 +1,31 @@
using EntryModel = Discord.API.AuditLogEntry;
using Model = Discord.API.AuditLog;
namespace Discord.WebSocket;
/// <summary>
/// Contains a piece of audit log data related to a voice channel status update.
/// </summary>
public class SocketVoiceChannelStatusUpdatedAuditLogData : ISocketAuditLogData
{
private SocketVoiceChannelStatusUpdatedAuditLogData(string status, ulong channelId)
{
Status = status;
ChannelId = channelId;
}
internal static SocketVoiceChannelStatusUpdatedAuditLogData Create(DiscordSocketClient discord, EntryModel entry)
{
return new (entry.Options.Status, entry.TargetId!.Value);
}
/// <summary>
/// Gets the status that was set in the voice channel.
/// </summary>
public string Status { get; }
/// <summary>
/// Get the id of the channel status was updated in.
/// </summary>
public ulong ChannelId { get; }
}

View File

@@ -8,9 +8,9 @@ namespace Discord.WebSocket;
/// <summary> /// <summary>
/// Contains a piece of audit log data related to a webhook deletion. /// Contains a piece of audit log data related to a webhook deletion.
/// </summary> /// </summary>
public class SocketWebhookDeleteAuditLogData : ISocketAuditLogData public class SocketWebhookDeletedAuditLogData : ISocketAuditLogData
{ {
private SocketWebhookDeleteAuditLogData(ulong id, Model model) private SocketWebhookDeletedAuditLogData(ulong id, Model model)
{ {
WebhookId = id; WebhookId = id;
ChannelId = model.ChannelId!.Value; ChannelId = model.ChannelId!.Value;
@@ -19,13 +19,13 @@ public class SocketWebhookDeleteAuditLogData : ISocketAuditLogData
Avatar = model.AvatarHash; Avatar = model.AvatarHash;
} }
internal static SocketWebhookDeleteAuditLogData Create(DiscordSocketClient discord, EntryModel entry) internal static SocketWebhookDeletedAuditLogData Create(DiscordSocketClient discord, EntryModel entry)
{ {
var changes = entry.Changes; var changes = entry.Changes;
var (data, _) = AuditLogHelper.CreateAuditLogEntityInfo<Model>(changes, discord); var (data, _) = AuditLogHelper.CreateAuditLogEntityInfo<Model>(changes, discord);
return new SocketWebhookDeleteAuditLogData(entry.TargetId!.Value,data); return new SocketWebhookDeletedAuditLogData(entry.TargetId!.Value,data);
} }
/// <summary> /// <summary>

View File

@@ -40,7 +40,7 @@ internal static class SocketAuditLogHelper
[ActionType.WebhookCreated] = SocketWebhookCreateAuditLogData.Create, [ActionType.WebhookCreated] = SocketWebhookCreateAuditLogData.Create,
[ActionType.WebhookUpdated] = SocketWebhookUpdateAuditLogData.Create, [ActionType.WebhookUpdated] = SocketWebhookUpdateAuditLogData.Create,
[ActionType.WebhookDeleted] = SocketWebhookDeleteAuditLogData.Create, [ActionType.WebhookDeleted] = SocketWebhookDeletedAuditLogData.Create,
[ActionType.EmojiCreated] = SocketEmoteCreateAuditLogData.Create, [ActionType.EmojiCreated] = SocketEmoteCreateAuditLogData.Create,
[ActionType.EmojiUpdated] = SocketEmoteUpdateAuditLogData.Create, [ActionType.EmojiUpdated] = SocketEmoteUpdateAuditLogData.Create,
@@ -84,6 +84,10 @@ internal static class SocketAuditLogHelper
[ActionType.OnboardingQuestionCreated] = SocketOnboardingPromptCreatedAuditLogData.Create, [ActionType.OnboardingQuestionCreated] = SocketOnboardingPromptCreatedAuditLogData.Create,
[ActionType.OnboardingQuestionUpdated] = SocketOnboardingPromptUpdatedAuditLogData.Create, [ActionType.OnboardingQuestionUpdated] = SocketOnboardingPromptUpdatedAuditLogData.Create,
[ActionType.OnboardingUpdated] = SocketOnboardingUpdatedAuditLogData.Create, [ActionType.OnboardingUpdated] = SocketOnboardingUpdatedAuditLogData.Create,
[ActionType.VoiceChannelStatusUpdated] = SocketVoiceChannelStatusUpdatedAuditLogData.Create,
[ActionType.VoiceChannelStatusDeleted] = SocketVoiceChannelStatusDeleteAuditLogData.Create,
}; };
public static ISocketAuditLogData CreateData(DiscordSocketClient discord, EntryModel entry) public static ISocketAuditLogData CreateData(DiscordSocketClient discord, EntryModel entry)

View File

@@ -43,6 +43,12 @@ namespace Discord.WebSocket
public IReadOnlyCollection<SocketGuildUser> Speakers public IReadOnlyCollection<SocketGuildUser> Speakers
=> Users.Where(x => !x.IsSuppressed).ToImmutableArray(); => Users.Where(x => !x.IsSuppressed).ToImmutableArray();
/// <inheritdoc/>
/// <remarks>
/// This property is not supported in stage channels and will always return <see cref="string.Empty"/>.
/// </remarks>
public override string Status => string.Empty;
internal new SocketStageChannel Clone() => MemberwiseClone() as SocketStageChannel; internal new SocketStageChannel Clone() => MemberwiseClone() as SocketStageChannel;
internal SocketStageChannel(DiscordSocketClient discord, ulong id, SocketGuild guild) internal SocketStageChannel(DiscordSocketClient discord, ulong id, SocketGuild guild)
@@ -156,5 +162,13 @@ namespace Discord.WebSocket
return Discord.ApiClient.ModifyUserVoiceState(Guild.Id, user.Id, args); return Discord.ApiClient.ModifyUserVoiceState(Guild.Id, user.Id, args);
} }
/// <inheritdoc />
/// <remarks>
/// Setting voice channel status is not supported in stage channels.
/// </remarks>
/// <exception cref="NotSupportedException">Setting voice channel status is not supported in stage channels.</exception>
public override Task SetStatusAsync(string status, RequestOptions options = null)
=> throw new NotSupportedException("Setting voice channel status is not supported in stage channels.");
} }
} }

View File

@@ -38,6 +38,11 @@ namespace Discord.WebSocket
/// <inheritdoc/> /// <inheritdoc/>
public VideoQualityMode VideoQualityMode { get; private set; } public VideoQualityMode VideoQualityMode { get; private set; }
/// <summary>
/// Gets the voice channel status set in this channel. <see langword="null" /> if it is not set.
/// </summary>
public virtual string Status { get; private set; }
/// <summary> /// <summary>
/// Gets a collection of users that are currently connected to this voice channel. /// Gets a collection of users that are currently connected to this voice channel.
/// </summary> /// </summary>
@@ -51,12 +56,19 @@ namespace Discord.WebSocket
: base(discord, id, guild) : base(discord, id, guild)
{ {
} }
internal new static SocketVoiceChannel Create(SocketGuild guild, ClientState state, Model model) internal new static SocketVoiceChannel Create(SocketGuild guild, ClientState state, Model model)
{ {
var entity = new SocketVoiceChannel(guild?.Discord, model.Id, guild); var entity = new SocketVoiceChannel(guild?.Discord, model.Id, guild);
entity.Update(state, model); entity.Update(state, model);
return entity; return entity;
} }
internal void UpdateVoiceStatus(string status)
{
Status = status;
}
/// <inheritdoc /> /// <inheritdoc />
internal override void Update(ClientState state, Model model) internal override void Update(ClientState state, Model model)
{ {
@@ -65,8 +77,13 @@ namespace Discord.WebSocket
UserLimit = model.UserLimit.GetValueOrDefault() != 0 ? model.UserLimit.Value : (int?)null; UserLimit = model.UserLimit.GetValueOrDefault() != 0 ? model.UserLimit.Value : (int?)null;
VideoQualityMode = model.VideoQualityMode.GetValueOrDefault(VideoQualityMode.Auto); VideoQualityMode = model.VideoQualityMode.GetValueOrDefault(VideoQualityMode.Auto);
RTCRegion = model.RTCRegion.GetValueOrDefault(null); RTCRegion = model.RTCRegion.GetValueOrDefault(null);
Status = model.Status.GetValueOrDefault(null);
} }
/// <inheritdoc />
public virtual Task SetStatusAsync(string status, RequestOptions options = null)
=> ChannelHelper.ModifyVoiceChannelStatusAsync(this, status, Discord, options);
/// <inheritdoc /> /// <inheritdoc />
public Task ModifyAsync(Action<VoiceChannelProperties> func, RequestOptions options = null) public Task ModifyAsync(Action<VoiceChannelProperties> func, RequestOptions options = null)
=> ChannelHelper.ModifyAsync(this, Discord, func, options); => ChannelHelper.ModifyAsync(this, Discord, func, options);

View File

@@ -93,6 +93,7 @@ namespace Discord
AssertFlag(() => new ChannelPermissions(useSoundboard: true), ChannelPermission.UseSoundboard); AssertFlag(() => new ChannelPermissions(useSoundboard: true), ChannelPermission.UseSoundboard);
AssertFlag(() => new ChannelPermissions(createEvents: true), ChannelPermission.CreateEvents); AssertFlag(() => new ChannelPermissions(createEvents: true), ChannelPermission.CreateEvents);
AssertFlag(() => new ChannelPermissions(sendVoiceMessages: true), ChannelPermission.SendVoiceMessages); AssertFlag(() => new ChannelPermissions(sendVoiceMessages: true), ChannelPermission.SendVoiceMessages);
AssertFlag(() => new ChannelPermissions(setVoiceChannelStatus: true), ChannelPermission.SetVoiceChannelStatus);
} }
/// <summary> /// <summary>
@@ -158,6 +159,7 @@ namespace Discord
AssertUtil(ChannelPermission.PrioritySpeaker, x => x.PrioritySpeaker, (p, enable) => p.Modify(prioritySpeaker: enable)); AssertUtil(ChannelPermission.PrioritySpeaker, x => x.PrioritySpeaker, (p, enable) => p.Modify(prioritySpeaker: enable));
AssertUtil(ChannelPermission.Stream, x => x.Stream, (p, enable) => p.Modify(stream: enable)); AssertUtil(ChannelPermission.Stream, x => x.Stream, (p, enable) => p.Modify(stream: enable));
AssertUtil(ChannelPermission.SendVoiceMessages, x => x.SendVoiceMessages, (p, enable) => p.Modify(sendVoiceMessages: enable)); AssertUtil(ChannelPermission.SendVoiceMessages, x => x.SendVoiceMessages, (p, enable) => p.Modify(sendVoiceMessages: enable));
AssertUtil(ChannelPermission.SetVoiceChannelStatus, x => x.SetVoiceChannelStatus, (p, enable) => p.Modify(setVoiceChannelStatus: enable));
} }
/// <summary> /// <summary>

View File

@@ -103,6 +103,7 @@ namespace Discord
AssertFlag(() => new GuildPermissions(viewMonetizationAnalytics: true), GuildPermission.ViewMonetizationAnalytics); AssertFlag(() => new GuildPermissions(viewMonetizationAnalytics: true), GuildPermission.ViewMonetizationAnalytics);
AssertFlag(() => new GuildPermissions(useSoundboard: true), GuildPermission.UseSoundboard); AssertFlag(() => new GuildPermissions(useSoundboard: true), GuildPermission.UseSoundboard);
AssertFlag(() => new GuildPermissions(sendVoiceMessages: true), GuildPermission.SendVoiceMessages); AssertFlag(() => new GuildPermissions(sendVoiceMessages: true), GuildPermission.SendVoiceMessages);
AssertFlag(() => new GuildPermissions(setVoiceChannelStatus: true), GuildPermission.SetVoiceChannelStatus);
} }
/// <summary> /// <summary>
@@ -184,6 +185,7 @@ namespace Discord
AssertUtil(GuildPermission.ViewMonetizationAnalytics, x => x.ViewMonetizationAnalytics, (p, enable) => p.Modify(viewMonetizationAnalytics: enable)); AssertUtil(GuildPermission.ViewMonetizationAnalytics, x => x.ViewMonetizationAnalytics, (p, enable) => p.Modify(viewMonetizationAnalytics: enable));
AssertUtil(GuildPermission.UseSoundboard, x => x.UseSoundboard, (p, enable) => p.Modify(useSoundboard: enable)); AssertUtil(GuildPermission.UseSoundboard, x => x.UseSoundboard, (p, enable) => p.Modify(useSoundboard: enable));
AssertUtil(GuildPermission.SendVoiceMessages, x => x.SendVoiceMessages, (p, enable) => p.Modify(sendVoiceMessages: enable)); AssertUtil(GuildPermission.SendVoiceMessages, x => x.SendVoiceMessages, (p, enable) => p.Modify(sendVoiceMessages: enable));
AssertUtil(GuildPermission.SetVoiceChannelStatus, x => x.SetVoiceChannelStatus, (p, enable) => p.Modify(setVoiceChannelStatus: enable));
} }
} }
} }

View File

@@ -75,6 +75,8 @@ namespace Discord
public Task<IGuildUser> GetUserAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null) => throw new NotImplementedException(); public Task<IGuildUser> GetUserAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null) => throw new NotImplementedException();
public IAsyncEnumerable<IReadOnlyCollection<IGuildUser>> GetUsersAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null) => throw new NotImplementedException(); public IAsyncEnumerable<IReadOnlyCollection<IGuildUser>> GetUsersAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null) => throw new NotImplementedException();
public Task ModifyAsync(Action<VoiceChannelProperties> func, RequestOptions options = null) => throw new NotImplementedException(); public Task ModifyAsync(Action<VoiceChannelProperties> func, RequestOptions options = null) => throw new NotImplementedException();
public Task SetStatusAsync(string status, RequestOptions options = null) => throw new NotImplementedException();
public Task ModifyAsync(Action<GuildChannelProperties> func, RequestOptions options = null) => throw new NotImplementedException(); public Task ModifyAsync(Action<GuildChannelProperties> func, RequestOptions options = null) => throw new NotImplementedException();
public Task ModifyAsync(Action<AudioChannelProperties> func, RequestOptions options = null) => throw new NotImplementedException(); public Task ModifyAsync(Action<AudioChannelProperties> func, RequestOptions options = null) => throw new NotImplementedException();
public Task<IUserMessage> ModifyMessageAsync(ulong messageId, Action<MessageProperties> func, RequestOptions options = null) => throw new NotImplementedException(); public Task<IUserMessage> ModifyMessageAsync(ulong messageId, Action<MessageProperties> func, RequestOptions options = null) => throw new NotImplementedException();