Untitled Diff

Created Diff never expires
9 removals
Lines
Total
Removed
Words
Total
Removed
To continue using this feature, upgrade to
Diffchecker logo
Diffchecker Pro
201 lines
19 additions
Lines
Total
Added
Words
Total
Added
To continue using this feature, upgrade to
Diffchecker logo
Diffchecker Pro
208 lines
/*
/*
* File: DiscordClientWrapper.cs
* File: DiscordShardedClientWrapper.cs
* Author: Angelo Breuer
* Author: Angelo Breuer
*
*
* The MIT License (MIT)
* The MIT License (MIT)
*
*
* Copyright (c) Angelo Breuer 2020
* Copyright (c) Angelo Breuer 2021
*
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* furnished to do so, subject to the following conditions:
*
*
* The above copyright notice and this permission notice shall be included in
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* all copies or substantial portions of the Software.
*
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
* THE SOFTWARE.
*/
*/


namespace Lavalink4NET.DSharpPlus
namespace Lavalink4NET.DSharpPlus
{
{
using System;
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using System.Linq;
using System.Threading.Tasks;
using System.Threading.Tasks;
using global::DSharpPlus;
using global::DSharpPlus;
using global::DSharpPlus.EventArgs;
using global::DSharpPlus.EventArgs;
using Lavalink4NET.Events;
using Lavalink4NET.Events;
using Newtonsoft.Json;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Linq;


/// <summary>
/// <summary>
/// A wrapper for the discord client from the "DSharpPlus" discord client library. (https://github.com/DSharpPlus/DSharpPlus)
/// A wrapper for the discord sharded client from the "DSharpPlus" discord client library. (https://github.com/DSharpPlus/DSharpPlus)
/// </summary>
/// </summary>
public sealed class DiscordClientWrapper : IDiscordClientWrapper, IDisposable
public sealed class DiscordShardedClientWrapper : IDiscordClientWrapper, IDisposable
{
{
private readonly DiscordClient _client;
private readonly DiscordShardedClient _client;
private bool _disposed;
private bool _disposed;


/// <summary>
/// <summary>
/// Initializes a new instance of the <see cref="DiscordClientWrapper"/> class.
/// Initializes a new instance of the <see cref="DiscordClientWrapper"/> class.
/// </summary>
/// </summary>
/// <param name="client">the sharded discord client</param>
/// <param name="client">the sharded discord client</param>
/// <exception cref="ArgumentNullException">
/// <exception cref="ArgumentNullException">
/// thrown if the specified <paramref name="client"/> is <see langword="null"/>.
/// thrown if the specified <paramref name="client"/> is <see langword="null"/>.
/// </exception>
/// </exception>
public DiscordClientWrapper(DiscordClient client)
public DiscordShardedClientWrapper(DiscordShardedClient client)
{
{
_client = client ?? throw new ArgumentNullException(nameof(client));
_client = client ?? throw new ArgumentNullException(nameof(client));


_client.VoiceStateUpdated += OnVoiceStateUpdated;
_client.VoiceStateUpdated += OnVoiceStateUpdated;
_client.VoiceServerUpdated += OnVoiceServerUpdated;
_client.VoiceServerUpdated += OnVoiceServerUpdated;
}
}


/// <inheritdoc/>
/// <inheritdoc/>
public event AsyncEventHandler<VoiceServer>? VoiceServerUpdated;
public event AsyncEventHandler<VoiceServer>? VoiceServerUpdated;


/// <inheritdoc/>
/// <inheritdoc/>
public event AsyncEventHandler<Events.VoiceStateUpdateEventArgs>? VoiceStateUpdated;
public event AsyncEventHandler<Events.VoiceStateUpdateEventArgs>? VoiceStateUpdated;


/// <inheritdoc/>
/// <inheritdoc/>
public ulong CurrentUserId
public ulong CurrentUserId
{
{
get
get
{
{
EnsureNotDisposed();
EnsureNotDisposed();
return _client.CurrentUser.Id;
return _client.CurrentUser.Id;
}
}
}
}


/// <inheritdoc/>
/// <inheritdoc/>
public int ShardCount
public int ShardCount
{
{
get
get
{
{
EnsureNotDisposed();
EnsureNotDisposed();
return _client.ShardCount;
return _client.ShardClients.Count;
}
}
}
}


/// <inheritdoc/>
/// <inheritdoc/>
public void Dispose()
public void Dispose()
{
{
if (_disposed)
if (_disposed)
{
{
return;
return;
}
}


_disposed = true;
_disposed = true;


_client.VoiceStateUpdated -= OnVoiceStateUpdated;
_client.VoiceStateUpdated -= OnVoiceStateUpdated;
_client.VoiceServerUpdated -= OnVoiceServerUpdated;
_client.VoiceServerUpdated -= OnVoiceServerUpdated;
}
}


/// <inheritdoc/>
/// <inheritdoc/>
/// <exception cref="ObjectDisposedException">thrown if the instance is disposed</exception>
/// <exception cref="ObjectDisposedException">thrown if the instance is disposed</exception>
public async Task<IEnumerable<ulong>> GetChannelUsersAsync(ulong guildId, ulong voiceChannelId)
public async Task<IEnumerable<ulong>> GetChannelUsersAsync(ulong guildId, ulong voiceChannelId)
{
{
EnsureNotDisposed();
EnsureNotDisposed();


var guild = await _client.GetGuildAsync(guildId)
var shard = _client.GetShard(guildId)
?? throw new InvalidOperationException("Shard is not served by this client.");

var guild = await shard.GetGuildAsync(guildId).ConfigureAwait(false)
?? throw new ArgumentException("Invalid or inaccessible guild: " + guildId, nameof(guildId));
?? throw new ArgumentException("Invalid or inaccessible guild: " + guildId, nameof(guildId));


var channel = guild.GetChannel(voiceChannelId)
var channel = guild.GetChannel(voiceChannelId)
?? throw new ArgumentException("Invalid or inaccessible voice channel: " + voiceChannelId, nameof(voiceChannelId));
?? throw new ArgumentException("Invalid or inaccessible voice channel: " + voiceChannelId, nameof(voiceChannelId));


return channel.Users.Select(s => s.Id);
return channel.Users.Select(s => s.Id);
}
}


/// <inheritdoc/>
/// <inheritdoc/>
/// <exception cref="ObjectDisposedException">thrown if the instance is disposed</exception>
/// <exception cref="ObjectDisposedException">thrown if the instance is disposed</exception>
public async Task InitializeAsync()
public async Task InitializeAsync()
{
{
EnsureNotDisposed();
EnsureNotDisposed();


var startTime = DateTimeOffset.UtcNow;
var startTime = DateTimeOffset.UtcNow;


// await until current user arrived
// await until current user arrived
while (_client.CurrentUser is null)
while (_client.CurrentUser is null)
{
{
await Task.Delay(10);
await Task.Delay(10).ConfigureAwait(false);


// timeout exceeded
// timeout exceeded
if (DateTimeOffset.UtcNow - startTime > TimeSpan.FromSeconds(10))
if (DateTimeOffset.UtcNow - startTime > TimeSpan.FromSeconds(10))
{
{
throw new TimeoutException("Waited 10 seconds for current user to arrive! Make sure you start " +
throw new TimeoutException("Waited 10 seconds for current user to arrive! Make sure you start " +
"the discord client, before initializing the discord wrapper!");
"the discord client, before initializing the discord wrapper!");
}
}
}
}
}
}


/// <inheritdoc/>
/// <inheritdoc/>
/// <exception cref="ObjectDisposedException">thrown if the instance is disposed</exception>
/// <exception cref="ObjectDisposedException">thrown if the instance is disposed</exception>
public async Task SendVoiceUpdateAsync(ulong guildId, ulong? voiceChannelId, bool selfDeaf = false, bool selfMute = false)
public async Task SendVoiceUpdateAsync(ulong guildId, ulong? voiceChannelId, bool selfDeaf = false, bool selfMute = false)
{
{
EnsureNotDisposed();
EnsureNotDisposed();


var payload = new JObject();
var payload = new JObject();
var data = new VoiceStateUpdatePayload(guildId, voiceChannelId, selfMute, selfDeaf);
var data = new VoiceStateUpdatePayload(guildId, voiceChannelId, selfMute, selfDeaf);


payload.Add("op", 4);
payload.Add("op", 4);
payload.Add("d", JObject.FromObject(data));
payload.Add("d", JObject.FromObject(data));


var message = JsonConvert.SerializeObject(payload, Formatting.None);
var message = JsonConvert.SerializeObject(payload, Formatting.None);
await _client.GetWebSocketClient().SendMessageAsync(message);

var shard = _client.GetShard(guildId)
?? throw new InvalidOperationException("Shard is not served by this client.");

await shard.GetWebSocketClient().SendMessageAsync(message).ConfigureAwait(false);
}
}


/// <summary>
/// <summary>
/// Throws an <see cref="ObjectDisposedException"/> if the <see
/// Throws an <see cref="ObjectDisposedException"/> if the <see
/// cref="DiscordClientWrapper"/> is disposed.
/// cref="DiscordClientWrapper"/> is disposed.
/// </summary>
/// </summary>
/// <exception cref="ObjectDisposedException">thrown if the instance is disposed</exception>
/// <exception cref="ObjectDisposedException">thrown if the instance is disposed</exception>
private void EnsureNotDisposed()
private void EnsureNotDisposed()
{
{
if (_disposed)
if (_disposed)
{
{
throw new ObjectDisposedException(nameof(DiscordClientWrapper));
throw new ObjectDisposedException(nameof(DiscordClientWrapper));
}
}
}
}


/// <inheritdoc/>
/// <inheritdoc/>
/// <exception cref="ObjectDisposedException">thrown if the instance is disposed</exception>
/// <exception cref="ObjectDisposedException">thrown if the instance is disposed</exception>
private Task OnVoiceServerUpdated(DiscordClient _, VoiceServerUpdateEventArgs voiceServer)
private Task OnVoiceServerUpdated(DiscordClient _, VoiceServerUpdateEventArgs voiceServer)
{
{
EnsureNotDisposed();
EnsureNotDisposed();


var args = new VoiceServer(voiceServer.Guild.Id, voiceServer.GetVoiceToken(), voiceServer.Endpoint);
var args = new VoiceServer(voiceServer.Guild.Id, voiceServer.GetVoiceToken(), voiceServer.Endpoint);
return VoiceServerUpdated.InvokeAsync(this, args);
return VoiceServerUpdated.InvokeAsync(this, args);
}
}


/// <inheritdoc/>
/// <inheritdoc/>
/// <exception cref="ObjectDisposedException">thrown if the instance is disposed</exception>
/// <exception cref="ObjectDisposedException">thrown if the instance is disposed</exception>
private Task OnVoiceStateUpdated(DiscordClient _, global::DSharpPlus.EventArgs.VoiceStateUpdateEventArgs eventArgs)
private Task OnVoiceStateUpdated(DiscordClient _, global::DSharpPlus.EventArgs.VoiceStateUpdateEventArgs eventArgs)
{
{
EnsureNotDisposed();
EnsureNotDisposed();


// session id is the same as the resume key so DSharpPlus should be able to give us the
// session id is the same as the resume key so DSharpPlus should be able to give us the
// session key in either before or after voice state
// session key in either before or after voice state
var sessionId = eventArgs.Before?.GetSessionId() ?? eventArgs.After.GetSessionId();
var sessionId = eventArgs.Before?.GetSessionId() ?? eventArgs.After.GetSessionId();


// create voice state
// create voice state
var voiceState = new VoiceState(
var voiceState = new VoiceState(
voiceChannelId: eventArgs.After?.Channel?.Id,
voiceChannelId: eventArgs.After?.Channel?.Id,
guildId: eventArgs.Guild.Id,
guildId: eventArgs.Guild.Id,
voiceSessionId: sessionId);
voiceSessionId: sessionId);


// invoke event
// invoke event
return VoiceStateUpdated.InvokeAsync(this,
return VoiceStateUpdated.InvokeAsync(this,
new Events.VoiceStateUpdateEventArgs(eventArgs.User.Id, voiceState));
new Events.VoiceStateUpdateEventArgs(eventArgs.User.Id, voiceState));
}
}
}
}
}
}