diff --git a/src/Discord.Net.Core/DiscordConfig.cs b/src/Discord.Net.Core/DiscordConfig.cs index 8686c84523..7386464f92 100644 --- a/src/Discord.Net.Core/DiscordConfig.cs +++ b/src/Discord.Net.Core/DiscordConfig.cs @@ -238,6 +238,11 @@ public class DiscordConfig /// 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 a7e4efaa23..3288517be0 100644 --- a/src/Discord.Net.Core/Entities/AuditLogs/ActionType.cs +++ b/src/Discord.Net.Core/Entities/AuditLogs/ActionType.cs @@ -249,6 +249,16 @@ public enum ActionType /// /// 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 77f1e9d51d..00f03d8262 100644 --- a/src/Discord.Net.Core/Entities/Channels/IVoiceChannel.cs +++ b/src/Discord.Net.Core/Entities/Channels/IVoiceChannel.cs @@ -41,5 +41,15 @@ public interface IVoiceChannel : ITextChannel, IAudioChannel /// /// 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 becbba1a19..a0ee23fa17 100644 --- a/src/Discord.Net.Core/Entities/Permissions/ChannelPermission.cs +++ b/src/Discord.Net.Core/Entities/Permissions/ChannelPermission.cs @@ -182,5 +182,10 @@ public enum ChannelPermission : ulong /// 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 ca1ddc2902..d33d5a4471 100644 --- a/src/Discord.Net.Core/Entities/Permissions/ChannelPermissions.cs +++ b/src/Discord.Net.Core/Entities/Permissions/ChannelPermissions.cs @@ -23,7 +23,7 @@ public struct ChannelPermissions /// /// 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 @@ public static ChannelPermissions All(IChannel channel) 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 @@ private ChannelPermissions(ulong initialValue, 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 @@ private ChannelPermissions(ulong initialValue, 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 @@ public ChannelPermissions( 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 @@ public ChannelPermissions Modify( 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 @@ public ChannelPermissions Modify( 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 1485e0ba8d..0646ee709e 100644 --- a/src/Discord.Net.Core/Entities/Permissions/GuildPermission.cs +++ b/src/Discord.Net.Core/Entities/Permissions/GuildPermission.cs @@ -267,5 +267,10 @@ public enum GuildPermission : ulong /// 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 c4d20948de..3b9b84e31d 100644 --- a/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs +++ b/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs @@ -110,6 +110,8 @@ public struct GuildPermissions 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 @@ private GuildPermissions(ulong initialValue, 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 @@ private GuildPermissions(ulong initialValue, 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 @@ public GuildPermissions( 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 @@ public GuildPermissions( 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 @@ public GuildPermissions Modify( 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 f91c6d3286..ea6339f2e4 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 bbfe432e58..2bc2676747 100644 --- a/src/Discord.Net.Rest/API/Common/Channel.cs +++ b/src/Discord.Net.Rest/API/Common/Channel.cs @@ -46,6 +46,9 @@ internal class Channel [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 0000000000..179e8aaf81 --- /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 4a6d62741b..f79eb83232 100644 --- a/src/Discord.Net.Rest/DiscordRestApiClient.cs +++ b/src/Discord.Net.Rest/DiscordRestApiClient.cs @@ -467,6 +467,17 @@ public async Task ModifyGuildChannelsAsync(ulong guildId, IEnumerable $"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 8bc431ed67..37121bcffa 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/AuditLogHelper.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/AuditLogHelper.cs @@ -86,6 +86,9 @@ private static readonly Dictionary +/// 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 0000000000..339a6cc9a3 --- /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 751fbfc0ae..a5dd522fde 100644 --- a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs +++ b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs @@ -636,5 +636,16 @@ public static async Task SyncPermissionsAsync(INestedChannel channel, BaseDiscor 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 47a19967ac..fe46cd808d 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestStageChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestStageChannel.cs @@ -150,5 +150,13 @@ public Task RemoveFromSpeakerAsync(IGuildUser user, RequestOptions options = nul 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 eac40538d6..3e979eaf76 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestVoiceChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestVoiceChannel.cs @@ -69,6 +69,10 @@ public async Task ModifyAsync(Action func, RequestOption 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 0000000000..757e50b9b3 --- /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 1109f4f7dd..88b601dc3b 100644 --- a/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs +++ b/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs @@ -76,6 +76,22 @@ public event Func ChannelUpdated 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 f88d92140e..d71452159d 100644 --- a/src/Discord.Net.WebSocket/DiscordShardedClient.cs +++ b/src/Discord.Net.WebSocket/DiscordShardedClient.cs @@ -519,6 +519,8 @@ private void RegisterEvents(DiscordSocketClient client, bool isPrimary) 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 d11d86a668..00aaa7d602 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -2333,6 +2333,24 @@ private async Task ProcessMessageAsync(GatewayOpCode opCode, int? seq, string ty } 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 0000000000..94a698489d --- /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 0000000000..dbc84d300a --- /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 d168398d84..1def1ebb33 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 @@ private SocketWebhookDeleteAuditLogData(ulong id, Model model) 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 e90c1cf073..867f9d017d 100644 --- a/src/Discord.Net.WebSocket/Entities/AuditLogs/SocketAuditLogHelper.cs +++ b/src/Discord.Net.WebSocket/Entities/AuditLogs/SocketAuditLogHelper.cs @@ -40,7 +40,7 @@ private static readonly Dictionary 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 @@ public Task RemoveFromSpeakerAsync(IGuildUser user, RequestOptions options = nul 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 6e3b9a77d0..e04398800e 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketVoiceChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketVoiceChannel.cs @@ -38,6 +38,11 @@ public class SocketVoiceChannel : SocketTextChannel, IVoiceChannel, ISocketAudio /// 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 @@ internal SocketVoiceChannel(DiscordSocketClient discord, ulong id, SocketGuild g : 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 @@ internal override void Update(ClientState state, Model model) 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 bf88341ad8..97b5782a4d 100644 --- a/test/Discord.Net.Tests.Unit/ChannelPermissionsTests.cs +++ b/test/Discord.Net.Tests.Unit/ChannelPermissionsTests.cs @@ -93,6 +93,7 @@ void AssertFlag(Func cstr, ChannelPermission flag) 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 @@ void AssertUtil(ChannelPermission permission, 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 73f4a69206..6c58e3ad82 100644 --- a/test/Discord.Net.Tests.Unit/GuildPermissionsTests.cs +++ b/test/Discord.Net.Tests.Unit/GuildPermissionsTests.cs @@ -103,6 +103,7 @@ void AssertFlag(Func cstr, GuildPermission flag) 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 @@ void AssertUtil(GuildPermission permission, 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 2c2becf817..4e725bf1d2 100644 --- a/test/Discord.Net.Tests.Unit/MockedEntities/MockedVoiceChannel.cs +++ b/test/Discord.Net.Tests.Unit/MockedEntities/MockedVoiceChannel.cs @@ -75,6 +75,8 @@ internal sealed class MockedVoiceChannel : IVoiceChannel 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();