* 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 <lynchquin@gmail.com>
tags/3.0.0
| @@ -0,0 +1,18 @@ | |||||
| namespace Discord | |||||
| { | |||||
| /// <summary> | |||||
| /// Provides properties that are used to modify an <see cref="IAudioChannel" /> with the specified changes. | |||||
| /// </summary> | |||||
| public class AudioChannelProperties | |||||
| { | |||||
| /// <summary> | |||||
| /// Sets whether the user should be muted. | |||||
| /// </summary> | |||||
| public Optional<bool> SelfMute { get; set; } | |||||
| /// <summary> | |||||
| /// Sets whether the user should be deafened. | |||||
| /// </summary> | |||||
| public Optional<bool> SelfDeaf { get; set; } | |||||
| } | |||||
| } | |||||
| @@ -1,4 +1,5 @@ | |||||
| using Discord.Audio; | using Discord.Audio; | ||||
| using System; | |||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
| namespace Discord | namespace Discord | ||||
| @@ -27,5 +28,16 @@ namespace Discord | |||||
| /// A task representing the asynchronous operation for disconnecting from the audio channel. | /// A task representing the asynchronous operation for disconnecting from the audio channel. | ||||
| /// </returns> | /// </returns> | ||||
| Task DisconnectAsync(); | Task DisconnectAsync(); | ||||
| /// <summary> | |||||
| /// Modifies this audio channel. | |||||
| /// </summary> | |||||
| /// <param name="func">The properties to modify the channel with.</param> | |||||
| /// <param name="options">The options to be used when sending the request.</param> | |||||
| /// <returns> | |||||
| /// A task that represents the asynchronous modification operation. | |||||
| /// </returns> | |||||
| /// <seealso cref="AudioChannelProperties"/> | |||||
| Task ModifyAsync(Action<AudioChannelProperties> func, RequestOptions options = null); | |||||
| } | } | ||||
| } | } | ||||
| @@ -211,6 +211,7 @@ namespace Discord.Rest | |||||
| /// <exception cref="NotSupportedException">Connecting to a group channel is not supported.</exception> | /// <exception cref="NotSupportedException">Connecting to a group channel is not supported.</exception> | ||||
| Task<IAudioClient> IAudioChannel.ConnectAsync(bool selfDeaf, bool selfMute, bool external) { throw new NotSupportedException(); } | Task<IAudioClient> IAudioChannel.ConnectAsync(bool selfDeaf, bool selfMute, bool external) { throw new NotSupportedException(); } | ||||
| Task IAudioChannel.DisconnectAsync() { throw new NotSupportedException(); } | Task IAudioChannel.DisconnectAsync() { throw new NotSupportedException(); } | ||||
| Task IAudioChannel.ModifyAsync(Action<AudioChannelProperties> func, RequestOptions options) { throw new NotSupportedException(); } | |||||
| #endregion | #endregion | ||||
| #region IChannel | #region IChannel | ||||
| @@ -95,6 +95,7 @@ namespace Discord.Rest | |||||
| /// <exception cref="NotSupportedException">Connecting to a REST-based channel is not supported.</exception> | /// <exception cref="NotSupportedException">Connecting to a REST-based channel is not supported.</exception> | ||||
| Task<IAudioClient> IAudioChannel.ConnectAsync(bool selfDeaf, bool selfMute, bool external) { throw new NotSupportedException(); } | Task<IAudioClient> IAudioChannel.ConnectAsync(bool selfDeaf, bool selfMute, bool external) { throw new NotSupportedException(); } | ||||
| Task IAudioChannel.DisconnectAsync() { throw new NotSupportedException(); } | Task IAudioChannel.DisconnectAsync() { throw new NotSupportedException(); } | ||||
| Task IAudioChannel.ModifyAsync(Action<AudioChannelProperties> func, RequestOptions options) { throw new NotSupportedException(); } | |||||
| #endregion | #endregion | ||||
| #region IGuildChannel | #region IGuildChannel | ||||
| @@ -311,7 +311,6 @@ namespace Discord.API | |||||
| } | } | ||||
| public async Task SendVoiceStateUpdateAsync(ulong guildId, ulong? channelId, bool selfDeaf, bool selfMute, RequestOptions options = null) | public async Task SendVoiceStateUpdateAsync(ulong guildId, ulong? channelId, bool selfDeaf, bool selfMute, RequestOptions options = null) | ||||
| { | { | ||||
| options = RequestOptions.CreateOrClone(options); | |||||
| var payload = new VoiceStateUpdateParams | var payload = new VoiceStateUpdateParams | ||||
| { | { | ||||
| GuildId = guildId, | GuildId = guildId, | ||||
| @@ -319,6 +318,12 @@ namespace Discord.API | |||||
| SelfDeaf = selfDeaf, | SelfDeaf = selfDeaf, | ||||
| SelfMute = selfMute | 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); | await SendGatewayAsync(GatewayOpCode.VoiceStateUpdate, payload, options: options).ConfigureAwait(false); | ||||
| } | } | ||||
| public async Task SendGuildSyncAsync(IEnumerable<ulong> guildIds, RequestOptions options = null) | public async Task SendGuildSyncAsync(IEnumerable<ulong> guildIds, RequestOptions options = null) | ||||
| @@ -344,6 +344,7 @@ namespace Discord.WebSocket | |||||
| /// <exception cref="NotSupportedException">Connecting to a group channel is not supported.</exception> | /// <exception cref="NotSupportedException">Connecting to a group channel is not supported.</exception> | ||||
| Task<IAudioClient> IAudioChannel.ConnectAsync(bool selfDeaf, bool selfMute, bool external) { throw new NotSupportedException(); } | Task<IAudioClient> IAudioChannel.ConnectAsync(bool selfDeaf, bool selfMute, bool external) { throw new NotSupportedException(); } | ||||
| Task IAudioChannel.DisconnectAsync() { throw new NotSupportedException(); } | Task IAudioChannel.DisconnectAsync() { throw new NotSupportedException(); } | ||||
| Task IAudioChannel.ModifyAsync(Action<AudioChannelProperties> func, RequestOptions options) { throw new NotSupportedException(); } | |||||
| #endregion | #endregion | ||||
| #region IChannel | #region IChannel | ||||
| @@ -82,6 +82,12 @@ namespace Discord.WebSocket | |||||
| public async Task DisconnectAsync() | public async Task DisconnectAsync() | ||||
| => await Guild.DisconnectAudioAsync(); | => await Guild.DisconnectAudioAsync(); | ||||
| /// <inheritdoc /> | |||||
| public async Task ModifyAsync(Action<AudioChannelProperties> func, RequestOptions options = null) | |||||
| { | |||||
| await Guild.ModifyAudioAsync(Id, func, options).ConfigureAwait(false); | |||||
| } | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public override SocketGuildUser GetUser(ulong id) | public override SocketGuildUser GetUser(ulong id) | ||||
| { | { | ||||
| @@ -1,3 +1,4 @@ | |||||
| using Discord.API.Gateway; | |||||
| using Discord.Audio; | using Discord.Audio; | ||||
| using Discord.Rest; | using Discord.Rest; | ||||
| using System; | using System; | ||||
| @@ -45,6 +46,7 @@ namespace Discord.WebSocket | |||||
| private ImmutableArray<GuildEmote> _emotes; | private ImmutableArray<GuildEmote> _emotes; | ||||
| private AudioClient _audioClient; | private AudioClient _audioClient; | ||||
| private VoiceStateUpdateParams _voiceStateUpdateParams; | |||||
| #pragma warning restore IDISP002, IDISP006 | #pragma warning restore IDISP002, IDISP006 | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| @@ -1593,11 +1595,19 @@ namespace Discord.WebSocket | |||||
| promise = new TaskCompletionSource<AudioClient>(); | promise = new TaskCompletionSource<AudioClient>(); | ||||
| _audioConnectPromise = promise; | _audioConnectPromise = promise; | ||||
| _voiceStateUpdateParams = new VoiceStateUpdateParams | |||||
| { | |||||
| GuildId = Id, | |||||
| ChannelId = channelId, | |||||
| SelfDeaf = selfDeaf, | |||||
| SelfMute = selfMute | |||||
| }; | |||||
| if (external) | if (external) | ||||
| { | { | ||||
| #pragma warning disable IDISP001 | #pragma warning disable IDISP001 | ||||
| var _ = promise.TrySetResultAsync(null); | var _ = promise.TrySetResultAsync(null); | ||||
| await Discord.ApiClient.SendVoiceStateUpdateAsync(Id, channelId, selfDeaf, selfMute).ConfigureAwait(false); | |||||
| await Discord.ApiClient.SendVoiceStateUpdateAsync(_voiceStateUpdateParams).ConfigureAwait(false); | |||||
| return null; | return null; | ||||
| #pragma warning restore IDISP001 | #pragma warning restore IDISP001 | ||||
| } | } | ||||
| @@ -1632,7 +1642,7 @@ namespace Discord.WebSocket | |||||
| #pragma warning restore IDISP003 | #pragma warning restore IDISP003 | ||||
| } | } | ||||
| await Discord.ApiClient.SendVoiceStateUpdateAsync(Id, channelId, selfDeaf, selfMute).ConfigureAwait(false); | |||||
| await Discord.ApiClient.SendVoiceStateUpdateAsync(_voiceStateUpdateParams).ConfigureAwait(false); | |||||
| } | } | ||||
| catch | catch | ||||
| { | { | ||||
| @@ -1679,7 +1689,38 @@ namespace Discord.WebSocket | |||||
| await Discord.ApiClient.SendVoiceStateUpdateAsync(Id, null, false, false).ConfigureAwait(false); | await Discord.ApiClient.SendVoiceStateUpdateAsync(Id, null, false, false).ConfigureAwait(false); | ||||
| _audioClient?.Dispose(); | _audioClient?.Dispose(); | ||||
| _audioClient = null; | _audioClient = null; | ||||
| _voiceStateUpdateParams = null; | |||||
| } | } | ||||
| internal async Task ModifyAudioAsync(ulong channelId, Action<AudioChannelProperties> 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<AudioChannelProperties> 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) | internal async Task FinishConnectAudio(string url, string token) | ||||
| { | { | ||||
| //TODO: Mem Leak: Disconnected/Connected handlers aren't cleaned up | //TODO: Mem Leak: Disconnected/Connected handlers aren't cleaned up | ||||
| @@ -41,6 +41,11 @@ namespace Discord | |||||
| throw new NotImplementedException(); | throw new NotImplementedException(); | ||||
| } | } | ||||
| public Task ModifyAsync(Action<AudioChannelProperties> func, RequestOptions options) | |||||
| { | |||||
| throw new NotImplementedException(); | |||||
| } | |||||
| public IDisposable EnterTypingState(RequestOptions options = null) | public IDisposable EnterTypingState(RequestOptions options = null) | ||||
| { | { | ||||
| throw new NotImplementedException(); | throw new NotImplementedException(); | ||||
| @@ -64,6 +64,11 @@ namespace Discord | |||||
| throw new NotImplementedException(); | throw new NotImplementedException(); | ||||
| } | } | ||||
| public Task ModifyAsync(Action<AudioChannelProperties> func, RequestOptions options) | |||||
| { | |||||
| throw new NotImplementedException(); | |||||
| } | |||||
| public Task<ICategoryChannel> GetCategoryAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null) | public Task<ICategoryChannel> GetCategoryAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null) | ||||
| { | { | ||||
| throw new NotImplementedException(); | throw new NotImplementedException(); | ||||