Global usings and file scoped namespaces

This commit is contained in:
Kwoth
2021-12-19 05:14:11 +01:00
parent bc31dae965
commit ee33313519
548 changed files with 47528 additions and 49115 deletions

View File

@@ -1,5 +1,4 @@
using System.Collections.Concurrent;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
@@ -7,215 +6,214 @@ using Ayu.Discord.Voice;
using Discord.WebSocket;
using NadekoBot.Services;
namespace NadekoBot.Modules.Music.Services
namespace NadekoBot.Modules.Music.Services;
public sealed class AyuVoiceStateService : INService
{
public sealed class AyuVoiceStateService : INService
// public delegate Task VoiceProxyUpdatedDelegate(ulong guildId, IVoiceProxy proxy);
// public event VoiceProxyUpdatedDelegate OnVoiceProxyUpdate = delegate { return Task.CompletedTask; };
private readonly ConcurrentDictionary<ulong, IVoiceProxy> _voiceProxies = new ConcurrentDictionary<ulong, IVoiceProxy>();
private readonly ConcurrentDictionary<ulong, SemaphoreSlim> _voiceGatewayLocks = new ConcurrentDictionary<ulong, SemaphoreSlim>();
private readonly DiscordSocketClient _client;
private readonly MethodInfo _sendVoiceStateUpdateMethodInfo;
private readonly object _dnetApiClient;
private readonly ulong _currentUserId;
public AyuVoiceStateService(DiscordSocketClient client)
{
// public delegate Task VoiceProxyUpdatedDelegate(ulong guildId, IVoiceProxy proxy);
// public event VoiceProxyUpdatedDelegate OnVoiceProxyUpdate = delegate { return Task.CompletedTask; };
private readonly ConcurrentDictionary<ulong, IVoiceProxy> _voiceProxies = new ConcurrentDictionary<ulong, IVoiceProxy>();
private readonly ConcurrentDictionary<ulong, SemaphoreSlim> _voiceGatewayLocks = new ConcurrentDictionary<ulong, SemaphoreSlim>();
private readonly DiscordSocketClient _client;
private readonly MethodInfo _sendVoiceStateUpdateMethodInfo;
private readonly object _dnetApiClient;
private readonly ulong _currentUserId;
_client = client;
_currentUserId = _client.CurrentUser.Id;
public AyuVoiceStateService(DiscordSocketClient client)
{
_client = client;
_currentUserId = _client.CurrentUser.Id;
var prop = _client.GetType()
.GetProperties(BindingFlags.NonPublic | BindingFlags.Instance)
.First(x => x.Name == "ApiClient" && x.PropertyType.Name == "DiscordSocketApiClient");
_dnetApiClient = prop.GetValue(_client, null);
_sendVoiceStateUpdateMethodInfo = _dnetApiClient.GetType().GetMethod("SendVoiceStateUpdateAsync");
var prop = _client.GetType()
.GetProperties(BindingFlags.NonPublic | BindingFlags.Instance)
.First(x => x.Name == "ApiClient" && x.PropertyType.Name == "DiscordSocketApiClient");
_dnetApiClient = prop.GetValue(_client, null);
_sendVoiceStateUpdateMethodInfo = _dnetApiClient.GetType().GetMethod("SendVoiceStateUpdateAsync");
_client.LeftGuild += ClientOnLeftGuild;
_client.LeftGuild += ClientOnLeftGuild;
}
private Task ClientOnLeftGuild(SocketGuild guild)
{
if (_voiceProxies.TryRemove(guild.Id, out var proxy))
{
proxy.StopGateway();
proxy.SetGateway(null);
}
private Task ClientOnLeftGuild(SocketGuild guild)
return Task.CompletedTask;
}
private Task InvokeSendVoiceStateUpdateAsync(ulong guildId, ulong? channelId = null, bool isDeafened = false, bool isMuted = false)
{
// return _voiceStateUpdate(guildId, channelId, isDeafened, isMuted);
return (Task) _sendVoiceStateUpdateMethodInfo.Invoke(_dnetApiClient, new object[] {guildId, channelId, isMuted, isDeafened, null});
}
private Task SendLeaveVoiceChannelInternalAsync(ulong guildId)
=> InvokeSendVoiceStateUpdateAsync(guildId);
private Task SendJoinVoiceChannelInternalAsync(ulong guildId, ulong channelId)
=> InvokeSendVoiceStateUpdateAsync(guildId, channelId);
private SemaphoreSlim GetVoiceGatewayLock(ulong guildId) => _voiceGatewayLocks.GetOrAdd(guildId, new SemaphoreSlim(1, 1));
private async Task LeaveVoiceChannelInternalAsync(ulong guildId)
{
var complete = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
Task OnUserVoiceStateUpdated(SocketUser user, SocketVoiceState oldState, SocketVoiceState newState)
{
if (_voiceProxies.TryRemove(guild.Id, out var proxy))
if (user is SocketGuildUser guildUser
&& guildUser.Guild.Id == guildId
&& newState.VoiceChannel?.Id is null)
{
proxy.StopGateway();
proxy.SetGateway(null);
complete.TrySetResult(true);
}
return Task.CompletedTask;
}
private Task InvokeSendVoiceStateUpdateAsync(ulong guildId, ulong? channelId = null, bool isDeafened = false, bool isMuted = false)
try
{
// return _voiceStateUpdate(guildId, channelId, isDeafened, isMuted);
return (Task) _sendVoiceStateUpdateMethodInfo.Invoke(_dnetApiClient, new object[] {guildId, channelId, isMuted, isDeafened, null});
_client.UserVoiceStateUpdated += OnUserVoiceStateUpdated;
if (_voiceProxies.TryGetValue(guildId, out var proxy))
{
_ = proxy.StopGateway();
proxy.SetGateway(null);
}
await SendLeaveVoiceChannelInternalAsync(guildId);
await Task.WhenAny(Task.Delay(1500), complete.Task);
}
finally
{
_client.UserVoiceStateUpdated -= OnUserVoiceStateUpdated;
}
}
public async Task LeaveVoiceChannel(ulong guildId)
{
var gwLock = GetVoiceGatewayLock(guildId);
await gwLock.WaitAsync().ConfigureAwait(false);
try
{
await LeaveVoiceChannelInternalAsync(guildId);
}
finally
{
gwLock.Release();
}
}
private async Task<IVoiceProxy> InternalConnectToVcAsync(ulong guildId, ulong channelId)
{
var voiceStateUpdatedSource = new TaskCompletionSource<string>(TaskCreationOptions.RunContinuationsAsynchronously);
var voiceServerUpdatedSource = new TaskCompletionSource<SocketVoiceServer>(TaskCreationOptions.RunContinuationsAsynchronously);
Task OnUserVoiceStateUpdated(SocketUser user, SocketVoiceState oldState, SocketVoiceState newState)
{
if (user is SocketGuildUser guildUser && guildUser.Guild.Id == guildId)
{
if (newState.VoiceChannel?.Id == channelId)
voiceStateUpdatedSource.TrySetResult(newState.VoiceSessionId);
voiceStateUpdatedSource.TrySetResult(null);
}
return Task.CompletedTask;
}
private Task SendLeaveVoiceChannelInternalAsync(ulong guildId)
=> InvokeSendVoiceStateUpdateAsync(guildId);
private Task SendJoinVoiceChannelInternalAsync(ulong guildId, ulong channelId)
=> InvokeSendVoiceStateUpdateAsync(guildId, channelId);
private SemaphoreSlim GetVoiceGatewayLock(ulong guildId) => _voiceGatewayLocks.GetOrAdd(guildId, new SemaphoreSlim(1, 1));
private async Task LeaveVoiceChannelInternalAsync(ulong guildId)
Task OnVoiceServerUpdated(SocketVoiceServer data)
{
var complete = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
Task OnUserVoiceStateUpdated(SocketUser user, SocketVoiceState oldState, SocketVoiceState newState)
if (data.Guild.Id == guildId)
{
if (user is SocketGuildUser guildUser
&& guildUser.Guild.Id == guildId
&& newState.VoiceChannel?.Id is null)
{
complete.TrySetResult(true);
}
return Task.CompletedTask;
voiceServerUpdatedSource.TrySetResult(data);
}
try
{
_client.UserVoiceStateUpdated += OnUserVoiceStateUpdated;
if (_voiceProxies.TryGetValue(guildId, out var proxy))
{
_ = proxy.StopGateway();
proxy.SetGateway(null);
}
await SendLeaveVoiceChannelInternalAsync(guildId);
await Task.WhenAny(Task.Delay(1500), complete.Task);
}
finally
{
_client.UserVoiceStateUpdated -= OnUserVoiceStateUpdated;
}
return Task.CompletedTask;
}
public async Task LeaveVoiceChannel(ulong guildId)
{
var gwLock = GetVoiceGatewayLock(guildId);
await gwLock.WaitAsync().ConfigureAwait(false);
try
{
await LeaveVoiceChannelInternalAsync(guildId);
}
finally
{
gwLock.Release();
}
}
private async Task<IVoiceProxy> InternalConnectToVcAsync(ulong guildId, ulong channelId)
{
var voiceStateUpdatedSource = new TaskCompletionSource<string>(TaskCreationOptions.RunContinuationsAsynchronously);
var voiceServerUpdatedSource = new TaskCompletionSource<SocketVoiceServer>(TaskCreationOptions.RunContinuationsAsynchronously);
Task OnUserVoiceStateUpdated(SocketUser user, SocketVoiceState oldState, SocketVoiceState newState)
{
if (user is SocketGuildUser guildUser && guildUser.Guild.Id == guildId)
{
if (newState.VoiceChannel?.Id == channelId)
voiceStateUpdatedSource.TrySetResult(newState.VoiceSessionId);
voiceStateUpdatedSource.TrySetResult(null);
}
return Task.CompletedTask;
}
Task OnVoiceServerUpdated(SocketVoiceServer data)
{
if (data.Guild.Id == guildId)
{
voiceServerUpdatedSource.TrySetResult(data);
}
return Task.CompletedTask;
}
try
try
{
_client.VoiceServerUpdated += OnVoiceServerUpdated;
_client.UserVoiceStateUpdated += OnUserVoiceStateUpdated;
await SendJoinVoiceChannelInternalAsync(guildId, channelId);
// create a delay task, how much to wait for gateway response
var delayTask = Task.Delay(2500);
// either delay or successful voiceStateUpdate
var maybeUpdateTask = Task.WhenAny(delayTask, voiceStateUpdatedSource.Task);
// either delay or successful voiceServerUpdate
var maybeServerTask = Task.WhenAny(delayTask, voiceServerUpdatedSource.Task);
// wait for both to end (max 1s) and check if either of them is a delay task
var results = await Task.WhenAll(maybeUpdateTask, maybeServerTask);
if (results[0] == delayTask || results[1] == delayTask)
{
_client.VoiceServerUpdated += OnVoiceServerUpdated;
_client.UserVoiceStateUpdated += OnUserVoiceStateUpdated;
// if either is delay, return null - connection unsuccessful
return null;
}
await SendJoinVoiceChannelInternalAsync(guildId, channelId);
// if both are succesful, that means we can safely get
// the values from completion sources
// create a delay task, how much to wait for gateway response
var delayTask = Task.Delay(2500);
var session = await voiceStateUpdatedSource.Task;
// either delay or successful voiceStateUpdate
var maybeUpdateTask = Task.WhenAny(delayTask, voiceStateUpdatedSource.Task);
// either delay or successful voiceServerUpdate
var maybeServerTask = Task.WhenAny(delayTask, voiceServerUpdatedSource.Task);
// session can be null. Means we disconnected, or connected to the wrong channel (?!)
if (session is null)
return null;
// wait for both to end (max 1s) and check if either of them is a delay task
var results = await Task.WhenAll(maybeUpdateTask, maybeServerTask);
if (results[0] == delayTask || results[1] == delayTask)
{
// if either is delay, return null - connection unsuccessful
return null;
}
// if both are succesful, that means we can safely get
// the values from completion sources
var session = await voiceStateUpdatedSource.Task;
// session can be null. Means we disconnected, or connected to the wrong channel (?!)
if (session is null)
return null;
var voiceServerData = await voiceServerUpdatedSource.Task;
var voiceServerData = await voiceServerUpdatedSource.Task;
VoiceGateway CreateVoiceGatewayLocal() =>
new VoiceGateway(
guildId,
_currentUserId,
session,
voiceServerData.Token,
voiceServerData.Endpoint
);
var current = _voiceProxies.AddOrUpdate(
VoiceGateway CreateVoiceGatewayLocal() =>
new VoiceGateway(
guildId,
(gid) => new VoiceProxy(CreateVoiceGatewayLocal()),
(gid, currentProxy) =>
{
_ = currentProxy.StopGateway();
currentProxy.SetGateway(CreateVoiceGatewayLocal());
return currentProxy;
}
_currentUserId,
session,
voiceServerData.Token,
voiceServerData.Endpoint
);
_ = current.StartGateway(); // don't await, this blocks until gateway is closed
return current;
}
finally
{
_client.VoiceServerUpdated -= OnVoiceServerUpdated;
_client.UserVoiceStateUpdated -= OnUserVoiceStateUpdated;
}
}
var current = _voiceProxies.AddOrUpdate(
guildId,
(gid) => new VoiceProxy(CreateVoiceGatewayLocal()),
(gid, currentProxy) =>
{
_ = currentProxy.StopGateway();
currentProxy.SetGateway(CreateVoiceGatewayLocal());
return currentProxy;
}
);
public async Task<IVoiceProxy> JoinVoiceChannel(ulong guildId, ulong channelId, bool forceReconnect = true)
_ = current.StartGateway(); // don't await, this blocks until gateway is closed
return current;
}
finally
{
var gwLock = GetVoiceGatewayLock(guildId);
await gwLock.WaitAsync().ConfigureAwait(false);
try
{
await LeaveVoiceChannelInternalAsync(guildId);
return await InternalConnectToVcAsync(guildId, channelId);
}
finally
{
gwLock.Release();
}
_client.VoiceServerUpdated -= OnVoiceServerUpdated;
_client.UserVoiceStateUpdated -= OnUserVoiceStateUpdated;
}
public bool TryGetProxy(ulong guildId, out IVoiceProxy proxy)
=> _voiceProxies.TryGetValue(guildId, out proxy);
}
public async Task<IVoiceProxy> JoinVoiceChannel(ulong guildId, ulong channelId, bool forceReconnect = true)
{
var gwLock = GetVoiceGatewayLock(guildId);
await gwLock.WaitAsync().ConfigureAwait(false);
try
{
await LeaveVoiceChannelInternalAsync(guildId);
return await InternalConnectToVcAsync(guildId, channelId);
}
finally
{
gwLock.Release();
}
}
public bool TryGetProxy(ulong guildId, out IVoiceProxy proxy)
=> _voiceProxies.TryGetValue(guildId, out proxy);
}