diff --git a/src/Discord.Net.Commands/CommandService.cs b/src/Discord.Net.Commands/CommandService.cs index cb4bea564d..dbf2535288 100644 --- a/src/Discord.Net.Commands/CommandService.cs +++ b/src/Discord.Net.Commands/CommandService.cs @@ -1,3 +1,4 @@ +using AsyncKeyedLock; using Discord.Commands.Builders; using Discord.Logging; using System; @@ -6,7 +7,6 @@ using System.Collections.Immutable; using System.Linq; using System.Reflection; -using System.Threading; using System.Threading.Tasks; namespace Discord.Commands @@ -46,7 +46,7 @@ public class CommandService : IDisposable public event Func, ICommandContext, IResult, Task> CommandExecuted { add { _commandExecutedEvent.Add(value); } remove { _commandExecutedEvent.Remove(value); } } internal readonly AsyncEvent, ICommandContext, IResult, Task>> _commandExecutedEvent = new AsyncEvent, ICommandContext, IResult, Task>>(); - private readonly SemaphoreSlim _moduleLock; + private readonly AsyncNonKeyedLocker _moduleLock; private readonly ConcurrentDictionary _typedModuleDefs; private readonly ConcurrentDictionary> _typeReaders; private readonly ConcurrentDictionary _defaultTypeReaders; @@ -105,7 +105,7 @@ public CommandService(CommandServiceConfig config) _logManager.Message += async msg => await _logEvent.InvokeAsync(msg).ConfigureAwait(false); _cmdLogger = _logManager.CreateLogger("Command"); - _moduleLock = new SemaphoreSlim(1, 1); + _moduleLock = new(); _typedModuleDefs = new ConcurrentDictionary(); _moduleDefs = new HashSet(); _map = new CommandMap(this); @@ -137,20 +137,13 @@ public CommandService(CommandServiceConfig config) #region Modules public async Task CreateModuleAsync(string primaryAlias, Action buildFunc) { - await _moduleLock.WaitAsync().ConfigureAwait(false); - try - { - var builder = new ModuleBuilder(this, null, primaryAlias); - buildFunc(builder); + using var _ = await _moduleLock.LockAsync().ConfigureAwait(false); + var builder = new ModuleBuilder(this, null, primaryAlias); + buildFunc(builder); - var module = builder.Build(this, null); + var module = builder.Build(this, null); - return LoadModuleInternal(module); - } - finally - { - _moduleLock.Release(); - } + return LoadModuleInternal(module); } /// @@ -191,27 +184,20 @@ public async Task AddModuleAsync(Type type, IServiceProvider service { services ??= EmptyServiceProvider.Instance; - await _moduleLock.WaitAsync().ConfigureAwait(false); - try - { - var typeInfo = type.GetTypeInfo(); + using var _ = await _moduleLock.LockAsync().ConfigureAwait(false); + var typeInfo = type.GetTypeInfo(); - if (_typedModuleDefs.ContainsKey(type)) - throw new ArgumentException("This module has already been added."); + if (_typedModuleDefs.ContainsKey(type)) + throw new ArgumentException("This module has already been added."); - var module = (await ModuleClassBuilder.BuildAsync(this, services, typeInfo).ConfigureAwait(false)).FirstOrDefault(); + var module = (await ModuleClassBuilder.BuildAsync(this, services, typeInfo).ConfigureAwait(false)).FirstOrDefault(); - if (module.Value == default(ModuleInfo)) - throw new InvalidOperationException($"Could not build the module {type.FullName}, did you pass an invalid type?"); + if (module.Value == default(ModuleInfo)) + throw new InvalidOperationException($"Could not build the module {type.FullName}, did you pass an invalid type?"); - _typedModuleDefs[module.Key] = module.Value; + _typedModuleDefs[module.Key] = module.Value; - return LoadModuleInternal(module.Value); - } - finally - { - _moduleLock.Release(); - } + return LoadModuleInternal(module.Value); } /// /// Add command modules from an . @@ -226,24 +212,17 @@ public async Task> AddModulesAsync(Assembly assembly, IS { services ??= EmptyServiceProvider.Instance; - await _moduleLock.WaitAsync().ConfigureAwait(false); - try - { - var types = await ModuleClassBuilder.SearchAsync(assembly, this).ConfigureAwait(false); - var moduleDefs = await ModuleClassBuilder.BuildAsync(types, this, services).ConfigureAwait(false); - - foreach (var info in moduleDefs) - { - _typedModuleDefs[info.Key] = info.Value; - LoadModuleInternal(info.Value); - } + using var _ = await _moduleLock.LockAsync().ConfigureAwait(false); + var types = await ModuleClassBuilder.SearchAsync(assembly, this).ConfigureAwait(false); + var moduleDefs = await ModuleClassBuilder.BuildAsync(types, this, services).ConfigureAwait(false); - return moduleDefs.Select(x => x.Value).ToImmutableArray(); - } - finally + foreach (var info in moduleDefs) { - _moduleLock.Release(); + _typedModuleDefs[info.Key] = info.Value; + LoadModuleInternal(info.Value); } + + return moduleDefs.Select(x => x.Value).ToImmutableArray(); } private ModuleInfo LoadModuleInternal(ModuleInfo module) { @@ -267,20 +246,13 @@ private ModuleInfo LoadModuleInternal(ModuleInfo module) /// public async Task RemoveModuleAsync(ModuleInfo module) { - await _moduleLock.WaitAsync().ConfigureAwait(false); - try - { - var typeModulePair = _typedModuleDefs.FirstOrDefault(x => x.Value.Equals(module)); + using var _ = await _moduleLock.LockAsync().ConfigureAwait(false); + var typeModulePair = _typedModuleDefs.FirstOrDefault(x => x.Value.Equals(module)); - if (!typeModulePair.Equals(default(KeyValuePair))) - _typedModuleDefs.TryRemove(typeModulePair.Key, out var _); + if (!typeModulePair.Equals(default(KeyValuePair))) + _typedModuleDefs.TryRemove(typeModulePair.Key, out var _); - return RemoveModuleInternal(module); - } - finally - { - _moduleLock.Release(); - } + return RemoveModuleInternal(module); } /// /// Removes the command module. @@ -301,18 +273,11 @@ public async Task RemoveModuleAsync(ModuleInfo module) /// public async Task RemoveModuleAsync(Type type) { - await _moduleLock.WaitAsync().ConfigureAwait(false); - try - { - if (!_typedModuleDefs.TryRemove(type, out var module)) - return false; + using var _ = await _moduleLock.LockAsync().ConfigureAwait(false); + if (!_typedModuleDefs.TryRemove(type, out var module)) + return false; - return RemoveModuleInternal(module); - } - finally - { - _moduleLock.Release(); - } + return RemoveModuleInternal(module); } private bool RemoveModuleInternal(ModuleInfo module) { diff --git a/src/Discord.Net.Core/Discord.Net.Core.csproj b/src/Discord.Net.Core/Discord.Net.Core.csproj index 56b9e76e3a..9a07387b4b 100644 --- a/src/Discord.Net.Core/Discord.Net.Core.csproj +++ b/src/Discord.Net.Core/Discord.Net.Core.csproj @@ -14,6 +14,7 @@ snupkg + diff --git a/src/Discord.Net.Interactions/InteractionService.cs b/src/Discord.Net.Interactions/InteractionService.cs index d8f3bffca5..1a94226715 100644 --- a/src/Discord.Net.Interactions/InteractionService.cs +++ b/src/Discord.Net.Interactions/InteractionService.cs @@ -1,3 +1,4 @@ +using AsyncKeyedLock; using Discord.Interactions.Builders; using Discord.Logging; using Discord.Rest; @@ -100,7 +101,7 @@ public event Func InteractionE private readonly TypeMap _modalInputTypeConverterMap; private readonly ConcurrentDictionary _autocompleteHandlers = new(); private readonly ConcurrentDictionary _modalInfos = new(); - private readonly SemaphoreSlim _lock; + private readonly AsyncNonKeyedLocker _lock; internal readonly Logger _cmdLogger; internal readonly LogManager _logManager; internal readonly Func _getRestClient; @@ -165,7 +166,7 @@ private InteractionService(Func getRestClient, InteractionSer { config ??= new InteractionServiceConfig(); - _lock = new SemaphoreSlim(1, 1); + _lock = new(); _typedModuleDefs = new ConcurrentDictionary(); _moduleDefs = new HashSet(); @@ -258,21 +259,14 @@ public async Task CreateModuleAsync(string name, IServiceProvider se { services ??= EmptyServiceProvider.Instance; - await _lock.WaitAsync().ConfigureAwait(false); - try - { - var builder = new ModuleBuilder(this, name); - buildFunc(builder); + using var _ = await _lock.LockAsync().ConfigureAwait(false); + var builder = new ModuleBuilder(this, name); + buildFunc(builder); - var moduleInfo = builder.Build(this, services); - LoadModuleInternal(moduleInfo); + var moduleInfo = builder.Build(this, services); + LoadModuleInternal(moduleInfo); - return moduleInfo; - } - finally - { - _lock.Release(); - } + return moduleInfo; } /// @@ -287,24 +281,16 @@ public async Task> AddModulesAsync(Assembly assembly, IS { services ??= EmptyServiceProvider.Instance; - await _lock.WaitAsync().ConfigureAwait(false); + using var _ = await _lock.LockAsync().ConfigureAwait(false); + var types = await ModuleClassBuilder.SearchAsync(assembly, this); + var moduleDefs = await ModuleClassBuilder.BuildAsync(types, this, services); - try + foreach (var info in moduleDefs) { - var types = await ModuleClassBuilder.SearchAsync(assembly, this); - var moduleDefs = await ModuleClassBuilder.BuildAsync(types, this, services); - - foreach (var info in moduleDefs) - { - _typedModuleDefs[info.Key] = info.Value; - LoadModuleInternal(info.Value); - } - return moduleDefs.Values; - } - finally - { - _lock.Release(); + _typedModuleDefs[info.Key] = info.Value; + LoadModuleInternal(info.Value); } + return moduleDefs.Values; } /// @@ -345,32 +331,24 @@ public async Task AddModuleAsync(Type type, IServiceProvider service services ??= EmptyServiceProvider.Instance; - await _lock.WaitAsync().ConfigureAwait(false); - - try - { - var typeInfo = type.GetTypeInfo(); + using var _ = await _lock.LockAsync().ConfigureAwait(false); + var typeInfo = type.GetTypeInfo(); - if (_typedModuleDefs.ContainsKey(typeInfo)) - throw new ArgumentException("Module definition for this type already exists."); + if (_typedModuleDefs.ContainsKey(typeInfo)) + throw new ArgumentException("Module definition for this type already exists."); - var moduleDef = (await ModuleClassBuilder.BuildAsync(new List { typeInfo }, this, services).ConfigureAwait(false)).FirstOrDefault(); + var moduleDef = (await ModuleClassBuilder.BuildAsync(new List { typeInfo }, this, services).ConfigureAwait(false)).FirstOrDefault(); - if (moduleDef.Value == default) - throw new InvalidOperationException($"Could not build the module {typeInfo.FullName}, did you pass an invalid type?"); + if (moduleDef.Value == default) + throw new InvalidOperationException($"Could not build the module {typeInfo.FullName}, did you pass an invalid type?"); - if (!_typedModuleDefs.TryAdd(type, moduleDef.Value)) - throw new ArgumentException("Module definition for this type already exists."); + if (!_typedModuleDefs.TryAdd(type, moduleDef.Value)) + throw new ArgumentException("Module definition for this type already exists."); - _typedModuleDefs[moduleDef.Key] = moduleDef.Value; - LoadModuleInternal(moduleDef.Value); + _typedModuleDefs[moduleDef.Key] = moduleDef.Value; + LoadModuleInternal(moduleDef.Value); - return moduleDef.Value; - } - finally - { - _lock.Release(); - } + return moduleDef.Value; } /// @@ -641,19 +619,11 @@ public Task RemoveModuleAsync() => /// public async Task RemoveModuleAsync(Type type) { - await _lock.WaitAsync().ConfigureAwait(false); - - try - { - if (!_typedModuleDefs.TryRemove(type, out var module)) - return false; + using var _ = await _lock.LockAsync().ConfigureAwait(false); + if (!_typedModuleDefs.TryRemove(type, out var module)) + return false; - return RemoveModuleInternal(module); - } - finally - { - _lock.Release(); - } + return RemoveModuleInternal(module); } /// @@ -666,21 +636,13 @@ public async Task RemoveModuleAsync(Type type) /// public async Task RemoveModuleAsync(ModuleInfo module) { - await _lock.WaitAsync().ConfigureAwait(false); - - try - { - var typeModulePair = _typedModuleDefs.FirstOrDefault(x => x.Value.Equals(module)); + using var _ = await _lock.LockAsync().ConfigureAwait(false); + var typeModulePair = _typedModuleDefs.FirstOrDefault(x => x.Value.Equals(module)); - if (!typeModulePair.Equals(default(KeyValuePair))) - _typedModuleDefs.TryRemove(typeModulePair.Key, out var _); + if (!typeModulePair.Equals(default(KeyValuePair))) + _typedModuleDefs.TryRemove(typeModulePair.Key, out var _); - return RemoveModuleInternal(module); - } - finally - { - _lock.Release(); - } + return RemoveModuleInternal(module); } /// diff --git a/src/Discord.Net.Rest/BaseDiscordClient.cs b/src/Discord.Net.Rest/BaseDiscordClient.cs index a242dc77ea..d1a6986eda 100644 --- a/src/Discord.Net.Rest/BaseDiscordClient.cs +++ b/src/Discord.Net.Rest/BaseDiscordClient.cs @@ -1,3 +1,4 @@ +using AsyncKeyedLock; using Discord.Logging; using System; using System.Collections.Generic; @@ -28,7 +29,7 @@ public abstract class BaseDiscordClient : IDiscordClient public event Func SentRequest { add { _sentRequest.Add(value); } remove { _sentRequest.Remove(value); } } internal readonly Logger _restLogger; - private readonly SemaphoreSlim _stateLock; + private readonly AsyncNonKeyedLocker _stateLock; private bool _isFirstLogin, _isDisposed; internal API.DiscordRestApiClient ApiClient { get; } @@ -54,7 +55,7 @@ internal BaseDiscordClient(DiscordRestConfig config, API.DiscordRestApiClient cl LogManager = new LogManager(config.LogLevel); LogManager.Message += async msg => await _logEvent.InvokeAsync(msg).ConfigureAwait(false); - _stateLock = new SemaphoreSlim(1, 1); + _stateLock = new(); _restLogger = LogManager.CreateLogger("Rest"); _isFirstLogin = config.DisplayInitialLog; @@ -75,12 +76,8 @@ internal BaseDiscordClient(DiscordRestConfig config, API.DiscordRestApiClient cl public async Task LoginAsync(TokenType tokenType, string token, bool validateToken = true) { - await _stateLock.WaitAsync().ConfigureAwait(false); - try - { - await LoginInternalAsync(tokenType, token, validateToken).ConfigureAwait(false); - } - finally { _stateLock.Release(); } + using var _ = await _stateLock.LockAsync().ConfigureAwait(false); + await LoginInternalAsync(tokenType, token, validateToken).ConfigureAwait(false); } internal virtual async Task LoginInternalAsync(TokenType tokenType, string token, bool validateToken) { @@ -128,12 +125,8 @@ internal virtual Task OnLoginAsync(TokenType tokenType, string token) public async Task LogoutAsync() { - await _stateLock.WaitAsync().ConfigureAwait(false); - try - { - await LogoutInternalAsync().ConfigureAwait(false); - } - finally { _stateLock.Release(); } + using var _ = await _stateLock.LockAsync().ConfigureAwait(false); + await LogoutInternalAsync().ConfigureAwait(false); } internal virtual async Task LogoutInternalAsync() { diff --git a/src/Discord.Net.Rest/DiscordRestApiClient.cs b/src/Discord.Net.Rest/DiscordRestApiClient.cs index a89ca6489d..d2ad096276 100644 --- a/src/Discord.Net.Rest/DiscordRestApiClient.cs +++ b/src/Discord.Net.Rest/DiscordRestApiClient.cs @@ -1,3 +1,4 @@ +using AsyncKeyedLock; using Discord.API.Rest; using Discord.Net; using Discord.Net.Converters; @@ -34,7 +35,7 @@ internal class DiscordRestApiClient : IDisposable, IAsyncDisposable private readonly AsyncEvent> _sentRequestEvent = new AsyncEvent>(); protected readonly JsonSerializer _serializer; - protected readonly SemaphoreSlim _stateLock; + protected readonly AsyncNonKeyedLocker _stateLock; private readonly RestClientProvider _restClientProvider; protected bool _isDisposed; @@ -66,7 +67,7 @@ public DiscordRestApiClient(RestClientProvider restClientProvider, string userAg DefaultRatelimitCallback = defaultRatelimitCallback; RequestQueue = new RequestQueue(); - _stateLock = new SemaphoreSlim(1, 1); + _stateLock = new(); SetBaseUrl(DiscordConfig.APIUrl); } @@ -129,12 +130,8 @@ internal virtual async ValueTask DisposeAsync(bool disposing) public async Task LoginAsync(TokenType tokenType, string token, RequestOptions options = null) { - await _stateLock.WaitAsync().ConfigureAwait(false); - try - { - await LoginInternalAsync(tokenType, token, options).ConfigureAwait(false); - } - finally { _stateLock.Release(); } + using var _ = await _stateLock.LockAsync().ConfigureAwait(false); + await LoginInternalAsync(tokenType, token, options).ConfigureAwait(false); } private async Task LoginInternalAsync(TokenType tokenType, string token, RequestOptions options = null) { @@ -167,12 +164,8 @@ private async Task LoginInternalAsync(TokenType tokenType, string token, Request public async Task LogoutAsync() { - await _stateLock.WaitAsync().ConfigureAwait(false); - try - { - await LogoutInternalAsync().ConfigureAwait(false); - } - finally { _stateLock.Release(); } + using var _ = await _stateLock.LockAsync().ConfigureAwait(false); + await LogoutInternalAsync().ConfigureAwait(false); } private async Task LogoutInternalAsync() { diff --git a/src/Discord.Net.Rest/Net/Queue/RequestQueue.cs b/src/Discord.Net.Rest/Net/Queue/RequestQueue.cs index 3da8b3dc04..c41eeb1aa6 100644 --- a/src/Discord.Net.Rest/Net/Queue/RequestQueue.cs +++ b/src/Discord.Net.Rest/Net/Queue/RequestQueue.cs @@ -1,3 +1,4 @@ +using AsyncKeyedLock; using Newtonsoft.Json.Bson; using System; using System.Collections.Concurrent; @@ -16,7 +17,7 @@ internal class RequestQueue : IDisposable, IAsyncDisposable public event Func RateLimitTriggered; private readonly ConcurrentDictionary _buckets; - private readonly SemaphoreSlim _tokenLock; + private readonly AsyncNonKeyedLocker _tokenLock; private readonly CancellationTokenSource _cancelTokenSource; //Dispose token private CancellationTokenSource _clearToken; private CancellationToken _parentToken; @@ -28,7 +29,7 @@ internal class RequestQueue : IDisposable, IAsyncDisposable public RequestQueue() { - _tokenLock = new SemaphoreSlim(1, 1); + _tokenLock = new(); _clearToken = new CancellationTokenSource(); _cancelTokenSource = new CancellationTokenSource(); @@ -42,29 +43,21 @@ public RequestQueue() public async Task SetCancelTokenAsync(CancellationToken cancelToken) { - await _tokenLock.WaitAsync().ConfigureAwait(false); - try - { - _parentToken = cancelToken; - _requestCancelTokenSource?.Dispose(); - _requestCancelTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancelToken, _clearToken.Token); - _requestCancelToken = _requestCancelTokenSource.Token; - } - finally { _tokenLock.Release(); } + using var _ = await _tokenLock.LockAsync().ConfigureAwait(false); + _parentToken = cancelToken; + _requestCancelTokenSource?.Dispose(); + _requestCancelTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancelToken, _clearToken.Token); + _requestCancelToken = _requestCancelTokenSource.Token; } public async Task ClearAsync() { - await _tokenLock.WaitAsync().ConfigureAwait(false); - try - { - _clearToken?.Cancel(); - _clearToken?.Dispose(); - _clearToken = new CancellationTokenSource(); - _requestCancelTokenSource?.Dispose(); - _requestCancelTokenSource = CancellationTokenSource.CreateLinkedTokenSource(_clearToken.Token, _parentToken); - _requestCancelToken = _requestCancelTokenSource.Token; - } - finally { _tokenLock.Release(); } + using var _ = await _tokenLock.LockAsync().ConfigureAwait(false); + _clearToken?.Cancel(); + _clearToken?.Dispose(); + _clearToken = new CancellationTokenSource(); + _requestCancelTokenSource?.Dispose(); + _requestCancelTokenSource = CancellationTokenSource.CreateLinkedTokenSource(_clearToken.Token, _parentToken); + _requestCancelToken = _requestCancelTokenSource.Token; } public async Task SendAsync(RestRequest request) diff --git a/src/Discord.Net.WebSocket/Audio/AudioClient.cs b/src/Discord.Net.WebSocket/Audio/AudioClient.cs index 1682d7356e..e55a3ecf4b 100644 --- a/src/Discord.Net.WebSocket/Audio/AudioClient.cs +++ b/src/Discord.Net.WebSocket/Audio/AudioClient.cs @@ -1,3 +1,4 @@ +using AsyncKeyedLock; using Discord.API.Voice; using Discord.Audio.Streams; using Discord.Logging; @@ -41,7 +42,7 @@ public StreamPair(AudioInStream reader, AudioOutStream writer) private readonly Logger _audioLogger; private readonly JsonSerializer _serializer; private readonly ConnectionManager _connection; - private readonly SemaphoreSlim _stateLock; + private readonly AsyncNonKeyedLocker _stateLock; private readonly ConcurrentQueue _heartbeatTimes; private readonly ConcurrentQueue> _keepaliveTimes; private readonly SsrcMap _ssrcMap; @@ -82,7 +83,7 @@ internal AudioClient(SocketGuild guild, int clientId, ulong channelId) ApiClient.ReceivedEvent += ProcessMessageAsync; ApiClient.ReceivedPacket += ProcessPacketAsync; - _stateLock = new SemaphoreSlim(1, 1); + _stateLock = new(); _connection = new ConnectionManager(_stateLock, _audioLogger, ConnectionTimeoutMs, OnConnectingAsync, OnDisconnectingAsync, x => ApiClient.Disconnected += x); _connection.Connected += () => _connectedEvent.InvokeAsync(); diff --git a/src/Discord.Net.WebSocket/ConnectionManager.cs b/src/Discord.Net.WebSocket/ConnectionManager.cs index aaad7b164f..d554111f8e 100644 --- a/src/Discord.Net.WebSocket/ConnectionManager.cs +++ b/src/Discord.Net.WebSocket/ConnectionManager.cs @@ -1,3 +1,4 @@ +using AsyncKeyedLock; using Discord.Logging; using Discord.Net; using System; @@ -14,7 +15,8 @@ internal class ConnectionManager : IDisposable public event Func Disconnected { add { _disconnectedEvent.Add(value); } remove { _disconnectedEvent.Remove(value); } } private readonly AsyncEvent> _disconnectedEvent = new AsyncEvent>(); - private readonly SemaphoreSlim _stateLock; + private readonly AsyncNonKeyedLocker _stateLock; + private IDisposable _stateLockReleaser = null; private readonly Logger _logger; private readonly int _connectionTimeout; private readonly Func _onConnecting; @@ -29,7 +31,7 @@ internal class ConnectionManager : IDisposable public ConnectionState State { get; private set; } public CancellationToken CancelToken { get; private set; } - internal ConnectionManager(SemaphoreSlim stateLock, Logger logger, int connectionTimeout, + internal ConnectionManager(AsyncNonKeyedLocker stateLock, Logger logger, int connectionTimeout, Func onConnecting, Func onDisconnecting, Action> clientDisconnectHandler) { _stateLock = stateLock; @@ -114,7 +116,7 @@ public virtual async Task StartAsync() } } } - finally { _stateLock.Release(); } + finally { _stateLockReleaser.Dispose(); } }); } public virtual Task StopAsync() @@ -214,8 +216,11 @@ private async Task AcquireConnectionLock() while (true) { await StopAsync().ConfigureAwait(false); - if (await _stateLock.WaitAsync(0).ConfigureAwait(false)) + + if (await _stateLock.LockOrNullAsync(0).ConfigureAwait(false) is not null) + { break; + } } } diff --git a/src/Discord.Net.WebSocket/DiscordShardedClient.cs b/src/Discord.Net.WebSocket/DiscordShardedClient.cs index 959e2eb92b..24dd5a0975 100644 --- a/src/Discord.Net.WebSocket/DiscordShardedClient.cs +++ b/src/Discord.Net.WebSocket/DiscordShardedClient.cs @@ -1,3 +1,4 @@ +using AsyncKeyedLock; using Discord.API; using Discord.Rest; using System; @@ -20,13 +21,7 @@ public partial class DiscordShardedClient : BaseSocketClient, IDiscordClient private DiscordSocketClient[] _shards; private ImmutableArray> _defaultStickers; private int _totalShards; - private SemaphoreSlim[] _identifySemaphores; -#if NET9_0_OR_GREATER - private readonly Lock _semaphoreResetLock; -#else - private readonly object _semaphoreResetLock; -#endif - private Task _semaphoreResetTask; + private StripedAsyncKeyedLocker _identifySemaphores; private bool _isDisposed; @@ -84,7 +79,6 @@ private DiscordShardedClient(int[] ids, DiscordSocketConfig config, API.DiscordS if (ids != null && config.TotalShards == null) throw new ArgumentException($"Custom ids are not supported when {nameof(config.TotalShards)} is not specified."); - _semaphoreResetLock = new(); _shardIdsToIndex = new Dictionary(); config.DisplayInitialLog = false; _baseConfig = config; @@ -97,9 +91,7 @@ private DiscordShardedClient(int[] ids, DiscordSocketConfig config, API.DiscordS _totalShards = config.TotalShards.Value; _shardIds = ids ?? Enumerable.Range(0, _totalShards).ToArray(); _shards = new DiscordSocketClient[_shardIds.Length]; - _identifySemaphores = new SemaphoreSlim[config.IdentifyMaxConcurrency]; - for (int i = 0; i < config.IdentifyMaxConcurrency; i++) - _identifySemaphores[i] = new SemaphoreSlim(1, 1); + _identifySemaphores = new(config.IdentifyMaxConcurrency); for (int i = 0; i < _shardIds.Length; i++) { _shardIdsToIndex.Add(_shardIds[i], i); @@ -114,31 +106,9 @@ private static API.DiscordSocketApiClient CreateApiClient(DiscordSocketConfig co => new DiscordSocketApiClient(config.RestClientProvider, config.WebSocketProvider, DiscordRestConfig.UserAgent, config.GatewayHost, useSystemClock: config.UseSystemClock, defaultRatelimitCallback: config.DefaultRatelimitCallback); - internal Task AcquireIdentifyLockAsync(int shardId, CancellationToken token) + internal ValueTask AcquireIdentifyLockAsync(int shardId, bool condition, CancellationToken token) { - int semaphoreIdx = shardId % _baseConfig.IdentifyMaxConcurrency; - return _identifySemaphores[semaphoreIdx].WaitAsync(token); - } - - internal void ReleaseIdentifyLock() - { - lock (_semaphoreResetLock) - { - if (_semaphoreResetTask == null) - _semaphoreResetTask = ResetSemaphoresAsync(); - } - } - - private async Task ResetSemaphoresAsync() - { - await Task.Delay(5000).ConfigureAwait(false); - lock (_semaphoreResetLock) - { - foreach (var semaphore in _identifySemaphores) - if (semaphore.CurrentCount == 0) - semaphore.Release(); - _semaphoreResetTask = null; - } + return _identifySemaphores.ConditionalLockAsync(shardId, condition, token); } internal override async Task OnLoginAsync(TokenType tokenType, string token) @@ -151,9 +121,7 @@ internal override async Task OnLoginAsync(TokenType tokenType, string token) _shards = new DiscordSocketClient[_shardIds.Length]; int maxConcurrency = botGateway.SessionStartLimit.MaxConcurrency; _baseConfig.IdentifyMaxConcurrency = maxConcurrency; - _identifySemaphores = new SemaphoreSlim[maxConcurrency]; - for (int i = 0; i < maxConcurrency; i++) - _identifySemaphores[i] = new SemaphoreSlim(1, 1); + _identifySemaphores = new(maxConcurrency); for (int i = 0; i < _shardIds.Length; i++) { _shardIdsToIndex.Add(_shardIds[i], i); diff --git a/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs b/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs index f33199ba90..195ea43e6f 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs @@ -197,12 +197,8 @@ private static string FormatGatewayUrl(string gatewayUrl) public async Task ConnectAsync() { - await _stateLock.WaitAsync().ConfigureAwait(false); - try - { - await ConnectInternalAsync().ConfigureAwait(false); - } - finally { _stateLock.Release(); } + using var _ = await _stateLock.LockAsync().ConfigureAwait(false); + await ConnectInternalAsync().ConfigureAwait(false); } /// The client must be logged in before connecting. /// This client is not configured with WebSocket support. @@ -262,12 +258,8 @@ internal override async Task ConnectInternalAsync() public async Task DisconnectAsync(Exception ex = null) { - await _stateLock.WaitAsync().ConfigureAwait(false); - try - { - await DisconnectInternalAsync(ex).ConfigureAwait(false); - } - finally { _stateLock.Release(); } + using var _ = await _stateLock.LockAsync().ConfigureAwait(false); + await DisconnectInternalAsync(ex).ConfigureAwait(false); } /// This client is not configured with WebSocket support. internal override async Task DisconnectInternalAsync(Exception ex = null) diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.EventHandling.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.EventHandling.cs index 352f52b7f5..b1b4cf95f5 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.EventHandling.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.EventHandling.cs @@ -27,201 +27,209 @@ private async Task ProcessMessageAsync(GatewayOpCode opCode, int? seq, string ty switch (opCode) { case GatewayOpCode.Hello: - { - await _gatewayLogger.DebugAsync("Received Hello").ConfigureAwait(false); - var data = (payload as JToken).ToObject(_serializer); + { + await _gatewayLogger.DebugAsync("Received Hello").ConfigureAwait(false); + var data = (payload as JToken).ToObject(_serializer); - _heartbeatTask = RunHeartbeatAsync(data.HeartbeatInterval, _connection.CancelToken); - } - break; + _heartbeatTask = RunHeartbeatAsync(data.HeartbeatInterval, _connection.CancelToken); + } + break; case GatewayOpCode.Heartbeat: - { - await _gatewayLogger.DebugAsync("Received Heartbeat").ConfigureAwait(false); + { + await _gatewayLogger.DebugAsync("Received Heartbeat").ConfigureAwait(false); - await ApiClient.SendHeartbeatAsync(_lastSeq).ConfigureAwait(false); - } - break; + await ApiClient.SendHeartbeatAsync(_lastSeq).ConfigureAwait(false); + } + break; case GatewayOpCode.HeartbeatAck: - { - await _gatewayLogger.DebugAsync("Received HeartbeatAck").ConfigureAwait(false); - - if (_heartbeatTimes.TryDequeue(out long time)) { - int latency = (int)(Environment.TickCount - time); - int before = Latency; - Latency = latency; + await _gatewayLogger.DebugAsync("Received HeartbeatAck").ConfigureAwait(false); + + if (_heartbeatTimes.TryDequeue(out long time)) + { + int latency = (int)(Environment.TickCount - time); + int before = Latency; + Latency = latency; - await TimedInvokeAsync(_latencyUpdatedEvent, nameof(LatencyUpdated), before, latency).ConfigureAwait(false); + await TimedInvokeAsync(_latencyUpdatedEvent, nameof(LatencyUpdated), before, latency).ConfigureAwait(false); + } } - } - break; + break; case GatewayOpCode.InvalidSession: - { - await _gatewayLogger.DebugAsync("Received InvalidSession").ConfigureAwait(false); - await _gatewayLogger.WarningAsync("Failed to resume previous session").ConfigureAwait(false); + { + await _gatewayLogger.DebugAsync("Received InvalidSession").ConfigureAwait(false); + await _gatewayLogger.WarningAsync("Failed to resume previous session").ConfigureAwait(false); - _sessionId = null; - _lastSeq = 0; - ApiClient.ResumeGatewayUrl = null; + _sessionId = null; + _lastSeq = 0; + ApiClient.ResumeGatewayUrl = null; - if (_shardedClient != null) - { - await _shardedClient.AcquireIdentifyLockAsync(ShardId, _connection.CancelToken).ConfigureAwait(false); - try - { - await ApiClient.SendIdentifyAsync(shardID: ShardId, totalShards: TotalShards, gatewayIntents: _gatewayIntents, presence: BuildCurrentStatus()).ConfigureAwait(false); - } - finally - { - _shardedClient.ReleaseIdentifyLock(); - } - } - else + using var _ = await _shardedClient.AcquireIdentifyLockAsync(ShardId, _shardedClient != null, _connection.CancelToken).ConfigureAwait(false); await ApiClient.SendIdentifyAsync(shardID: ShardId, totalShards: TotalShards, gatewayIntents: _gatewayIntents, presence: BuildCurrentStatus()).ConfigureAwait(false); - } - break; + } + break; case GatewayOpCode.Reconnect: - { - await _gatewayLogger.DebugAsync("Received Reconnect").ConfigureAwait(false); - _connection.Error(new GatewayReconnectException("Server requested a reconnect")); - } - break; + { + await _gatewayLogger.DebugAsync("Received Reconnect").ConfigureAwait(false); + _connection.Error(new GatewayReconnectException("Server requested a reconnect")); + } + break; case GatewayOpCode.Dispatch: switch (type) { #region Connection case "READY": - { - try - { - await _gatewayLogger.DebugAsync("Received Dispatch (READY)").ConfigureAwait(false); - - var data = (payload as JToken).ToObject(_serializer); - var state = new ClientState(data.Guilds.Length, data.PrivateChannels.Length); - - var currentUser = SocketSelfUser.Create(this, state, data.User); - Rest.CreateRestSelfUser(data.User); - var activities = _activity.IsSpecified ? ImmutableList.Create(_activity.Value) : null; - currentUser.Presence = new SocketPresence(Status, null, activities); - ApiClient.CurrentUserId = currentUser.Id; - ApiClient.CurrentApplicationId = data.Application?.Id; - Rest.CurrentUser = RestSelfUser.Create(this, data.User); - int unavailableGuilds = 0; - for (int i = 0; i < data.Guilds.Length; i++) - { - var model = data.Guilds[i]; - var guild = AddGuild(model, state); - if (!guild.IsAvailable) - unavailableGuilds++; - else - await GuildAvailableAsync(guild).ConfigureAwait(false); - } - for (int i = 0; i < data.PrivateChannels.Length; i++) - AddPrivateChannel(data.PrivateChannels[i], state); - - _sessionId = data.SessionId; - ApiClient.ResumeGatewayUrl = data.ResumeGatewayUrl; - _unavailableGuildCount = unavailableGuilds; - CurrentUser = currentUser; - _previousSessionUser = CurrentUser; - State = state; - } - catch (Exception ex) { - _connection.CriticalError(new Exception("Processing READY failed", ex)); - return; - } - - _lastGuildAvailableTime = Environment.TickCount; - _guildDownloadTask = WaitForGuildsAsync(_connection.CancelToken, _gatewayLogger) - .ContinueWith(async x => + try { - if (x.IsFaulted) + await _gatewayLogger.DebugAsync("Received Dispatch (READY)").ConfigureAwait(false); + + var data = (payload as JToken).ToObject(_serializer); + var state = new ClientState(data.Guilds.Length, data.PrivateChannels.Length); + + var currentUser = SocketSelfUser.Create(this, state, data.User); + Rest.CreateRestSelfUser(data.User); + var activities = _activity.IsSpecified ? ImmutableList.Create(_activity.Value) : null; + currentUser.Presence = new SocketPresence(Status, null, activities); + ApiClient.CurrentUserId = currentUser.Id; + ApiClient.CurrentApplicationId = data.Application?.Id; + Rest.CurrentUser = RestSelfUser.Create(this, data.User); + int unavailableGuilds = 0; + for (int i = 0; i < data.Guilds.Length; i++) { - _connection.Error(x.Exception); - return; + var model = data.Guilds[i]; + var guild = AddGuild(model, state); + if (!guild.IsAvailable) + unavailableGuilds++; + else + await GuildAvailableAsync(guild).ConfigureAwait(false); } - else if (_connection.CancelToken.IsCancellationRequested) - return; + for (int i = 0; i < data.PrivateChannels.Length; i++) + AddPrivateChannel(data.PrivateChannels[i], state); - if (BaseConfig.AlwaysDownloadUsers) - try - { - _ = DownloadUsersAsync(Guilds.Where(x => x.IsAvailable && !x.HasAllMembers)); - } - catch (Exception ex) + _sessionId = data.SessionId; + ApiClient.ResumeGatewayUrl = data.ResumeGatewayUrl; + _unavailableGuildCount = unavailableGuilds; + CurrentUser = currentUser; + _previousSessionUser = CurrentUser; + State = state; + } + catch (Exception ex) + { + _connection.CriticalError(new Exception("Processing READY failed", ex)); + return; + } + + _lastGuildAvailableTime = Environment.TickCount; + _guildDownloadTask = WaitForGuildsAsync(_connection.CancelToken, _gatewayLogger) + .ContinueWith(async x => + { + if (x.IsFaulted) { - await _gatewayLogger.WarningAsync(ex); + _connection.Error(x.Exception); + return; } + else if (_connection.CancelToken.IsCancellationRequested) + return; - await TimedInvokeAsync(_readyEvent, nameof(Ready)).ConfigureAwait(false); - await _gatewayLogger.InfoAsync("Ready").ConfigureAwait(false); - }); - _ = _connection.CompleteAsync(); - } - break; + if (BaseConfig.AlwaysDownloadUsers) + try + { + _ = DownloadUsersAsync(Guilds.Where(x => x.IsAvailable && !x.HasAllMembers)); + } + catch (Exception ex) + { + await _gatewayLogger.WarningAsync(ex); + } + + await TimedInvokeAsync(_readyEvent, nameof(Ready)).ConfigureAwait(false); + await _gatewayLogger.InfoAsync("Ready").ConfigureAwait(false); + }); + _ = _connection.CompleteAsync(); + } + break; case "RESUMED": - { - await _gatewayLogger.DebugAsync("Received Dispatch (RESUMED)").ConfigureAwait(false); + { + await _gatewayLogger.DebugAsync("Received Dispatch (RESUMED)").ConfigureAwait(false); - _ = _connection.CompleteAsync(); + _ = _connection.CompleteAsync(); - //Notify the client that these guilds are available again - foreach (var guild in State.Guilds) - { - if (guild.IsAvailable) - await GuildAvailableAsync(guild).ConfigureAwait(false); - } + //Notify the client that these guilds are available again + foreach (var guild in State.Guilds) + { + if (guild.IsAvailable) + await GuildAvailableAsync(guild).ConfigureAwait(false); + } - // Restore the previous sessions current user - CurrentUser = _previousSessionUser; + // Restore the previous sessions current user + CurrentUser = _previousSessionUser; - await _gatewayLogger.InfoAsync("Resumed previous session").ConfigureAwait(false); - } - break; + await _gatewayLogger.InfoAsync("Resumed previous session").ConfigureAwait(false); + } + break; #endregion #region Guilds case "GUILD_CREATE": - { - var data = (payload as JToken).ToObject(_serializer); - - if (data.Unavailable == false) { - type = "GUILD_AVAILABLE"; - _lastGuildAvailableTime = Environment.TickCount; - await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_AVAILABLE)").ConfigureAwait(false); + var data = (payload as JToken).ToObject(_serializer); - var guild = State.GetGuild(data.Id); - if (guild != null) + if (data.Unavailable == false) { - guild.Update(State, data); + type = "GUILD_AVAILABLE"; + _lastGuildAvailableTime = Environment.TickCount; + await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_AVAILABLE)").ConfigureAwait(false); - if (_unavailableGuildCount != 0) - _unavailableGuildCount--; - await GuildAvailableAsync(guild).ConfigureAwait(false); + var guild = State.GetGuild(data.Id); + if (guild != null) + { + guild.Update(State, data); - if (guild.DownloadedMemberCount >= guild.MemberCount && !guild.DownloaderPromise.IsCompleted) + if (_unavailableGuildCount != 0) + _unavailableGuildCount--; + await GuildAvailableAsync(guild).ConfigureAwait(false); + + if (guild.DownloadedMemberCount >= guild.MemberCount && !guild.DownloaderPromise.IsCompleted) + { + guild.CompleteDownloadUsers(); + await TimedInvokeAsync(_guildMembersDownloadedEvent, nameof(GuildMembersDownloaded), guild).ConfigureAwait(false); + } + } + else { - guild.CompleteDownloadUsers(); - await TimedInvokeAsync(_guildMembersDownloadedEvent, nameof(GuildMembersDownloaded), guild).ConfigureAwait(false); + await UnknownGuildAsync(type, data.Id).ConfigureAwait(false); + return; } } else { - await UnknownGuildAsync(type, data.Id).ConfigureAwait(false); - return; + await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_CREATE)").ConfigureAwait(false); + + var guild = AddGuild(data, State); + if (guild != null) + { + await TimedInvokeAsync(_joinedGuildEvent, nameof(JoinedGuild), guild).ConfigureAwait(false); + await GuildAvailableAsync(guild).ConfigureAwait(false); + } + else + { + await UnknownGuildAsync(type, data.Id).ConfigureAwait(false); + return; + } } } - else + break; + case "GUILD_UPDATE": { - await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_CREATE)").ConfigureAwait(false); + await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_UPDATE)").ConfigureAwait(false); - var guild = AddGuild(data, State); + var data = (payload as JToken).ToObject(_serializer); + var guild = State.GetGuild(data.Id); if (guild != null) { - await TimedInvokeAsync(_joinedGuildEvent, nameof(JoinedGuild), guild).ConfigureAwait(false); - await GuildAvailableAsync(guild).ConfigureAwait(false); + var before = guild.Clone(); + guild.Update(State, data); + await TimedInvokeAsync(_guildUpdatedEvent, nameof(GuildUpdated), before, guild).ConfigureAwait(false); } else { @@ -229,2248 +237,2228 @@ private async Task ProcessMessageAsync(GatewayOpCode opCode, int? seq, string ty return; } } - } - break; - case "GUILD_UPDATE": - { - await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_UPDATE)").ConfigureAwait(false); - - var data = (payload as JToken).ToObject(_serializer); - var guild = State.GetGuild(data.Id); - if (guild != null) - { - var before = guild.Clone(); - guild.Update(State, data); - await TimedInvokeAsync(_guildUpdatedEvent, nameof(GuildUpdated), before, guild).ConfigureAwait(false); - } - else - { - await UnknownGuildAsync(type, data.Id).ConfigureAwait(false); - return; - } - } - break; + break; case "GUILD_EMOJIS_UPDATE": - { - await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_EMOJIS_UPDATE)").ConfigureAwait(false); - - var data = (payload as JToken).ToObject(_serializer); - var guild = State.GetGuild(data.GuildId); - if (guild != null) { - var before = guild.Clone(); - guild.Update(State, data); - await TimedInvokeAsync(_guildUpdatedEvent, nameof(GuildUpdated), before, guild).ConfigureAwait(false); - } - else - { - await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false); - return; - } - } - break; - case "GUILD_SYNC": - { - await _gatewayLogger.DebugAsync("Ignored Dispatch (GUILD_SYNC)").ConfigureAwait(false); - /*await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_SYNC)").ConfigureAwait(false); //TODO remove? userbot related - var data = (payload as JToken).ToObject(_serializer); - var guild = State.GetGuild(data.Id); - if (guild != null) - { - var before = guild.Clone(); - guild.Update(State, data); - //This is treated as an extension of GUILD_AVAILABLE - _unavailableGuildCount--; - _lastGuildAvailableTime = Environment.TickCount; - await GuildAvailableAsync(guild).ConfigureAwait(false); - await TimedInvokeAsync(_guildUpdatedEvent, nameof(GuildUpdated), before, guild).ConfigureAwait(false); - } - else - { - await UnknownGuildAsync(type, data.Id).ConfigureAwait(false); - return; - }*/ - } - break; - case "GUILD_DELETE": - { - var data = (payload as JToken).ToObject(_serializer); - if (data.Unavailable == true) - { - type = "GUILD_UNAVAILABLE"; - await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_UNAVAILABLE)").ConfigureAwait(false); + await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_EMOJIS_UPDATE)").ConfigureAwait(false); - var guild = State.GetGuild(data.Id); + var data = (payload as JToken).ToObject(_serializer); + var guild = State.GetGuild(data.GuildId); if (guild != null) { - await GuildUnavailableAsync(guild).ConfigureAwait(false); - _unavailableGuildCount++; + var before = guild.Clone(); + guild.Update(State, data); + await TimedInvokeAsync(_guildUpdatedEvent, nameof(GuildUpdated), before, guild).ConfigureAwait(false); } else { - await UnknownGuildAsync(type, data.Id).ConfigureAwait(false); + await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false); return; } } - else + break; + case "GUILD_SYNC": { - await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_DELETE)").ConfigureAwait(false); - - var guild = RemoveGuild(data.Id); + await _gatewayLogger.DebugAsync("Ignored Dispatch (GUILD_SYNC)").ConfigureAwait(false); + /*await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_SYNC)").ConfigureAwait(false); //TODO remove? userbot related + var data = (payload as JToken).ToObject(_serializer); + var guild = State.GetGuild(data.Id); if (guild != null) { - await GuildUnavailableAsync(guild).ConfigureAwait(false); - await TimedInvokeAsync(_leftGuildEvent, nameof(LeftGuild), guild).ConfigureAwait(false); - (guild as IDisposable).Dispose(); + var before = guild.Clone(); + guild.Update(State, data); + //This is treated as an extension of GUILD_AVAILABLE + _unavailableGuildCount--; + _lastGuildAvailableTime = Environment.TickCount; + await GuildAvailableAsync(guild).ConfigureAwait(false); + await TimedInvokeAsync(_guildUpdatedEvent, nameof(GuildUpdated), before, guild).ConfigureAwait(false); } else { await UnknownGuildAsync(type, data.Id).ConfigureAwait(false); return; + }*/ + } + break; + case "GUILD_DELETE": + { + var data = (payload as JToken).ToObject(_serializer); + if (data.Unavailable == true) + { + type = "GUILD_UNAVAILABLE"; + await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_UNAVAILABLE)").ConfigureAwait(false); + + var guild = State.GetGuild(data.Id); + if (guild != null) + { + await GuildUnavailableAsync(guild).ConfigureAwait(false); + _unavailableGuildCount++; + } + else + { + await UnknownGuildAsync(type, data.Id).ConfigureAwait(false); + return; + } + } + else + { + await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_DELETE)").ConfigureAwait(false); + + var guild = RemoveGuild(data.Id); + if (guild != null) + { + await GuildUnavailableAsync(guild).ConfigureAwait(false); + await TimedInvokeAsync(_leftGuildEvent, nameof(LeftGuild), guild).ConfigureAwait(false); + (guild as IDisposable).Dispose(); + } + else + { + await UnknownGuildAsync(type, data.Id).ConfigureAwait(false); + return; + } } } - } - break; + break; case "GUILD_STICKERS_UPDATE": - { - await _gatewayLogger.DebugAsync($"Received Dispatch (GUILD_STICKERS_UPDATE)").ConfigureAwait(false); + { + await _gatewayLogger.DebugAsync($"Received Dispatch (GUILD_STICKERS_UPDATE)").ConfigureAwait(false); - var data = (payload as JToken).ToObject(_serializer); + var data = (payload as JToken).ToObject(_serializer); - var guild = State.GetGuild(data.GuildId); + var guild = State.GetGuild(data.GuildId); - if (guild == null) - { - await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false); - return; - } + if (guild == null) + { + await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false); + return; + } - var newStickers = data.Stickers.Where(x => !guild.Stickers.Any(y => y.Id == x.Id)); - var deletedStickers = guild.Stickers.Where(x => !data.Stickers.Any(y => y.Id == x.Id)); - var updatedStickers = data.Stickers.Select(x => - { - var s = guild.Stickers.FirstOrDefault(y => y.Id == x.Id); - if (s == null) - return null; + var newStickers = data.Stickers.Where(x => !guild.Stickers.Any(y => y.Id == x.Id)); + var deletedStickers = guild.Stickers.Where(x => !data.Stickers.Any(y => y.Id == x.Id)); + var updatedStickers = data.Stickers.Select(x => + { + var s = guild.Stickers.FirstOrDefault(y => y.Id == x.Id); + if (s == null) + return null; + + var e = s.Equals(x); + if (!e) + { + return (s, x) as (SocketCustomSticker Entity, API.Sticker Model)?; + } + else + { + return null; + } + }).Where(x => x.HasValue).Select(x => x.Value).ToArray(); - var e = s.Equals(x); - if (!e) + foreach (var model in newStickers) { - return (s, x) as (SocketCustomSticker Entity, API.Sticker Model)?; + var entity = guild.AddSticker(model); + await TimedInvokeAsync(_guildStickerCreated, nameof(GuildStickerCreated), entity); } - else + foreach (var sticker in deletedStickers) { - return null; + var entity = guild.RemoveSticker(sticker.Id); + await TimedInvokeAsync(_guildStickerDeleted, nameof(GuildStickerDeleted), entity); } - }).Where(x => x.HasValue).Select(x => x.Value).ToArray(); - - foreach (var model in newStickers) - { - var entity = guild.AddSticker(model); - await TimedInvokeAsync(_guildStickerCreated, nameof(GuildStickerCreated), entity); - } - foreach (var sticker in deletedStickers) - { - var entity = guild.RemoveSticker(sticker.Id); - await TimedInvokeAsync(_guildStickerDeleted, nameof(GuildStickerDeleted), entity); - } - foreach (var entityModelPair in updatedStickers) - { - var before = entityModelPair.Entity.Clone(); + foreach (var entityModelPair in updatedStickers) + { + var before = entityModelPair.Entity.Clone(); - entityModelPair.Entity.Update(entityModelPair.Model); + entityModelPair.Entity.Update(entityModelPair.Model); - await TimedInvokeAsync(_guildStickerUpdated, nameof(GuildStickerUpdated), before, entityModelPair.Entity); + await TimedInvokeAsync(_guildStickerUpdated, nameof(GuildStickerUpdated), before, entityModelPair.Entity); + } } - } - break; + break; #endregion #region Channels case "CHANNEL_CREATE": - { - await _gatewayLogger.DebugAsync("Received Dispatch (CHANNEL_CREATE)").ConfigureAwait(false); - - var data = (payload as JToken).ToObject(_serializer); - SocketChannel channel = null; - if (data.GuildId.IsSpecified) { - var guild = State.GetGuild(data.GuildId.Value); - if (guild != null) + await _gatewayLogger.DebugAsync("Received Dispatch (CHANNEL_CREATE)").ConfigureAwait(false); + + var data = (payload as JToken).ToObject(_serializer); + SocketChannel channel = null; + if (data.GuildId.IsSpecified) { - channel = guild.AddChannel(State, data); + var guild = State.GetGuild(data.GuildId.Value); + if (guild != null) + { + channel = guild.AddChannel(State, data); - if (!guild.IsSynced) + if (!guild.IsSynced) + { + await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); + return; + } + } + else { - await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); + await UnknownGuildAsync(type, data.GuildId.Value).ConfigureAwait(false); return; } } else { - await UnknownGuildAsync(type, data.GuildId.Value).ConfigureAwait(false); - return; + channel = State.GetChannel(data.Id); + if (channel != null) + return; //Discord may send duplicate CHANNEL_CREATEs for DMs + channel = AddPrivateChannel(data, State) as SocketChannel; } - } - else - { - channel = State.GetChannel(data.Id); + if (channel != null) - return; //Discord may send duplicate CHANNEL_CREATEs for DMs - channel = AddPrivateChannel(data, State) as SocketChannel; + await TimedInvokeAsync(_channelCreatedEvent, nameof(ChannelCreated), channel).ConfigureAwait(false); } - - if (channel != null) - await TimedInvokeAsync(_channelCreatedEvent, nameof(ChannelCreated), channel).ConfigureAwait(false); - } - break; + break; case "CHANNEL_UPDATE": - { - await _gatewayLogger.DebugAsync("Received Dispatch (CHANNEL_UPDATE)").ConfigureAwait(false); - - var data = (payload as JToken).ToObject(_serializer); - var channel = State.GetChannel(data.Id); - if (channel != null) { - var before = channel.Clone(); - channel.Update(State, data); + await _gatewayLogger.DebugAsync("Received Dispatch (CHANNEL_UPDATE)").ConfigureAwait(false); - var guild = (channel as SocketGuildChannel)?.Guild; - if (!(guild?.IsSynced ?? true)) + var data = (payload as JToken).ToObject(_serializer); + var channel = State.GetChannel(data.Id); + if (channel != null) { - await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); + var before = channel.Clone(); + channel.Update(State, data); + + var guild = (channel as SocketGuildChannel)?.Guild; + if (!(guild?.IsSynced ?? true)) + { + await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); + return; + } + + await TimedInvokeAsync(_channelUpdatedEvent, nameof(ChannelUpdated), before, channel).ConfigureAwait(false); + } + else + { + await UnknownChannelAsync(type, data.Id).ConfigureAwait(false); return; } - - await TimedInvokeAsync(_channelUpdatedEvent, nameof(ChannelUpdated), before, channel).ConfigureAwait(false); } - else - { - await UnknownChannelAsync(type, data.Id).ConfigureAwait(false); - return; - } - } - break; + break; case "CHANNEL_DELETE": - { - await _gatewayLogger.DebugAsync("Received Dispatch (CHANNEL_DELETE)").ConfigureAwait(false); - - SocketChannel channel = null; - var data = (payload as JToken).ToObject(_serializer); - if (data.GuildId.IsSpecified) { - var guild = State.GetGuild(data.GuildId.Value); - if (guild != null) + await _gatewayLogger.DebugAsync("Received Dispatch (CHANNEL_DELETE)").ConfigureAwait(false); + + SocketChannel channel = null; + var data = (payload as JToken).ToObject(_serializer); + if (data.GuildId.IsSpecified) { - channel = guild.RemoveChannel(State, data.Id); + var guild = State.GetGuild(data.GuildId.Value); + if (guild != null) + { + channel = guild.RemoveChannel(State, data.Id); - if (!guild.IsSynced) + if (!guild.IsSynced) + { + await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); + return; + } + } + else { - await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); + await UnknownGuildAsync(type, data.GuildId.Value).ConfigureAwait(false); return; } } + else + channel = RemovePrivateChannel(data.Id) as SocketChannel; + + if (channel != null) + await TimedInvokeAsync(_channelDestroyedEvent, nameof(ChannelDestroyed), channel).ConfigureAwait(false); else { - await UnknownGuildAsync(type, data.GuildId.Value).ConfigureAwait(false); + await UnknownChannelAsync(type, data.Id, data.GuildId.GetValueOrDefault(0)).ConfigureAwait(false); return; } } - else - channel = RemovePrivateChannel(data.Id) as SocketChannel; - - if (channel != null) - await TimedInvokeAsync(_channelDestroyedEvent, nameof(ChannelDestroyed), channel).ConfigureAwait(false); - else - { - await UnknownChannelAsync(type, data.Id, data.GuildId.GetValueOrDefault(0)).ConfigureAwait(false); - return; - } - } - break; + break; #endregion #region Members case "GUILD_MEMBER_ADD": - { - await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_MEMBER_ADD)").ConfigureAwait(false); - - var data = (payload as JToken).ToObject(_serializer); - var guild = State.GetGuild(data.GuildId); - if (guild != null) { - var user = guild.AddOrUpdateUser(data); - guild.MemberCount++; + await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_MEMBER_ADD)").ConfigureAwait(false); - if (!guild.IsSynced) + var data = (payload as JToken).ToObject(_serializer); + var guild = State.GetGuild(data.GuildId); + if (guild != null) { - await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); - return; - } + var user = guild.AddOrUpdateUser(data); + guild.MemberCount++; - await TimedInvokeAsync(_userJoinedEvent, nameof(UserJoined), user).ConfigureAwait(false); - } - else - { - await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false); - return; - } - } - break; - case "GUILD_MEMBER_UPDATE": - { - await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_MEMBER_UPDATE)").ConfigureAwait(false); - - var data = (payload as JToken).ToObject(_serializer); - var guild = State.GetGuild(data.GuildId); - if (guild != null) - { - var user = guild.GetUser(data.User.Id); + if (!guild.IsSynced) + { + await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); + return; + } - if (!guild.IsSynced) + await TimedInvokeAsync(_userJoinedEvent, nameof(UserJoined), user).ConfigureAwait(false); + } + else { - await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); + await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false); return; } + } + break; + case "GUILD_MEMBER_UPDATE": + { + await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_MEMBER_UPDATE)").ConfigureAwait(false); - if (user != null) + var data = (payload as JToken).ToObject(_serializer); + var guild = State.GetGuild(data.GuildId); + if (guild != null) { - var before = user.Clone(); - if (user.GlobalUser.Update(State, data.User)) + var user = guild.GetUser(data.User.Id); + + if (!guild.IsSynced) { - //Global data was updated, trigger UserUpdated - await TimedInvokeAsync(_userUpdatedEvent, nameof(UserUpdated), before.GlobalUser, user).ConfigureAwait(false); + await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); + return; } - user.Update(State, data); + if (user != null) + { + var before = user.Clone(); + if (user.GlobalUser.Update(State, data.User)) + { + //Global data was updated, trigger UserUpdated + await TimedInvokeAsync(_userUpdatedEvent, nameof(UserUpdated), before.GlobalUser, user).ConfigureAwait(false); + } + + user.Update(State, data); - var cacheableBefore = new Cacheable(before, user.Id, true, () => Task.FromResult(null)); - await TimedInvokeAsync(_guildMemberUpdatedEvent, nameof(GuildMemberUpdated), cacheableBefore, user).ConfigureAwait(false); + var cacheableBefore = new Cacheable(before, user.Id, true, () => Task.FromResult(null)); + await TimedInvokeAsync(_guildMemberUpdatedEvent, nameof(GuildMemberUpdated), cacheableBefore, user).ConfigureAwait(false); + } + else + { + user = guild.AddOrUpdateUser(data); + var cacheableBefore = new Cacheable(null, user.Id, false, () => Task.FromResult(null)); + await TimedInvokeAsync(_guildMemberUpdatedEvent, nameof(GuildMemberUpdated), cacheableBefore, user).ConfigureAwait(false); + } } else { - user = guild.AddOrUpdateUser(data); - var cacheableBefore = new Cacheable(null, user.Id, false, () => Task.FromResult(null)); - await TimedInvokeAsync(_guildMemberUpdatedEvent, nameof(GuildMemberUpdated), cacheableBefore, user).ConfigureAwait(false); + await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false); + return; } } - else - { - await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false); - return; - } - } - break; + break; case "GUILD_MEMBER_REMOVE": - { - await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_MEMBER_REMOVE)").ConfigureAwait(false); - - var data = (payload as JToken).ToObject(_serializer); - var guild = State.GetGuild(data.GuildId); - if (guild != null) { - SocketUser user = guild.RemoveUser(data.User.Id); - guild.MemberCount--; + await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_MEMBER_REMOVE)").ConfigureAwait(false); - if (!guild.IsSynced) + var data = (payload as JToken).ToObject(_serializer); + var guild = State.GetGuild(data.GuildId); + if (guild != null) { - await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); - return; - } + SocketUser user = guild.RemoveUser(data.User.Id); + guild.MemberCount--; + + if (!guild.IsSynced) + { + await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); + return; + } - user ??= State.GetUser(data.User.Id); + user ??= State.GetUser(data.User.Id); - if (user != null) - user.Update(State, data.User); - else - user = State.GetOrAddUser(data.User.Id, (x) => SocketGlobalUser.Create(this, State, data.User)); + if (user != null) + user.Update(State, data.User); + else + user = State.GetOrAddUser(data.User.Id, (x) => SocketGlobalUser.Create(this, State, data.User)); - await TimedInvokeAsync(_userLeftEvent, nameof(UserLeft), guild, user).ConfigureAwait(false); - } - else - { - await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false); - return; + await TimedInvokeAsync(_userLeftEvent, nameof(UserLeft), guild, user).ConfigureAwait(false); + } + else + { + await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false); + return; + } } - } - break; + break; case "GUILD_MEMBERS_CHUNK": - { - await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_MEMBERS_CHUNK)").ConfigureAwait(false); - - var data = (payload as JToken).ToObject(_serializer); - var guild = State.GetGuild(data.GuildId); - if (guild != null) { - foreach (var memberModel in data.Members) - guild.AddOrUpdateUser(memberModel); + await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_MEMBERS_CHUNK)").ConfigureAwait(false); - if (guild.DownloadedMemberCount >= guild.MemberCount && !guild.DownloaderPromise.IsCompleted) + var data = (payload as JToken).ToObject(_serializer); + var guild = State.GetGuild(data.GuildId); + if (guild != null) { - guild.CompleteDownloadUsers(); - await TimedInvokeAsync(_guildMembersDownloadedEvent, nameof(GuildMembersDownloaded), guild).ConfigureAwait(false); + foreach (var memberModel in data.Members) + guild.AddOrUpdateUser(memberModel); + + if (guild.DownloadedMemberCount >= guild.MemberCount && !guild.DownloaderPromise.IsCompleted) + { + guild.CompleteDownloadUsers(); + await TimedInvokeAsync(_guildMembersDownloadedEvent, nameof(GuildMembersDownloaded), guild).ConfigureAwait(false); + } + } + else + { + await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false); + return; } } - else - { - await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false); - return; - } - } - break; + break; case "GUILD_JOIN_REQUEST_DELETE": - { - await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_JOIN_REQUEST_DELETE)").ConfigureAwait(false); + { + await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_JOIN_REQUEST_DELETE)").ConfigureAwait(false); - var data = (payload as JToken).ToObject(_serializer); + var data = (payload as JToken).ToObject(_serializer); - var guild = State.GetGuild(data.GuildId); + var guild = State.GetGuild(data.GuildId); - if (guild == null) - { - await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false); - return; - } + if (guild == null) + { + await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false); + return; + } - var user = guild.RemoveUser(data.UserId); - guild.MemberCount--; + var user = guild.RemoveUser(data.UserId); + guild.MemberCount--; - var cacheableUser = new Cacheable(user, data.UserId, user != null, () => Task.FromResult((SocketGuildUser)null)); + var cacheableUser = new Cacheable(user, data.UserId, user != null, () => Task.FromResult((SocketGuildUser)null)); - await TimedInvokeAsync(_guildJoinRequestDeletedEvent, nameof(GuildJoinRequestDeleted), cacheableUser, guild).ConfigureAwait(false); - } - break; + await TimedInvokeAsync(_guildJoinRequestDeletedEvent, nameof(GuildJoinRequestDeleted), cacheableUser, guild).ConfigureAwait(false); + } + break; #endregion #region DM Channels case "CHANNEL_RECIPIENT_ADD": - { - await _gatewayLogger.DebugAsync("Received Dispatch (CHANNEL_RECIPIENT_ADD)").ConfigureAwait(false); - - var data = (payload as JToken).ToObject(_serializer); - if (State.GetChannel(data.ChannelId) is SocketGroupChannel channel) - { - var user = channel.GetOrAddUser(data.User); - await TimedInvokeAsync(_recipientAddedEvent, nameof(RecipientAdded), user).ConfigureAwait(false); - } - else { - await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); - return; - } - } - break; - case "CHANNEL_RECIPIENT_REMOVE": - { - await _gatewayLogger.DebugAsync("Received Dispatch (CHANNEL_RECIPIENT_REMOVE)").ConfigureAwait(false); + await _gatewayLogger.DebugAsync("Received Dispatch (CHANNEL_RECIPIENT_ADD)").ConfigureAwait(false); - var data = (payload as JToken).ToObject(_serializer); - if (State.GetChannel(data.ChannelId) is SocketGroupChannel channel) - { - var user = channel.RemoveUser(data.User.Id); - if (user != null) - await TimedInvokeAsync(_recipientRemovedEvent, nameof(RecipientRemoved), user).ConfigureAwait(false); + var data = (payload as JToken).ToObject(_serializer); + if (State.GetChannel(data.ChannelId) is SocketGroupChannel channel) + { + var user = channel.GetOrAddUser(data.User); + await TimedInvokeAsync(_recipientAddedEvent, nameof(RecipientAdded), user).ConfigureAwait(false); + } else { - await UnknownChannelUserAsync(type, data.User.Id, data.ChannelId).ConfigureAwait(false); + await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); return; } } - else + break; + case "CHANNEL_RECIPIENT_REMOVE": { - await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); - return; + await _gatewayLogger.DebugAsync("Received Dispatch (CHANNEL_RECIPIENT_REMOVE)").ConfigureAwait(false); + + var data = (payload as JToken).ToObject(_serializer); + if (State.GetChannel(data.ChannelId) is SocketGroupChannel channel) + { + var user = channel.RemoveUser(data.User.Id); + if (user != null) + await TimedInvokeAsync(_recipientRemovedEvent, nameof(RecipientRemoved), user).ConfigureAwait(false); + else + { + await UnknownChannelUserAsync(type, data.User.Id, data.ChannelId).ConfigureAwait(false); + return; + } + } + else + { + await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); + return; + } } - } - break; + break; #endregion #region Roles case "GUILD_ROLE_CREATE": - { - await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_ROLE_CREATE)").ConfigureAwait(false); - - var data = (payload as JToken).ToObject(_serializer); - var guild = State.GetGuild(data.GuildId); - if (guild != null) - { - var role = guild.AddRole(data.Role); - - if (!guild.IsSynced) - { - await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); - return; - } - await TimedInvokeAsync(_roleCreatedEvent, nameof(RoleCreated), role).ConfigureAwait(false); - } - else { - await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false); - return; - } - } - break; - case "GUILD_ROLE_UPDATE": - { - await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_ROLE_UPDATE)").ConfigureAwait(false); + await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_ROLE_CREATE)").ConfigureAwait(false); - var data = (payload as JToken).ToObject(_serializer); - var guild = State.GetGuild(data.GuildId); - if (guild != null) - { - var role = guild.GetRole(data.Role.Id); - if (role != null) + var data = (payload as JToken).ToObject(_serializer); + var guild = State.GetGuild(data.GuildId); + if (guild != null) { - var before = role.Clone(); - role.Update(State, data.Role); + var role = guild.AddRole(data.Role); if (!guild.IsSynced) { await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); return; } - - await TimedInvokeAsync(_roleUpdatedEvent, nameof(RoleUpdated), before, role).ConfigureAwait(false); + await TimedInvokeAsync(_roleCreatedEvent, nameof(RoleCreated), role).ConfigureAwait(false); } else { - await UnknownRoleAsync(type, data.Role.Id, guild.Id).ConfigureAwait(false); + await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false); return; } } - else + break; + case "GUILD_ROLE_UPDATE": { - await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false); - return; - } - } - break; - case "GUILD_ROLE_DELETE": - { - await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_ROLE_DELETE)").ConfigureAwait(false); + await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_ROLE_UPDATE)").ConfigureAwait(false); - var data = (payload as JToken).ToObject(_serializer); - var guild = State.GetGuild(data.GuildId); - if (guild != null) - { - var role = guild.RemoveRole(data.RoleId); - if (role != null) + var data = (payload as JToken).ToObject(_serializer); + var guild = State.GetGuild(data.GuildId); + if (guild != null) { - if (!guild.IsSynced) + var role = guild.GetRole(data.Role.Id); + if (role != null) { - await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); + var before = role.Clone(); + role.Update(State, data.Role); + + if (!guild.IsSynced) + { + await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); + return; + } + + await TimedInvokeAsync(_roleUpdatedEvent, nameof(RoleUpdated), before, role).ConfigureAwait(false); + } + else + { + await UnknownRoleAsync(type, data.Role.Id, guild.Id).ConfigureAwait(false); return; } - - await TimedInvokeAsync(_roleDeletedEvent, nameof(RoleDeleted), role).ConfigureAwait(false); } else { - await UnknownRoleAsync(type, data.RoleId, guild.Id).ConfigureAwait(false); + await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false); return; } } - else + break; + case "GUILD_ROLE_DELETE": { - await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false); - return; - } - } - break; - #endregion - - #region Bans - case "GUILD_BAN_ADD": - { - await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_BAN_ADD)").ConfigureAwait(false); + await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_ROLE_DELETE)").ConfigureAwait(false); - var data = (payload as JToken).ToObject(_serializer); - var guild = State.GetGuild(data.GuildId); - if (guild != null) - { - if (!guild.IsSynced) + var data = (payload as JToken).ToObject(_serializer); + var guild = State.GetGuild(data.GuildId); + if (guild != null) { - await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); - return; - } - - SocketUser user = guild.GetUser(data.User.Id); - if (user == null) - user = SocketUnknownUser.Create(this, State, data.User); - await TimedInvokeAsync(_userBannedEvent, nameof(UserBanned), user, guild).ConfigureAwait(false); - } - else - { - await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false); - return; - } - } - break; - case "GUILD_BAN_REMOVE": - { - await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_BAN_REMOVE)").ConfigureAwait(false); + var role = guild.RemoveRole(data.RoleId); + if (role != null) + { + if (!guild.IsSynced) + { + await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); + return; + } - var data = (payload as JToken).ToObject(_serializer); - var guild = State.GetGuild(data.GuildId); - if (guild != null) - { - if (!guild.IsSynced) + await TimedInvokeAsync(_roleDeletedEvent, nameof(RoleDeleted), role).ConfigureAwait(false); + } + else + { + await UnknownRoleAsync(type, data.RoleId, guild.Id).ConfigureAwait(false); + return; + } + } + else { - await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); + await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false); return; } - - SocketUser user = State.GetUser(data.User.Id); - if (user == null) - user = SocketUnknownUser.Create(this, State, data.User); - await TimedInvokeAsync(_userUnbannedEvent, nameof(UserUnbanned), user, guild).ConfigureAwait(false); - } - else - { - await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false); - return; } - } - break; + break; #endregion - #region Messages - case "MESSAGE_CREATE": - { - await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_CREATE)").ConfigureAwait(false); - - var data = (payload as JToken).ToObject(_serializer); - var channel = GetChannel(data.ChannelId) as ISocketMessageChannel; - - var guild = (channel as SocketGuildChannel)?.Guild; - if (guild != null && !guild.IsSynced) + #region Bans + case "GUILD_BAN_ADD": { - await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); - return; - } + await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_BAN_ADD)").ConfigureAwait(false); - if (channel == null) - { - if (!data.GuildId.IsSpecified) // assume it is a DM + var data = (payload as JToken).ToObject(_serializer); + var guild = State.GetGuild(data.GuildId); + if (guild != null) { - channel = CreateDMChannel(data.ChannelId, data.Author.Value, State); + if (!guild.IsSynced) + { + await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); + return; + } + + SocketUser user = guild.GetUser(data.User.Id); + if (user == null) + user = SocketUnknownUser.Create(this, State, data.User); + await TimedInvokeAsync(_userBannedEvent, nameof(UserBanned), user, guild).ConfigureAwait(false); } else { - await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); + await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false); return; } } - - SocketUser author; - if (guild != null) + break; + case "GUILD_BAN_REMOVE": { - if (data.WebhookId.IsSpecified) - author = SocketWebhookUser.Create(guild, State, data.Author.Value, data.WebhookId.Value); - else - author = guild.GetUser(data.Author.Value.Id); - } - else - author = (channel as SocketChannel).GetUser(data.Author.Value.Id); + await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_BAN_REMOVE)").ConfigureAwait(false); - if (author == null) - { + var data = (payload as JToken).ToObject(_serializer); + var guild = State.GetGuild(data.GuildId); if (guild != null) { - if (data.Member.IsSpecified) // member isn't always included, but use it when we can + if (!guild.IsSynced) { - data.Member.Value.User = data.Author.Value; - author = guild.AddOrUpdateUser(data.Member.Value); + await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); + return; } - else - author = guild.AddOrUpdateUser(data.Author.Value); // user has no guild-specific data + + SocketUser user = State.GetUser(data.User.Id); + if (user == null) + user = SocketUnknownUser.Create(this, State, data.User); + await TimedInvokeAsync(_userUnbannedEvent, nameof(UserUnbanned), user, guild).ConfigureAwait(false); } - else if (channel is SocketGroupChannel groupChannel) - author = groupChannel.GetOrAddUser(data.Author.Value); else { - await UnknownChannelUserAsync(type, data.Author.Value.Id, channel.Id).ConfigureAwait(false); + await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false); return; } } + break; + #endregion - var msg = SocketMessage.Create(this, State, author, channel, data); - SocketChannelHelper.AddMessage(channel, this, msg); - await TimedInvokeAsync(_messageReceivedEvent, nameof(MessageReceived), msg).ConfigureAwait(false); - } - break; - case "MESSAGE_UPDATE": - { - await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_UPDATE)").ConfigureAwait(false); + #region Messages + case "MESSAGE_CREATE": + { + await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_CREATE)").ConfigureAwait(false); - var data = (payload as JToken).ToObject(_serializer); - var channel = GetChannel(data.ChannelId) as ISocketMessageChannel; + var data = (payload as JToken).ToObject(_serializer); + var channel = GetChannel(data.ChannelId) as ISocketMessageChannel; - var guild = (channel as SocketGuildChannel)?.Guild; - if (guild != null && !guild.IsSynced) - { - await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); - return; - } + var guild = (channel as SocketGuildChannel)?.Guild; + if (guild != null && !guild.IsSynced) + { + await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); + return; + } - SocketMessage before = null, after = null; - SocketMessage cachedMsg = channel?.GetCachedMessage(data.Id); - bool isCached = cachedMsg != null; - if (isCached) - { - before = cachedMsg.Clone(); - cachedMsg.Update(State, data); - after = cachedMsg; - } - else - { - //Edited message isn't in cache, create a detached one - SocketUser author; - if (data.Author.IsSpecified) + if (channel == null) { - if (guild != null) + if (!data.GuildId.IsSpecified) // assume it is a DM { - if (data.WebhookId.IsSpecified) - author = SocketWebhookUser.Create(guild, State, data.Author.Value, data.WebhookId.Value); - else - author = guild.GetUser(data.Author.Value.Id); + channel = CreateDMChannel(data.ChannelId, data.Author.Value, State); } else - author = (channel as SocketChannel)?.GetUser(data.Author.Value.Id); - - if (author == null) { - if (guild != null) - { - if (data.Member.IsSpecified) // member isn't always included, but use it when we can - { - data.Member.Value.User = data.Author.Value; - author = guild.AddOrUpdateUser(data.Member.Value); - } - else - author = guild.AddOrUpdateUser(data.Author.Value); // user has no guild-specific data - } - else if (channel is SocketGroupChannel groupChannel) - author = groupChannel.GetOrAddUser(data.Author.Value); + await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); + return; } } - else - // Message author wasn't specified in the payload, so create a completely anonymous unknown user - author = new SocketUnknownUser(this, id: 0); - if (channel == null) - { - if (!data.GuildId.IsSpecified) // assume it is a DM + SocketUser author; + if (guild != null) + { + if (data.WebhookId.IsSpecified) + author = SocketWebhookUser.Create(guild, State, data.Author.Value, data.WebhookId.Value); + else + author = guild.GetUser(data.Author.Value.Id); + } + else + author = (channel as SocketChannel).GetUser(data.Author.Value.Id); + + if (author == null) + { + if (guild != null) { - if (data.Author.IsSpecified) + if (data.Member.IsSpecified) // member isn't always included, but use it when we can { - var dmChannel = CreateDMChannel(data.ChannelId, data.Author.Value, State); - channel = dmChannel; - author = dmChannel.Recipient; + data.Member.Value.User = data.Author.Value; + author = guild.AddOrUpdateUser(data.Member.Value); } else - channel = CreateDMChannel(data.ChannelId, author, State); + author = guild.AddOrUpdateUser(data.Author.Value); // user has no guild-specific data } + else if (channel is SocketGroupChannel groupChannel) + author = groupChannel.GetOrAddUser(data.Author.Value); else { - await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); + await UnknownChannelUserAsync(type, data.Author.Value.Id, channel.Id).ConfigureAwait(false); return; } } - after = SocketMessage.Create(this, State, author, channel, data); + var msg = SocketMessage.Create(this, State, author, channel, data); + SocketChannelHelper.AddMessage(channel, this, msg); + await TimedInvokeAsync(_messageReceivedEvent, nameof(MessageReceived), msg).ConfigureAwait(false); } - var cacheableBefore = new Cacheable(before, data.Id, isCached, async () => await channel.GetMessageAsync(data.Id).ConfigureAwait(false)); + break; + case "MESSAGE_UPDATE": + { + await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_UPDATE)").ConfigureAwait(false); - await TimedInvokeAsync(_messageUpdatedEvent, nameof(MessageUpdated), cacheableBefore, after, channel).ConfigureAwait(false); - } - break; - case "MESSAGE_DELETE": - { - await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_DELETE)").ConfigureAwait(false); + var data = (payload as JToken).ToObject(_serializer); + var channel = GetChannel(data.ChannelId) as ISocketMessageChannel; - var data = (payload as JToken).ToObject(_serializer); - var channel = GetChannel(data.ChannelId) as ISocketMessageChannel; + var guild = (channel as SocketGuildChannel)?.Guild; + if (guild != null && !guild.IsSynced) + { + await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); + return; + } - var guild = (channel as SocketGuildChannel)?.Guild; - if (!(guild?.IsSynced ?? true)) - { - await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); - return; - } + SocketMessage before = null, after = null; + SocketMessage cachedMsg = channel?.GetCachedMessage(data.Id); + bool isCached = cachedMsg != null; + if (isCached) + { + before = cachedMsg.Clone(); + cachedMsg.Update(State, data); + after = cachedMsg; + } + else + { + //Edited message isn't in cache, create a detached one + SocketUser author; + if (data.Author.IsSpecified) + { + if (guild != null) + { + if (data.WebhookId.IsSpecified) + author = SocketWebhookUser.Create(guild, State, data.Author.Value, data.WebhookId.Value); + else + author = guild.GetUser(data.Author.Value.Id); + } + else + author = (channel as SocketChannel)?.GetUser(data.Author.Value.Id); - SocketMessage msg = null; - if (channel != null) - msg = SocketChannelHelper.RemoveMessage(channel, this, data.Id); - var cacheableMsg = new Cacheable(msg, data.Id, msg != null, () => Task.FromResult((IMessage)null)); - var cacheableChannel = new Cacheable(channel, data.ChannelId, channel != null, async () => await GetChannelAsync(data.ChannelId).ConfigureAwait(false) as IMessageChannel); + if (author == null) + { + if (guild != null) + { + if (data.Member.IsSpecified) // member isn't always included, but use it when we can + { + data.Member.Value.User = data.Author.Value; + author = guild.AddOrUpdateUser(data.Member.Value); + } + else + author = guild.AddOrUpdateUser(data.Author.Value); // user has no guild-specific data + } + else if (channel is SocketGroupChannel groupChannel) + author = groupChannel.GetOrAddUser(data.Author.Value); + } + } + else + // Message author wasn't specified in the payload, so create a completely anonymous unknown user + author = new SocketUnknownUser(this, id: 0); - await TimedInvokeAsync(_messageDeletedEvent, nameof(MessageDeleted), cacheableMsg, cacheableChannel).ConfigureAwait(false); - } - break; - case "MESSAGE_REACTION_ADD": - { - await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_REACTION_ADD)").ConfigureAwait(false); + if (channel == null) + { + if (!data.GuildId.IsSpecified) // assume it is a DM + { + if (data.Author.IsSpecified) + { + var dmChannel = CreateDMChannel(data.ChannelId, data.Author.Value, State); + channel = dmChannel; + author = dmChannel.Recipient; + } + else + channel = CreateDMChannel(data.ChannelId, author, State); + } + else + { + await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); + return; + } + } - var data = (payload as JToken).ToObject(_serializer); - var channel = GetChannel(data.ChannelId) as ISocketMessageChannel; + after = SocketMessage.Create(this, State, author, channel, data); + } + var cacheableBefore = new Cacheable(before, data.Id, isCached, async () => await channel.GetMessageAsync(data.Id).ConfigureAwait(false)); - var cachedMsg = channel?.GetCachedMessage(data.MessageId) as SocketUserMessage; - bool isMsgCached = cachedMsg != null; - IUser user = null; - if (channel != null) - user = await channel.GetUserAsync(data.UserId, CacheMode.CacheOnly).ConfigureAwait(false); + await TimedInvokeAsync(_messageUpdatedEvent, nameof(MessageUpdated), cacheableBefore, after, channel).ConfigureAwait(false); + } + break; + case "MESSAGE_DELETE": + { + await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_DELETE)").ConfigureAwait(false); - var optionalMsg = !isMsgCached - ? Optional.Create() - : Optional.Create(cachedMsg); + var data = (payload as JToken).ToObject(_serializer); + var channel = GetChannel(data.ChannelId) as ISocketMessageChannel; - if (data.Member.IsSpecified) - { var guild = (channel as SocketGuildChannel)?.Guild; + if (!(guild?.IsSynced ?? true)) + { + await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); + return; + } - if (guild != null) - user = guild.AddOrUpdateUser(data.Member.Value); + SocketMessage msg = null; + if (channel != null) + msg = SocketChannelHelper.RemoveMessage(channel, this, data.Id); + var cacheableMsg = new Cacheable(msg, data.Id, msg != null, () => Task.FromResult((IMessage)null)); + var cacheableChannel = new Cacheable(channel, data.ChannelId, channel != null, async () => await GetChannelAsync(data.ChannelId).ConfigureAwait(false) as IMessageChannel); + + await TimedInvokeAsync(_messageDeletedEvent, nameof(MessageDeleted), cacheableMsg, cacheableChannel).ConfigureAwait(false); } - else - user = GetUser(data.UserId); + break; + case "MESSAGE_REACTION_ADD": + { + await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_REACTION_ADD)").ConfigureAwait(false); - var optionalUser = user is null - ? Optional.Create() - : Optional.Create(user); + var data = (payload as JToken).ToObject(_serializer); + var channel = GetChannel(data.ChannelId) as ISocketMessageChannel; - var cacheableChannel = new Cacheable(channel, data.ChannelId, channel != null, async () => await GetChannelAsync(data.ChannelId).ConfigureAwait(false) as IMessageChannel); - var cacheableMsg = new Cacheable(cachedMsg, data.MessageId, isMsgCached, async () => - { - var channelObj = await cacheableChannel.GetOrDownloadAsync().ConfigureAwait(false); - return await channelObj.GetMessageAsync(data.MessageId).ConfigureAwait(false) as IUserMessage; - }); - var reaction = SocketReaction.Create(data, channel, optionalMsg, optionalUser); + var cachedMsg = channel?.GetCachedMessage(data.MessageId) as SocketUserMessage; + bool isMsgCached = cachedMsg != null; + IUser user = null; + if (channel != null) + user = await channel.GetUserAsync(data.UserId, CacheMode.CacheOnly).ConfigureAwait(false); - cachedMsg?.AddReaction(reaction); + var optionalMsg = !isMsgCached + ? Optional.Create() + : Optional.Create(cachedMsg); - await TimedInvokeAsync(_reactionAddedEvent, nameof(ReactionAdded), cacheableMsg, cacheableChannel, reaction).ConfigureAwait(false); - } - break; - case "MESSAGE_REACTION_REMOVE": - { - await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_REACTION_REMOVE)").ConfigureAwait(false); + if (data.Member.IsSpecified) + { + var guild = (channel as SocketGuildChannel)?.Guild; - var data = (payload as JToken).ToObject(_serializer); - var channel = GetChannel(data.ChannelId) as ISocketMessageChannel; + if (guild != null) + user = guild.AddOrUpdateUser(data.Member.Value); + } + else + user = GetUser(data.UserId); - var cachedMsg = channel?.GetCachedMessage(data.MessageId) as SocketUserMessage; - bool isMsgCached = cachedMsg != null; - IUser user = null; - if (channel != null) - user = await channel.GetUserAsync(data.UserId, CacheMode.CacheOnly).ConfigureAwait(false); - else if (!data.GuildId.IsSpecified) - user = GetUser(data.UserId); + var optionalUser = user is null + ? Optional.Create() + : Optional.Create(user); - var optionalMsg = !isMsgCached - ? Optional.Create() - : Optional.Create(cachedMsg); + var cacheableChannel = new Cacheable(channel, data.ChannelId, channel != null, async () => await GetChannelAsync(data.ChannelId).ConfigureAwait(false) as IMessageChannel); + var cacheableMsg = new Cacheable(cachedMsg, data.MessageId, isMsgCached, async () => + { + var channelObj = await cacheableChannel.GetOrDownloadAsync().ConfigureAwait(false); + return await channelObj.GetMessageAsync(data.MessageId).ConfigureAwait(false) as IUserMessage; + }); + var reaction = SocketReaction.Create(data, channel, optionalMsg, optionalUser); - var optionalUser = user is null - ? Optional.Create() - : Optional.Create(user); + cachedMsg?.AddReaction(reaction); - var cacheableChannel = new Cacheable(channel, data.ChannelId, channel != null, async () => await GetChannelAsync(data.ChannelId).ConfigureAwait(false) as IMessageChannel); - var cacheableMsg = new Cacheable(cachedMsg, data.MessageId, isMsgCached, async () => + await TimedInvokeAsync(_reactionAddedEvent, nameof(ReactionAdded), cacheableMsg, cacheableChannel, reaction).ConfigureAwait(false); + } + break; + case "MESSAGE_REACTION_REMOVE": { - var channelObj = await cacheableChannel.GetOrDownloadAsync().ConfigureAwait(false); - return await channelObj.GetMessageAsync(data.MessageId).ConfigureAwait(false) as IUserMessage; - }); - var reaction = SocketReaction.Create(data, channel, optionalMsg, optionalUser); + await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_REACTION_REMOVE)").ConfigureAwait(false); - cachedMsg?.RemoveReaction(reaction); + var data = (payload as JToken).ToObject(_serializer); + var channel = GetChannel(data.ChannelId) as ISocketMessageChannel; - await TimedInvokeAsync(_reactionRemovedEvent, nameof(ReactionRemoved), cacheableMsg, cacheableChannel, reaction).ConfigureAwait(false); - } - break; - case "MESSAGE_REACTION_REMOVE_ALL": - { - await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_REACTION_REMOVE_ALL)").ConfigureAwait(false); + var cachedMsg = channel?.GetCachedMessage(data.MessageId) as SocketUserMessage; + bool isMsgCached = cachedMsg != null; + IUser user = null; + if (channel != null) + user = await channel.GetUserAsync(data.UserId, CacheMode.CacheOnly).ConfigureAwait(false); + else if (!data.GuildId.IsSpecified) + user = GetUser(data.UserId); + + var optionalMsg = !isMsgCached + ? Optional.Create() + : Optional.Create(cachedMsg); + + var optionalUser = user is null + ? Optional.Create() + : Optional.Create(user); + + var cacheableChannel = new Cacheable(channel, data.ChannelId, channel != null, async () => await GetChannelAsync(data.ChannelId).ConfigureAwait(false) as IMessageChannel); + var cacheableMsg = new Cacheable(cachedMsg, data.MessageId, isMsgCached, async () => + { + var channelObj = await cacheableChannel.GetOrDownloadAsync().ConfigureAwait(false); + return await channelObj.GetMessageAsync(data.MessageId).ConfigureAwait(false) as IUserMessage; + }); + var reaction = SocketReaction.Create(data, channel, optionalMsg, optionalUser); - var data = (payload as JToken).ToObject(_serializer); - var channel = GetChannel(data.ChannelId) as ISocketMessageChannel; + cachedMsg?.RemoveReaction(reaction); - var cacheableChannel = new Cacheable(channel, data.ChannelId, channel != null, async () => await GetChannelAsync(data.ChannelId).ConfigureAwait(false) as IMessageChannel); - var cachedMsg = channel?.GetCachedMessage(data.MessageId) as SocketUserMessage; - bool isMsgCached = cachedMsg != null; - var cacheableMsg = new Cacheable(cachedMsg, data.MessageId, isMsgCached, async () => + await TimedInvokeAsync(_reactionRemovedEvent, nameof(ReactionRemoved), cacheableMsg, cacheableChannel, reaction).ConfigureAwait(false); + } + break; + case "MESSAGE_REACTION_REMOVE_ALL": { - var channelObj = await cacheableChannel.GetOrDownloadAsync().ConfigureAwait(false); - return await channelObj.GetMessageAsync(data.MessageId).ConfigureAwait(false) as IUserMessage; - }); + await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_REACTION_REMOVE_ALL)").ConfigureAwait(false); - cachedMsg?.ClearReactions(); + var data = (payload as JToken).ToObject(_serializer); + var channel = GetChannel(data.ChannelId) as ISocketMessageChannel; - await TimedInvokeAsync(_reactionsClearedEvent, nameof(ReactionsCleared), cacheableMsg, cacheableChannel).ConfigureAwait(false); - } - break; + var cacheableChannel = new Cacheable(channel, data.ChannelId, channel != null, async () => await GetChannelAsync(data.ChannelId).ConfigureAwait(false) as IMessageChannel); + var cachedMsg = channel?.GetCachedMessage(data.MessageId) as SocketUserMessage; + bool isMsgCached = cachedMsg != null; + var cacheableMsg = new Cacheable(cachedMsg, data.MessageId, isMsgCached, async () => + { + var channelObj = await cacheableChannel.GetOrDownloadAsync().ConfigureAwait(false); + return await channelObj.GetMessageAsync(data.MessageId).ConfigureAwait(false) as IUserMessage; + }); + + cachedMsg?.ClearReactions(); + + await TimedInvokeAsync(_reactionsClearedEvent, nameof(ReactionsCleared), cacheableMsg, cacheableChannel).ConfigureAwait(false); + } + break; case "MESSAGE_REACTION_REMOVE_EMOJI": - { - await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_REACTION_REMOVE_EMOJI)").ConfigureAwait(false); + { + await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_REACTION_REMOVE_EMOJI)").ConfigureAwait(false); - var data = (payload as JToken).ToObject(_serializer); - var channel = GetChannel(data.ChannelId) as ISocketMessageChannel; + var data = (payload as JToken).ToObject(_serializer); + var channel = GetChannel(data.ChannelId) as ISocketMessageChannel; - var cachedMsg = channel?.GetCachedMessage(data.MessageId) as SocketUserMessage; - bool isMsgCached = cachedMsg != null; + var cachedMsg = channel?.GetCachedMessage(data.MessageId) as SocketUserMessage; + bool isMsgCached = cachedMsg != null; - var optionalMsg = !isMsgCached - ? Optional.Create() - : Optional.Create(cachedMsg); + var optionalMsg = !isMsgCached + ? Optional.Create() + : Optional.Create(cachedMsg); - var cacheableChannel = new Cacheable(channel, data.ChannelId, channel != null, async () => await GetChannelAsync(data.ChannelId).ConfigureAwait(false) as IMessageChannel); - var cacheableMsg = new Cacheable(cachedMsg, data.MessageId, isMsgCached, async () => - { - var channelObj = await cacheableChannel.GetOrDownloadAsync().ConfigureAwait(false); - return await channelObj.GetMessageAsync(data.MessageId).ConfigureAwait(false) as IUserMessage; - }); - var emote = data.Emoji.ToIEmote(); + var cacheableChannel = new Cacheable(channel, data.ChannelId, channel != null, async () => await GetChannelAsync(data.ChannelId).ConfigureAwait(false) as IMessageChannel); + var cacheableMsg = new Cacheable(cachedMsg, data.MessageId, isMsgCached, async () => + { + var channelObj = await cacheableChannel.GetOrDownloadAsync().ConfigureAwait(false); + return await channelObj.GetMessageAsync(data.MessageId).ConfigureAwait(false) as IUserMessage; + }); + var emote = data.Emoji.ToIEmote(); - cachedMsg?.RemoveReactionsForEmote(emote); + cachedMsg?.RemoveReactionsForEmote(emote); - await TimedInvokeAsync(_reactionsRemovedForEmoteEvent, nameof(ReactionsRemovedForEmote), cacheableMsg, cacheableChannel, emote).ConfigureAwait(false); - } - break; + await TimedInvokeAsync(_reactionsRemovedForEmoteEvent, nameof(ReactionsRemovedForEmote), cacheableMsg, cacheableChannel, emote).ConfigureAwait(false); + } + break; case "MESSAGE_DELETE_BULK": - { - await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_DELETE_BULK)").ConfigureAwait(false); + { + await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_DELETE_BULK)").ConfigureAwait(false); - var data = (payload as JToken).ToObject(_serializer); - var channel = GetChannel(data.ChannelId) as ISocketMessageChannel; + var data = (payload as JToken).ToObject(_serializer); + var channel = GetChannel(data.ChannelId) as ISocketMessageChannel; - var guild = (channel as SocketGuildChannel)?.Guild; - if (!(guild?.IsSynced ?? true)) - { - await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); - return; - } + var guild = (channel as SocketGuildChannel)?.Guild; + if (!(guild?.IsSynced ?? true)) + { + await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); + return; + } - var cacheableChannel = new Cacheable(channel, data.ChannelId, channel != null, async () => await GetChannelAsync(data.ChannelId).ConfigureAwait(false) as IMessageChannel); - var cacheableList = new List>(data.Ids.Length); - foreach (ulong id in data.Ids) - { - SocketMessage msg = null; - if (channel != null) - msg = SocketChannelHelper.RemoveMessage(channel, this, id); - bool isMsgCached = msg != null; - var cacheableMsg = new Cacheable(msg, id, isMsgCached, () => Task.FromResult((IMessage)null)); - cacheableList.Add(cacheableMsg); - } + var cacheableChannel = new Cacheable(channel, data.ChannelId, channel != null, async () => await GetChannelAsync(data.ChannelId).ConfigureAwait(false) as IMessageChannel); + var cacheableList = new List>(data.Ids.Length); + foreach (ulong id in data.Ids) + { + SocketMessage msg = null; + if (channel != null) + msg = SocketChannelHelper.RemoveMessage(channel, this, id); + bool isMsgCached = msg != null; + var cacheableMsg = new Cacheable(msg, id, isMsgCached, () => Task.FromResult((IMessage)null)); + cacheableList.Add(cacheableMsg); + } - await TimedInvokeAsync(_messagesBulkDeletedEvent, nameof(MessagesBulkDeleted), cacheableList, cacheableChannel).ConfigureAwait(false); - } - break; + await TimedInvokeAsync(_messagesBulkDeletedEvent, nameof(MessagesBulkDeleted), cacheableList, cacheableChannel).ConfigureAwait(false); + } + break; #endregion #region Polls case "MESSAGE_POLL_VOTE_ADD": - { - await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_POLL_VOTE_ADD)").ConfigureAwait(false); - - var data = (payload as JToken).ToObject(_serializer); + { + await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_POLL_VOTE_ADD)").ConfigureAwait(false); - Cacheable? guildCacheable = null; + var data = (payload as JToken).ToObject(_serializer); - Cacheable userCacheable; - Cacheable channelCacheable; - Cacheable messageCacheable; + Cacheable? guildCacheable = null; - if (data.GuildId.IsSpecified) - { - var guild = State.GetGuild(data.GuildId.Value); - guildCacheable = new(guild, data.GuildId.Value, guild is not null, () => Rest.GetGuildAsync(data.GuildId.Value)); + Cacheable userCacheable; + Cacheable channelCacheable; + Cacheable messageCacheable; - if (guild is not null) + if (data.GuildId.IsSpecified) { - var user = guild.GetUser(data.UserId); - userCacheable = new(user, data.UserId, user is not null, async () => await Rest.GetGuildUserAsync(data.GuildId.Value, data.UserId)); + var guild = State.GetGuild(data.GuildId.Value); + guildCacheable = new(guild, data.GuildId.Value, guild is not null, () => Rest.GetGuildAsync(data.GuildId.Value)); - var channel = guild.GetTextChannel(data.ChannelId); - channelCacheable = new(channel, data.ChannelId, channel is not null, async () => (RestTextChannel)await Rest.GetChannelAsync(data.ChannelId)); + if (guild is not null) + { + var user = guild.GetUser(data.UserId); + userCacheable = new(user, data.UserId, user is not null, async () => await Rest.GetGuildUserAsync(data.GuildId.Value, data.UserId)); - var message = channel?.GetCachedMessage(data.MessageId) as IUserMessage; - messageCacheable = new(message, data.MessageId, message is not null, - async () => (channel ?? (ITextChannel)await Rest.GetChannelAsync(data.ChannelId)).GetMessageAsync(data.MessageId) as IUserMessage); + var channel = guild.GetTextChannel(data.ChannelId); + channelCacheable = new(channel, data.ChannelId, channel is not null, async () => (RestTextChannel)await Rest.GetChannelAsync(data.ChannelId)); + + var message = channel?.GetCachedMessage(data.MessageId) as IUserMessage; + messageCacheable = new(message, data.MessageId, message is not null, + async () => (channel ?? (ITextChannel)await Rest.GetChannelAsync(data.ChannelId)).GetMessageAsync(data.MessageId) as IUserMessage); + } + else + { + userCacheable = new(null, data.UserId, false, async () => await Rest.GetGuildUserAsync(data.GuildId.Value, data.UserId)); + channelCacheable = new(null, data.ChannelId, false, async () => (RestTextChannel)(await Rest.GetChannelAsync(data.ChannelId))); + messageCacheable = new(null, data.MessageId, false, + async () => await ((ITextChannel)await Rest.GetChannelAsync(data.ChannelId)).GetMessageAsync(data.MessageId) as IUserMessage); + } } else { - userCacheable = new(null, data.UserId, false, async () => await Rest.GetGuildUserAsync(data.GuildId.Value, data.UserId)); - channelCacheable = new(null, data.ChannelId, false, async () => (RestTextChannel)(await Rest.GetChannelAsync(data.ChannelId))); - messageCacheable = new(null, data.MessageId, false, - async () => await ((ITextChannel)await Rest.GetChannelAsync(data.ChannelId)).GetMessageAsync(data.MessageId) as IUserMessage); - } - } - else - { - var user = State.GetUser(data.UserId); - userCacheable = new(user, data.UserId, user is not null, async () => await GetUserAsync(data.UserId)); + var user = State.GetUser(data.UserId); + userCacheable = new(user, data.UserId, user is not null, async () => await GetUserAsync(data.UserId)); - var channel = State.GetChannel(data.ChannelId) as ISocketMessageChannel; - channelCacheable = new(channel, data.ChannelId, channel is not null, async () => await Rest.GetDMChannelAsync(data.ChannelId) as IRestMessageChannel); + var channel = State.GetChannel(data.ChannelId) as ISocketMessageChannel; + channelCacheable = new(channel, data.ChannelId, channel is not null, async () => await Rest.GetDMChannelAsync(data.ChannelId) as IRestMessageChannel); - var message = channel?.GetCachedMessage(data.MessageId) as IUserMessage; - messageCacheable = new(message, data.MessageId, message is not null, async () => await (channel ?? (IMessageChannel)await Rest.GetDMChannelAsync(data.ChannelId)).GetMessageAsync(data.MessageId) as IUserMessage); - } + var message = channel?.GetCachedMessage(data.MessageId) as IUserMessage; + messageCacheable = new(message, data.MessageId, message is not null, async () => await (channel ?? (IMessageChannel)await Rest.GetDMChannelAsync(data.ChannelId)).GetMessageAsync(data.MessageId) as IUserMessage); + } - await TimedInvokeAsync(_pollVoteAdded, nameof(PollVoteAdded), userCacheable, channelCacheable, messageCacheable, guildCacheable, data.AnswerId); - } - break; + await TimedInvokeAsync(_pollVoteAdded, nameof(PollVoteAdded), userCacheable, channelCacheable, messageCacheable, guildCacheable, data.AnswerId); + } + break; case "MESSAGE_POLL_VOTE_REMOVE": - { - await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_POLL_VOTE_REMOVE)").ConfigureAwait(false); - - var data = (payload as JToken).ToObject(_serializer); + { + await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_POLL_VOTE_REMOVE)").ConfigureAwait(false); - Cacheable? guildCacheable = null; + var data = (payload as JToken).ToObject(_serializer); - Cacheable userCacheable; - Cacheable channelCacheable; - Cacheable messageCacheable; + Cacheable? guildCacheable = null; - if (data.GuildId.IsSpecified) - { - var guild = State.GetGuild(data.GuildId.Value); - guildCacheable = new(guild, data.GuildId.Value, guild is not null, () => Rest.GetGuildAsync(data.GuildId.Value)); + Cacheable userCacheable; + Cacheable channelCacheable; + Cacheable messageCacheable; - if (guild is not null) + if (data.GuildId.IsSpecified) { - var user = guild.GetUser(data.UserId); - userCacheable = new(user, data.UserId, user is not null, async () => await Rest.GetGuildUserAsync(data.GuildId.Value, data.UserId)); + var guild = State.GetGuild(data.GuildId.Value); + guildCacheable = new(guild, data.GuildId.Value, guild is not null, () => Rest.GetGuildAsync(data.GuildId.Value)); + + if (guild is not null) + { + var user = guild.GetUser(data.UserId); + userCacheable = new(user, data.UserId, user is not null, async () => await Rest.GetGuildUserAsync(data.GuildId.Value, data.UserId)); - var channel = guild.GetTextChannel(data.ChannelId); - channelCacheable = new(channel, data.ChannelId, channel is not null, async () => (RestTextChannel)await Rest.GetChannelAsync(data.ChannelId)); + var channel = guild.GetTextChannel(data.ChannelId); + channelCacheable = new(channel, data.ChannelId, channel is not null, async () => (RestTextChannel)await Rest.GetChannelAsync(data.ChannelId)); - var message = channel?.GetCachedMessage(data.MessageId) as IUserMessage; - messageCacheable = new(message, data.MessageId, message is not null, - async () => (channel ?? (ITextChannel)await Rest.GetChannelAsync(data.ChannelId)).GetMessageAsync(data.MessageId) as IUserMessage); + var message = channel?.GetCachedMessage(data.MessageId) as IUserMessage; + messageCacheable = new(message, data.MessageId, message is not null, + async () => (channel ?? (ITextChannel)await Rest.GetChannelAsync(data.ChannelId)).GetMessageAsync(data.MessageId) as IUserMessage); + } + else + { + userCacheable = new(null, data.UserId, false, async () => await Rest.GetGuildUserAsync(data.GuildId.Value, data.UserId)); + channelCacheable = new(null, data.ChannelId, false, async () => (RestTextChannel)(await Rest.GetChannelAsync(data.ChannelId))); + messageCacheable = new(null, data.MessageId, false, + async () => await ((ITextChannel)await Rest.GetChannelAsync(data.ChannelId)).GetMessageAsync(data.MessageId) as IUserMessage); + } } else { - userCacheable = new(null, data.UserId, false, async () => await Rest.GetGuildUserAsync(data.GuildId.Value, data.UserId)); - channelCacheable = new(null, data.ChannelId, false, async () => (RestTextChannel)(await Rest.GetChannelAsync(data.ChannelId))); - messageCacheable = new(null, data.MessageId, false, - async () => await ((ITextChannel)await Rest.GetChannelAsync(data.ChannelId)).GetMessageAsync(data.MessageId) as IUserMessage); - } - } - else - { - var user = State.GetUser(data.UserId); - userCacheable = new(user, data.UserId, user is not null, async () => await GetUserAsync(data.UserId)); + var user = State.GetUser(data.UserId); + userCacheable = new(user, data.UserId, user is not null, async () => await GetUserAsync(data.UserId)); - var channel = State.GetChannel(data.ChannelId) as ISocketMessageChannel; - channelCacheable = new(channel, data.ChannelId, channel is not null, async () => await Rest.GetDMChannelAsync(data.ChannelId) as IRestMessageChannel); + var channel = State.GetChannel(data.ChannelId) as ISocketMessageChannel; + channelCacheable = new(channel, data.ChannelId, channel is not null, async () => await Rest.GetDMChannelAsync(data.ChannelId) as IRestMessageChannel); - var message = channel?.GetCachedMessage(data.MessageId) as IUserMessage; - messageCacheable = new(message, data.MessageId, message is not null, async () => await (channel ?? (IMessageChannel)await Rest.GetDMChannelAsync(data.ChannelId)).GetMessageAsync(data.MessageId) as IUserMessage); - } + var message = channel?.GetCachedMessage(data.MessageId) as IUserMessage; + messageCacheable = new(message, data.MessageId, message is not null, async () => await (channel ?? (IMessageChannel)await Rest.GetDMChannelAsync(data.ChannelId)).GetMessageAsync(data.MessageId) as IUserMessage); + } - await TimedInvokeAsync(_pollVoteRemoved, nameof(PollVoteRemoved), userCacheable, channelCacheable, messageCacheable, guildCacheable, data.AnswerId); - } - break; + await TimedInvokeAsync(_pollVoteRemoved, nameof(PollVoteRemoved), userCacheable, channelCacheable, messageCacheable, guildCacheable, data.AnswerId); + } + break; #endregion #region Statuses case "PRESENCE_UPDATE": - { - await _gatewayLogger.DebugAsync("Received Dispatch (PRESENCE_UPDATE)").ConfigureAwait(false); - - var data = (payload as JToken).ToObject(_serializer); + { + await _gatewayLogger.DebugAsync("Received Dispatch (PRESENCE_UPDATE)").ConfigureAwait(false); - SocketUser user = null; + var data = (payload as JToken).ToObject(_serializer); - if (data.GuildId.IsSpecified) - { - var guild = State.GetGuild(data.GuildId.Value); - if (guild == null) - { - await UnknownGuildAsync(type, data.GuildId.Value).ConfigureAwait(false); - return; - } - if (!guild.IsSynced) - { - await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); - return; - } + SocketUser user = null; - user = guild.GetUser(data.User.Id); - if (user == null) + if (data.GuildId.IsSpecified) { - if (data.Status == UserStatus.Offline) + var guild = State.GetGuild(data.GuildId.Value); + if (guild == null) + { + await UnknownGuildAsync(type, data.GuildId.Value).ConfigureAwait(false); + return; + } + if (!guild.IsSynced) { + await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); return; } - user = guild.AddOrUpdateUser(data); + + user = guild.GetUser(data.User.Id); + if (user == null) + { + if (data.Status == UserStatus.Offline) + { + return; + } + user = guild.AddOrUpdateUser(data); + } + else + { + var globalBefore = user.GlobalUser.Clone(); + if (user.GlobalUser.Update(State, data.User)) + { + //Global data was updated, trigger UserUpdated + await TimedInvokeAsync(_userUpdatedEvent, nameof(UserUpdated), globalBefore, user).ConfigureAwait(false); + } + } } else { - var globalBefore = user.GlobalUser.Clone(); - if (user.GlobalUser.Update(State, data.User)) + user = State.GetUser(data.User.Id); + if (user == null) { - //Global data was updated, trigger UserUpdated - await TimedInvokeAsync(_userUpdatedEvent, nameof(UserUpdated), globalBefore, user).ConfigureAwait(false); + await UnknownGlobalUserAsync(type, data.User.Id).ConfigureAwait(false); + return; } } + + var before = user.Presence?.Clone(); + user.Update(State, data.User); + user.Update(data); + await TimedInvokeAsync(_presenceUpdated, nameof(PresenceUpdated), user, before, user.Presence).ConfigureAwait(false); } - else + break; + case "TYPING_START": { - user = State.GetUser(data.User.Id); - if (user == null) + await _gatewayLogger.DebugAsync("Received Dispatch (TYPING_START)").ConfigureAwait(false); + + var data = (payload as JToken).ToObject(_serializer); + var channel = GetChannel(data.ChannelId) as ISocketMessageChannel; + + var guild = (channel as SocketGuildChannel)?.Guild; + if (!(guild?.IsSynced ?? true)) { - await UnknownGlobalUserAsync(type, data.User.Id).ConfigureAwait(false); + await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); return; } - } - - var before = user.Presence?.Clone(); - user.Update(State, data.User); - user.Update(data); - await TimedInvokeAsync(_presenceUpdated, nameof(PresenceUpdated), user, before, user.Presence).ConfigureAwait(false); - } - break; - case "TYPING_START": - { - await _gatewayLogger.DebugAsync("Received Dispatch (TYPING_START)").ConfigureAwait(false); - var data = (payload as JToken).ToObject(_serializer); - var channel = GetChannel(data.ChannelId) as ISocketMessageChannel; - - var guild = (channel as SocketGuildChannel)?.Guild; - if (!(guild?.IsSynced ?? true)) - { - await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); - return; - } + var cacheableChannel = new Cacheable(channel, data.ChannelId, channel != null, async () => await GetChannelAsync(data.ChannelId).ConfigureAwait(false) as IMessageChannel); - var cacheableChannel = new Cacheable(channel, data.ChannelId, channel != null, async () => await GetChannelAsync(data.ChannelId).ConfigureAwait(false) as IMessageChannel); + var user = (channel as SocketChannel)?.GetUser(data.UserId); + if (user == null) + { + if (guild != null && data.Member.IsSpecified) + user = guild.AddOrUpdateUser(data.Member.Value); + } + var cacheableUser = new Cacheable(user, data.UserId, user != null, async () => await GetUserAsync(data.UserId).ConfigureAwait(false)); - var user = (channel as SocketChannel)?.GetUser(data.UserId); - if (user == null) - { - if (guild != null && data.Member.IsSpecified) - user = guild.AddOrUpdateUser(data.Member.Value); + await TimedInvokeAsync(_userIsTypingEvent, nameof(UserIsTyping), cacheableUser, cacheableChannel).ConfigureAwait(false); } - var cacheableUser = new Cacheable(user, data.UserId, user != null, async () => await GetUserAsync(data.UserId).ConfigureAwait(false)); - - await TimedInvokeAsync(_userIsTypingEvent, nameof(UserIsTyping), cacheableUser, cacheableChannel).ConfigureAwait(false); - } - break; + break; #endregion #region Integrations case "INTEGRATION_CREATE": - { - await _gatewayLogger.DebugAsync("Received Dispatch (INTEGRATION_CREATE)").ConfigureAwait(false); + { + await _gatewayLogger.DebugAsync("Received Dispatch (INTEGRATION_CREATE)").ConfigureAwait(false); - var data = (payload as JToken).ToObject(_serializer); + var data = (payload as JToken).ToObject(_serializer); - // Integrations from Gateway should always have guild IDs specified. - if (!data.GuildId.IsSpecified) - return; + // Integrations from Gateway should always have guild IDs specified. + if (!data.GuildId.IsSpecified) + return; - var guild = State.GetGuild(data.GuildId.Value); + var guild = State.GetGuild(data.GuildId.Value); - if (guild != null) - { - if (!guild.IsSynced) + if (guild != null) { - await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); + if (!guild.IsSynced) + { + await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); + return; + } + + await TimedInvokeAsync(_integrationCreated, nameof(IntegrationCreated), RestIntegration.Create(this, guild, data)).ConfigureAwait(false); + } + else + { + await UnknownGuildAsync(type, data.GuildId.Value).ConfigureAwait(false); return; } - - await TimedInvokeAsync(_integrationCreated, nameof(IntegrationCreated), RestIntegration.Create(this, guild, data)).ConfigureAwait(false); - } - else - { - await UnknownGuildAsync(type, data.GuildId.Value).ConfigureAwait(false); - return; } - } - break; + break; case "INTEGRATION_UPDATE": - { - await _gatewayLogger.DebugAsync("Received Dispatch (INTEGRATION_UPDATE)").ConfigureAwait(false); + { + await _gatewayLogger.DebugAsync("Received Dispatch (INTEGRATION_UPDATE)").ConfigureAwait(false); - var data = (payload as JToken).ToObject(_serializer); + var data = (payload as JToken).ToObject(_serializer); - // Integrations from Gateway should always have guild IDs specified. - if (!data.GuildId.IsSpecified) - return; + // Integrations from Gateway should always have guild IDs specified. + if (!data.GuildId.IsSpecified) + return; - var guild = State.GetGuild(data.GuildId.Value); + var guild = State.GetGuild(data.GuildId.Value); - if (guild != null) - { - if (!guild.IsSynced) + if (guild != null) { - await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); - return; - } - - await TimedInvokeAsync(_integrationUpdated, nameof(IntegrationUpdated), RestIntegration.Create(this, guild, data)).ConfigureAwait(false); - } - else - { - await UnknownGuildAsync(type, data.GuildId.Value).ConfigureAwait(false); - return; - } - } - break; - case "INTEGRATION_DELETE": - { - await _gatewayLogger.DebugAsync("Received Dispatch (INTEGRATION_DELETE)").ConfigureAwait(false); - - var data = (payload as JToken).ToObject(_serializer); - - var guild = State.GetGuild(data.GuildId); + if (!guild.IsSynced) + { + await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); + return; + } - if (guild != null) - { - if (!guild.IsSynced) + await TimedInvokeAsync(_integrationUpdated, nameof(IntegrationUpdated), RestIntegration.Create(this, guild, data)).ConfigureAwait(false); + } + else { - await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); + await UnknownGuildAsync(type, data.GuildId.Value).ConfigureAwait(false); return; } - - await TimedInvokeAsync(_integrationDeleted, nameof(IntegrationDeleted), guild, data.Id, data.ApplicationID).ConfigureAwait(false); - } - else - { - await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false); - return; } - } - break; - #endregion - - #region Users - case "USER_UPDATE": - { - await _gatewayLogger.DebugAsync("Received Dispatch (USER_UPDATE)").ConfigureAwait(false); - - var data = (payload as JToken).ToObject(_serializer); - if (data.Id == CurrentUser.Id) - { - var before = CurrentUser.Clone(); - CurrentUser.Update(State, data); - await TimedInvokeAsync(_selfUpdatedEvent, nameof(CurrentUserUpdated), before, CurrentUser).ConfigureAwait(false); - } - else + break; + case "INTEGRATION_DELETE": { - await _gatewayLogger.WarningAsync("Received USER_UPDATE for wrong user.").ConfigureAwait(false); - return; - } - } - break; - #endregion + await _gatewayLogger.DebugAsync("Received Dispatch (INTEGRATION_DELETE)").ConfigureAwait(false); - #region Voice - case "VOICE_STATE_UPDATE": - { - await _gatewayLogger.DebugAsync("Received Dispatch (VOICE_STATE_UPDATE)").ConfigureAwait(false); + var data = (payload as JToken).ToObject(_serializer); - var data = (payload as JToken).ToObject(_serializer); - SocketUser user; - SocketVoiceState before, after; - if (data.GuildId != null) - { - var guild = State.GetGuild(data.GuildId.Value); - if (guild == null) - { - await UnknownGuildAsync(type, data.GuildId.Value).ConfigureAwait(false); - return; - } - else if (!guild.IsSynced) - { - await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); - return; - } + var guild = State.GetGuild(data.GuildId); - if (data.ChannelId != null) + if (guild != null) { - before = guild.GetVoiceState(data.UserId)?.Clone() ?? SocketVoiceState.Default; - after = await guild.AddOrUpdateVoiceStateAsync(State, data).ConfigureAwait(false); - /*if (data.UserId == CurrentUser.Id) + if (!guild.IsSynced) { - var _ = guild.FinishJoinAudioChannel().ConfigureAwait(false); - }*/ + await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); + return; + } + + await TimedInvokeAsync(_integrationDeleted, nameof(IntegrationDeleted), guild, data.Id, data.ApplicationID).ConfigureAwait(false); } else { - before = await guild.RemoveVoiceStateAsync(data.UserId).ConfigureAwait(false) ?? SocketVoiceState.Default; - after = SocketVoiceState.Create(null, data); - } - - //Per g250k, this should always be sent, but apparently not always - user = guild.GetUser(data.UserId) - ?? (data.Member.IsSpecified ? guild.AddOrUpdateUser(data.Member.Value) : null); - if (user == null) - { - await UnknownGuildUserAsync(type, data.UserId, guild.Id).ConfigureAwait(false); + await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false); return; } } - else + break; + #endregion + + #region Users + case "USER_UPDATE": { - var groupChannel = GetChannel(data.ChannelId.Value) as SocketGroupChannel; - if (groupChannel == null) - { - await UnknownChannelAsync(type, data.ChannelId.Value).ConfigureAwait(false); - return; - } - if (data.ChannelId != null) + await _gatewayLogger.DebugAsync("Received Dispatch (USER_UPDATE)").ConfigureAwait(false); + + var data = (payload as JToken).ToObject(_serializer); + if (data.Id == CurrentUser.Id) { - before = groupChannel.GetVoiceState(data.UserId)?.Clone() ?? SocketVoiceState.Default; - after = groupChannel.AddOrUpdateVoiceState(State, data); + var before = CurrentUser.Clone(); + CurrentUser.Update(State, data); + await TimedInvokeAsync(_selfUpdatedEvent, nameof(CurrentUserUpdated), before, CurrentUser).ConfigureAwait(false); } else { - before = groupChannel.RemoveVoiceState(data.UserId) ?? SocketVoiceState.Default; - after = SocketVoiceState.Create(null, data); - } - user = groupChannel.GetUser(data.UserId); - if (user == null) - { - await UnknownChannelUserAsync(type, data.UserId, groupChannel.Id).ConfigureAwait(false); + await _gatewayLogger.WarningAsync("Received USER_UPDATE for wrong user.").ConfigureAwait(false); return; } } + break; + #endregion - if (user is SocketGuildUser guildUser && data.ChannelId.HasValue) + #region Voice + case "VOICE_STATE_UPDATE": { - SocketStageChannel stage = guildUser.Guild.GetStageChannel(data.ChannelId.Value); + await _gatewayLogger.DebugAsync("Received Dispatch (VOICE_STATE_UPDATE)").ConfigureAwait(false); - if (stage != null && before.VoiceChannel != null && after.VoiceChannel != null) + var data = (payload as JToken).ToObject(_serializer); + SocketUser user; + SocketVoiceState before, after; + if (data.GuildId != null) { - if (!before.RequestToSpeakTimestamp.HasValue && after.RequestToSpeakTimestamp.HasValue) + var guild = State.GetGuild(data.GuildId.Value); + if (guild == null) { - await TimedInvokeAsync(_requestToSpeak, nameof(RequestToSpeak), stage, guildUser); + await UnknownGuildAsync(type, data.GuildId.Value).ConfigureAwait(false); return; } - if (before.IsSuppressed && !after.IsSuppressed) + else if (!guild.IsSynced) { - await TimedInvokeAsync(_speakerAdded, nameof(SpeakerAdded), stage, guildUser); + await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); return; } - if (!before.IsSuppressed && after.IsSuppressed) + + if (data.ChannelId != null) { - await TimedInvokeAsync(_speakerRemoved, nameof(SpeakerRemoved), stage, guildUser); + before = guild.GetVoiceState(data.UserId)?.Clone() ?? SocketVoiceState.Default; + after = await guild.AddOrUpdateVoiceStateAsync(State, data).ConfigureAwait(false); + /*if (data.UserId == CurrentUser.Id) + { + var _ = guild.FinishJoinAudioChannel().ConfigureAwait(false); + }*/ + } + else + { + before = await guild.RemoveVoiceStateAsync(data.UserId).ConfigureAwait(false) ?? SocketVoiceState.Default; + after = SocketVoiceState.Create(null, data); } - } - } - await TimedInvokeAsync(_userVoiceStateUpdatedEvent, nameof(UserVoiceStateUpdated), user, before, after).ConfigureAwait(false); - } - break; - case "VOICE_SERVER_UPDATE": - { - await _gatewayLogger.DebugAsync("Received Dispatch (VOICE_SERVER_UPDATE)").ConfigureAwait(false); + //Per g250k, this should always be sent, but apparently not always + user = guild.GetUser(data.UserId) + ?? (data.Member.IsSpecified ? guild.AddOrUpdateUser(data.Member.Value) : null); + if (user == null) + { + await UnknownGuildUserAsync(type, data.UserId, guild.Id).ConfigureAwait(false); + return; + } + } + else + { + var groupChannel = GetChannel(data.ChannelId.Value) as SocketGroupChannel; + if (groupChannel == null) + { + await UnknownChannelAsync(type, data.ChannelId.Value).ConfigureAwait(false); + return; + } + if (data.ChannelId != null) + { + before = groupChannel.GetVoiceState(data.UserId)?.Clone() ?? SocketVoiceState.Default; + after = groupChannel.AddOrUpdateVoiceState(State, data); + } + else + { + before = groupChannel.RemoveVoiceState(data.UserId) ?? SocketVoiceState.Default; + after = SocketVoiceState.Create(null, data); + } + user = groupChannel.GetUser(data.UserId); + if (user == null) + { + await UnknownChannelUserAsync(type, data.UserId, groupChannel.Id).ConfigureAwait(false); + return; + } + } - var data = (payload as JToken).ToObject(_serializer); - var guild = State.GetGuild(data.GuildId); - var isCached = guild != null; - var cachedGuild = new Cacheable(guild, data.GuildId, isCached, - () => Task.FromResult(State.GetGuild(data.GuildId) as IGuild)); + if (user is SocketGuildUser guildUser && data.ChannelId.HasValue) + { + SocketStageChannel stage = guildUser.Guild.GetStageChannel(data.ChannelId.Value); - var voiceServer = new SocketVoiceServer(cachedGuild, data.Endpoint, data.Token); - await TimedInvokeAsync(_voiceServerUpdatedEvent, nameof(UserVoiceStateUpdated), voiceServer).ConfigureAwait(false); + if (stage != null && before.VoiceChannel != null && after.VoiceChannel != null) + { + if (!before.RequestToSpeakTimestamp.HasValue && after.RequestToSpeakTimestamp.HasValue) + { + await TimedInvokeAsync(_requestToSpeak, nameof(RequestToSpeak), stage, guildUser); + return; + } + if (before.IsSuppressed && !after.IsSuppressed) + { + await TimedInvokeAsync(_speakerAdded, nameof(SpeakerAdded), stage, guildUser); + return; + } + if (!before.IsSuppressed && after.IsSuppressed) + { + await TimedInvokeAsync(_speakerRemoved, nameof(SpeakerRemoved), stage, guildUser); + } + } + } - if (isCached) - { - var _ = guild.FinishConnectAudio(data.Endpoint, data.Token).ConfigureAwait(false); + await TimedInvokeAsync(_userVoiceStateUpdatedEvent, nameof(UserVoiceStateUpdated), user, before, after).ConfigureAwait(false); } - else + break; + case "VOICE_SERVER_UPDATE": { - await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false); - } + await _gatewayLogger.DebugAsync("Received Dispatch (VOICE_SERVER_UPDATE)").ConfigureAwait(false); - } - break; + var data = (payload as JToken).ToObject(_serializer); + var guild = State.GetGuild(data.GuildId); + var isCached = guild != null; + var cachedGuild = new Cacheable(guild, data.GuildId, isCached, + () => Task.FromResult(State.GetGuild(data.GuildId) as IGuild)); + + var voiceServer = new SocketVoiceServer(cachedGuild, data.Endpoint, data.Token); + await TimedInvokeAsync(_voiceServerUpdatedEvent, nameof(UserVoiceStateUpdated), voiceServer).ConfigureAwait(false); + + if (isCached) + { + var _ = guild.FinishConnectAudio(data.Endpoint, data.Token).ConfigureAwait(false); + } + else + { + await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false); + } + + } + break; case "VOICE_CHANNEL_STATUS_UPDATE": - { - await _gatewayLogger.DebugAsync("Received Dispatch (VOICE_CHANNEL_STATUS_UPDATE)").ConfigureAwait(false); + { + await _gatewayLogger.DebugAsync("Received Dispatch (VOICE_CHANNEL_STATUS_UPDATE)").ConfigureAwait(false); - var data = (payload as JToken).ToObject(_serializer); - var guild = State.GetGuild(data.GuildId); + var data = (payload as JToken).ToObject(_serializer); + var guild = State.GetGuild(data.GuildId); - var channel = State.GetChannel(data.Id) as SocketVoiceChannel; - var channelCacheable = new Cacheable(channel, data.Id, channel is not null, () => null); + var channel = State.GetChannel(data.Id) as SocketVoiceChannel; + var channelCacheable = new Cacheable(channel, data.Id, channel is not null, () => null); - var before = (string)channel?.Status?.Clone(); - var after = data.Status; - channel?.UpdateVoiceStatus(data.Status); + var before = (string)channel?.Status?.Clone(); + var after = data.Status; + channel?.UpdateVoiceStatus(data.Status); - await TimedInvokeAsync(_voiceChannelStatusUpdated, nameof(VoiceChannelStatusUpdated), channelCacheable, before, after); - } - break; + await TimedInvokeAsync(_voiceChannelStatusUpdated, nameof(VoiceChannelStatusUpdated), channelCacheable, before, after); + } + break; #endregion #region Invites case "INVITE_CREATE": - { - await _gatewayLogger.DebugAsync("Received Dispatch (INVITE_CREATE)").ConfigureAwait(false); - - var data = (payload as JToken).ToObject(_serializer); - if (State.GetChannel(data.ChannelId) is SocketGuildChannel channel) { - var guild = channel.Guild; - if (!guild.IsSynced) + await _gatewayLogger.DebugAsync("Received Dispatch (INVITE_CREATE)").ConfigureAwait(false); + + var data = (payload as JToken).ToObject(_serializer); + if (State.GetChannel(data.ChannelId) is SocketGuildChannel channel) { - await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); - return; - } + var guild = channel.Guild; + if (!guild.IsSynced) + { + await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); + return; + } - SocketGuildUser inviter = data.Inviter.IsSpecified - ? (guild.GetUser(data.Inviter.Value.Id) ?? guild.AddOrUpdateUser(data.Inviter.Value)) - : null; + SocketGuildUser inviter = data.Inviter.IsSpecified + ? (guild.GetUser(data.Inviter.Value.Id) ?? guild.AddOrUpdateUser(data.Inviter.Value)) + : null; - SocketUser target = data.TargetUser.IsSpecified - ? (guild.GetUser(data.TargetUser.Value.Id) ?? (SocketUser)SocketUnknownUser.Create(this, State, data.TargetUser.Value)) - : null; + SocketUser target = data.TargetUser.IsSpecified + ? (guild.GetUser(data.TargetUser.Value.Id) ?? (SocketUser)SocketUnknownUser.Create(this, State, data.TargetUser.Value)) + : null; - var invite = SocketInvite.Create(this, guild, channel, inviter, target, data); + var invite = SocketInvite.Create(this, guild, channel, inviter, target, data); - await TimedInvokeAsync(_inviteCreatedEvent, nameof(InviteCreated), invite).ConfigureAwait(false); - } - else - { - await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); - return; + await TimedInvokeAsync(_inviteCreatedEvent, nameof(InviteCreated), invite).ConfigureAwait(false); + } + else + { + await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); + return; + } } - } - break; + break; case "INVITE_DELETE": - { - await _gatewayLogger.DebugAsync("Received Dispatch (INVITE_DELETE)").ConfigureAwait(false); - - var data = (payload as JToken).ToObject(_serializer); - if (State.GetChannel(data.ChannelId) is SocketGuildChannel channel) { - var guild = channel.Guild; - if (!guild.IsSynced) + await _gatewayLogger.DebugAsync("Received Dispatch (INVITE_DELETE)").ConfigureAwait(false); + + var data = (payload as JToken).ToObject(_serializer); + if (State.GetChannel(data.ChannelId) is SocketGuildChannel channel) { - await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); + var guild = channel.Guild; + if (!guild.IsSynced) + { + await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); + return; + } + + await TimedInvokeAsync(_inviteDeletedEvent, nameof(InviteDeleted), channel, data.Code).ConfigureAwait(false); + } + else + { + await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); return; } - - await TimedInvokeAsync(_inviteDeletedEvent, nameof(InviteDeleted), channel, data.Code).ConfigureAwait(false); - } - else - { - await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); - return; } - } - break; + break; #endregion #region Interactions case "INTERACTION_CREATE": - { - await _gatewayLogger.DebugAsync("Received Dispatch (INTERACTION_CREATE)").ConfigureAwait(false); - - var data = (payload as JToken).ToObject(_serializer); + { + await _gatewayLogger.DebugAsync("Received Dispatch (INTERACTION_CREATE)").ConfigureAwait(false); - var guild = data.GuildId.IsSpecified ? GetGuild(data.GuildId.Value) : null; + var data = (payload as JToken).ToObject(_serializer); - if (guild != null && !guild.IsSynced) - { - await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); - } + var guild = data.GuildId.IsSpecified ? GetGuild(data.GuildId.Value) : null; - SocketUser user = data.User.IsSpecified - ? State.GetOrAddUser(data.User.Value.Id, (_) => SocketGlobalUser.Create(this, State, data.User.Value)) - : guild != null - ? guild.AddOrUpdateUser(data.Member.Value) // null if the bot scope isn't set, so the guild cannot be retrieved. - : State.GetOrAddUser(data.Member.Value.User.Id, (_) => SocketGlobalUser.Create(this, State, data.Member.Value.User)); + if (guild != null && !guild.IsSynced) + { + await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); + } - SocketChannel channel = null; - if (data.ChannelId.IsSpecified) - { - channel = State.GetChannel(data.ChannelId.Value); + SocketUser user = data.User.IsSpecified + ? State.GetOrAddUser(data.User.Value.Id, (_) => SocketGlobalUser.Create(this, State, data.User.Value)) + : guild != null + ? guild.AddOrUpdateUser(data.Member.Value) // null if the bot scope isn't set, so the guild cannot be retrieved. + : State.GetOrAddUser(data.Member.Value.User.Id, (_) => SocketGlobalUser.Create(this, State, data.Member.Value.User)); - if (channel == null) + SocketChannel channel = null; + if (data.ChannelId.IsSpecified) { - if (!data.GuildId.IsSpecified) // assume it is a DM + channel = State.GetChannel(data.ChannelId.Value); + + if (channel == null) { - channel = CreateDMChannel(data.ChannelId.Value, user, State); - } + if (!data.GuildId.IsSpecified) // assume it is a DM + { + channel = CreateDMChannel(data.ChannelId.Value, user, State); + } - // The channel isn't required when responding to an interaction, so we can leave the channel null. + // The channel isn't required when responding to an interaction, so we can leave the channel null. + } + } + else if (data.User.IsSpecified) + { + channel = State.GetDMChannel(data.User.Value.Id); } - } - else if (data.User.IsSpecified) - { - channel = State.GetDMChannel(data.User.Value.Id); - } - var interaction = SocketInteraction.Create(this, data, channel as ISocketMessageChannel, user); + var interaction = SocketInteraction.Create(this, data, channel as ISocketMessageChannel, user); - await TimedInvokeAsync(_interactionCreatedEvent, nameof(InteractionCreated), interaction).ConfigureAwait(false); + await TimedInvokeAsync(_interactionCreatedEvent, nameof(InteractionCreated), interaction).ConfigureAwait(false); - switch (interaction) - { - case SocketSlashCommand slashCommand: - await TimedInvokeAsync(_slashCommandExecuted, nameof(SlashCommandExecuted), slashCommand).ConfigureAwait(false); - break; - case SocketMessageComponent messageComponent: - if (messageComponent.Data.Type.IsSelectType()) - await TimedInvokeAsync(_selectMenuExecuted, nameof(SelectMenuExecuted), messageComponent).ConfigureAwait(false); - if (messageComponent.Data.Type == ComponentType.Button) - await TimedInvokeAsync(_buttonExecuted, nameof(ButtonExecuted), messageComponent).ConfigureAwait(false); - break; - case SocketUserCommand userCommand: - await TimedInvokeAsync(_userCommandExecuted, nameof(UserCommandExecuted), userCommand).ConfigureAwait(false); - break; - case SocketMessageCommand messageCommand: - await TimedInvokeAsync(_messageCommandExecuted, nameof(MessageCommandExecuted), messageCommand).ConfigureAwait(false); - break; - case SocketAutocompleteInteraction autocomplete: - await TimedInvokeAsync(_autocompleteExecuted, nameof(AutocompleteExecuted), autocomplete).ConfigureAwait(false); - break; - case SocketModal modal: - await TimedInvokeAsync(_modalSubmitted, nameof(ModalSubmitted), modal).ConfigureAwait(false); - break; + switch (interaction) + { + case SocketSlashCommand slashCommand: + await TimedInvokeAsync(_slashCommandExecuted, nameof(SlashCommandExecuted), slashCommand).ConfigureAwait(false); + break; + case SocketMessageComponent messageComponent: + if (messageComponent.Data.Type.IsSelectType()) + await TimedInvokeAsync(_selectMenuExecuted, nameof(SelectMenuExecuted), messageComponent).ConfigureAwait(false); + if (messageComponent.Data.Type == ComponentType.Button) + await TimedInvokeAsync(_buttonExecuted, nameof(ButtonExecuted), messageComponent).ConfigureAwait(false); + break; + case SocketUserCommand userCommand: + await TimedInvokeAsync(_userCommandExecuted, nameof(UserCommandExecuted), userCommand).ConfigureAwait(false); + break; + case SocketMessageCommand messageCommand: + await TimedInvokeAsync(_messageCommandExecuted, nameof(MessageCommandExecuted), messageCommand).ConfigureAwait(false); + break; + case SocketAutocompleteInteraction autocomplete: + await TimedInvokeAsync(_autocompleteExecuted, nameof(AutocompleteExecuted), autocomplete).ConfigureAwait(false); + break; + case SocketModal modal: + await TimedInvokeAsync(_modalSubmitted, nameof(ModalSubmitted), modal).ConfigureAwait(false); + break; + } } - } - break; + break; case "APPLICATION_COMMAND_CREATE": - { - await _gatewayLogger.DebugAsync("Received Dispatch (APPLICATION_COMMAND_CREATE)").ConfigureAwait(false); + { + await _gatewayLogger.DebugAsync("Received Dispatch (APPLICATION_COMMAND_CREATE)").ConfigureAwait(false); - var data = (payload as JToken).ToObject(_serializer); + var data = (payload as JToken).ToObject(_serializer); - if (data.GuildId.IsSpecified) - { - var guild = State.GetGuild(data.GuildId.Value); - if (guild == null) + if (data.GuildId.IsSpecified) { - await UnknownGuildAsync(type, data.GuildId.Value).ConfigureAwait(false); - return; + var guild = State.GetGuild(data.GuildId.Value); + if (guild == null) + { + await UnknownGuildAsync(type, data.GuildId.Value).ConfigureAwait(false); + return; + } } - } - var applicationCommand = SocketApplicationCommand.Create(this, data); + var applicationCommand = SocketApplicationCommand.Create(this, data); - State.AddCommand(applicationCommand); + State.AddCommand(applicationCommand); - await TimedInvokeAsync(_applicationCommandCreated, nameof(ApplicationCommandCreated), applicationCommand).ConfigureAwait(false); - } - break; + await TimedInvokeAsync(_applicationCommandCreated, nameof(ApplicationCommandCreated), applicationCommand).ConfigureAwait(false); + } + break; case "APPLICATION_COMMAND_UPDATE": - { - await _gatewayLogger.DebugAsync("Received Dispatch (APPLICATION_COMMAND_UPDATE)").ConfigureAwait(false); + { + await _gatewayLogger.DebugAsync("Received Dispatch (APPLICATION_COMMAND_UPDATE)").ConfigureAwait(false); - var data = (payload as JToken).ToObject(_serializer); + var data = (payload as JToken).ToObject(_serializer); - if (data.GuildId.IsSpecified) - { - var guild = State.GetGuild(data.GuildId.Value); - if (guild == null) + if (data.GuildId.IsSpecified) { - await UnknownGuildAsync(type, data.GuildId.Value).ConfigureAwait(false); - return; + var guild = State.GetGuild(data.GuildId.Value); + if (guild == null) + { + await UnknownGuildAsync(type, data.GuildId.Value).ConfigureAwait(false); + return; + } } - } - var applicationCommand = SocketApplicationCommand.Create(this, data); + var applicationCommand = SocketApplicationCommand.Create(this, data); - State.AddCommand(applicationCommand); + State.AddCommand(applicationCommand); - await TimedInvokeAsync(_applicationCommandUpdated, nameof(ApplicationCommandUpdated), applicationCommand).ConfigureAwait(false); - } - break; + await TimedInvokeAsync(_applicationCommandUpdated, nameof(ApplicationCommandUpdated), applicationCommand).ConfigureAwait(false); + } + break; case "APPLICATION_COMMAND_DELETE": - { - await _gatewayLogger.DebugAsync("Received Dispatch (APPLICATION_COMMAND_DELETE)").ConfigureAwait(false); + { + await _gatewayLogger.DebugAsync("Received Dispatch (APPLICATION_COMMAND_DELETE)").ConfigureAwait(false); - var data = (payload as JToken).ToObject(_serializer); + var data = (payload as JToken).ToObject(_serializer); - if (data.GuildId.IsSpecified) - { - var guild = State.GetGuild(data.GuildId.Value); - if (guild == null) + if (data.GuildId.IsSpecified) { - await UnknownGuildAsync(type, data.GuildId.Value).ConfigureAwait(false); - return; + var guild = State.GetGuild(data.GuildId.Value); + if (guild == null) + { + await UnknownGuildAsync(type, data.GuildId.Value).ConfigureAwait(false); + return; + } } - } - var applicationCommand = SocketApplicationCommand.Create(this, data); + var applicationCommand = SocketApplicationCommand.Create(this, data); - State.RemoveCommand(applicationCommand.Id); + State.RemoveCommand(applicationCommand.Id); - await TimedInvokeAsync(_applicationCommandDeleted, nameof(ApplicationCommandDeleted), applicationCommand).ConfigureAwait(false); - } - break; + await TimedInvokeAsync(_applicationCommandDeleted, nameof(ApplicationCommandDeleted), applicationCommand).ConfigureAwait(false); + } + break; #endregion #region Threads case "THREAD_CREATE": - { - await _gatewayLogger.DebugAsync("Received Dispatch (THREAD_CREATE)").ConfigureAwait(false); + { + await _gatewayLogger.DebugAsync("Received Dispatch (THREAD_CREATE)").ConfigureAwait(false); - var data = (payload as JToken).ToObject(_serializer); + var data = (payload as JToken).ToObject(_serializer); - var guild = State.GetGuild(data.GuildId.Value); + var guild = State.GetGuild(data.GuildId.Value); - if (guild == null) - { - await UnknownGuildAsync(type, data.GuildId.Value); - return; - } + if (guild == null) + { + await UnknownGuildAsync(type, data.GuildId.Value); + return; + } - SocketThreadChannel threadChannel = null; + SocketThreadChannel threadChannel = null; - if ((threadChannel = guild.ThreadChannels.FirstOrDefault(x => x.Id == data.Id)) != null) - { - threadChannel.Update(State, data); + if ((threadChannel = guild.ThreadChannels.FirstOrDefault(x => x.Id == data.Id)) != null) + { + threadChannel.Update(State, data); - if (data.ThreadMember.IsSpecified) - threadChannel.AddOrUpdateThreadMember(data.ThreadMember.Value, guild.CurrentUser); + if (data.ThreadMember.IsSpecified) + threadChannel.AddOrUpdateThreadMember(data.ThreadMember.Value, guild.CurrentUser); + } + else + { + threadChannel = (SocketThreadChannel)guild.AddChannel(State, data); + if (data.ThreadMember.IsSpecified) + threadChannel.AddOrUpdateThreadMember(data.ThreadMember.Value, guild.CurrentUser); + } + + await TimedInvokeAsync(_threadCreated, nameof(ThreadCreated), threadChannel).ConfigureAwait(false); } - else + + break; + case "THREAD_UPDATE": { - threadChannel = (SocketThreadChannel)guild.AddChannel(State, data); - if (data.ThreadMember.IsSpecified) - threadChannel.AddOrUpdateThreadMember(data.ThreadMember.Value, guild.CurrentUser); - } + await _gatewayLogger.DebugAsync("Received Dispatch (THREAD_UPDATE)").ConfigureAwait(false); - await TimedInvokeAsync(_threadCreated, nameof(ThreadCreated), threadChannel).ConfigureAwait(false); - } + var data = (payload as JToken).ToObject(_serializer); + var guild = State.GetGuild(data.GuildId.Value); + if (guild == null) + { + await UnknownGuildAsync(type, data.GuildId.Value); + return; + } - break; - case "THREAD_UPDATE": - { - await _gatewayLogger.DebugAsync("Received Dispatch (THREAD_UPDATE)").ConfigureAwait(false); + var threadChannel = guild.ThreadChannels.FirstOrDefault(x => x.Id == data.Id); + var before = threadChannel != null + ? new Cacheable(threadChannel.Clone(), data.Id, true, () => Task.FromResult((SocketThreadChannel)null)) + : new Cacheable(null, data.Id, false, () => Task.FromResult((SocketThreadChannel)null)); - var data = (payload as JToken).ToObject(_serializer); - var guild = State.GetGuild(data.GuildId.Value); - if (guild == null) - { - await UnknownGuildAsync(type, data.GuildId.Value); - return; - } + if (threadChannel != null) + { + threadChannel.Update(State, data); - var threadChannel = guild.ThreadChannels.FirstOrDefault(x => x.Id == data.Id); - var before = threadChannel != null - ? new Cacheable(threadChannel.Clone(), data.Id, true, () => Task.FromResult((SocketThreadChannel)null)) - : new Cacheable(null, data.Id, false, () => Task.FromResult((SocketThreadChannel)null)); + if (data.ThreadMember.IsSpecified) + threadChannel.AddOrUpdateThreadMember(data.ThreadMember.Value, guild.CurrentUser); + } + else + { + //Thread is updated but was not cached, likely meaning the thread was unarchived. + threadChannel = (SocketThreadChannel)guild.AddChannel(State, data); + if (data.ThreadMember.IsSpecified) + threadChannel.AddOrUpdateThreadMember(data.ThreadMember.Value, guild.CurrentUser); + } - if (threadChannel != null) - { - threadChannel.Update(State, data); + if (!(guild?.IsSynced ?? true)) + { + await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); + return; + } - if (data.ThreadMember.IsSpecified) - threadChannel.AddOrUpdateThreadMember(data.ThreadMember.Value, guild.CurrentUser); + await TimedInvokeAsync(_threadUpdated, nameof(ThreadUpdated), before, threadChannel).ConfigureAwait(false); } - else + break; + case "THREAD_DELETE": { - //Thread is updated but was not cached, likely meaning the thread was unarchived. - threadChannel = (SocketThreadChannel)guild.AddChannel(State, data); - if (data.ThreadMember.IsSpecified) - threadChannel.AddOrUpdateThreadMember(data.ThreadMember.Value, guild.CurrentUser); - } + await _gatewayLogger.DebugAsync("Received Dispatch (THREAD_DELETE)").ConfigureAwait(false); - if (!(guild?.IsSynced ?? true)) - { - await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); - return; - } + var data = (payload as JToken).ToObject(_serializer); - await TimedInvokeAsync(_threadUpdated, nameof(ThreadUpdated), before, threadChannel).ConfigureAwait(false); - } - break; - case "THREAD_DELETE": - { - await _gatewayLogger.DebugAsync("Received Dispatch (THREAD_DELETE)").ConfigureAwait(false); + var guild = State.GetGuild(data.GuildId.Value); + + if (guild == null) + { + await UnknownGuildAsync(type, data.GuildId.Value).ConfigureAwait(false); + return; + } - var data = (payload as JToken).ToObject(_serializer); + var thread = (SocketThreadChannel)guild.RemoveChannel(State, data.Id); - var guild = State.GetGuild(data.GuildId.Value); + var cacheable = new Cacheable(thread, data.Id, thread != null, null); - if (guild == null) - { - await UnknownGuildAsync(type, data.GuildId.Value).ConfigureAwait(false); - return; + await TimedInvokeAsync(_threadDeleted, nameof(ThreadDeleted), cacheable).ConfigureAwait(false); } - - var thread = (SocketThreadChannel)guild.RemoveChannel(State, data.Id); - - var cacheable = new Cacheable(thread, data.Id, thread != null, null); - - await TimedInvokeAsync(_threadDeleted, nameof(ThreadDeleted), cacheable).ConfigureAwait(false); - } - break; + break; case "THREAD_LIST_SYNC": - { - await _gatewayLogger.DebugAsync("Received Dispatch (THREAD_LIST_SYNC)").ConfigureAwait(false); - - var data = (payload as JToken).ToObject(_serializer); - - var guild = State.GetGuild(data.GuildId); - - if (guild == null) { - await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false); - return; - } + await _gatewayLogger.DebugAsync("Received Dispatch (THREAD_LIST_SYNC)").ConfigureAwait(false); - foreach (var thread in data.Threads) - { - var entity = guild.ThreadChannels.FirstOrDefault(x => x.Id == thread.Id); + var data = (payload as JToken).ToObject(_serializer); - if (entity == null) - { - entity = (SocketThreadChannel)guild.AddChannel(State, thread); - } - else + var guild = State.GetGuild(data.GuildId); + + if (guild == null) { - entity.Update(State, thread); + await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false); + return; } - foreach (var member in data.Members.Where(x => x.Id.Value == entity.Id)) + foreach (var thread in data.Threads) { - var guildMember = guild.GetUser(member.Id.Value); + var entity = guild.ThreadChannels.FirstOrDefault(x => x.Id == thread.Id); + + if (entity == null) + { + entity = (SocketThreadChannel)guild.AddChannel(State, thread); + } + else + { + entity.Update(State, thread); + } - entity.AddOrUpdateThreadMember(member, guildMember); + foreach (var member in data.Members.Where(x => x.Id.Value == entity.Id)) + { + var guildMember = guild.GetUser(member.Id.Value); + + entity.AddOrUpdateThreadMember(member, guildMember); + } } } - } - break; + break; case "THREAD_MEMBER_UPDATE": - { - await _gatewayLogger.DebugAsync("Received Dispatch (THREAD_MEMBER_UPDATE)").ConfigureAwait(false); + { + await _gatewayLogger.DebugAsync("Received Dispatch (THREAD_MEMBER_UPDATE)").ConfigureAwait(false); - var data = (payload as JToken).ToObject(_serializer); + var data = (payload as JToken).ToObject(_serializer); - var thread = (SocketThreadChannel)State.GetChannel(data.Id.Value); + var thread = (SocketThreadChannel)State.GetChannel(data.Id.Value); - if (thread == null) - { - await UnknownChannelAsync(type, data.Id.Value); - return; - } + if (thread == null) + { + await UnknownChannelAsync(type, data.Id.Value); + return; + } - thread.AddOrUpdateThreadMember(data, thread.Guild.CurrentUser); - } + thread.AddOrUpdateThreadMember(data, thread.Guild.CurrentUser); + } - break; + break; case "THREAD_MEMBERS_UPDATE": - { - await _gatewayLogger.DebugAsync("Received Dispatch (THREAD_MEMBERS_UPDATE)").ConfigureAwait(false); + { + await _gatewayLogger.DebugAsync("Received Dispatch (THREAD_MEMBERS_UPDATE)").ConfigureAwait(false); - var data = (payload as JToken).ToObject(_serializer); + var data = (payload as JToken).ToObject(_serializer); - var guild = State.GetGuild(data.GuildId); + var guild = State.GetGuild(data.GuildId); - if (guild == null) - { - await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false); - return; - } + if (guild == null) + { + await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false); + return; + } - var thread = (SocketThreadChannel)guild.GetChannel(data.Id); + var thread = (SocketThreadChannel)guild.GetChannel(data.Id); - if (thread == null) - { - await UnknownChannelAsync(type, data.Id); - return; - } + if (thread == null) + { + await UnknownChannelAsync(type, data.Id); + return; + } - IReadOnlyCollection leftUsers = null; - IReadOnlyCollection joinUsers = null; + IReadOnlyCollection leftUsers = null; + IReadOnlyCollection joinUsers = null; - if (data.RemovedMemberIds.IsSpecified) - { - leftUsers = thread.RemoveUsers(data.RemovedMemberIds.Value); - } + if (data.RemovedMemberIds.IsSpecified) + { + leftUsers = thread.RemoveUsers(data.RemovedMemberIds.Value); + } - if (data.AddedMembers.IsSpecified) - { - List newThreadMembers = new List(); - foreach (var threadMember in data.AddedMembers.Value) + if (data.AddedMembers.IsSpecified) { - SocketGuildUser guildMember; + List newThreadMembers = new List(); + foreach (var threadMember in data.AddedMembers.Value) + { + SocketGuildUser guildMember; - guildMember = guild.GetUser(threadMember.UserId.Value); + guildMember = guild.GetUser(threadMember.UserId.Value); - if (guildMember == null) - { - await UnknownGuildUserAsync("THREAD_MEMBERS_UPDATE", threadMember.UserId.Value, guild.Id); + if (guildMember == null) + { + await UnknownGuildUserAsync("THREAD_MEMBERS_UPDATE", threadMember.UserId.Value, guild.Id); + } + else + newThreadMembers.Add(thread.AddOrUpdateThreadMember(threadMember, guildMember)); } - else - newThreadMembers.Add(thread.AddOrUpdateThreadMember(threadMember, guildMember)); - } - if (newThreadMembers.Any()) - joinUsers = newThreadMembers.ToImmutableArray(); - } + if (newThreadMembers.Any()) + joinUsers = newThreadMembers.ToImmutableArray(); + } - if (leftUsers != null) - { - foreach (var threadUser in leftUsers) + if (leftUsers != null) { - await TimedInvokeAsync(_threadMemberLeft, nameof(ThreadMemberLeft), threadUser).ConfigureAwait(false); + foreach (var threadUser in leftUsers) + { + await TimedInvokeAsync(_threadMemberLeft, nameof(ThreadMemberLeft), threadUser).ConfigureAwait(false); + } } - } - if (joinUsers != null) - { - foreach (var threadUser in joinUsers) + if (joinUsers != null) { - await TimedInvokeAsync(_threadMemberJoined, nameof(ThreadMemberJoined), threadUser).ConfigureAwait(false); + foreach (var threadUser in joinUsers) + { + await TimedInvokeAsync(_threadMemberJoined, nameof(ThreadMemberJoined), threadUser).ConfigureAwait(false); + } } } - } - break; + break; #endregion #region Stage Channels case "STAGE_INSTANCE_CREATE" or "STAGE_INSTANCE_UPDATE" or "STAGE_INSTANCE_DELETE": - { - await _gatewayLogger.DebugAsync($"Received Dispatch ({type})").ConfigureAwait(false); + { + await _gatewayLogger.DebugAsync($"Received Dispatch ({type})").ConfigureAwait(false); - var data = (payload as JToken).ToObject(_serializer); + var data = (payload as JToken).ToObject(_serializer); - var guild = State.GetGuild(data.GuildId); + var guild = State.GetGuild(data.GuildId); - if (guild == null) - { - await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false); - return; - } + if (guild == null) + { + await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false); + return; + } - var stageChannel = guild.GetStageChannel(data.ChannelId); + var stageChannel = guild.GetStageChannel(data.ChannelId); - if (stageChannel == null) - { - await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); - return; - } + if (stageChannel == null) + { + await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); + return; + } - SocketStageChannel before = type == "STAGE_INSTANCE_UPDATE" ? stageChannel.Clone() : null; + SocketStageChannel before = type == "STAGE_INSTANCE_UPDATE" ? stageChannel.Clone() : null; - stageChannel.Update(data, type == "STAGE_INSTANCE_CREATE"); + stageChannel.Update(data, type == "STAGE_INSTANCE_CREATE"); - switch (type) - { - case "STAGE_INSTANCE_CREATE": - await TimedInvokeAsync(_stageStarted, nameof(StageStarted), stageChannel).ConfigureAwait(false); - return; - case "STAGE_INSTANCE_DELETE": - await TimedInvokeAsync(_stageEnded, nameof(StageEnded), stageChannel).ConfigureAwait(false); - return; - case "STAGE_INSTANCE_UPDATE": - await TimedInvokeAsync(_stageUpdated, nameof(StageUpdated), before, stageChannel).ConfigureAwait(false); - return; + switch (type) + { + case "STAGE_INSTANCE_CREATE": + await TimedInvokeAsync(_stageStarted, nameof(StageStarted), stageChannel).ConfigureAwait(false); + return; + case "STAGE_INSTANCE_DELETE": + await TimedInvokeAsync(_stageEnded, nameof(StageEnded), stageChannel).ConfigureAwait(false); + return; + case "STAGE_INSTANCE_UPDATE": + await TimedInvokeAsync(_stageUpdated, nameof(StageUpdated), before, stageChannel).ConfigureAwait(false); + return; + } } - } - break; + break; #endregion #region Guild Scheduled Events case "GUILD_SCHEDULED_EVENT_CREATE": - { - await _gatewayLogger.DebugAsync($"Received Dispatch ({type})").ConfigureAwait(false); + { + await _gatewayLogger.DebugAsync($"Received Dispatch ({type})").ConfigureAwait(false); - var data = (payload as JToken).ToObject(_serializer); + var data = (payload as JToken).ToObject(_serializer); - var guild = State.GetGuild(data.GuildId); + var guild = State.GetGuild(data.GuildId); - if (guild == null) - { - await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false); - return; - } + if (guild == null) + { + await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false); + return; + } - var newEvent = guild.AddOrUpdateEvent(data); + var newEvent = guild.AddOrUpdateEvent(data); - await TimedInvokeAsync(_guildScheduledEventCreated, nameof(GuildScheduledEventCreated), newEvent).ConfigureAwait(false); - } - break; + await TimedInvokeAsync(_guildScheduledEventCreated, nameof(GuildScheduledEventCreated), newEvent).ConfigureAwait(false); + } + break; case "GUILD_SCHEDULED_EVENT_UPDATE": - { - await _gatewayLogger.DebugAsync($"Received Dispatch ({type})").ConfigureAwait(false); + { + await _gatewayLogger.DebugAsync($"Received Dispatch ({type})").ConfigureAwait(false); - var data = (payload as JToken).ToObject(_serializer); + var data = (payload as JToken).ToObject(_serializer); - var guild = State.GetGuild(data.GuildId); + var guild = State.GetGuild(data.GuildId); - if (guild == null) - { - await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false); - return; - } + if (guild == null) + { + await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false); + return; + } - var before = guild.GetEvent(data.Id)?.Clone(); + var before = guild.GetEvent(data.Id)?.Clone(); - var beforeCacheable = new Cacheable(before, data.Id, before != null, () => Task.FromResult((SocketGuildEvent)null)); + var beforeCacheable = new Cacheable(before, data.Id, before != null, () => Task.FromResult((SocketGuildEvent)null)); - var after = guild.AddOrUpdateEvent(data); + var after = guild.AddOrUpdateEvent(data); - if ((before != null ? before.Status != GuildScheduledEventStatus.Completed : true) && data.Status == GuildScheduledEventStatus.Completed) - { - await TimedInvokeAsync(_guildScheduledEventCompleted, nameof(GuildScheduledEventCompleted), after).ConfigureAwait(false); - } - else if ((before != null ? before.Status != GuildScheduledEventStatus.Active : false) && data.Status == GuildScheduledEventStatus.Active) - { - await TimedInvokeAsync(_guildScheduledEventStarted, nameof(GuildScheduledEventStarted), after).ConfigureAwait(false); + if ((before != null ? before.Status != GuildScheduledEventStatus.Completed : true) && data.Status == GuildScheduledEventStatus.Completed) + { + await TimedInvokeAsync(_guildScheduledEventCompleted, nameof(GuildScheduledEventCompleted), after).ConfigureAwait(false); + } + else if ((before != null ? before.Status != GuildScheduledEventStatus.Active : false) && data.Status == GuildScheduledEventStatus.Active) + { + await TimedInvokeAsync(_guildScheduledEventStarted, nameof(GuildScheduledEventStarted), after).ConfigureAwait(false); + } + else + await TimedInvokeAsync(_guildScheduledEventUpdated, nameof(GuildScheduledEventUpdated), beforeCacheable, after).ConfigureAwait(false); } - else - await TimedInvokeAsync(_guildScheduledEventUpdated, nameof(GuildScheduledEventUpdated), beforeCacheable, after).ConfigureAwait(false); - } - break; + break; case "GUILD_SCHEDULED_EVENT_DELETE": - { - await _gatewayLogger.DebugAsync($"Received Dispatch ({type})").ConfigureAwait(false); + { + await _gatewayLogger.DebugAsync($"Received Dispatch ({type})").ConfigureAwait(false); - var data = (payload as JToken).ToObject(_serializer); + var data = (payload as JToken).ToObject(_serializer); - var guild = State.GetGuild(data.GuildId); + var guild = State.GetGuild(data.GuildId); - if (guild == null) - { - await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false); - return; - } + if (guild == null) + { + await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false); + return; + } - var guildEvent = guild.RemoveEvent(data.Id) ?? SocketGuildEvent.Create(this, guild, data); + var guildEvent = guild.RemoveEvent(data.Id) ?? SocketGuildEvent.Create(this, guild, data); - await TimedInvokeAsync(_guildScheduledEventCancelled, nameof(GuildScheduledEventCancelled), guildEvent).ConfigureAwait(false); - } - break; + await TimedInvokeAsync(_guildScheduledEventCancelled, nameof(GuildScheduledEventCancelled), guildEvent).ConfigureAwait(false); + } + break; case "GUILD_SCHEDULED_EVENT_USER_ADD" or "GUILD_SCHEDULED_EVENT_USER_REMOVE": - { - await _gatewayLogger.DebugAsync($"Received Dispatch ({type})").ConfigureAwait(false); + { + await _gatewayLogger.DebugAsync($"Received Dispatch ({type})").ConfigureAwait(false); - var data = (payload as JToken).ToObject(_serializer); + var data = (payload as JToken).ToObject(_serializer); - var guild = State.GetGuild(data.GuildId); + var guild = State.GetGuild(data.GuildId); - if (guild == null) - { - await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false); - return; - } + if (guild == null) + { + await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false); + return; + } - var guildEvent = guild.GetEvent(data.EventId); + var guildEvent = guild.GetEvent(data.EventId); - if (guildEvent == null) - { - await UnknownGuildEventAsync(type, data.EventId, data.GuildId).ConfigureAwait(false); - return; - } + if (guildEvent == null) + { + await UnknownGuildEventAsync(type, data.EventId, data.GuildId).ConfigureAwait(false); + return; + } - var user = (SocketUser)guild.GetUser(data.UserId) ?? State.GetUser(data.UserId); + var user = (SocketUser)guild.GetUser(data.UserId) ?? State.GetUser(data.UserId); - var cacheableUser = new Cacheable(user, data.UserId, user != null, () => Rest.GetUserAsync(data.UserId)); + var cacheableUser = new Cacheable(user, data.UserId, user != null, () => Rest.GetUserAsync(data.UserId)); - switch (type) - { - case "GUILD_SCHEDULED_EVENT_USER_ADD": - await TimedInvokeAsync(_guildScheduledEventUserAdd, nameof(GuildScheduledEventUserAdd), cacheableUser, guildEvent).ConfigureAwait(false); - break; - case "GUILD_SCHEDULED_EVENT_USER_REMOVE": - await TimedInvokeAsync(_guildScheduledEventUserRemove, nameof(GuildScheduledEventUserRemove), cacheableUser, guildEvent).ConfigureAwait(false); - break; + switch (type) + { + case "GUILD_SCHEDULED_EVENT_USER_ADD": + await TimedInvokeAsync(_guildScheduledEventUserAdd, nameof(GuildScheduledEventUserAdd), cacheableUser, guildEvent).ConfigureAwait(false); + break; + case "GUILD_SCHEDULED_EVENT_USER_REMOVE": + await TimedInvokeAsync(_guildScheduledEventUserRemove, nameof(GuildScheduledEventUserRemove), cacheableUser, guildEvent).ConfigureAwait(false); + break; + } } - } - break; + break; #endregion #region Webhooks case "WEBHOOKS_UPDATE": - { - var data = (payload as JToken).ToObject(_serializer); - type = "WEBHOOKS_UPDATE"; - await _gatewayLogger.DebugAsync("Received Dispatch (WEBHOOKS_UPDATE)").ConfigureAwait(false); + { + var data = (payload as JToken).ToObject(_serializer); + type = "WEBHOOKS_UPDATE"; + await _gatewayLogger.DebugAsync("Received Dispatch (WEBHOOKS_UPDATE)").ConfigureAwait(false); - var guild = State.GetGuild(data.GuildId); - var channel = State.GetChannel(data.ChannelId); + var guild = State.GetGuild(data.GuildId); + var channel = State.GetChannel(data.ChannelId); - await TimedInvokeAsync(_webhooksUpdated, nameof(WebhooksUpdated), guild, channel); - } - break; + await TimedInvokeAsync(_webhooksUpdated, nameof(WebhooksUpdated), guild, channel); + } + break; #endregion #region Audit Logs case "GUILD_AUDIT_LOG_ENTRY_CREATE": - { - var data = (payload as JToken).ToObject(_serializer); - type = "GUILD_AUDIT_LOG_ENTRY_CREATE"; - await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_AUDIT_LOG_ENTRY_CREATE)").ConfigureAwait(false); + { + var data = (payload as JToken).ToObject(_serializer); + type = "GUILD_AUDIT_LOG_ENTRY_CREATE"; + await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_AUDIT_LOG_ENTRY_CREATE)").ConfigureAwait(false); - var guild = State.GetGuild(data.GuildId); - var auditLog = SocketAuditLogEntry.Create(this, data); - guild.AddAuditLog(auditLog); + var guild = State.GetGuild(data.GuildId); + var auditLog = SocketAuditLogEntry.Create(this, data); + guild.AddAuditLog(auditLog); - await TimedInvokeAsync(_auditLogCreated, nameof(AuditLogCreated), auditLog, guild); - } - break; + await TimedInvokeAsync(_auditLogCreated, nameof(AuditLogCreated), auditLog, guild); + } + break; #endregion #region Auto Moderation case "AUTO_MODERATION_RULE_CREATE": - { - var data = (payload as JToken).ToObject(_serializer); + { + var data = (payload as JToken).ToObject(_serializer); - var guild = State.GetGuild(data.GuildId); + var guild = State.GetGuild(data.GuildId); - var rule = guild.AddOrUpdateAutoModRule(data); + var rule = guild.AddOrUpdateAutoModRule(data); - await TimedInvokeAsync(_autoModRuleCreated, nameof(AutoModRuleCreated), rule); - } - break; + await TimedInvokeAsync(_autoModRuleCreated, nameof(AutoModRuleCreated), rule); + } + break; case "AUTO_MODERATION_RULE_UPDATE": - { - var data = (payload as JToken).ToObject(_serializer); + { + var data = (payload as JToken).ToObject(_serializer); - var guild = State.GetGuild(data.GuildId); + var guild = State.GetGuild(data.GuildId); - var cachedRule = guild.GetAutoModRule(data.Id); - var cacheableBefore = new Cacheable(cachedRule?.Clone(), - data.Id, - cachedRule is not null, - async () => await guild.GetAutoModRuleAsync(data.Id)); + var cachedRule = guild.GetAutoModRule(data.Id); + var cacheableBefore = new Cacheable(cachedRule?.Clone(), + data.Id, + cachedRule is not null, + async () => await guild.GetAutoModRuleAsync(data.Id)); - await TimedInvokeAsync(_autoModRuleUpdated, nameof(AutoModRuleUpdated), cacheableBefore, guild.AddOrUpdateAutoModRule(data)); - } - break; + await TimedInvokeAsync(_autoModRuleUpdated, nameof(AutoModRuleUpdated), cacheableBefore, guild.AddOrUpdateAutoModRule(data)); + } + break; case "AUTO_MODERATION_RULE_DELETE": - { - var data = (payload as JToken).ToObject(_serializer); + { + var data = (payload as JToken).ToObject(_serializer); - var guild = State.GetGuild(data.GuildId); + var guild = State.GetGuild(data.GuildId); - var rule = guild.RemoveAutoModRule(data); + var rule = guild.RemoveAutoModRule(data); - await TimedInvokeAsync(_autoModRuleDeleted, nameof(AutoModRuleDeleted), rule); - } - break; + await TimedInvokeAsync(_autoModRuleDeleted, nameof(AutoModRuleDeleted), rule); + } + break; case "AUTO_MODERATION_ACTION_EXECUTION": - { - var data = (payload as JToken).ToObject(_serializer); - - var guild = State.GetGuild(data.GuildId); - var action = new AutoModRuleAction(data.Action.Type, - data.Action.Metadata.IsSpecified - ? data.Action.Metadata.Value.ChannelId.IsSpecified - ? data.Action.Metadata.Value.ChannelId.Value - : null - : null, - data.Action.Metadata.IsSpecified - ? data.Action.Metadata.Value.DurationSeconds.IsSpecified - ? data.Action.Metadata.Value.DurationSeconds.Value - : null - : null, - data.Action.Metadata.IsSpecified - ? data.Action.Metadata.Value.CustomMessage.IsSpecified - ? data.Action.Metadata.Value.CustomMessage.Value - : null - : null); - - - var member = guild.GetUser(data.UserId); - - var cacheableUser = new Cacheable(member, - data.UserId, - member is not null, - async () => - { - var model = await ApiClient.GetGuildMemberAsync(data.GuildId, data.UserId); - return guild.AddOrUpdateUser(model); - } - ); - - ISocketMessageChannel channel = null; - if (data.ChannelId.IsSpecified) - channel = GetChannel(data.ChannelId.Value) as ISocketMessageChannel; + { + var data = (payload as JToken).ToObject(_serializer); + + var guild = State.GetGuild(data.GuildId); + var action = new AutoModRuleAction(data.Action.Type, + data.Action.Metadata.IsSpecified + ? data.Action.Metadata.Value.ChannelId.IsSpecified + ? data.Action.Metadata.Value.ChannelId.Value + : null + : null, + data.Action.Metadata.IsSpecified + ? data.Action.Metadata.Value.DurationSeconds.IsSpecified + ? data.Action.Metadata.Value.DurationSeconds.Value + : null + : null, + data.Action.Metadata.IsSpecified + ? data.Action.Metadata.Value.CustomMessage.IsSpecified + ? data.Action.Metadata.Value.CustomMessage.Value + : null + : null); + + + var member = guild.GetUser(data.UserId); + + var cacheableUser = new Cacheable(member, + data.UserId, + member is not null, + async () => + { + var model = await ApiClient.GetGuildMemberAsync(data.GuildId, data.UserId); + return guild.AddOrUpdateUser(model); + } + ); - var cacheableChannel = new Cacheable(channel, - data.ChannelId.GetValueOrDefault(0), - channel != null, - async () => - { - if (data.ChannelId.IsSpecified) - return await GetChannelAsync(data.ChannelId.Value).ConfigureAwait(false) as ISocketMessageChannel; - return null; - }); + ISocketMessageChannel channel = null; + if (data.ChannelId.IsSpecified) + channel = GetChannel(data.ChannelId.Value) as ISocketMessageChannel; + var cacheableChannel = new Cacheable(channel, + data.ChannelId.GetValueOrDefault(0), + channel != null, + async () => + { + if (data.ChannelId.IsSpecified) + return await GetChannelAsync(data.ChannelId.Value).ConfigureAwait(false) as ISocketMessageChannel; + return null; + }); - IUserMessage cachedMsg = null; - if (data.MessageId.IsSpecified) - cachedMsg = channel?.GetCachedMessage(data.MessageId.GetValueOrDefault(0)) as IUserMessage; - var cacheableMessage = new Cacheable(cachedMsg, - data.MessageId.GetValueOrDefault(0), - cachedMsg is not null, - async () => - { - if (data.MessageId.IsSpecified) - return (await channel!.GetMessageAsync(data.MessageId.Value).ConfigureAwait(false)) as IUserMessage; - return null; - }); + IUserMessage cachedMsg = null; + if (data.MessageId.IsSpecified) + cachedMsg = channel?.GetCachedMessage(data.MessageId.GetValueOrDefault(0)) as IUserMessage; - var cachedRule = guild.GetAutoModRule(data.RuleId); - - var cacheableRule = new Cacheable(cachedRule, - data.RuleId, - cachedRule is not null, - async () => await guild.GetAutoModRuleAsync(data.RuleId)); - - var eventData = new AutoModActionExecutedData( - cacheableRule, - data.TriggerType, - cacheableUser, - cacheableChannel, - data.MessageId.IsSpecified ? cacheableMessage : null, - data.AlertSystemMessageId.GetValueOrDefault(0), - data.Content, - data.MatchedContent.IsSpecified - ? data.MatchedContent.Value - : null, - data.MatchedKeyword.IsSpecified - ? data.MatchedKeyword.Value - : null); - - await TimedInvokeAsync(_autoModActionExecuted, nameof(AutoModActionExecuted), guild, action, eventData); - } - break; + var cacheableMessage = new Cacheable(cachedMsg, + data.MessageId.GetValueOrDefault(0), + cachedMsg is not null, + async () => + { + if (data.MessageId.IsSpecified) + return (await channel!.GetMessageAsync(data.MessageId.Value).ConfigureAwait(false)) as IUserMessage; + return null; + }); + + var cachedRule = guild.GetAutoModRule(data.RuleId); + + var cacheableRule = new Cacheable(cachedRule, + data.RuleId, + cachedRule is not null, + async () => await guild.GetAutoModRuleAsync(data.RuleId)); + + var eventData = new AutoModActionExecutedData( + cacheableRule, + data.TriggerType, + cacheableUser, + cacheableChannel, + data.MessageId.IsSpecified ? cacheableMessage : null, + data.AlertSystemMessageId.GetValueOrDefault(0), + data.Content, + data.MatchedContent.IsSpecified + ? data.MatchedContent.Value + : null, + data.MatchedKeyword.IsSpecified + ? data.MatchedKeyword.Value + : null); + + await TimedInvokeAsync(_autoModActionExecuted, nameof(AutoModActionExecuted), guild, action, eventData); + } + break; #endregion #region App Subscriptions case "ENTITLEMENT_CREATE": - { - await _gatewayLogger.DebugAsync("Received Dispatch (ENTITLEMENT_CREATE)").ConfigureAwait(false); - var data = (payload as JToken).ToObject(_serializer); + { + await _gatewayLogger.DebugAsync("Received Dispatch (ENTITLEMENT_CREATE)").ConfigureAwait(false); + var data = (payload as JToken).ToObject(_serializer); - var entitlement = SocketEntitlement.Create(this, data); - State.AddEntitlement(data.Id, entitlement); + var entitlement = SocketEntitlement.Create(this, data); + State.AddEntitlement(data.Id, entitlement); - await TimedInvokeAsync(_entitlementCreated, nameof(EntitlementCreated), entitlement); - } - break; + await TimedInvokeAsync(_entitlementCreated, nameof(EntitlementCreated), entitlement); + } + break; case "ENTITLEMENT_UPDATE": - { - await _gatewayLogger.DebugAsync("Received Dispatch (ENTITLEMENT_UPDATE)").ConfigureAwait(false); - var data = (payload as JToken).ToObject(_serializer); + { + await _gatewayLogger.DebugAsync("Received Dispatch (ENTITLEMENT_UPDATE)").ConfigureAwait(false); + var data = (payload as JToken).ToObject(_serializer); - var entitlement = State.GetEntitlement(data.Id); + var entitlement = State.GetEntitlement(data.Id); - var cacheableBefore = new Cacheable(entitlement?.Clone(), data.Id, - entitlement is not null, () => null); + var cacheableBefore = new Cacheable(entitlement?.Clone(), data.Id, + entitlement is not null, () => null); - if (entitlement is null) - { - entitlement = SocketEntitlement.Create(this, data); - State.AddEntitlement(data.Id, entitlement); - } - else - { - entitlement.Update(data); - } + if (entitlement is null) + { + entitlement = SocketEntitlement.Create(this, data); + State.AddEntitlement(data.Id, entitlement); + } + else + { + entitlement.Update(data); + } - await TimedInvokeAsync(_entitlementUpdated, nameof(EntitlementUpdated), cacheableBefore, entitlement); - } - break; + await TimedInvokeAsync(_entitlementUpdated, nameof(EntitlementUpdated), cacheableBefore, entitlement); + } + break; case "ENTITLEMENT_DELETE": - { - await _gatewayLogger.DebugAsync("Received Dispatch (ENTITLEMENT_DELETE)").ConfigureAwait(false); - var data = (payload as JToken).ToObject(_serializer); + { + await _gatewayLogger.DebugAsync("Received Dispatch (ENTITLEMENT_DELETE)").ConfigureAwait(false); + var data = (payload as JToken).ToObject(_serializer); - var entitlement = State.RemoveEntitlement(data.Id); + var entitlement = State.RemoveEntitlement(data.Id); - if (entitlement is null) - entitlement = SocketEntitlement.Create(this, data); - else - entitlement.Update(data); + if (entitlement is null) + entitlement = SocketEntitlement.Create(this, data); + else + entitlement.Update(data); - var cacheableEntitlement = new Cacheable(entitlement, data.Id, - entitlement is not null, () => null); + var cacheableEntitlement = new Cacheable(entitlement, data.Id, + entitlement is not null, () => null); - await TimedInvokeAsync(_entitlementDeleted, nameof(EntitlementDeleted), cacheableEntitlement); - } - break; + await TimedInvokeAsync(_entitlementDeleted, nameof(EntitlementDeleted), cacheableEntitlement); + } + break; case "SUBSCRIPTION_CREATE": - { - await _gatewayLogger.DebugAsync("Received Dispatch (SUBSCRIPTION_CREATE)").ConfigureAwait(false); - var data = (payload as JToken).ToObject(_serializer); + { + await _gatewayLogger.DebugAsync("Received Dispatch (SUBSCRIPTION_CREATE)").ConfigureAwait(false); + var data = (payload as JToken).ToObject(_serializer); - var subscription = SocketSubscription.Create(this, data); - State.AddSubscription(data.Id, subscription); + var subscription = SocketSubscription.Create(this, data); + State.AddSubscription(data.Id, subscription); - await TimedInvokeAsync(_subscriptionCreated, nameof(SubscriptionCreated), subscription); - } - break; + await TimedInvokeAsync(_subscriptionCreated, nameof(SubscriptionCreated), subscription); + } + break; case "SUBSCRIPTION_UPDATE": - { - await _gatewayLogger.DebugAsync("Received Dispatch (SUBSCRIPTION_UPDATE)").ConfigureAwait(false); - var data = (payload as JToken).ToObject(_serializer); + { + await _gatewayLogger.DebugAsync("Received Dispatch (SUBSCRIPTION_UPDATE)").ConfigureAwait(false); + var data = (payload as JToken).ToObject(_serializer); - var subscription = State.GetSubscription(data.Id); + var subscription = State.GetSubscription(data.Id); - var cacheableBefore = new Cacheable(subscription?.Clone(), data.Id, - hasValue: subscription is not null, () => null); + var cacheableBefore = new Cacheable(subscription?.Clone(), data.Id, + hasValue: subscription is not null, () => null); - if (subscription is null) - { - subscription = SocketSubscription.Create(this, data); - State.AddSubscription(data.Id, subscription); - } - else - { - subscription.Update(data); - } + if (subscription is null) + { + subscription = SocketSubscription.Create(this, data); + State.AddSubscription(data.Id, subscription); + } + else + { + subscription.Update(data); + } - await TimedInvokeAsync(_subscriptionUpdated, nameof(SubscriptionUpdated), cacheableBefore, subscription); - } - break; + await TimedInvokeAsync(_subscriptionUpdated, nameof(SubscriptionUpdated), cacheableBefore, subscription); + } + break; case "SUBSCRIPTION_DELETE": - { - await _gatewayLogger.DebugAsync("Received Dispatch (SUBSCRIPTION_DELETE)").ConfigureAwait(false); - var data = (payload as JToken).ToObject(_serializer); + { + await _gatewayLogger.DebugAsync("Received Dispatch (SUBSCRIPTION_DELETE)").ConfigureAwait(false); + var data = (payload as JToken).ToObject(_serializer); - var subscription = State.RemoveSubscription(data.Id); + var subscription = State.RemoveSubscription(data.Id); - if (subscription is null) - subscription = SocketSubscription.Create(this, data); - else - subscription.Update(data); + if (subscription is null) + subscription = SocketSubscription.Create(this, data); + else + subscription.Update(data); - var cacheableSubscription = new Cacheable(subscription, data.Id, - subscription is not null, () => null); + var cacheableSubscription = new Cacheable(subscription, data.Id, + subscription is not null, () => null); - await TimedInvokeAsync(_subscriptionDeleted, nameof(SubscriptionDeleted), cacheableSubscription); - } - break; + await TimedInvokeAsync(_subscriptionDeleted, nameof(SubscriptionDeleted), cacheableSubscription); + } + break; #endregion diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs index cb7ba9b9d0..526d79bfb8 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -1,3 +1,4 @@ +using AsyncKeyedLock; using Discord.API; using Discord.API.Gateway; using Discord.Logging; @@ -5,10 +6,8 @@ using Discord.Net.Udp; using Discord.Net.WebSockets; using Discord.Rest; -using Discord.Utils; using Newtonsoft.Json; -using Newtonsoft.Json.Linq; using System; using System.Collections.Concurrent; @@ -36,7 +35,7 @@ public partial class DiscordSocketClient : BaseSocketClient, IDiscordClient private readonly ConcurrentQueue _heartbeatTimes; private readonly ConnectionManager _connection; private readonly Logger _gatewayLogger; - private readonly SemaphoreSlim _stateLock; + private readonly AsyncNonKeyedLocker _stateLock; private string _sessionId; private int _lastSeq; @@ -167,7 +166,7 @@ private DiscordSocketClient(DiscordSocketConfig config, API.DiscordSocketApiClie _gatewayIntents = config.GatewayIntents; _defaultStickers = ImmutableArray.Create>(); - _stateLock = new SemaphoreSlim(1, 1); + _stateLock = new(); _gatewayLogger = LogManager.CreateLogger(ShardId == 0 && TotalShards == 1 ? "Gateway" : $"Shard #{ShardId}"); _connection = new ConnectionManager(_stateLock, _gatewayLogger, config.ConnectionTimeout, OnConnectingAsync, OnDisconnectingAsync, x => ApiClient.Disconnected += x); @@ -295,13 +294,7 @@ public override Task StopAsync() private async Task OnConnectingAsync() { - bool locked = false; - if (_shardedClient != null && _sessionId == null) - { - await _shardedClient.AcquireIdentifyLockAsync(ShardId, _connection.CancelToken).ConfigureAwait(false); - locked = true; - } - try + using (await _shardedClient.AcquireIdentifyLockAsync(ShardId, _shardedClient != null && _sessionId == null, _connection.CancelToken).ConfigureAwait(false)) { await _gatewayLogger.DebugAsync("Connecting ApiClient").ConfigureAwait(false); await ApiClient.ConnectAsync().ConfigureAwait(false); @@ -317,11 +310,6 @@ private async Task OnConnectingAsync() await ApiClient.SendIdentifyAsync(shardID: ShardId, totalShards: TotalShards, gatewayIntents: _gatewayIntents, presence: BuildCurrentStatus()).ConfigureAwait(false); } } - finally - { - if (locked) - _shardedClient.ReleaseIdentifyLock(); - } //Wait for READY await _connection.WaitAsync().ConfigureAwait(false); diff --git a/src/Discord.Net.WebSocket/DiscordVoiceApiClient.cs b/src/Discord.Net.WebSocket/DiscordVoiceApiClient.cs index 2383c2c717..73b172be52 100644 --- a/src/Discord.Net.WebSocket/DiscordVoiceApiClient.cs +++ b/src/Discord.Net.WebSocket/DiscordVoiceApiClient.cs @@ -1,3 +1,4 @@ +using AsyncKeyedLock; using Discord.API; using Discord.API.Voice; using Discord.Net.Converters; @@ -38,7 +39,7 @@ internal class DiscordVoiceAPIClient : IDisposable private readonly AsyncEvent> _disconnectedEvent = new AsyncEvent>(); private readonly JsonSerializer _serializer; - private readonly SemaphoreSlim _connectionLock; + private readonly AsyncNonKeyedLocker _connectionLock; private readonly IUdpSocket _udp; private CancellationTokenSource _connectCancelToken; private bool _isDisposed; @@ -53,7 +54,7 @@ internal class DiscordVoiceAPIClient : IDisposable internal DiscordVoiceAPIClient(ulong guildId, WebSocketProvider webSocketProvider, UdpSocketProvider udpSocketProvider, JsonSerializer serializer = null) { GuildId = guildId; - _connectionLock = new SemaphoreSlim(1, 1); + _connectionLock = new(); _udp = udpSocketProvider(); _udp.ReceivedDatagram += (data, index, count) => { @@ -186,12 +187,8 @@ public Task SendResume(string token, string sessionId) public async Task ConnectAsync(string url) { - await _connectionLock.WaitAsync().ConfigureAwait(false); - try - { - await ConnectInternalAsync(url).ConfigureAwait(false); - } - finally { _connectionLock.Release(); } + using var _ = await _connectionLock.LockAsync().ConfigureAwait(false); + await ConnectInternalAsync(url).ConfigureAwait(false); } private async Task ConnectInternalAsync(string url) @@ -220,12 +217,8 @@ private async Task ConnectInternalAsync(string url) public async Task DisconnectAsync() { - await _connectionLock.WaitAsync().ConfigureAwait(false); - try - { - await DisconnectInternalAsync().ConfigureAwait(false); - } - finally { _connectionLock.Release(); } + using var _ = await _connectionLock.LockAsync().ConfigureAwait(false); + await DisconnectInternalAsync().ConfigureAwait(false); } private async Task DisconnectInternalAsync() { diff --git a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs index 026991a2a8..2a1faf517b 100644 --- a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs +++ b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs @@ -1,3 +1,4 @@ +using AsyncKeyedLock; using Discord.API.Gateway; using Discord.Audio; using Discord.Rest; @@ -37,7 +38,7 @@ public class SocketGuild : SocketEntity, IGuild, IDisposable { #region SocketGuild #pragma warning disable IDISP002, IDISP006 - private readonly SemaphoreSlim _audioLock; + private readonly AsyncNonKeyedLocker _audioLock; private TaskCompletionSource _syncPromise, _downloaderPromise; private TaskCompletionSource _audioConnectPromise; private ConcurrentDictionary _channels; @@ -417,7 +418,7 @@ public IReadOnlyCollection Stickers internal SocketGuild(DiscordSocketClient client, ulong id) : base(client, id) { - _audioLock = new SemaphoreSlim(1, 1); + _audioLock = new(); _emotes = ImmutableArray.Create(); _automodRules = new ConcurrentDictionary(); _auditLogs = new AuditLogCache(client); @@ -1343,7 +1344,7 @@ public Task> SearchUsersAsync(string query, i /// public Task SearchUsersAsyncV2(int limit = DiscordConfig.MaxUsersPerBatch, MemberSearchPropertiesV2 args = null, RequestOptions options = null) => GuildHelper.SearchUsersAsyncV2(this, Discord, limit, args, options); - + /// public Task ModifyCurrentUserAsync(Action props, RequestOptions options = null) { @@ -1646,7 +1647,7 @@ public async ValueTask> GetStickersAsyn /// /// A task that represents the asynchronous creation operation. The task result contains the created sticker. /// - public async Task CreateStickerAsync(string name, Image image, IEnumerable tags, string description = null, + public async Task CreateStickerAsync(string name, Image image, IEnumerable tags, string description = null, RequestOptions options = null) { var model = await GuildHelper.CreateStickerAsync(Discord, this, name, image, tags, description, options).ConfigureAwait(false); @@ -1664,11 +1665,11 @@ public async Task CreateStickerAsync(string name, Image im /// /// A task that represents the asynchronous creation operation. The task result contains the created sticker. /// - public async Task CreateStickerAsync(string name, string path, IEnumerable tags, string description = null, + public async Task CreateStickerAsync(string name, string path, IEnumerable tags, string description = null, RequestOptions options = null) { using var fs = File.OpenRead(path); - return await CreateStickerAsync(name, fs, Path.GetFileName(fs.Name), tags, description, options); + return await CreateStickerAsync(name, fs, Path.GetFileName(fs.Name), tags, description, options); } /// /// Creates a new sticker in this guild @@ -1764,65 +1765,63 @@ internal async Task ConnectAudioAsync(ulong channelId, bool selfDe TaskCompletionSource promise; - await _audioLock.WaitAsync().ConfigureAwait(false); - try + using (await _audioLock.LockAsync().ConfigureAwait(false)) { - if (disconnect || !external) - await DisconnectAudioInternalAsync().ConfigureAwait(false); - promise = new TaskCompletionSource(); - _audioConnectPromise = promise; - - _voiceStateUpdateParams = new VoiceStateUpdateParams - { - GuildId = Id, - ChannelId = channelId, - SelfDeaf = selfDeaf, - SelfMute = selfMute - }; - - if (external) + try { - _ = promise.TrySetResultAsync(null); - await Discord.ApiClient.SendVoiceStateUpdateAsync(_voiceStateUpdateParams).ConfigureAwait(false); - return null; - } + if (disconnect || !external) + await DisconnectAudioInternalAsync().ConfigureAwait(false); + promise = new TaskCompletionSource(); + _audioConnectPromise = promise; - if (_audioClient == null) - { - var audioClient = new AudioClient(this, Discord.GetAudioId(), channelId); - audioClient.Disconnected += async ex => + _voiceStateUpdateParams = new VoiceStateUpdateParams { - if (promise.Task.IsCompleted && audioClient.IsFinished) - { - try - { audioClient.Dispose(); } - catch { } - _audioClient = null; - if (ex != null) - await promise.TrySetExceptionAsync(ex); - else - await promise.TrySetCanceledAsync(); - } + GuildId = Id, + ChannelId = channelId, + SelfDeaf = selfDeaf, + SelfMute = selfMute }; - audioClient.Connected += () => + + if (external) { - _ = promise.TrySetResultAsync(_audioClient); - return Task.CompletedTask; - }; + _ = promise.TrySetResultAsync(null); + await Discord.ApiClient.SendVoiceStateUpdateAsync(_voiceStateUpdateParams).ConfigureAwait(false); + return null; + } - _audioClient = audioClient; - } + if (_audioClient == null) + { + var audioClient = new AudioClient(this, Discord.GetAudioId(), channelId); + audioClient.Disconnected += async ex => + { + if (promise.Task.IsCompleted && audioClient.IsFinished) + { + try + { audioClient.Dispose(); } + catch { } + _audioClient = null; + if (ex != null) + await promise.TrySetExceptionAsync(ex); + else + await promise.TrySetCanceledAsync(); + } + }; + audioClient.Connected += () => + { + _ = promise.TrySetResultAsync(_audioClient); + return Task.CompletedTask; + }; - await Discord.ApiClient.SendVoiceStateUpdateAsync(_voiceStateUpdateParams).ConfigureAwait(false); - } - catch - { - await DisconnectAudioInternalAsync().ConfigureAwait(false); - throw; - } - finally - { - _audioLock.Release(); + _audioClient = audioClient; + } + + await Discord.ApiClient.SendVoiceStateUpdateAsync(_voiceStateUpdateParams).ConfigureAwait(false); + } + catch + { + await DisconnectAudioInternalAsync().ConfigureAwait(false); + throw; + } } try @@ -1841,15 +1840,8 @@ internal async Task ConnectAudioAsync(ulong channelId, bool selfDe internal async Task DisconnectAudioAsync() { - await _audioLock.WaitAsync().ConfigureAwait(false); - try - { - await DisconnectAudioInternalAsync().ConfigureAwait(false); - } - finally - { - _audioLock.Release(); - } + using var _ = await _audioLock.LockAsync().ConfigureAwait(false); + await DisconnectAudioInternalAsync().ConfigureAwait(false); } private async Task DisconnectAudioInternalAsync() { @@ -1865,15 +1857,8 @@ private async Task DisconnectAudioInternalAsync() 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(); - } + using var _ = await _audioLock.LockAsync().ConfigureAwait(false); + await ModifyAudioInternalAsync(channelId, func, options).ConfigureAwait(false); } private Task ModifyAudioInternalAsync(ulong channelId, Action func, RequestOptions options) @@ -1897,7 +1882,7 @@ internal async Task FinishConnectAudio(string url, string token) //TODO: Mem Leak: Disconnected/Connected handlers aren't cleaned up var voiceState = GetVoiceState(Discord.CurrentUser.Id).Value; - await _audioLock.WaitAsync().ConfigureAwait(false); + using var _ = await _audioLock.LockAsync().ConfigureAwait(false); try { if (_audioClient != null) @@ -1930,10 +1915,6 @@ internal async Task FinishConnectAudio(string url, string token) await _audioConnectPromise.SetExceptionAsync(e).ConfigureAwait(false); await DisconnectAudioInternalAsync().ConfigureAwait(false); } - finally - { - _audioLock.Release(); - } } internal async Task RepopulateAudioStreamsAsync() diff --git a/src/Discord.Net.WebSocket/Net/DefaultUdpSocket.cs b/src/Discord.Net.WebSocket/Net/DefaultUdpSocket.cs index bf698bb4b8..678ce2921a 100644 --- a/src/Discord.Net.WebSocket/Net/DefaultUdpSocket.cs +++ b/src/Discord.Net.WebSocket/Net/DefaultUdpSocket.cs @@ -1,3 +1,4 @@ +using AsyncKeyedLock; using System; using System.Net; using System.Net.Sockets; @@ -10,7 +11,7 @@ internal class DefaultUdpSocket : IUdpSocket, IDisposable { public event Func ReceivedDatagram; - private readonly SemaphoreSlim _lock; + private readonly AsyncNonKeyedLocker _lock; private UdpClient _udp; private IPEndPoint _destination; private CancellationTokenSource _stopCancelTokenSource, _cancelTokenSource; @@ -22,7 +23,7 @@ internal class DefaultUdpSocket : IUdpSocket, IDisposable public DefaultUdpSocket() { - _lock = new SemaphoreSlim(1, 1); + _lock = new(); _stopCancelTokenSource = new CancellationTokenSource(); } private void Dispose(bool disposing) @@ -47,15 +48,8 @@ public void Dispose() public async Task StartAsync() { - await _lock.WaitAsync().ConfigureAwait(false); - try - { - await StartInternalAsync(_cancelToken).ConfigureAwait(false); - } - finally - { - _lock.Release(); - } + using var _ = await _lock.LockAsync().ConfigureAwait(false); + await StartInternalAsync(_cancelToken).ConfigureAwait(false); } public async Task StartInternalAsync(CancellationToken cancelToken) { @@ -75,15 +69,8 @@ public async Task StartInternalAsync(CancellationToken cancelToken) } public async Task StopAsync() { - await _lock.WaitAsync().ConfigureAwait(false); - try - { - await StopInternalAsync().ConfigureAwait(false); - } - finally - { - _lock.Release(); - } + using var _ = await _lock.LockAsync().ConfigureAwait(false); + await StopInternalAsync().ConfigureAwait(false); } public async Task StopInternalAsync(bool isDisposing = false) { diff --git a/src/Discord.Net.WebSocket/Net/DefaultWebSocketClient.cs b/src/Discord.Net.WebSocket/Net/DefaultWebSocketClient.cs index 2f888b89bf..7c451d213e 100644 --- a/src/Discord.Net.WebSocket/Net/DefaultWebSocketClient.cs +++ b/src/Discord.Net.WebSocket/Net/DefaultWebSocketClient.cs @@ -1,3 +1,4 @@ +using AsyncKeyedLock; using System; using System.Collections.Generic; using System.ComponentModel; @@ -20,7 +21,7 @@ internal class DefaultWebSocketClient : IWebSocketClient, IDisposable public event Func TextMessage; public event Func Closed; - private readonly SemaphoreSlim _lock; + private readonly AsyncNonKeyedLocker _lock; private readonly Dictionary _headers; private readonly IWebProxy _proxy; private ClientWebSocket _client; @@ -31,7 +32,7 @@ internal class DefaultWebSocketClient : IWebSocketClient, IDisposable public DefaultWebSocketClient(IWebProxy proxy = null) { - _lock = new SemaphoreSlim(1, 1); + _lock = new(); _disconnectTokenSource = new CancellationTokenSource(); _cancelToken = CancellationToken.None; _parentToken = CancellationToken.None; @@ -59,15 +60,8 @@ public void Dispose() public async Task ConnectAsync(string host) { - await _lock.WaitAsync().ConfigureAwait(false); - try - { - await ConnectInternalAsync(host).ConfigureAwait(false); - } - finally - { - _lock.Release(); - } + using var _ = await _lock.LockAsync().ConfigureAwait(false); + await ConnectInternalAsync(host).ConfigureAwait(false); } private async Task ConnectInternalAsync(string host) { @@ -96,21 +90,13 @@ private async Task ConnectInternalAsync(string host) public async Task DisconnectAsync(int closeCode = 1000) { - await _lock.WaitAsync().ConfigureAwait(false); - try - { - await DisconnectInternalAsync(closeCode: closeCode).ConfigureAwait(false); - } - finally - { - _lock.Release(); - } + using var _ = await _lock.LockAsync().ConfigureAwait(false); + await DisconnectInternalAsync(closeCode: closeCode).ConfigureAwait(false); } private async Task DisconnectInternalAsync(int closeCode = 1000, bool isDisposing = false) { _isDisconnecting = true; - if (_client != null) { if (!isDisposing) @@ -150,15 +136,10 @@ private async Task OnClosed(Exception ex) if (_isDisconnecting) return; //Ignore, this disconnect was requested. - await _lock.WaitAsync().ConfigureAwait(false); - try + using (await _lock.LockAsync().ConfigureAwait(false)) { await DisconnectInternalAsync(isDisposing: false); } - finally - { - _lock.Release(); - } await Closed(ex); } @@ -179,14 +160,7 @@ public async Task SendAsync(byte[] data, int index, int count, bool isText) { try { - await _lock.WaitAsync(_cancelToken).ConfigureAwait(false); - } - catch (TaskCanceledException) - { - return; - } - try - { + using var _ = await _lock.LockAsync(_cancelToken).ConfigureAwait(false); if (_client == null) return; @@ -206,9 +180,9 @@ public async Task SendAsync(byte[] data, int index, int count, bool isText) await _client.SendAsync(new ArraySegment(data, index, count), type, isLast, _cancelToken).ConfigureAwait(false); } } - finally + catch (TaskCanceledException) { - _lock.Release(); + return; } }