From 53397978e0cf880f8fd6191b4cfb7b1962426806 Mon Sep 17 00:00:00 2001 From: Pawel Panek Date: Wed, 7 Jul 2021 14:04:33 +0200 Subject: [PATCH] Add IAudioChannel.ModifyAsync API --- .../Channels/AudioChannelProperties.cs | 18 ++++++++ .../Entities/Channels/IAudioChannel.cs | 12 +++++ .../Entities/Channels/RestGroupChannel.cs | 1 + .../Entities/Channels/RestVoiceChannel.cs | 1 + .../DiscordSocketApiClient.cs | 6 ++- .../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, 97 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 2b0ab8b42..89665eefe 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs @@ -196,6 +196,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(); } //IChannel Task IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) diff --git a/src/Discord.Net.Rest/Entities/Channels/RestVoiceChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestVoiceChannel.cs index 3f3aa96c6..9d62938a8 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestVoiceChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestVoiceChannel.cs @@ -76,6 +76,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(); } //IGuildChannel /// diff --git a/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs b/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs index 65fd23d3f..8379e1caf 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs @@ -284,7 +284,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, @@ -294,6 +293,11 @@ namespace Discord.API }; 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) { options = RequestOptions.CreateOrClone(options); diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs index ab8c76aeb..fa274cf8b 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs @@ -311,6 +311,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(); } //IChannel /// diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketVoiceChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketVoiceChannel.cs index bf4a63c9f..27820372f 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketVoiceChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketVoiceChannel.cs @@ -76,6 +76,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 9af4ad57e..0153ad833 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; @@ -39,6 +40,7 @@ namespace Discord.WebSocket private ImmutableArray _emotes; private ImmutableArray _features; private AudioClient _audioClient; + private VoiceStateUpdateParams _voiceStateUpdateParams; #pragma warning restore IDISP002, IDISP006 /// @@ -1054,11 +1056,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 } @@ -1093,7 +1103,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 { @@ -1140,7 +1150,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 arent cleaned up diff --git a/test/Discord.Net.Tests.Unit/MockedEntities/MockedGroupChannel.cs b/test/Discord.Net.Tests.Unit/MockedEntities/MockedGroupChannel.cs index 6daf6a9c8..007dd0409 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 eb617125d..55066a82b 100644 --- a/test/Discord.Net.Tests.Unit/MockedEntities/MockedVoiceChannel.cs +++ b/test/Discord.Net.Tests.Unit/MockedEntities/MockedVoiceChannel.cs @@ -58,6 +58,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();