From a4b0c4f1e3d96efb1568f08a6f696c9cf147a90c Mon Sep 17 00:00:00 2001 From: ppaneksamsung Date: Sun, 19 Dec 2021 08:42:08 +0100 Subject: [PATCH] Allow clients to send 'Gateway Voice State Update' command (#1888) * Expose SendVoiceStateUpdateAsync API to clients Fixes #1882 * Revert "Expose SendVoiceStateUpdateAsync API to clients" This reverts commit 1a11cae7 * Add IAudioChannel.ModifyAsync API * fix NRE with request options Co-authored-by: Quin Lynch <49576606+quinchs@users.noreply.github.com> Co-authored-by: quin lynch --- .../Channels/AudioChannelProperties.cs | 18 ++++++++ .../Entities/Channels/IAudioChannel.cs | 12 +++++ .../Entities/Channels/RestGroupChannel.cs | 1 + .../Entities/Channels/RestVoiceChannel.cs | 1 + .../DiscordSocketApiClient.cs | 7 ++- .../Entities/Channels/SocketGroupChannel.cs | 1 + .../Entities/Channels/SocketVoiceChannel.cs | 6 +++ .../Entities/Guilds/SocketGuild.cs | 45 ++++++++++++++++++- .../MockedEntities/MockedGroupChannel.cs | 5 +++ .../MockedEntities/MockedVoiceChannel.cs | 5 +++ 10 files changed, 98 insertions(+), 3 deletions(-) create mode 100644 src/Discord.Net.Core/Entities/Channels/AudioChannelProperties.cs diff --git a/src/Discord.Net.Core/Entities/Channels/AudioChannelProperties.cs b/src/Discord.Net.Core/Entities/Channels/AudioChannelProperties.cs new file mode 100644 index 000000000..01d436f2a --- /dev/null +++ b/src/Discord.Net.Core/Entities/Channels/AudioChannelProperties.cs @@ -0,0 +1,18 @@ +namespace Discord +{ + /// + /// Provides properties that are used to modify an with the specified changes. + /// + public class AudioChannelProperties + { + /// + /// Sets whether the user should be muted. + /// + public Optional SelfMute { get; set; } + + /// + /// Sets whether the user should be deafened. + /// + public Optional SelfDeaf { get; set; } + } +} diff --git a/src/Discord.Net.Core/Entities/Channels/IAudioChannel.cs b/src/Discord.Net.Core/Entities/Channels/IAudioChannel.cs index 179f4b03e..dfab58f07 100644 --- a/src/Discord.Net.Core/Entities/Channels/IAudioChannel.cs +++ b/src/Discord.Net.Core/Entities/Channels/IAudioChannel.cs @@ -1,4 +1,5 @@ using Discord.Audio; +using System; using System.Threading.Tasks; namespace Discord @@ -27,5 +28,16 @@ namespace Discord /// A task representing the asynchronous operation for disconnecting from the audio channel. /// Task DisconnectAsync(); + + /// + /// Modifies this audio channel. + /// + /// The properties to modify the channel with. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous modification operation. + /// + /// + Task ModifyAsync(Action func, RequestOptions options = null); } } diff --git a/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs index 267b4dc52..1240f6d67 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs @@ -211,6 +211,7 @@ namespace Discord.Rest /// Connecting to a group channel is not supported. Task IAudioChannel.ConnectAsync(bool selfDeaf, bool selfMute, bool external) { throw new NotSupportedException(); } Task IAudioChannel.DisconnectAsync() { throw new NotSupportedException(); } + Task IAudioChannel.ModifyAsync(Action func, RequestOptions options) { throw new NotSupportedException(); } #endregion #region IChannel diff --git a/src/Discord.Net.Rest/Entities/Channels/RestVoiceChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestVoiceChannel.cs index 239c00467..98ff6ac6e 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestVoiceChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestVoiceChannel.cs @@ -95,6 +95,7 @@ namespace Discord.Rest /// Connecting to a REST-based channel is not supported. Task IAudioChannel.ConnectAsync(bool selfDeaf, bool selfMute, bool external) { throw new NotSupportedException(); } Task IAudioChannel.DisconnectAsync() { throw new NotSupportedException(); } + Task IAudioChannel.ModifyAsync(Action func, RequestOptions options) { throw new NotSupportedException(); } #endregion #region IGuildChannel diff --git a/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs b/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs index 3c0f3d4a8..150da9d89 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs @@ -311,7 +311,6 @@ namespace Discord.API } public async Task SendVoiceStateUpdateAsync(ulong guildId, ulong? channelId, bool selfDeaf, bool selfMute, RequestOptions options = null) { - options = RequestOptions.CreateOrClone(options); var payload = new VoiceStateUpdateParams { GuildId = guildId, @@ -319,6 +318,12 @@ namespace Discord.API SelfDeaf = selfDeaf, SelfMute = selfMute }; + options = RequestOptions.CreateOrClone(options); + await SendGatewayAsync(GatewayOpCode.VoiceStateUpdate, payload, options: options).ConfigureAwait(false); + } + public async Task SendVoiceStateUpdateAsync(VoiceStateUpdateParams payload, RequestOptions options = null) + { + options = RequestOptions.CreateOrClone(options); await SendGatewayAsync(GatewayOpCode.VoiceStateUpdate, payload, options: options).ConfigureAwait(false); } public async Task SendGuildSyncAsync(IEnumerable guildIds, RequestOptions options = null) diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs index f1debb864..0451f97ab 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs @@ -344,6 +344,7 @@ namespace Discord.WebSocket /// Connecting to a group channel is not supported. Task IAudioChannel.ConnectAsync(bool selfDeaf, bool selfMute, bool external) { throw new NotSupportedException(); } Task IAudioChannel.DisconnectAsync() { throw new NotSupportedException(); } + Task IAudioChannel.ModifyAsync(Action func, RequestOptions options) { throw new NotSupportedException(); } #endregion #region IChannel diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketVoiceChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketVoiceChannel.cs index 08b976bfe..498862697 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketVoiceChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketVoiceChannel.cs @@ -82,6 +82,12 @@ namespace Discord.WebSocket public async Task DisconnectAsync() => await Guild.DisconnectAudioAsync(); + /// + public async Task ModifyAsync(Action func, RequestOptions options = null) + { + await Guild.ModifyAudioAsync(Id, func, options).ConfigureAwait(false); + } + /// public override SocketGuildUser GetUser(ulong id) { diff --git a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs index 697d5fe82..084a42c55 100644 --- a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs +++ b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs @@ -1,3 +1,4 @@ +using Discord.API.Gateway; using Discord.Audio; using Discord.Rest; using System; @@ -45,6 +46,7 @@ namespace Discord.WebSocket private ImmutableArray _emotes; private AudioClient _audioClient; + private VoiceStateUpdateParams _voiceStateUpdateParams; #pragma warning restore IDISP002, IDISP006 /// @@ -1593,11 +1595,19 @@ namespace Discord.WebSocket promise = new TaskCompletionSource(); _audioConnectPromise = promise; + _voiceStateUpdateParams = new VoiceStateUpdateParams + { + GuildId = Id, + ChannelId = channelId, + SelfDeaf = selfDeaf, + SelfMute = selfMute + }; + if (external) { #pragma warning disable IDISP001 var _ = promise.TrySetResultAsync(null); - await Discord.ApiClient.SendVoiceStateUpdateAsync(Id, channelId, selfDeaf, selfMute).ConfigureAwait(false); + await Discord.ApiClient.SendVoiceStateUpdateAsync(_voiceStateUpdateParams).ConfigureAwait(false); return null; #pragma warning restore IDISP001 } @@ -1632,7 +1642,7 @@ namespace Discord.WebSocket #pragma warning restore IDISP003 } - await Discord.ApiClient.SendVoiceStateUpdateAsync(Id, channelId, selfDeaf, selfMute).ConfigureAwait(false); + await Discord.ApiClient.SendVoiceStateUpdateAsync(_voiceStateUpdateParams).ConfigureAwait(false); } catch { @@ -1679,7 +1689,38 @@ namespace Discord.WebSocket await Discord.ApiClient.SendVoiceStateUpdateAsync(Id, null, false, false).ConfigureAwait(false); _audioClient?.Dispose(); _audioClient = null; + _voiceStateUpdateParams = null; } + + internal async Task ModifyAudioAsync(ulong channelId, Action func, RequestOptions options) + { + await _audioLock.WaitAsync().ConfigureAwait(false); + try + { + await ModifyAudioInternalAsync(channelId, func, options).ConfigureAwait(false); + } + finally + { + _audioLock.Release(); + } + } + + private async Task ModifyAudioInternalAsync(ulong channelId, Action func, RequestOptions options) + { + if (_voiceStateUpdateParams == null || _voiceStateUpdateParams.ChannelId != channelId) + throw new InvalidOperationException("Cannot modify properties of not connected audio channel"); + + var props = new AudioChannelProperties(); + func(props); + + if (props.SelfDeaf.IsSpecified) + _voiceStateUpdateParams.SelfDeaf = props.SelfDeaf.Value; + if (props.SelfMute.IsSpecified) + _voiceStateUpdateParams.SelfMute = props.SelfMute.Value; + + await Discord.ApiClient.SendVoiceStateUpdateAsync(_voiceStateUpdateParams, options).ConfigureAwait(false); + } + internal async Task FinishConnectAudio(string url, string token) { //TODO: Mem Leak: Disconnected/Connected handlers aren't cleaned up diff --git a/test/Discord.Net.Tests.Unit/MockedEntities/MockedGroupChannel.cs b/test/Discord.Net.Tests.Unit/MockedEntities/MockedGroupChannel.cs index 6b134d92f..3330b7bce 100644 --- a/test/Discord.Net.Tests.Unit/MockedEntities/MockedGroupChannel.cs +++ b/test/Discord.Net.Tests.Unit/MockedEntities/MockedGroupChannel.cs @@ -41,6 +41,11 @@ namespace Discord throw new NotImplementedException(); } + public Task ModifyAsync(Action func, RequestOptions options) + { + throw new NotImplementedException(); + } + public IDisposable EnterTypingState(RequestOptions options = null) { throw new NotImplementedException(); diff --git a/test/Discord.Net.Tests.Unit/MockedEntities/MockedVoiceChannel.cs b/test/Discord.Net.Tests.Unit/MockedEntities/MockedVoiceChannel.cs index 4514dfc97..e053636a9 100644 --- a/test/Discord.Net.Tests.Unit/MockedEntities/MockedVoiceChannel.cs +++ b/test/Discord.Net.Tests.Unit/MockedEntities/MockedVoiceChannel.cs @@ -64,6 +64,11 @@ namespace Discord throw new NotImplementedException(); } + public Task ModifyAsync(Action func, RequestOptions options) + { + throw new NotImplementedException(); + } + public Task GetCategoryAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null) { throw new NotImplementedException();