diff --git a/src/Discord.Net.Core/DiscordConfig.cs b/src/Discord.Net.Core/DiscordConfig.cs index 8686c845..7386464f 100644 --- a/src/Discord.Net.Core/DiscordConfig.cs +++ b/src/Discord.Net.Core/DiscordConfig.cs @@ -238,6 +238,11 @@ namespace Discord /// public const int MaxApplicationTagCount = 5; + /// + /// Returns the maximum length of a voice channel status. + /// + public const int MaxVoiceChannelStatusLength = 500; + /// /// Returns the maximum number of entitlements that can be gotten per-batch. /// diff --git a/src/Discord.Net.Core/Entities/AuditLogs/ActionType.cs b/src/Discord.Net.Core/Entities/AuditLogs/ActionType.cs index a7e4efaa..3288517b 100644 --- a/src/Discord.Net.Core/Entities/AuditLogs/ActionType.cs +++ b/src/Discord.Net.Core/Entities/AuditLogs/ActionType.cs @@ -249,6 +249,16 @@ namespace Discord /// /// Guild Onboarding was updated. /// - OnboardingUpdated = 167 + OnboardingUpdated = 167, + + /// + /// A voice channel status was updated by a user. + /// + VoiceChannelStatusUpdated = 192, + + /// + /// A voice channel status was deleted by a user. + /// + VoiceChannelStatusDeleted = 193 } } diff --git a/src/Discord.Net.Core/Entities/Channels/IVoiceChannel.cs b/src/Discord.Net.Core/Entities/Channels/IVoiceChannel.cs index 77f1e9d5..00f03d82 100644 --- a/src/Discord.Net.Core/Entities/Channels/IVoiceChannel.cs +++ b/src/Discord.Net.Core/Entities/Channels/IVoiceChannel.cs @@ -41,5 +41,15 @@ namespace Discord /// /// Task ModifyAsync(Action func, RequestOptions options = null); + + /// + /// Sets the voice channel status in the current channel. + /// + /// The string to set as status. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous modification operation. + /// + Task SetStatusAsync(string status, RequestOptions options = null); } } diff --git a/src/Discord.Net.Core/Entities/Permissions/ChannelPermission.cs b/src/Discord.Net.Core/Entities/Permissions/ChannelPermission.cs index becbba1a..a0ee23fa 100644 --- a/src/Discord.Net.Core/Entities/Permissions/ChannelPermission.cs +++ b/src/Discord.Net.Core/Entities/Permissions/ChannelPermission.cs @@ -182,5 +182,10 @@ namespace Discord /// Allows sending voice messages. /// SendVoiceMessages = 1L << 46, + + /// + /// Allows setting voice channel status. + /// + SetVoiceChannelStatus = 1L << 48, } } diff --git a/src/Discord.Net.Core/Entities/Permissions/ChannelPermissions.cs b/src/Discord.Net.Core/Entities/Permissions/ChannelPermissions.cs index ca1ddc29..d33d5a44 100644 --- a/src/Discord.Net.Core/Entities/Permissions/ChannelPermissions.cs +++ b/src/Discord.Net.Core/Entities/Permissions/ChannelPermissions.cs @@ -23,7 +23,7 @@ namespace Discord /// /// Gets a that grants all permissions for voice channels. /// - 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); /// /// Gets a that grants all permissions for stage channels. @@ -142,6 +142,9 @@ namespace Discord public bool CreateEvents => Permissions.GetValue(RawValue, ChannelPermission.CreateEvents); /// If , a user can send voice messages in this channel. public bool SendVoiceMessages => Permissions.GetValue(RawValue, ChannelPermission.SendVoiceMessages); + /// If , a user can set the status of a voice channel. + public bool SetVoiceChannelStatus => Permissions.GetValue(RawValue, GuildPermission.SetVoiceChannelStatus); + /// Creates a new with the provided packed value. public ChannelPermissions(ulong rawValue) { RawValue = rawValue; } @@ -179,7 +182,8 @@ namespace Discord bool? startEmbeddedActivities = null, bool? useSoundboard = null, bool? createEvents = null, - bool? sendVoiceMessages = null) + bool? sendVoiceMessages = null, + bool? setVoiceChannelStatus = null) { ulong value = initialValue; @@ -216,6 +220,7 @@ namespace Discord Permissions.SetValue(ref value, useSoundboard, ChannelPermission.UseSoundboard); Permissions.SetValue(ref value, createEvents, ChannelPermission.CreateEvents); Permissions.SetValue(ref value, sendVoiceMessages, ChannelPermission.SendVoiceMessages); + Permissions.SetValue(ref value, setVoiceChannelStatus, ChannelPermission.SetVoiceChannelStatus); RawValue = value; } @@ -254,12 +259,13 @@ namespace Discord bool startEmbeddedActivities = false, bool useSoundboard = false, bool createEvents = false, - bool sendVoiceMessages = false) + bool sendVoiceMessages = false, + bool setVoiceChannelStatus = false) : this(0, createInstantInvite, manageChannel, addReactions, viewChannel, sendMessages, sendTTSMessages, manageMessages, embedLinks, attachFiles, readMessageHistory, mentionEveryone, useExternalEmojis, connect, speak, muteMembers, deafenMembers, moveMembers, useVoiceActivation, prioritySpeaker, stream, manageRoles, manageWebhooks, useApplicationCommands, requestToSpeak, manageThreads, createPublicThreads, createPrivateThreads, useExternalStickers, sendMessagesInThreads, - startEmbeddedActivities, useSoundboard, createEvents, sendVoiceMessages) + startEmbeddedActivities, useSoundboard, createEvents, sendVoiceMessages, setVoiceChannelStatus) { } /// Creates a new from this one, changing the provided non-null permissions. @@ -296,7 +302,8 @@ namespace Discord bool? startEmbeddedActivities = null, bool? useSoundboard = null, bool? createEvents = null, - bool? sendVoiceMessages = null) + bool? sendVoiceMessages = null, + bool? setVoiceChannelStatus = null) => new ChannelPermissions(RawValue, createInstantInvite, manageChannel, @@ -330,7 +337,8 @@ namespace Discord startEmbeddedActivities, useSoundboard, createEvents, - sendVoiceMessages); + sendVoiceMessages, + setVoiceChannelStatus); public bool Has(ChannelPermission permission) => Permissions.GetValue(RawValue, permission); diff --git a/src/Discord.Net.Core/Entities/Permissions/GuildPermission.cs b/src/Discord.Net.Core/Entities/Permissions/GuildPermission.cs index 1485e0ba..0646ee70 100644 --- a/src/Discord.Net.Core/Entities/Permissions/GuildPermission.cs +++ b/src/Discord.Net.Core/Entities/Permissions/GuildPermission.cs @@ -267,5 +267,10 @@ namespace Discord /// Allows sending voice messages. /// SendVoiceMessages = 1L << 46, + + /// + /// Allows setting voice channel status. + /// + SetVoiceChannelStatus = 1L << 48, } } diff --git a/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs b/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs index c4d20948..3b9b84e3 100644 --- a/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs +++ b/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs @@ -110,6 +110,8 @@ namespace Discord public bool ViewMonetizationAnalytics => Permissions.GetValue(RawValue, GuildPermission.ViewMonetizationAnalytics); /// If , a user can send voice messages in this channel. public bool SendVoiceMessages => Permissions.GetValue(RawValue, GuildPermission.SendVoiceMessages); + /// If , a user can set the status of a voice channel. + public bool SetVoiceChannelStatus => Permissions.GetValue(RawValue, GuildPermission.SetVoiceChannelStatus); /// Creates a new with the provided packed value. public GuildPermissions(ulong rawValue) { RawValue = rawValue; } @@ -161,7 +163,8 @@ namespace Discord bool? moderateMembers = null, bool? useSoundboard = null, bool? viewMonetizationAnalytics = null, - bool? sendVoiceMessages = null) + bool? sendVoiceMessages = null, + bool? setVoiceChannelStatus = null) { ulong value = initialValue; @@ -209,6 +212,7 @@ namespace Discord Permissions.SetValue(ref value, useSoundboard, GuildPermission.UseSoundboard); Permissions.SetValue(ref value, viewMonetizationAnalytics, GuildPermission.ViewMonetizationAnalytics); Permissions.SetValue(ref value, sendVoiceMessages, GuildPermission.SendVoiceMessages); + Permissions.SetValue(ref value, setVoiceChannelStatus, GuildPermission.SetVoiceChannelStatus); RawValue = value; } @@ -258,7 +262,8 @@ namespace Discord bool moderateMembers = false, bool useSoundboard = false, bool viewMonetizationAnalytics = false, - bool sendVoiceMessages = false) + bool sendVoiceMessages = false, + bool setVoiceChannelStatus = false) : this(0, createInstantInvite: createInstantInvite, manageRoles: manageRoles, @@ -303,7 +308,8 @@ namespace Discord moderateMembers: moderateMembers, useSoundboard: useSoundboard, viewMonetizationAnalytics: viewMonetizationAnalytics, - sendVoiceMessages: sendVoiceMessages) + sendVoiceMessages: sendVoiceMessages, + setVoiceChannelStatus: setVoiceChannelStatus) { } /// Creates a new from this one, changing the provided non-null permissions. @@ -351,13 +357,14 @@ namespace Discord bool? moderateMembers = null, bool? useSoundboard = null, bool? viewMonetizationAnalytics = null, - bool? sendVoiceMessages = null) + bool? sendVoiceMessages = null, + bool? setVoiceChannelStatus = null) => new GuildPermissions(RawValue, createInstantInvite, kickMembers, banMembers, administrator, manageChannels, manageGuild, addReactions, viewAuditLog, viewGuildInsights, viewChannel, sendMessages, sendTTSMessages, manageMessages, embedLinks, attachFiles, readMessageHistory, mentionEveryone, useExternalEmojis, connect, speak, muteMembers, deafenMembers, moveMembers, useVoiceActivation, prioritySpeaker, stream, changeNickname, manageNicknames, manageRoles, manageWebhooks, manageEmojisAndStickers, useApplicationCommands, requestToSpeak, manageEvents, manageThreads, createPublicThreads, createPrivateThreads, useExternalStickers, sendMessagesInThreads, - startEmbeddedActivities, moderateMembers, useSoundboard, viewMonetizationAnalytics, sendVoiceMessages); + startEmbeddedActivities, moderateMembers, useSoundboard, viewMonetizationAnalytics, sendVoiceMessages, setVoiceChannelStatus); /// /// Returns a value that indicates if a specific is enabled diff --git a/src/Discord.Net.Rest/API/Common/AuditLogOptions.cs b/src/Discord.Net.Rest/API/Common/AuditLogOptions.cs index f91c6d32..ea6339f2 100644 --- a/src/Discord.Net.Rest/API/Common/AuditLogOptions.cs +++ b/src/Discord.Net.Rest/API/Common/AuditLogOptions.cs @@ -36,4 +36,7 @@ internal class AuditLogOptions [JsonProperty("auto_moderation_rule_trigger_type")] public AutoModTriggerType? AutoModRuleTriggerType { get; set; } + + [JsonProperty("status")] + public string Status { get; set; } } diff --git a/src/Discord.Net.Rest/API/Common/Channel.cs b/src/Discord.Net.Rest/API/Common/Channel.cs index bbfe432e..2bc26767 100644 --- a/src/Discord.Net.Rest/API/Common/Channel.cs +++ b/src/Discord.Net.Rest/API/Common/Channel.cs @@ -46,6 +46,9 @@ namespace Discord.API [JsonProperty("video_quality_mode")] public Optional VideoQualityMode { get; set; } + [JsonProperty("status")] + public Optional Status { get; set; } + //PrivateChannel [JsonProperty("recipients")] public Optional Recipients { get; set; } diff --git a/src/Discord.Net.Rest/API/Rest/ModifyVoiceStatusParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyVoiceStatusParams.cs new file mode 100644 index 00000000..179e8aaf --- /dev/null +++ b/src/Discord.Net.Rest/API/Rest/ModifyVoiceStatusParams.cs @@ -0,0 +1,9 @@ +using Newtonsoft.Json; + +namespace Discord.API.Rest; + +internal class ModifyVoiceStatusParams +{ + [JsonProperty("status")] + public string Status { get; set; } +} diff --git a/src/Discord.Net.Rest/DiscordRestApiClient.cs b/src/Discord.Net.Rest/DiscordRestApiClient.cs index a75b5c83..e4818fd3 100644 --- a/src/Discord.Net.Rest/DiscordRestApiClient.cs +++ b/src/Discord.Net.Rest/DiscordRestApiClient.cs @@ -467,6 +467,17 @@ namespace Discord.API 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 #region Threads diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/AuditLogHelper.cs b/src/Discord.Net.Rest/Entities/AuditLogs/AuditLogHelper.cs index 8bc431ed..37121bcf 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/AuditLogHelper.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/AuditLogHelper.cs @@ -86,6 +86,9 @@ internal static class AuditLogHelper [ActionType.OnboardingQuestionCreated] = OnboardingPromptCreatedAuditLogData.Create, [ActionType.OnboardingQuestionUpdated] = OnboardingPromptUpdatedAuditLogData.Create, [ActionType.OnboardingUpdated] = OnboardingUpdatedAuditLogData.Create, + + [ActionType.VoiceChannelStatusUpdated] = VoiceChannelStatusUpdateAuditLogData.Create, + [ActionType.VoiceChannelStatusDeleted] = VoiceChannelStatusDeletedAuditLogData.Create }; public static IAuditLogData CreateData(BaseDiscordClient discord, EntryModel entry, Model log = null) diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/VoiceChannelStatusDeletedAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/VoiceChannelStatusDeletedAuditLogData.cs new file mode 100644 index 00000000..321d3fe0 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/VoiceChannelStatusDeletedAuditLogData.cs @@ -0,0 +1,25 @@ +using EntryModel = Discord.API.AuditLogEntry; +using Model = Discord.API.AuditLog; + +namespace Discord.Rest; + +/// +/// Contains a piece of audit log data related to a voice channel status delete. +/// +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); + } + + /// + /// Get the id of the channel status was removed in. + /// + public ulong ChannelId { get; } +} diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/VoiceChannelStatusUpdateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/VoiceChannelStatusUpdateAuditLogData.cs new file mode 100644 index 00000000..339a6cc9 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/VoiceChannelStatusUpdateAuditLogData.cs @@ -0,0 +1,31 @@ +using EntryModel = Discord.API.AuditLogEntry; +using Model = Discord.API.AuditLog; + +namespace Discord.Rest; + +/// +/// Contains a piece of audit log data related to a voice channel status update. +/// +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); + } + + /// + /// Gets the status that was set in the voice channel. + /// + public string Status { get; } + + /// + /// Get the id of the channel status was updated in. + /// + public ulong ChannelId { get; } +} diff --git a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs index 751fbfc0..a5dd522f 100644 --- a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs +++ b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs @@ -636,5 +636,16 @@ namespace Discord.Rest await client.ApiClient.ModifyGuildChannelAsync(channel.Id, apiArgs, options).ConfigureAwait(false); } #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 } } diff --git a/src/Discord.Net.Rest/Entities/Channels/RestStageChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestStageChannel.cs index 47a19967..fe46cd80 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestStageChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestStageChannel.cs @@ -150,5 +150,13 @@ namespace Discord.Rest return Discord.ApiClient.ModifyUserVoiceState(Guild.Id, user.Id, args); } + + /// + /// + /// Setting voice channel status is not supported in stage channels. + /// + /// Setting voice channel status is not supported in stage channels. + public override Task SetStatusAsync(string status, RequestOptions options = null) + => throw new NotSupportedException("Setting voice channel status is not supported in stage channels."); } } diff --git a/src/Discord.Net.Rest/Entities/Channels/RestVoiceChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestVoiceChannel.cs index eac40538..3e979eaf 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestVoiceChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestVoiceChannel.cs @@ -69,6 +69,10 @@ namespace Discord.Rest public override Task 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"); + /// + public virtual Task SetStatusAsync(string status, RequestOptions options = null) + => ChannelHelper.ModifyVoiceChannelStatusAsync(this, status, Discord, options); + #endregion private string DebuggerDisplay => $"{Name} ({Id}, Voice)"; diff --git a/src/Discord.Net.WebSocket/API/Gateway/VoiceChannelStatusUpdateEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/VoiceChannelStatusUpdateEvent.cs new file mode 100644 index 00000000..757e50b9 --- /dev/null +++ b/src/Discord.Net.WebSocket/API/Gateway/VoiceChannelStatusUpdateEvent.cs @@ -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; } +} diff --git a/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs b/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs index 1109f4f7..88b601dc 100644 --- a/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs +++ b/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs @@ -76,6 +76,22 @@ namespace Discord.WebSocket remove { _channelUpdatedEvent.Remove(value); } } internal readonly AsyncEvent> _channelUpdatedEvent = new AsyncEvent>(); + + /// + /// Fired when status of a voice channel is updated. + /// + /// + /// The previous state of the channel is passed into the first parameter; the updated channel is passed into the second one. + /// + public event Func, string, string, Task> VoiceChannelStatusUpdated + { + add { _voiceChannelStatusUpdated.Add(value); } + remove { _voiceChannelStatusUpdated.Remove(value); } + } + + internal readonly AsyncEvent, string, string, Task>> _voiceChannelStatusUpdated = new(); + + #endregion #region Messages diff --git a/src/Discord.Net.WebSocket/DiscordShardedClient.cs b/src/Discord.Net.WebSocket/DiscordShardedClient.cs index f88d9214..d7145215 100644 --- a/src/Discord.Net.WebSocket/DiscordShardedClient.cs +++ b/src/Discord.Net.WebSocket/DiscordShardedClient.cs @@ -519,6 +519,8 @@ namespace Discord.WebSocket client.WebhooksUpdated += (arg1, arg2) => _webhooksUpdated.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.EntitlementUpdated += (arg1, arg2) => _entitlementUpdated.InvokeAsync(arg1, arg2); client.EntitlementDeleted += (arg1) => _entitlementDeleted.InvokeAsync(arg1); diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs index d11d86a6..00aaa7d6 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -2333,6 +2333,24 @@ namespace Discord.WebSocket } break; + + case "VOICE_CHANNEL_STATUS_UPDATE": + { + await _gatewayLogger.DebugAsync("Received Dispatch (VOICE_CHANNEL_STATUS_UPDATE)").ConfigureAwait(false); + + var data = (payload as JToken).ToObject(_serializer); + var guild = State.GetGuild(data.GuildId); + + var channel = State.GetChannel(data.Id) as SocketVoiceChannel; + var channelCacheable = new Cacheable(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 #region Invites diff --git a/src/Discord.Net.WebSocket/Entities/AuditLogs/DataTypes/SocketVoiceChannelStatusDeleteAuditLogData.cs b/src/Discord.Net.WebSocket/Entities/AuditLogs/DataTypes/SocketVoiceChannelStatusDeleteAuditLogData.cs new file mode 100644 index 00000000..94a69848 --- /dev/null +++ b/src/Discord.Net.WebSocket/Entities/AuditLogs/DataTypes/SocketVoiceChannelStatusDeleteAuditLogData.cs @@ -0,0 +1,25 @@ +using EntryModel = Discord.API.AuditLogEntry; +using Model = Discord.API.AuditLog; + +namespace Discord.WebSocket; + +/// +/// Contains a piece of audit log data related to a voice channel status delete. +/// +public class SocketVoiceChannelStatusDeleteAuditLogData : ISocketAuditLogData +{ + private SocketVoiceChannelStatusDeleteAuditLogData(ulong channelId) + { + ChannelId = channelId; + } + + internal static SocketVoiceChannelStatusDeleteAuditLogData Create(DiscordSocketClient discord, EntryModel entry) + { + return new (entry.TargetId!.Value); + } + + /// + /// Get the id of the channel status was removed in. + /// + public ulong ChannelId { get; } +} diff --git a/src/Discord.Net.WebSocket/Entities/AuditLogs/DataTypes/SocketVoiceChannelStatusUpdateAuditLogData.cs b/src/Discord.Net.WebSocket/Entities/AuditLogs/DataTypes/SocketVoiceChannelStatusUpdateAuditLogData.cs new file mode 100644 index 00000000..dbc84d30 --- /dev/null +++ b/src/Discord.Net.WebSocket/Entities/AuditLogs/DataTypes/SocketVoiceChannelStatusUpdateAuditLogData.cs @@ -0,0 +1,31 @@ +using EntryModel = Discord.API.AuditLogEntry; +using Model = Discord.API.AuditLog; + +namespace Discord.WebSocket; + +/// +/// Contains a piece of audit log data related to a voice channel status update. +/// +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); + } + + /// + /// Gets the status that was set in the voice channel. + /// + public string Status { get; } + + /// + /// Get the id of the channel status was updated in. + /// + public ulong ChannelId { get; } +} diff --git a/src/Discord.Net.WebSocket/Entities/AuditLogs/DataTypes/SocketWebhookDeleteAuditLogData.cs b/src/Discord.Net.WebSocket/Entities/AuditLogs/DataTypes/SocketWebhookDeletedAuditLogData.cs similarity index 85% rename from src/Discord.Net.WebSocket/Entities/AuditLogs/DataTypes/SocketWebhookDeleteAuditLogData.cs rename to src/Discord.Net.WebSocket/Entities/AuditLogs/DataTypes/SocketWebhookDeletedAuditLogData.cs index d168398d..1def1ebb 100644 --- a/src/Discord.Net.WebSocket/Entities/AuditLogs/DataTypes/SocketWebhookDeleteAuditLogData.cs +++ b/src/Discord.Net.WebSocket/Entities/AuditLogs/DataTypes/SocketWebhookDeletedAuditLogData.cs @@ -8,9 +8,9 @@ namespace Discord.WebSocket; /// /// Contains a piece of audit log data related to a webhook deletion. /// -public class SocketWebhookDeleteAuditLogData : ISocketAuditLogData +public class SocketWebhookDeletedAuditLogData : ISocketAuditLogData { - private SocketWebhookDeleteAuditLogData(ulong id, Model model) + private SocketWebhookDeletedAuditLogData(ulong id, Model model) { WebhookId = id; ChannelId = model.ChannelId!.Value; @@ -19,13 +19,13 @@ public class SocketWebhookDeleteAuditLogData : ISocketAuditLogData Avatar = model.AvatarHash; } - internal static SocketWebhookDeleteAuditLogData Create(DiscordSocketClient discord, EntryModel entry) + internal static SocketWebhookDeletedAuditLogData Create(DiscordSocketClient discord, EntryModel entry) { var changes = entry.Changes; var (data, _) = AuditLogHelper.CreateAuditLogEntityInfo(changes, discord); - return new SocketWebhookDeleteAuditLogData(entry.TargetId!.Value,data); + return new SocketWebhookDeletedAuditLogData(entry.TargetId!.Value,data); } /// diff --git a/src/Discord.Net.WebSocket/Entities/AuditLogs/SocketAuditLogHelper.cs b/src/Discord.Net.WebSocket/Entities/AuditLogs/SocketAuditLogHelper.cs index e90c1cf0..867f9d01 100644 --- a/src/Discord.Net.WebSocket/Entities/AuditLogs/SocketAuditLogHelper.cs +++ b/src/Discord.Net.WebSocket/Entities/AuditLogs/SocketAuditLogHelper.cs @@ -40,7 +40,7 @@ internal static class SocketAuditLogHelper [ActionType.WebhookCreated] = SocketWebhookCreateAuditLogData.Create, [ActionType.WebhookUpdated] = SocketWebhookUpdateAuditLogData.Create, - [ActionType.WebhookDeleted] = SocketWebhookDeleteAuditLogData.Create, + [ActionType.WebhookDeleted] = SocketWebhookDeletedAuditLogData.Create, [ActionType.EmojiCreated] = SocketEmoteCreateAuditLogData.Create, [ActionType.EmojiUpdated] = SocketEmoteUpdateAuditLogData.Create, @@ -84,6 +84,10 @@ internal static class SocketAuditLogHelper [ActionType.OnboardingQuestionCreated] = SocketOnboardingPromptCreatedAuditLogData.Create, [ActionType.OnboardingQuestionUpdated] = SocketOnboardingPromptUpdatedAuditLogData.Create, [ActionType.OnboardingUpdated] = SocketOnboardingUpdatedAuditLogData.Create, + + [ActionType.VoiceChannelStatusUpdated] = SocketVoiceChannelStatusUpdatedAuditLogData.Create, + [ActionType.VoiceChannelStatusDeleted] = SocketVoiceChannelStatusDeleteAuditLogData.Create, + }; public static ISocketAuditLogData CreateData(DiscordSocketClient discord, EntryModel entry) diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketStageChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketStageChannel.cs index c7aea573..4f200f08 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketStageChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketStageChannel.cs @@ -43,6 +43,12 @@ namespace Discord.WebSocket public IReadOnlyCollection Speakers => Users.Where(x => !x.IsSuppressed).ToImmutableArray(); + /// + /// + /// This property is not supported in stage channels and will always return . + /// + public override string Status => string.Empty; + internal new SocketStageChannel Clone() => MemberwiseClone() as SocketStageChannel; internal SocketStageChannel(DiscordSocketClient discord, ulong id, SocketGuild guild) @@ -156,5 +162,13 @@ namespace Discord.WebSocket return Discord.ApiClient.ModifyUserVoiceState(Guild.Id, user.Id, args); } + + /// + /// + /// Setting voice channel status is not supported in stage channels. + /// + /// Setting voice channel status is not supported in stage channels. + public override Task SetStatusAsync(string status, RequestOptions options = null) + => throw new NotSupportedException("Setting voice channel status is not supported in stage channels."); } } diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketVoiceChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketVoiceChannel.cs index 6e3b9a77..e0439880 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketVoiceChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketVoiceChannel.cs @@ -38,6 +38,11 @@ namespace Discord.WebSocket /// public VideoQualityMode VideoQualityMode { get; private set; } + /// + /// Gets the voice channel status set in this channel. if it is not set. + /// + public virtual string Status { get; private set; } + /// /// Gets a collection of users that are currently connected to this voice channel. /// @@ -51,12 +56,19 @@ namespace Discord.WebSocket : base(discord, id, guild) { } + internal new static SocketVoiceChannel Create(SocketGuild guild, ClientState state, Model model) { var entity = new SocketVoiceChannel(guild?.Discord, model.Id, guild); entity.Update(state, model); return entity; } + + internal void UpdateVoiceStatus(string status) + { + Status = status; + } + /// 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; VideoQualityMode = model.VideoQualityMode.GetValueOrDefault(VideoQualityMode.Auto); RTCRegion = model.RTCRegion.GetValueOrDefault(null); + Status = model.Status.GetValueOrDefault(null); } + /// + public virtual Task SetStatusAsync(string status, RequestOptions options = null) + => ChannelHelper.ModifyVoiceChannelStatusAsync(this, status, Discord, options); + /// public Task ModifyAsync(Action func, RequestOptions options = null) => ChannelHelper.ModifyAsync(this, Discord, func, options); diff --git a/test/Discord.Net.Tests.Unit/ChannelPermissionsTests.cs b/test/Discord.Net.Tests.Unit/ChannelPermissionsTests.cs index bf88341a..97b5782a 100644 --- a/test/Discord.Net.Tests.Unit/ChannelPermissionsTests.cs +++ b/test/Discord.Net.Tests.Unit/ChannelPermissionsTests.cs @@ -93,6 +93,7 @@ namespace Discord AssertFlag(() => new ChannelPermissions(useSoundboard: true), ChannelPermission.UseSoundboard); AssertFlag(() => new ChannelPermissions(createEvents: true), ChannelPermission.CreateEvents); AssertFlag(() => new ChannelPermissions(sendVoiceMessages: true), ChannelPermission.SendVoiceMessages); + AssertFlag(() => new ChannelPermissions(setVoiceChannelStatus: true), ChannelPermission.SetVoiceChannelStatus); } /// @@ -158,6 +159,7 @@ namespace Discord 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.SendVoiceMessages, x => x.SendVoiceMessages, (p, enable) => p.Modify(sendVoiceMessages: enable)); + AssertUtil(ChannelPermission.SetVoiceChannelStatus, x => x.SetVoiceChannelStatus, (p, enable) => p.Modify(setVoiceChannelStatus: enable)); } /// diff --git a/test/Discord.Net.Tests.Unit/GuildPermissionsTests.cs b/test/Discord.Net.Tests.Unit/GuildPermissionsTests.cs index 73f4a692..6c58e3ad 100644 --- a/test/Discord.Net.Tests.Unit/GuildPermissionsTests.cs +++ b/test/Discord.Net.Tests.Unit/GuildPermissionsTests.cs @@ -103,6 +103,7 @@ namespace Discord AssertFlag(() => new GuildPermissions(viewMonetizationAnalytics: true), GuildPermission.ViewMonetizationAnalytics); AssertFlag(() => new GuildPermissions(useSoundboard: true), GuildPermission.UseSoundboard); AssertFlag(() => new GuildPermissions(sendVoiceMessages: true), GuildPermission.SendVoiceMessages); + AssertFlag(() => new GuildPermissions(setVoiceChannelStatus: true), GuildPermission.SetVoiceChannelStatus); } /// @@ -184,6 +185,7 @@ namespace Discord 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.SendVoiceMessages, x => x.SendVoiceMessages, (p, enable) => p.Modify(sendVoiceMessages: enable)); + AssertUtil(GuildPermission.SetVoiceChannelStatus, x => x.SetVoiceChannelStatus, (p, enable) => p.Modify(setVoiceChannelStatus: enable)); } } } diff --git a/test/Discord.Net.Tests.Unit/MockedEntities/MockedVoiceChannel.cs b/test/Discord.Net.Tests.Unit/MockedEntities/MockedVoiceChannel.cs index 2c2becf8..4e725bf1 100644 --- a/test/Discord.Net.Tests.Unit/MockedEntities/MockedVoiceChannel.cs +++ b/test/Discord.Net.Tests.Unit/MockedEntities/MockedVoiceChannel.cs @@ -75,6 +75,8 @@ namespace Discord public Task GetUserAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null) => throw new NotImplementedException(); public IAsyncEnumerable> GetUsersAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null) => throw new NotImplementedException(); public Task ModifyAsync(Action func, RequestOptions options = null) => throw new NotImplementedException(); + public Task SetStatusAsync(string status, RequestOptions options = null) => throw new NotImplementedException(); + public Task ModifyAsync(Action func, RequestOptions options = null) => throw new NotImplementedException(); public Task ModifyAsync(Action func, RequestOptions options = null) => throw new NotImplementedException(); public Task ModifyMessageAsync(ulong messageId, Action func, RequestOptions options = null) => throw new NotImplementedException();