diff --git a/src/Discord.Net.Audio.Net45/Discord.Net.Audio.csproj b/src/Discord.Net.Audio.Net45/Discord.Net.Audio.csproj index 280dc619e..99cfdcc0b 100644 --- a/src/Discord.Net.Audio.Net45/Discord.Net.Audio.csproj +++ b/src/Discord.Net.Audio.Net45/Discord.Net.Audio.csproj @@ -62,8 +62,8 @@ InternalIsSpeakingEventArgs.cs - - Net\VoiceWebSocket.cs + + Net\VoiceSocket.cs Opus\OpusConverter.cs @@ -74,15 +74,15 @@ Opus\OpusEncoder.cs - - SimpleAudioClient.cs - Sodium\SecretBox.cs UserIsTalkingEventArgs.cs + + VirtualClient.cs + VoiceBuffer.cs diff --git a/src/Discord.Net.Audio/AudioClient.cs b/src/Discord.Net.Audio/AudioClient.cs index 9657b89a8..715a565d2 100644 --- a/src/Discord.Net.Audio/AudioClient.cs +++ b/src/Discord.Net.Audio/AudioClient.cs @@ -1,9 +1,12 @@ using Discord.API.Client.GatewaySocket; +using Discord.API.Client.Rest; using Discord.Logging; +using Discord.Net.Rest; using Discord.Net.WebSockets; using Newtonsoft.Json; using Nito.AsyncEx; using System; +using System.Diagnostics; using System.IO; using System.Threading; using System.Threading.Tasks; @@ -41,124 +44,190 @@ namespace Discord.Audio } } + private readonly DiscordConfig _config; private readonly AsyncLock _connectionLock; - private readonly JsonSerializer _serializer; - private CancellationTokenSource _cancelTokenSource; + private readonly TaskManager _taskManager; + private ConnectionState _gatewayState; - internal AudioService Service { get; } internal Logger Logger { get; } + + /// Gets the unique identifier for this client. public int Id { get; } + /// Gets the service managing this client. + public AudioService Service { get; } + /// Gets the configuration object used to make this client. + public AudioServiceConfig Config { get; } + /// Gets the internal RestClient for the Client API endpoint. + public RestClient ClientAPI { get; } + /// Gets the internal WebSocket for the Gateway event stream. public GatewaySocket GatewaySocket { get; } - public VoiceWebSocket VoiceSocket { get; } + /// Gets the internal WebSocket for the Voice control stream. + public VoiceSocket VoiceSocket { get; } + /// Gets the JSON serializer used by this client. + public JsonSerializer Serializer { get; } + /// public Stream OutputStream { get; } + /// Gets a cancellation token that triggers when the client is manually disconnected. + public CancellationToken CancelToken { get; private set; } + /// Gets the session id for the current connection. + public string SessionId { get; private set; } + + /// Gets the current state of this client. public ConnectionState State => VoiceSocket.State; + /// Gets the server this client is bound to. public Server Server => VoiceSocket.Server; + /// Gets the channel public Channel Channel => VoiceSocket.Channel; - public AudioClient(AudioService service, int clientId, Server server, GatewaySocket gatewaySocket, Logger logger) + public AudioClient(DiscordClient client, Server server, int id) { - Service = service; - _serializer = service.Client.Serializer; - Id = clientId; - GatewaySocket = gatewaySocket; - Logger = logger; - OutputStream = new OutStream(this); + Id = id; + _config = client.Config; + Service = client.Audio(); + Config = Service.Config; + Serializer = client.Serializer; + _gatewayState = (int)ConnectionState.Disconnected; - _connectionLock = new AsyncLock(); + //Logging + Logger = client.Log.CreateLogger($"AudioClient #{id}"); - GatewaySocket.ReceivedDispatch += OnReceivedDispatch; + //Async + _taskManager = new TaskManager(Cleanup, false); + _connectionLock = new AsyncLock(); + CancelToken = new CancellationToken(true); - VoiceSocket = new VoiceWebSocket(service.Client, this, logger); + //Networking + if (Config.EnableMultiserver) + { + ClientAPI = new RestClient(_config, DiscordConfig.ClientAPIUrl, client.Log.CreateLogger($"ClientAPI #{id}")); + GatewaySocket = new GatewaySocket(_config, client.Serializer, client.Log.CreateLogger($"Gateway #{id}")); + GatewaySocket.Connected += (s, e) => + { + if (_gatewayState == ConnectionState.Connecting) + EndGatewayConnect(); + }; + } + else + GatewaySocket = client.GatewaySocket; + GatewaySocket.ReceivedDispatch += (s, e) => OnReceivedEvent(e); + VoiceSocket = new VoiceSocket(_config, Config, client.Serializer, client.Log.CreateLogger($"Voice #{id}")); VoiceSocket.Server = server; - - /*_voiceSocket.Connected += (s, e) => RaiseVoiceConnected(); - _voiceSocket.Disconnected += async (s, e) => - { - _voiceSocket.CurrentServerId; - if (voiceServerId != null) - _gatewaySocket.SendLeaveVoice(voiceServerId.Value); - await _voiceSocket.Disconnect().ConfigureAwait(false); - RaiseVoiceDisconnected(socket.CurrentServerId.Value, e); - if (e.WasUnexpected) - await socket.Reconnect().ConfigureAwait(false); - };*/ - - /*_voiceSocket.IsSpeaking += (s, e) => - { - if (_voiceSocket.State == WebSocketState.Connected) - { - var user = _users[e.UserId, socket.CurrentServerId]; - bool value = e.IsSpeaking; - if (user.IsSpeaking != value) - { - user.IsSpeaking = value; - var channel = _channels[_voiceSocket.CurrentChannelId]; - RaiseUserIsSpeaking(user, channel, value); - if (Config.TrackActivity) - user.UpdateActivity(); - } - } - };*/ - - /*this.Connected += (s, e) => - { - _voiceSocket.ParentCancelToken = _cancelToken; - };*/ + OutputStream = new OutStream(this); } - public async Task Join(Channel channel) - { - if (channel == null) throw new ArgumentNullException(nameof(channel)); - if (channel.Type != ChannelType.Voice) - throw new ArgumentException("Channel must be a voice channel.", nameof(channel)); - if (channel.Server != VoiceSocket.Server) - throw new ArgumentException("This is channel is not part of the current server.", nameof(channel)); - if (channel == VoiceSocket.Channel) return; - if (VoiceSocket.Server == null) - throw new InvalidOperationException("This client has been closed."); - - using (await _connectionLock.LockAsync().ConfigureAwait(false)) + /// Connects to the Discord server with the provided token. + public async Task Connect() + { + if (Config.EnableMultiserver) + await BeginGatewayConnect().ConfigureAwait(false); + else { - VoiceSocket.Channel = channel; - - await Task.Run(() => + var cancelSource = new CancellationTokenSource(); + CancelToken = cancelSource.Token; + await _taskManager.Start(new Task[0], cancelSource).ConfigureAwait(false); + } + } + private async Task BeginGatewayConnect() + { + try + { + using (await _connectionLock.LockAsync().ConfigureAwait(false)) { - SendVoiceUpdate(); - VoiceSocket.WaitForConnection(_cancelTokenSource.Token); - }); + await Disconnect().ConfigureAwait(false); + _taskManager.ClearException(); + + ClientAPI.Token = Service.Client.ClientAPI.Token; + + Stopwatch stopwatch = null; + if (_config.LogLevel >= LogSeverity.Verbose) + stopwatch = Stopwatch.StartNew(); + _gatewayState = ConnectionState.Connecting; + + var cancelSource = new CancellationTokenSource(); + CancelToken = cancelSource.Token; + ClientAPI.CancelToken = CancelToken; + + await GatewaySocket.Connect(ClientAPI, CancelToken).ConfigureAwait(false); + + await _taskManager.Start(new Task[0], cancelSource).ConfigureAwait(false); + GatewaySocket.WaitForConnection(CancelToken); + + if (_config.LogLevel >= LogSeverity.Verbose) + { + stopwatch.Stop(); + double seconds = Math.Round(stopwatch.ElapsedTicks / (double)TimeSpan.TicksPerSecond, 2); + Logger.Verbose($"Connection took {seconds} sec"); + } + } + } + catch (Exception ex) + { + await _taskManager.SignalError(ex).ConfigureAwait(false); + throw; } } + private void EndGatewayConnect() + { + _gatewayState = ConnectionState.Connected; + } - public async Task Connect(bool connectGateway) + /// Disconnects from the Discord server, canceling any pending requests. + public async Task Disconnect() { - using (await _connectionLock.LockAsync().ConfigureAwait(false)) - { - _cancelTokenSource = new CancellationTokenSource(); - var cancelToken = _cancelTokenSource.Token; - VoiceSocket.ParentCancelToken = cancelToken; + await _taskManager.Stop(true).ConfigureAwait(false); + if (Config.EnableMultiserver) + ClientAPI.Token = null; + } + private async Task Cleanup() + { + var oldState = _gatewayState; + _gatewayState = ConnectionState.Disconnecting; - if (connectGateway) + if (Config.EnableMultiserver) + { + if (oldState == ConnectionState.Connected) { - GatewaySocket.ParentCancelToken = cancelToken; - await GatewaySocket.Connect().ConfigureAwait(false); - GatewaySocket.WaitForConnection(cancelToken); + try { await ClientAPI.Send(new LogoutRequest()).ConfigureAwait(false); } + catch (OperationCanceledException) { } } + + await GatewaySocket.Disconnect().ConfigureAwait(false); + ClientAPI.Token = null; } + + var server = VoiceSocket.Server; + VoiceSocket.Server = null; + VoiceSocket.Channel = null; + if (Config.EnableMultiserver) + await Service.RemoveClient(server, this).ConfigureAwait(false); + SendVoiceUpdate(server.Id, null); + + await VoiceSocket.Disconnect().ConfigureAwait(false); + if (Config.EnableMultiserver) + await GatewaySocket.Disconnect().ConfigureAwait(false); + + _gatewayState = (int)ConnectionState.Disconnected; } - public async Task Disconnect() + public async Task Join(Channel channel) { + if (channel == null) throw new ArgumentNullException(nameof(channel)); + if (channel.Type != ChannelType.Voice) + throw new ArgumentException("Channel must be a voice channel.", nameof(channel)); + if (channel == VoiceSocket.Channel) return; + var server = channel.Server; + if (server != VoiceSocket.Server) + throw new ArgumentException("This is channel is not part of the current server.", nameof(channel)); + if (VoiceSocket.Server == null) + throw new InvalidOperationException("This client has been closed."); + + SendVoiceUpdate(channel.Server.Id, channel.Id); using (await _connectionLock.LockAsync().ConfigureAwait(false)) - { - await Service.RemoveClient(VoiceSocket.Server, this).ConfigureAwait(false); - VoiceSocket.Channel = null; - SendVoiceUpdate(); - await VoiceSocket.Disconnect(); - } + await Task.Run(() => VoiceSocket.WaitForConnection(CancelToken)); } - private async void OnReceivedDispatch(object sender, WebSocketEventEventArgs e) + private async void OnReceivedEvent(WebSocketEventEventArgs e) { try { @@ -166,11 +235,11 @@ namespace Discord.Audio { case "VOICE_STATE_UPDATE": { - var data = e.Payload.ToObject(_serializer); + var data = e.Payload.ToObject(Serializer); if (data.GuildId == VoiceSocket.Server?.Id && data.UserId == Service.Client.CurrentUser?.Id) { if (data.ChannelId == null) - await Disconnect(); + await Disconnect().ConfigureAwait(false); else { var channel = Service.Client.GetChannel(data.ChannelId.Value); @@ -179,7 +248,7 @@ namespace Discord.Audio else { Logger.Warning("VOICE_STATE_UPDATE referenced an unknown channel, disconnecting."); - await Disconnect(); + await Disconnect().ConfigureAwait(false); } } } @@ -187,13 +256,16 @@ namespace Discord.Audio break; case "VOICE_SERVER_UPDATE": { - var data = e.Payload.ToObject(_serializer); + var data = e.Payload.ToObject(Serializer); if (data.GuildId == VoiceSocket.Server?.Id) { var client = Service.Client; - VoiceSocket.Token = data.Token; - VoiceSocket.Host = "wss://" + e.Payload.Value("endpoint").Split(':')[0]; - await VoiceSocket.Connect().ConfigureAwait(false); + var id = client.CurrentUser?.Id; + if (id != null) + { + var host = "wss://" + e.Payload.Value("endpoint").Split(':')[0]; + await VoiceSocket.Connect(host, data.Token, id.Value, GatewaySocket.SessionId, CancelToken).ConfigureAwait(false); + } } } break; @@ -233,15 +305,11 @@ namespace Discord.Audio VoiceSocket.WaitForQueue(); } - private void SendVoiceUpdate() + public void SendVoiceUpdate(ulong? serverId, ulong? channelId) { - var serverId = VoiceSocket.Server?.Id; - if (serverId != null) - { - GatewaySocket.SendUpdateVoice(serverId, VoiceSocket.Channel?.Id, - (Service.Config.Mode | AudioMode.Outgoing) == 0, - (Service.Config.Mode | AudioMode.Incoming) == 0); - } + GatewaySocket.SendUpdateVoice(serverId, channelId, + (Service.Config.Mode | AudioMode.Outgoing) == 0, + (Service.Config.Mode | AudioMode.Incoming) == 0); } } } diff --git a/src/Discord.Net.Audio/AudioClient.cs.old b/src/Discord.Net.Audio/AudioClient.cs.old new file mode 100644 index 000000000..17ee171b0 --- /dev/null +++ b/src/Discord.Net.Audio/AudioClient.cs.old @@ -0,0 +1,242 @@ +using Discord.API.Client.GatewaySocket; +using Discord.Logging; +using Discord.Net.Rest; +using Discord.Net.WebSockets; +using Newtonsoft.Json; +using Nito.AsyncEx; +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace Discord.Audio +{ + internal class AudioClient : IAudioClient + { + private class OutStream : Stream + { + public override bool CanRead => false; + public override bool CanSeek => false; + public override bool CanWrite => true; + + private readonly AudioClient _client; + + internal OutStream(AudioClient client) + { + _client = client; + } + + public override long Length { get { throw new InvalidOperationException(); } } + public override long Position + { + get { throw new InvalidOperationException(); } + set { throw new InvalidOperationException(); } + } + public override void Flush() { throw new InvalidOperationException(); } + public override long Seek(long offset, SeekOrigin origin) { throw new InvalidOperationException(); } + public override void SetLength(long value) { throw new InvalidOperationException(); } + public override int Read(byte[] buffer, int offset, int count) { throw new InvalidOperationException(); } + public override void Write(byte[] buffer, int offset, int count) + { + _client.Send(buffer, offset, count); + } + } + + private readonly JsonSerializer _serializer; + private readonly bool _ownsGateway; + private TaskManager _taskManager; + private CancellationToken _cancelToken; + + internal AudioService Service { get; } + internal Logger Logger { get; } + public int Id { get; } + public GatewaySocket GatewaySocket { get; } + public VoiceSocket VoiceSocket { get; } + public Stream OutputStream { get; } + + public ConnectionState State => VoiceSocket.State; + public Server Server => VoiceSocket.Server; + public Channel Channel => VoiceSocket.Channel; + + public AudioClient(AudioService service, int clientId, Server server, GatewaySocket gatewaySocket, bool ownsGateway, Logger logger) + { + Service = service; + _serializer = service.Client.Serializer; + Id = clientId; + GatewaySocket = gatewaySocket; + _ownsGateway = ownsGateway; + Logger = logger; + OutputStream = new OutStream(this); + _taskManager = new TaskManager(Cleanup, true); + + GatewaySocket.ReceivedDispatch += OnReceivedDispatch; + + VoiceSocket = new VoiceSocket(service.Client.Config, service.Config, service.Client.Serializer, logger); + VoiceSocket.Server = server; + + /*_voiceSocket.Connected += (s, e) => RaiseVoiceConnected(); + _voiceSocket.Disconnected += async (s, e) => + { + _voiceSocket.CurrentServerId; + if (voiceServerId != null) + _gatewaySocket.SendLeaveVoice(voiceServerId.Value); + await _voiceSocket.Disconnect().ConfigureAwait(false); + RaiseVoiceDisconnected(socket.CurrentServerId.Value, e); + if (e.WasUnexpected) + await socket.Reconnect().ConfigureAwait(false); + };*/ + + /*_voiceSocket.IsSpeaking += (s, e) => + { + if (_voiceSocket.State == WebSocketState.Connected) + { + var user = _users[e.UserId, socket.CurrentServerId]; + bool value = e.IsSpeaking; + if (user.IsSpeaking != value) + { + user.IsSpeaking = value; + var channel = _channels[_voiceSocket.CurrentChannelId]; + RaiseUserIsSpeaking(user, channel, value); + if (Config.TrackActivity) + user.UpdateActivity(); + } + } + };*/ + + /*this.Connected += (s, e) => + { + _voiceSocket.ParentCancelToken = _cancelToken; + };*/ + } + + public async Task Join(Channel channel) + { + if (channel == null) throw new ArgumentNullException(nameof(channel)); + if (channel.Type != ChannelType.Voice) + throw new ArgumentException("Channel must be a voice channel.", nameof(channel)); + if (channel.Server != VoiceSocket.Server) + throw new ArgumentException("This is channel is not part of the current server.", nameof(channel)); + if (channel == VoiceSocket.Channel) return; + if (VoiceSocket.Server == null) + throw new InvalidOperationException("This client has been closed."); + + SendVoiceUpdate(channel.Server.Id, channel.Id); + await Task.Run(() => VoiceSocket.WaitForConnection(_cancelToken)); + } + + public async Task Connect(RestClient rest = null) + { + var cancelSource = new CancellationTokenSource(); + _cancelToken = cancelSource.Token; + + Task[] tasks; + if (rest != null) + tasks = new Task[] { GatewaySocket.Connect(rest, _cancelToken) }; + else + tasks = new Task[0]; + + await _taskManager.Start(tasks, cancelSource); + } + + public Task Disconnect() => _taskManager.Stop(true); + + private async Task Cleanup() + { + var server = VoiceSocket.Server; + VoiceSocket.Server = null; + VoiceSocket.Channel = null; + + await Service.RemoveClient(server, this).ConfigureAwait(false); + SendVoiceUpdate(server.Id, null); + + await VoiceSocket.Disconnect().ConfigureAwait(false); + if (_ownsGateway) + await GatewaySocket.Disconnect().ConfigureAwait(false); + } + + private async void OnReceivedDispatch(object sender, WebSocketEventEventArgs e) + { + try + { + switch (e.Type) + { + case "VOICE_STATE_UPDATE": + { + var data = e.Payload.ToObject(_serializer); + if (data.GuildId == VoiceSocket.Server?.Id && data.UserId == Service.Client.CurrentUser?.Id) + { + if (data.ChannelId == null) + await Disconnect().ConfigureAwait(false); + else + { + var channel = Service.Client.GetChannel(data.ChannelId.Value); + if (channel != null) + VoiceSocket.Channel = channel; + else + { + Logger.Warning("VOICE_STATE_UPDATE referenced an unknown channel, disconnecting."); + await Disconnect().ConfigureAwait(false); + } + } + } + } + break; + case "VOICE_SERVER_UPDATE": + { + var data = e.Payload.ToObject(_serializer); + if (data.GuildId == VoiceSocket.Server?.Id) + { + var client = Service.Client; + var id = client.CurrentUser?.Id; + if (id != null) + { + var host = "wss://" + e.Payload.Value("endpoint").Split(':')[0]; + await VoiceSocket.Connect(host, data.Token, id.Value, GatewaySocket.SessionId, _cancelToken).ConfigureAwait(false); + } + } + } + break; + } + } + catch (Exception ex) + { + Logger.Error($"Error handling {e.Type} event", ex); + } + } + + /// Sends a PCM frame to the voice server. Will block until space frees up in the outgoing buffer. + /// PCM frame to send. This must be a single or collection of uncompressed 48Kz monochannel 20ms PCM frames. + /// Number of bytes in this frame. + public void Send(byte[] data, int offset, int count) + { + if (data == null) throw new ArgumentException(nameof(data)); + if (count < 0) throw new ArgumentOutOfRangeException(nameof(count)); + if (offset < 0) throw new ArgumentOutOfRangeException(nameof(count)); + if (VoiceSocket.Server == null) return; //Has been closed + if (count == 0) return; + + VoiceSocket.SendPCMFrames(data, offset, count); + } + + /// Clears the PCM buffer. + public void Clear() + { + if (VoiceSocket.Server == null) return; //Has been closed + VoiceSocket.ClearPCMFrames(); + } + + /// Returns a task that completes once the voice output buffer is empty. + public void Wait() + { + if (VoiceSocket.Server == null) return; //Has been closed + VoiceSocket.WaitForQueue(); + } + + public void SendVoiceUpdate(ulong? serverId, ulong? channelId) + { + GatewaySocket.SendUpdateVoice(serverId, channelId, + (Service.Config.Mode | AudioMode.Outgoing) == 0, + (Service.Config.Mode | AudioMode.Incoming) == 0); + } + } +} diff --git a/src/Discord.Net.Audio/AudioService.cs b/src/Discord.Net.Audio/AudioService.cs index ec134f884..b64bf53f8 100644 --- a/src/Discord.Net.Audio/AudioService.cs +++ b/src/Discord.Net.Audio/AudioService.cs @@ -1,4 +1,4 @@ -using Discord.Net.WebSockets; +using Nito.AsyncEx; using System; using System.Collections.Concurrent; using System.Linq; @@ -8,8 +8,10 @@ namespace Discord.Audio { public class AudioService : IService { - private AudioClient _defaultClient; - private ConcurrentDictionary _voiceClients; + private readonly AsyncLock _asyncLock; + private AudioClient _defaultClient; //Only used for single server + private VirtualClient _currentClient; //Only used for single server + private ConcurrentDictionary _voiceClients; private ConcurrentDictionary _talkingUsers; private int _nextClientId; @@ -30,18 +32,20 @@ namespace Discord.Audio public AudioService(AudioServiceConfig config) { Config = config; - } + _asyncLock = new AsyncLock(); + + } void IService.Install(DiscordClient client) { Client = client; Config.Lock(); if (Config.EnableMultiserver) - _voiceClients = new ConcurrentDictionary(); + _voiceClients = new ConcurrentDictionary(); else { var logger = Client.Log.CreateLogger("Voice"); - _defaultClient = new SimpleAudioClient(this, 0, logger); + _defaultClient = new AudioClient(Client, null, 0); } _talkingUsers = new ConcurrentDictionary(); @@ -75,68 +79,30 @@ namespace Discord.Audio { if (server == null) throw new ArgumentNullException(nameof(server)); - if (!Config.EnableMultiserver) + if (Config.EnableMultiserver) { - if (server == _defaultClient.Server) - return (_defaultClient as SimpleAudioClient).CurrentClient; + AudioClient client; + if (_voiceClients.TryGetValue(server.Id, out client)) + return client; else return null; } else { - IAudioClient client; - if (_voiceClients.TryGetValue(server.Id, out client)) - return client; + if (server == _currentClient.Server) + return _currentClient; else return null; } } - private async Task CreateClient(Server server) - { - var client = _voiceClients.GetOrAdd(server.Id, _ => null); //Placeholder, so we can't have two clients connecting at once - - if (client == null) - { - int id = unchecked(++_nextClientId); - - var gatewayLogger = Client.Log.CreateLogger($"Gateway #{id}"); - var voiceLogger = Client.Log.CreateLogger($"Voice #{id}"); - var gatewaySocket = new GatewaySocket(Client, gatewayLogger); - var voiceClient = new AudioClient(this, id, server, Client.GatewaySocket, voiceLogger); - - await voiceClient.Connect(true).ConfigureAwait(false); - - /*voiceClient.VoiceSocket.FrameReceived += (s, e) => - { - OnFrameReceieved(e); - }; - voiceClient.VoiceSocket.UserIsSpeaking += (s, e) => - { - var user = server.GetUser(e.UserId); - OnUserIsSpeakingUpdated(user, e.IsSpeaking); - };*/ - - //Update the placeholder only it still exists (RemoveClient wasnt called) - if (!_voiceClients.TryUpdate(server.Id, voiceClient, null)) - { - //If it was, cleanup - await voiceClient.Disconnect().ConfigureAwait(false); ; - await gatewaySocket.Disconnect().ConfigureAwait(false); ; - } - } - return client; - } - - //TODO: This isn't threadsafe - internal async Task RemoveClient(Server server, IAudioClient client) + + //Called from AudioClient.Disconnect + internal async Task RemoveClient(Server server, AudioClient client) { - if (Config.EnableMultiserver && server != null) + using (await _asyncLock.LockAsync().ConfigureAwait(false)) { - if (_voiceClients.TryRemove(server.Id, out client)) - { - await client.Disconnect(); - await (client as AudioClient).GatewaySocket.Disconnect(); - } + if (_voiceClients.TryUpdate(server.Id, null, client)) + _voiceClients.TryRemove(server.Id, out client); } } @@ -144,16 +110,48 @@ namespace Discord.Audio { if (channel == null) throw new ArgumentNullException(nameof(channel)); - if (!Config.EnableMultiserver) - { - await (_defaultClient as SimpleAudioClient).Connect(channel, false).ConfigureAwait(false); - return _defaultClient; - } - else + var server = channel.Server; + using (await _asyncLock.LockAsync().ConfigureAwait(false)) { - var client = await CreateClient(channel.Server).ConfigureAwait(false); - await client.Join(channel).ConfigureAwait(false); - return client; + if (Config.EnableMultiserver) + { + AudioClient client; + if (!_voiceClients.TryGetValue(server.Id, out client)) + { + client = new AudioClient(Client, server, unchecked(++_nextClientId)); + _voiceClients[server.Id] = client; + + await client.Connect().ConfigureAwait(false); + + /*voiceClient.VoiceSocket.FrameReceived += (s, e) => + { + OnFrameReceieved(e); + }; + voiceClient.VoiceSocket.UserIsSpeaking += (s, e) => + { + var user = server.GetUser(e.UserId); + OnUserIsSpeakingUpdated(user, e.IsSpeaking); + };*/ + } + + await client.Join(channel).ConfigureAwait(false); + return client; + } + else + { + if (_defaultClient.Server != server) + { + await _defaultClient.Disconnect(); + _defaultClient.VoiceSocket.Server = server; + await _defaultClient.Connect().ConfigureAwait(false); + } + var client = new VirtualClient(_defaultClient, server); + _currentClient = client; + + await client.Join(channel).ConfigureAwait(false); + return client; + } + } } @@ -163,15 +161,18 @@ namespace Discord.Audio if (Config.EnableMultiserver) { - IAudioClient client; + AudioClient client; if (_voiceClients.TryRemove(server.Id, out client)) await client.Disconnect().ConfigureAwait(false); } else { - IAudioClient client = GetClient(server); - if (client != null) - await (_defaultClient as SimpleAudioClient).Leave(client as SimpleAudioClient.VirtualClient).ConfigureAwait(false); + using (await _asyncLock.LockAsync().ConfigureAwait(false)) + { + var client = GetClient(server) as VirtualClient; + if (client != null) + await _defaultClient.Disconnect().ConfigureAwait(false); + } } } } diff --git a/src/Discord.Net.Audio/Net/VoiceWebSocket.cs b/src/Discord.Net.Audio/Net/VoiceSocket.cs similarity index 90% rename from src/Discord.Net.Audio/Net/VoiceWebSocket.cs rename to src/Discord.Net.Audio/Net/VoiceSocket.cs index c7c4ee0e2..bab928b53 100644 --- a/src/Discord.Net.Audio/Net/VoiceWebSocket.cs +++ b/src/Discord.Net.Audio/Net/VoiceSocket.cs @@ -19,7 +19,7 @@ using System.Threading.Tasks; namespace Discord.Net.WebSockets { - public partial class VoiceWebSocket : WebSocket + public partial class VoiceSocket : WebSocket { private const int MaxOpusSize = 4000; private const string EncryptedMode = "xsalsa20_poly1305"; @@ -27,8 +27,7 @@ namespace Discord.Net.WebSockets private readonly int _targetAudioBufferLength; private readonly ConcurrentDictionary _decoders; - private readonly AudioClient _audioClient; - private readonly AudioServiceConfig _config; + private readonly AudioServiceConfig _audioConfig; private Task _sendTask, _receiveTask; private VoiceBuffer _sendBuffer; private OpusEncoder _encoder; @@ -41,6 +40,8 @@ namespace Discord.Net.WebSockets private ushort _sequence; private string _encryptionMode; private int _ping; + private ulong? _userId; + private string _sessionId; public string Token { get; internal set; } public Server Server { get; internal set; } @@ -57,32 +58,37 @@ namespace Discord.Net.WebSockets internal void OnFrameReceived(ulong userId, ulong channelId, byte[] buffer, int offset, int count) => FrameReceived(this, new InternalFrameEventArgs(userId, channelId, buffer, offset, count)); - internal VoiceWebSocket(DiscordClient client, AudioClient audioClient, Logger logger) - : base(client, logger) + internal VoiceSocket(DiscordConfig config, AudioServiceConfig audioConfig, JsonSerializer serializer, Logger logger) + : base(config, serializer, logger) { - _audioClient = audioClient; - _config = client.Audio().Config; + _audioConfig = audioConfig; _decoders = new ConcurrentDictionary(); - _targetAudioBufferLength = _config.BufferLength / 20; //20 ms frames + _targetAudioBufferLength = _audioConfig.BufferLength / 20; //20 ms frames _encodingBuffer = new byte[MaxOpusSize]; _ssrcMapping = new ConcurrentDictionary(); - _encoder = new OpusEncoder(48000, _config.Channels, 20, _config.Bitrate, OpusApplication.MusicOrMixed); - _sendBuffer = new VoiceBuffer((int)Math.Ceiling(_config.BufferLength / (double)_encoder.FrameLength), _encoder.FrameSize); + _encoder = new OpusEncoder(48000, _audioConfig.Channels, 20, _audioConfig.Bitrate, OpusApplication.MusicOrMixed); + _sendBuffer = new VoiceBuffer((int)Math.Ceiling(_audioConfig.BufferLength / (double)_encoder.FrameLength), _encoder.FrameSize); } - public Task Connect() - => BeginConnect(); + public Task Connect(string host, string token, ulong userId, string sessionId, CancellationToken parentCancelToken) + { + Host = host; + Token = token; + _userId = userId; + _sessionId = sessionId; + return BeginConnect(parentCancelToken); + } private async Task Reconnect() { try { - var cancelToken = ParentCancelToken.Value; - await Task.Delay(_client.Config.ReconnectDelay, cancelToken).ConfigureAwait(false); + var cancelToken = _parentCancelToken; + await Task.Delay(_config.ReconnectDelay, cancelToken).ConfigureAwait(false); while (!cancelToken.IsCancellationRequested) { try { - await Connect().ConfigureAwait(false); + await BeginConnect(_parentCancelToken).ConfigureAwait(false); break; } catch (OperationCanceledException) { throw; } @@ -90,31 +96,35 @@ namespace Discord.Net.WebSockets { Logger.Error("Reconnect failed", ex); //Net is down? We can keep trying to reconnect until the user runs Disconnect() - await Task.Delay(_client.Config.FailedReconnectDelay, cancelToken).ConfigureAwait(false); + await Task.Delay(_config.FailedReconnectDelay, cancelToken).ConfigureAwait(false); } } } catch (OperationCanceledException) { } } - public Task Disconnect() => _taskManager.Stop(true); + public async Task Disconnect() + { + await _taskManager.Stop(true).ConfigureAwait(false); + _userId = null; + } protected override async Task Run() { _udp = new UdpClient(new IPEndPoint(IPAddress.Any, 0)); List tasks = new List(); - if (_config.Mode.HasFlag(AudioMode.Outgoing)) + if (_audioConfig.Mode.HasFlag(AudioMode.Outgoing)) _sendTask = Task.Run(() => SendVoiceAsync(CancelToken)); _receiveTask = Task.Run(() => ReceiveVoiceAsync(CancelToken)); - SendIdentify(); + SendIdentify(_userId.Value, _sessionId); #if !DOTNET5_4 tasks.Add(WatcherAsync()); #endif tasks.AddRange(_engine.GetTasks(CancelToken)); tasks.Add(HeartbeatAsync(CancelToken)); - await _taskManager.Start(tasks, _cancelTokenSource).ConfigureAwait(false); + await _taskManager.Start(tasks, _cancelSource).ConfigureAwait(false); } protected override async Task Cleanup() { @@ -148,7 +158,7 @@ namespace Discord.Net.WebSockets int packetLength, resultOffset, resultLength; IPEndPoint endpoint = new IPEndPoint(IPAddress.Any, 0); - if ((_config.Mode & AudioMode.Incoming) != 0) + if ((_audioConfig.Mode & AudioMode.Incoming) != 0) { decodingBuffer = new byte[MaxOpusSize]; nonce = new byte[24]; @@ -184,7 +194,7 @@ namespace Discord.Net.WebSockets int port = packet[68] | packet[69] << 8; SendSelectProtocol(ip, port); - if ((_config.Mode & AudioMode.Incoming) == 0) + if ((_audioConfig.Mode & AudioMode.Incoming) == 0) return; //We dont need this thread anymore } else @@ -395,7 +405,7 @@ namespace Discord.Net.WebSockets var address = (await Dns.GetHostAddressesAsync(Host.Replace("wss://", "")).ConfigureAwait(false)).FirstOrDefault(); _endpoint = new IPEndPoint(address, payload.Port); - if (_config.EnableEncryption) + if (_audioConfig.EnableEncryption) { if (payload.Modes.Contains(EncryptedMode)) { @@ -467,12 +477,12 @@ namespace Discord.Net.WebSockets public override void SendHeartbeat() => QueueMessage(new HeartbeatCommand()); - public void SendIdentify() + public void SendIdentify(ulong id, string sessionId) => QueueMessage(new IdentifyCommand { GuildId = Server.Id, - UserId = _client.CurrentUser.Id, - SessionId = _client.SessionId, + UserId = id, + SessionId = sessionId, Token = Token }); public void SendSelectProtocol(string externalAddress, int externalPort) diff --git a/src/Discord.Net.Audio/SimpleAudioClient.cs b/src/Discord.Net.Audio/SimpleAudioClient.cs deleted file mode 100644 index b073e2ed3..000000000 --- a/src/Discord.Net.Audio/SimpleAudioClient.cs +++ /dev/null @@ -1,72 +0,0 @@ -using Discord.Logging; -using Nito.AsyncEx; -using System.IO; -using System.Threading.Tasks; - -namespace Discord.Audio -{ - internal class SimpleAudioClient : AudioClient - { - internal class VirtualClient : IAudioClient - { - private readonly SimpleAudioClient _client; - - ConnectionState IAudioClient.State => _client.VoiceSocket.State; - Server IAudioClient.Server => _client.VoiceSocket.Server; - Channel IAudioClient.Channel => _client.VoiceSocket.Channel; - Stream IAudioClient.OutputStream => _client.OutputStream; - - public VirtualClient(SimpleAudioClient client) - { - _client = client; - } - - Task IAudioClient.Disconnect() => _client.Leave(this); - Task IAudioClient.Join(Channel channel) => _client.Join(channel); - - void IAudioClient.Send(byte[] data, int offset, int count) => _client.Send(data, offset, count); - void IAudioClient.Clear() => _client.Clear(); - void IAudioClient.Wait() => _client.Wait(); - } - - private readonly AsyncLock _connectionLock; - - internal VirtualClient CurrentClient { get; private set; } - - public SimpleAudioClient(AudioService service, int id, Logger logger) - : base(service, id, null, service.Client.GatewaySocket, logger) - { - _connectionLock = new AsyncLock(); - } - - //Only disconnects if is current a member of this server - public async Task Leave(VirtualClient client) - { - using (await _connectionLock.LockAsync().ConfigureAwait(false)) - { - if (CurrentClient == client) - { - CurrentClient = null; - await Disconnect().ConfigureAwait(false); - } - } - } - - internal async Task Connect(Channel channel, bool connectGateway) - { - using (await _connectionLock.LockAsync().ConfigureAwait(false)) - { - bool changeServer = channel.Server != VoiceSocket.Server; - if (changeServer || CurrentClient == null) - { - await Disconnect().ConfigureAwait(false); - CurrentClient = new VirtualClient(this); - VoiceSocket.Server = channel.Server; - await Connect(connectGateway).ConfigureAwait(false); - } - await Join(channel).ConfigureAwait(false); - return CurrentClient; - } - } - } -} diff --git a/src/Discord.Net.Audio/VirtualClient.cs b/src/Discord.Net.Audio/VirtualClient.cs new file mode 100644 index 000000000..0d197fb95 --- /dev/null +++ b/src/Discord.Net.Audio/VirtualClient.cs @@ -0,0 +1,29 @@ +using System.IO; +using System.Threading.Tasks; + +namespace Discord.Audio +{ + internal class VirtualClient : IAudioClient + { + private readonly AudioClient _client; + + public Server Server { get; } + + public ConnectionState State => _client.VoiceSocket.Server == Server ? _client.VoiceSocket.State : ConnectionState.Disconnected; + public Channel Channel => _client.VoiceSocket.Server == Server ? _client.VoiceSocket.Channel : null; + public Stream OutputStream => _client.VoiceSocket.Server == Server ? _client.OutputStream : null; + + public VirtualClient(AudioClient client, Server server) + { + _client = client; + Server = server; + } + + public Task Disconnect() => _client.Service.Leave(Server); + public Task Join(Channel channel) => _client.Join(channel); + + public void Send(byte[] data, int offset, int count) => _client.Send(data, offset, count); + public void Clear() => _client.Clear(); + public void Wait() => _client.Wait(); + } +} diff --git a/src/Discord.Net.Commands/Command.cs b/src/Discord.Net.Commands/Command.cs index 8aec0b90f..a8addc1b1 100644 --- a/src/Discord.Net.Commands/Command.cs +++ b/src/Discord.Net.Commands/Command.cs @@ -5,7 +5,8 @@ using System.Threading.Tasks; namespace Discord.Commands { - public sealed class Command + //TODO: Make this more friendly and expose it to be extendable + public class Command { private string[] _aliases; internal CommandParameter[] _parameters; diff --git a/src/Discord.Net.Commands/CommandBuilder.cs b/src/Discord.Net.Commands/CommandBuilder.cs index 3659f82c5..129dc24ab 100644 --- a/src/Discord.Net.Commands/CommandBuilder.cs +++ b/src/Discord.Net.Commands/CommandBuilder.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; namespace Discord.Commands { + //TODO: Make this more friendly and expose it to be extendable public sealed class CommandBuilder { private readonly CommandService _service; @@ -18,17 +19,20 @@ namespace Discord.Commands public CommandService Service => _service; - internal CommandBuilder(CommandService service, Command command, string prefix = "", string category = "", IEnumerable initialChecks = null) + internal CommandBuilder(CommandService service, string text, string prefix = "", string category = "", IEnumerable initialChecks = null) { - _service = service; - _command = command; - _command.Category = category; - _params = new List(); + _service = service; + _prefix = prefix; + + _command = new Command(AppendPrefix(prefix, text)); + _command.Category = category; + if (initialChecks != null) _checks = new List(initialChecks); else _checks = new List(); - _prefix = prefix; + + _params = new List(); _aliases = new List(); _allowRequiredParams = true; @@ -112,7 +116,7 @@ namespace Discord.Commands return prefix; } } - public sealed class CommandGroupBuilder + public class CommandGroupBuilder { private readonly CommandService _service; private readonly string _prefix; @@ -154,9 +158,6 @@ namespace Discord.Commands public CommandBuilder CreateCommand() => CreateCommand(""); public CommandBuilder CreateCommand(string cmd) - { - var command = new Command(CommandBuilder.AppendPrefix(_prefix, cmd)); - return new CommandBuilder(_service, command, _prefix, _category, _checks); - } + => new CommandBuilder(_service, cmd, _prefix, _category, _checks); } } diff --git a/src/Discord.Net.Commands/CommandParameter.cs b/src/Discord.Net.Commands/CommandParameter.cs index 413674b78..d7361bef4 100644 --- a/src/Discord.Net.Commands/CommandParameter.cs +++ b/src/Discord.Net.Commands/CommandParameter.cs @@ -11,13 +11,13 @@ /// Catches all remaining text as a single optional parameter. Unparsed } - public sealed class CommandParameter + public class CommandParameter { public string Name { get; } public int Id { get; internal set; } public ParameterType Type { get; } - public CommandParameter(string name, ParameterType type) + internal CommandParameter(string name, ParameterType type) { Name = name; Type = type; diff --git a/src/Discord.Net.Modules/ModuleManager.cs b/src/Discord.Net.Modules/ModuleManager.cs index bd6f27519..17b2b166b 100644 --- a/src/Discord.Net.Modules/ModuleManager.cs +++ b/src/Discord.Net.Modules/ModuleManager.cs @@ -7,7 +7,7 @@ using System.Linq; namespace Discord.Modules { - public sealed class ModuleManager + public class ModuleManager { public event EventHandler ServerEnabled = delegate { }; public event EventHandler ServerDisabled = delegate { }; diff --git a/src/Discord.Net/API/Client/Common/Channel.cs b/src/Discord.Net/API/Client/Common/Channel.cs index f54c11e5c..90ed8bf38 100644 --- a/src/Discord.Net/API/Client/Common/Channel.cs +++ b/src/Discord.Net/API/Client/Common/Channel.cs @@ -5,7 +5,7 @@ namespace Discord.API.Client { public class Channel : ChannelReference { - public sealed class PermissionOverwrite + public class PermissionOverwrite { [JsonProperty("type")] public string Type { get; set; } diff --git a/src/Discord.Net/API/Client/Common/ExtendedGuild.cs b/src/Discord.Net/API/Client/Common/ExtendedGuild.cs index 9da0355ca..bfb1971cc 100644 --- a/src/Discord.Net/API/Client/Common/ExtendedGuild.cs +++ b/src/Discord.Net/API/Client/Common/ExtendedGuild.cs @@ -4,7 +4,7 @@ namespace Discord.API.Client { public class ExtendedGuild : Guild { - public sealed class ExtendedMemberInfo : Member + public class ExtendedMemberInfo : Member { [JsonProperty("mute")] public bool? IsServerMuted { get; set; } diff --git a/src/Discord.Net/API/Client/Common/Guild.cs b/src/Discord.Net/API/Client/Common/Guild.cs index bfa047c57..fd4519147 100644 --- a/src/Discord.Net/API/Client/Common/Guild.cs +++ b/src/Discord.Net/API/Client/Common/Guild.cs @@ -6,7 +6,7 @@ namespace Discord.API.Client { public class Guild : GuildReference { - public sealed class EmojiData + public class EmojiData { [JsonProperty("id")] public string Id { get; set; } diff --git a/src/Discord.Net/API/Client/Common/InviteReference.cs b/src/Discord.Net/API/Client/Common/InviteReference.cs index 4c25d9ad3..194165173 100644 --- a/src/Discord.Net/API/Client/Common/InviteReference.cs +++ b/src/Discord.Net/API/Client/Common/InviteReference.cs @@ -4,7 +4,7 @@ namespace Discord.API.Client { public class InviteReference { - public sealed class GuildData : GuildReference + public class GuildData : GuildReference { [JsonProperty("splash_hash")] public string Splash { get; set; } diff --git a/src/Discord.Net/API/Client/Common/MemberPresence.cs b/src/Discord.Net/API/Client/Common/MemberPresence.cs index 283b9a1d1..589ad46c1 100644 --- a/src/Discord.Net/API/Client/Common/MemberPresence.cs +++ b/src/Discord.Net/API/Client/Common/MemberPresence.cs @@ -5,7 +5,7 @@ namespace Discord.API.Client { public class MemberPresence : MemberReference { - public sealed class GameInfo + public class GameInfo { [JsonProperty("name")] public string Name { get; set; } diff --git a/src/Discord.Net/API/Client/Common/Message.cs b/src/Discord.Net/API/Client/Common/Message.cs index f89d81d96..7736d0ef1 100644 --- a/src/Discord.Net/API/Client/Common/Message.cs +++ b/src/Discord.Net/API/Client/Common/Message.cs @@ -5,7 +5,7 @@ namespace Discord.API.Client { public class Message : MessageReference { - public sealed class Attachment + public class Attachment { [JsonProperty("id")] public string Id { get; set; } @@ -23,9 +23,9 @@ namespace Discord.API.Client public int Height { get; set; } } - public sealed class Embed + public class Embed { - public sealed class Reference + public class Reference { [JsonProperty("url")] public string Url { get; set; } @@ -33,7 +33,7 @@ namespace Discord.API.Client public string Name { get; set; } } - public sealed class ThumbnailInfo + public class ThumbnailInfo { [JsonProperty("url")] public string Url { get; set; } @@ -44,7 +44,7 @@ namespace Discord.API.Client [JsonProperty("height")] public int Height { get; set; } } - public sealed class VideoInfo + public class VideoInfo { [JsonProperty("url")] public string Url { get; set; } diff --git a/src/Discord.Net/API/Client/GatewaySocket/Commands/Heartbeat.cs b/src/Discord.Net/API/Client/GatewaySocket/Commands/Heartbeat.cs index 68a0e98e6..9f3f9cefb 100644 --- a/src/Discord.Net/API/Client/GatewaySocket/Commands/Heartbeat.cs +++ b/src/Discord.Net/API/Client/GatewaySocket/Commands/Heartbeat.cs @@ -3,7 +3,7 @@ namespace Discord.API.Client.GatewaySocket { [JsonObject(MemberSerialization.OptIn)] - public sealed class HeartbeatCommand : IWebSocketMessage + public class HeartbeatCommand : IWebSocketMessage { int IWebSocketMessage.OpCode => (int)OpCodes.Heartbeat; object IWebSocketMessage.Payload => EpochTime.GetMilliseconds(); diff --git a/src/Discord.Net/API/Client/GatewaySocket/Commands/Identify.cs b/src/Discord.Net/API/Client/GatewaySocket/Commands/Identify.cs index 76a3a3e6f..2a56143c8 100644 --- a/src/Discord.Net/API/Client/GatewaySocket/Commands/Identify.cs +++ b/src/Discord.Net/API/Client/GatewaySocket/Commands/Identify.cs @@ -4,7 +4,7 @@ using System.Collections.Generic; namespace Discord.API.Client.GatewaySocket { [JsonObject(MemberSerialization.OptIn)] - public sealed class IdentifyCommand : IWebSocketMessage + public class IdentifyCommand : IWebSocketMessage { int IWebSocketMessage.OpCode => (int)OpCodes.Identify; object IWebSocketMessage.Payload => this; diff --git a/src/Discord.Net/API/Client/GatewaySocket/Commands/RequestMembers.cs b/src/Discord.Net/API/Client/GatewaySocket/Commands/RequestMembers.cs index 2a2799c12..ea8ab4a75 100644 --- a/src/Discord.Net/API/Client/GatewaySocket/Commands/RequestMembers.cs +++ b/src/Discord.Net/API/Client/GatewaySocket/Commands/RequestMembers.cs @@ -4,7 +4,7 @@ using Newtonsoft.Json; namespace Discord.API.Client.GatewaySocket { [JsonObject(MemberSerialization.OptIn)] - public sealed class RequestMembersCommand : IWebSocketMessage + public class RequestMembersCommand : IWebSocketMessage { int IWebSocketMessage.OpCode => (int)OpCodes.RequestGuildMembers; object IWebSocketMessage.Payload => this; diff --git a/src/Discord.Net/API/Client/GatewaySocket/Commands/Resume.cs b/src/Discord.Net/API/Client/GatewaySocket/Commands/Resume.cs index f473369cf..15486e577 100644 --- a/src/Discord.Net/API/Client/GatewaySocket/Commands/Resume.cs +++ b/src/Discord.Net/API/Client/GatewaySocket/Commands/Resume.cs @@ -3,7 +3,7 @@ namespace Discord.API.Client.GatewaySocket { [JsonObject(MemberSerialization.OptIn)] - public sealed class ResumeCommand : IWebSocketMessage + public class ResumeCommand : IWebSocketMessage { int IWebSocketMessage.OpCode => (int)OpCodes.Resume; object IWebSocketMessage.Payload => this; diff --git a/src/Discord.Net/API/Client/GatewaySocket/Commands/UpdateStatus.cs b/src/Discord.Net/API/Client/GatewaySocket/Commands/UpdateStatus.cs index 75bfce892..dff18b08c 100644 --- a/src/Discord.Net/API/Client/GatewaySocket/Commands/UpdateStatus.cs +++ b/src/Discord.Net/API/Client/GatewaySocket/Commands/UpdateStatus.cs @@ -3,13 +3,13 @@ namespace Discord.API.Client.GatewaySocket { [JsonObject(MemberSerialization.OptIn)] - public sealed class UpdateStatusCommand : IWebSocketMessage + public class UpdateStatusCommand : IWebSocketMessage { int IWebSocketMessage.OpCode => (int)OpCodes.StatusUpdate; object IWebSocketMessage.Payload => this; bool IWebSocketMessage.IsPrivate => false; - public sealed class GameInfo + public class GameInfo { [JsonProperty("name")] public string Name { get; set; } diff --git a/src/Discord.Net/API/Client/GatewaySocket/Commands/UpdateVoice.cs b/src/Discord.Net/API/Client/GatewaySocket/Commands/UpdateVoice.cs index 4eced5dcf..3ccf92c65 100644 --- a/src/Discord.Net/API/Client/GatewaySocket/Commands/UpdateVoice.cs +++ b/src/Discord.Net/API/Client/GatewaySocket/Commands/UpdateVoice.cs @@ -4,7 +4,7 @@ using Newtonsoft.Json; namespace Discord.API.Client.GatewaySocket { [JsonObject(MemberSerialization.OptIn)] - public sealed class UpdateVoiceCommand : IWebSocketMessage + public class UpdateVoiceCommand : IWebSocketMessage { int IWebSocketMessage.OpCode => (int)OpCodes.VoiceStateUpdate; object IWebSocketMessage.Payload => this; diff --git a/src/Discord.Net/API/Client/GatewaySocket/Events/ChannelCreate.cs b/src/Discord.Net/API/Client/GatewaySocket/Events/ChannelCreate.cs index da4e47aa9..ca26fecc7 100644 --- a/src/Discord.Net/API/Client/GatewaySocket/Events/ChannelCreate.cs +++ b/src/Discord.Net/API/Client/GatewaySocket/Events/ChannelCreate.cs @@ -1,4 +1,4 @@ namespace Discord.API.Client.GatewaySocket { - public sealed class ChannelCreateEvent : Channel { } + public class ChannelCreateEvent : Channel { } } diff --git a/src/Discord.Net/API/Client/GatewaySocket/Events/ChannelDelete.cs b/src/Discord.Net/API/Client/GatewaySocket/Events/ChannelDelete.cs index 64452dbe2..2b61a7d78 100644 --- a/src/Discord.Net/API/Client/GatewaySocket/Events/ChannelDelete.cs +++ b/src/Discord.Net/API/Client/GatewaySocket/Events/ChannelDelete.cs @@ -1,4 +1,4 @@ namespace Discord.API.Client.GatewaySocket { - public sealed class ChannelDeleteEvent : Channel { } + public class ChannelDeleteEvent : Channel { } } diff --git a/src/Discord.Net/API/Client/GatewaySocket/Events/ChannelUpdate.cs b/src/Discord.Net/API/Client/GatewaySocket/Events/ChannelUpdate.cs index 908c65267..4565ce1bc 100644 --- a/src/Discord.Net/API/Client/GatewaySocket/Events/ChannelUpdate.cs +++ b/src/Discord.Net/API/Client/GatewaySocket/Events/ChannelUpdate.cs @@ -1,4 +1,4 @@ namespace Discord.API.Client.GatewaySocket { - public sealed class ChannelUpdateEvent : Channel { } + public class ChannelUpdateEvent : Channel { } } diff --git a/src/Discord.Net/API/Client/GatewaySocket/Events/GuildBanAdd.cs b/src/Discord.Net/API/Client/GatewaySocket/Events/GuildBanAdd.cs index 823e3506a..7ba24473a 100644 --- a/src/Discord.Net/API/Client/GatewaySocket/Events/GuildBanAdd.cs +++ b/src/Discord.Net/API/Client/GatewaySocket/Events/GuildBanAdd.cs @@ -1,4 +1,4 @@ namespace Discord.API.Client.GatewaySocket { - public sealed class GuildBanAddEvent : MemberReference { } + public class GuildBanAddEvent : MemberReference { } } diff --git a/src/Discord.Net/API/Client/GatewaySocket/Events/GuildBanRemove.cs b/src/Discord.Net/API/Client/GatewaySocket/Events/GuildBanRemove.cs index a977b8702..a56a98494 100644 --- a/src/Discord.Net/API/Client/GatewaySocket/Events/GuildBanRemove.cs +++ b/src/Discord.Net/API/Client/GatewaySocket/Events/GuildBanRemove.cs @@ -1,4 +1,4 @@ namespace Discord.API.Client.GatewaySocket { - public sealed class GuildBanRemoveEvent : MemberReference { } + public class GuildBanRemoveEvent : MemberReference { } } diff --git a/src/Discord.Net/API/Client/GatewaySocket/Events/GuildCreate.cs b/src/Discord.Net/API/Client/GatewaySocket/Events/GuildCreate.cs index 13e3393fc..41c1c71c7 100644 --- a/src/Discord.Net/API/Client/GatewaySocket/Events/GuildCreate.cs +++ b/src/Discord.Net/API/Client/GatewaySocket/Events/GuildCreate.cs @@ -1,4 +1,4 @@ namespace Discord.API.Client.GatewaySocket { - public sealed class GuildCreateEvent : ExtendedGuild { } + public class GuildCreateEvent : ExtendedGuild { } } diff --git a/src/Discord.Net/API/Client/GatewaySocket/Events/GuildDelete.cs b/src/Discord.Net/API/Client/GatewaySocket/Events/GuildDelete.cs index 6850cc2b9..cf824c40e 100644 --- a/src/Discord.Net/API/Client/GatewaySocket/Events/GuildDelete.cs +++ b/src/Discord.Net/API/Client/GatewaySocket/Events/GuildDelete.cs @@ -1,4 +1,4 @@ namespace Discord.API.Client.GatewaySocket { - public sealed class GuildDeleteEvent : ExtendedGuild { } + public class GuildDeleteEvent : ExtendedGuild { } } diff --git a/src/Discord.Net/API/Client/GatewaySocket/Events/GuildEmojisUpdate.cs b/src/Discord.Net/API/Client/GatewaySocket/Events/GuildEmojisUpdate.cs index 8d2400a7b..06255bdcf 100644 --- a/src/Discord.Net/API/Client/GatewaySocket/Events/GuildEmojisUpdate.cs +++ b/src/Discord.Net/API/Client/GatewaySocket/Events/GuildEmojisUpdate.cs @@ -1,4 +1,4 @@ namespace Discord.API.Client.GatewaySocket.Events { - //public sealed class GuildEmojisUpdateEvent { } + //public class GuildEmojisUpdateEvent { } } diff --git a/src/Discord.Net/API/Client/GatewaySocket/Events/GuildIntegrationsUpdate.cs b/src/Discord.Net/API/Client/GatewaySocket/Events/GuildIntegrationsUpdate.cs index d5ac59521..0767b2f8f 100644 --- a/src/Discord.Net/API/Client/GatewaySocket/Events/GuildIntegrationsUpdate.cs +++ b/src/Discord.Net/API/Client/GatewaySocket/Events/GuildIntegrationsUpdate.cs @@ -1,4 +1,4 @@ namespace Discord.API.Client.GatewaySocket { - //public sealed class GuildIntegrationsUpdateEvent { } + //public class GuildIntegrationsUpdateEvent { } } diff --git a/src/Discord.Net/API/Client/GatewaySocket/Events/GuildMemberAdd.cs b/src/Discord.Net/API/Client/GatewaySocket/Events/GuildMemberAdd.cs index ab543a41b..4d1d7fed5 100644 --- a/src/Discord.Net/API/Client/GatewaySocket/Events/GuildMemberAdd.cs +++ b/src/Discord.Net/API/Client/GatewaySocket/Events/GuildMemberAdd.cs @@ -1,4 +1,4 @@ namespace Discord.API.Client.GatewaySocket { - public sealed class GuildMemberAddEvent : Member { } + public class GuildMemberAddEvent : Member { } } diff --git a/src/Discord.Net/API/Client/GatewaySocket/Events/GuildMemberRemove.cs b/src/Discord.Net/API/Client/GatewaySocket/Events/GuildMemberRemove.cs index cd76cd81a..311186b11 100644 --- a/src/Discord.Net/API/Client/GatewaySocket/Events/GuildMemberRemove.cs +++ b/src/Discord.Net/API/Client/GatewaySocket/Events/GuildMemberRemove.cs @@ -1,4 +1,4 @@ namespace Discord.API.Client.GatewaySocket { - public sealed class GuildMemberRemoveEvent : Member { } + public class GuildMemberRemoveEvent : Member { } } diff --git a/src/Discord.Net/API/Client/GatewaySocket/Events/GuildMemberUpdate.cs b/src/Discord.Net/API/Client/GatewaySocket/Events/GuildMemberUpdate.cs index 8e67d5e12..9b56a95b0 100644 --- a/src/Discord.Net/API/Client/GatewaySocket/Events/GuildMemberUpdate.cs +++ b/src/Discord.Net/API/Client/GatewaySocket/Events/GuildMemberUpdate.cs @@ -1,4 +1,4 @@ namespace Discord.API.Client.GatewaySocket { - public sealed class GuildMemberUpdateEvent : Member { } + public class GuildMemberUpdateEvent : Member { } } diff --git a/src/Discord.Net/API/Client/GatewaySocket/Events/GuildMembersChunk.cs b/src/Discord.Net/API/Client/GatewaySocket/Events/GuildMembersChunk.cs index 1cb256c79..23be74855 100644 --- a/src/Discord.Net/API/Client/GatewaySocket/Events/GuildMembersChunk.cs +++ b/src/Discord.Net/API/Client/GatewaySocket/Events/GuildMembersChunk.cs @@ -3,7 +3,7 @@ using Newtonsoft.Json; namespace Discord.API.Client.GatewaySocket { - public sealed class GuildMembersChunkEvent + public class GuildMembersChunkEvent { [JsonProperty("guild_id"), JsonConverter(typeof(LongStringConverter))] public ulong GuildId { get; set; } diff --git a/src/Discord.Net/API/Client/GatewaySocket/Events/GuildRoleCreate.cs b/src/Discord.Net/API/Client/GatewaySocket/Events/GuildRoleCreate.cs index 28f999d5f..3d8e2f459 100644 --- a/src/Discord.Net/API/Client/GatewaySocket/Events/GuildRoleCreate.cs +++ b/src/Discord.Net/API/Client/GatewaySocket/Events/GuildRoleCreate.cs @@ -3,7 +3,7 @@ using Newtonsoft.Json; namespace Discord.API.Client.GatewaySocket { - public sealed class GuildRoleCreateEvent + public class GuildRoleCreateEvent { [JsonProperty("guild_id"), JsonConverter(typeof(LongStringConverter))] public ulong GuildId { get; set; } diff --git a/src/Discord.Net/API/Client/GatewaySocket/Events/GuildRoleDelete.cs b/src/Discord.Net/API/Client/GatewaySocket/Events/GuildRoleDelete.cs index 470420157..2ecd2edc5 100644 --- a/src/Discord.Net/API/Client/GatewaySocket/Events/GuildRoleDelete.cs +++ b/src/Discord.Net/API/Client/GatewaySocket/Events/GuildRoleDelete.cs @@ -1,4 +1,4 @@ namespace Discord.API.Client.GatewaySocket { - public sealed class GuildRoleDeleteEvent : RoleReference { } + public class GuildRoleDeleteEvent : RoleReference { } } diff --git a/src/Discord.Net/API/Client/GatewaySocket/Events/GuildRoleUpdate.cs b/src/Discord.Net/API/Client/GatewaySocket/Events/GuildRoleUpdate.cs index 291bab043..e26b65c4d 100644 --- a/src/Discord.Net/API/Client/GatewaySocket/Events/GuildRoleUpdate.cs +++ b/src/Discord.Net/API/Client/GatewaySocket/Events/GuildRoleUpdate.cs @@ -3,7 +3,7 @@ using Newtonsoft.Json; namespace Discord.API.Client.GatewaySocket { - public sealed class GuildRoleUpdateEvent + public class GuildRoleUpdateEvent { [JsonProperty("guild_id"), JsonConverter(typeof(LongStringConverter))] public ulong GuildId { get; set; } diff --git a/src/Discord.Net/API/Client/GatewaySocket/Events/GuildUpdate.cs b/src/Discord.Net/API/Client/GatewaySocket/Events/GuildUpdate.cs index ba1665f09..8fc0f1350 100644 --- a/src/Discord.Net/API/Client/GatewaySocket/Events/GuildUpdate.cs +++ b/src/Discord.Net/API/Client/GatewaySocket/Events/GuildUpdate.cs @@ -1,4 +1,4 @@ namespace Discord.API.Client.GatewaySocket { - public sealed class GuildUpdateEvent : Guild { } + public class GuildUpdateEvent : Guild { } } diff --git a/src/Discord.Net/API/Client/GatewaySocket/Events/MessageAck.cs b/src/Discord.Net/API/Client/GatewaySocket/Events/MessageAck.cs index ed4048a62..64c106ef5 100644 --- a/src/Discord.Net/API/Client/GatewaySocket/Events/MessageAck.cs +++ b/src/Discord.Net/API/Client/GatewaySocket/Events/MessageAck.cs @@ -1,4 +1,4 @@ namespace Discord.API.Client.GatewaySocket { - public sealed class MessageAckEvent : MessageReference { } + public class MessageAckEvent : MessageReference { } } diff --git a/src/Discord.Net/API/Client/GatewaySocket/Events/MessageCreate.cs b/src/Discord.Net/API/Client/GatewaySocket/Events/MessageCreate.cs index 9eb514b61..d6d2ec1cc 100644 --- a/src/Discord.Net/API/Client/GatewaySocket/Events/MessageCreate.cs +++ b/src/Discord.Net/API/Client/GatewaySocket/Events/MessageCreate.cs @@ -1,4 +1,4 @@ namespace Discord.API.Client.GatewaySocket { - public sealed class MessageCreateEvent : Message { } + public class MessageCreateEvent : Message { } } diff --git a/src/Discord.Net/API/Client/GatewaySocket/Events/MessageDelete.cs b/src/Discord.Net/API/Client/GatewaySocket/Events/MessageDelete.cs index 9d160eb58..cfc2df7ff 100644 --- a/src/Discord.Net/API/Client/GatewaySocket/Events/MessageDelete.cs +++ b/src/Discord.Net/API/Client/GatewaySocket/Events/MessageDelete.cs @@ -1,4 +1,4 @@ namespace Discord.API.Client.GatewaySocket { - public sealed class MessageDeleteEvent : MessageReference { } + public class MessageDeleteEvent : MessageReference { } } diff --git a/src/Discord.Net/API/Client/GatewaySocket/Events/MessageUpdate.cs b/src/Discord.Net/API/Client/GatewaySocket/Events/MessageUpdate.cs index 6fa852749..23521fd93 100644 --- a/src/Discord.Net/API/Client/GatewaySocket/Events/MessageUpdate.cs +++ b/src/Discord.Net/API/Client/GatewaySocket/Events/MessageUpdate.cs @@ -1,4 +1,4 @@ namespace Discord.API.Client.GatewaySocket { - public sealed class MessageUpdateEvent : Message { } + public class MessageUpdateEvent : Message { } } diff --git a/src/Discord.Net/API/Client/GatewaySocket/Events/PresenceUpdate.cs b/src/Discord.Net/API/Client/GatewaySocket/Events/PresenceUpdate.cs index 4ecbccf05..c40853336 100644 --- a/src/Discord.Net/API/Client/GatewaySocket/Events/PresenceUpdate.cs +++ b/src/Discord.Net/API/Client/GatewaySocket/Events/PresenceUpdate.cs @@ -1,4 +1,4 @@ namespace Discord.API.Client.GatewaySocket { - public sealed class PresenceUpdateEvent : MemberPresence { } + public class PresenceUpdateEvent : MemberPresence { } } diff --git a/src/Discord.Net/API/Client/GatewaySocket/Events/Ready.cs b/src/Discord.Net/API/Client/GatewaySocket/Events/Ready.cs index b17ffd61d..c672a30ae 100644 --- a/src/Discord.Net/API/Client/GatewaySocket/Events/Ready.cs +++ b/src/Discord.Net/API/Client/GatewaySocket/Events/Ready.cs @@ -2,9 +2,9 @@ namespace Discord.API.Client.GatewaySocket { - public sealed class ReadyEvent + public class ReadyEvent { - public sealed class ReadState + public class ReadState { [JsonProperty("id")] public string ChannelId { get; set; } diff --git a/src/Discord.Net/API/Client/GatewaySocket/Events/Redirect.cs b/src/Discord.Net/API/Client/GatewaySocket/Events/Redirect.cs index 26b2afd2d..fe9d644d4 100644 --- a/src/Discord.Net/API/Client/GatewaySocket/Events/Redirect.cs +++ b/src/Discord.Net/API/Client/GatewaySocket/Events/Redirect.cs @@ -2,7 +2,7 @@ namespace Discord.API.Client.GatewaySocket { - public sealed class RedirectEvent + public class RedirectEvent { [JsonProperty("url")] public string Url { get; set; } diff --git a/src/Discord.Net/API/Client/GatewaySocket/Events/Resumed.cs b/src/Discord.Net/API/Client/GatewaySocket/Events/Resumed.cs index 7fd608b25..6a50fbe32 100644 --- a/src/Discord.Net/API/Client/GatewaySocket/Events/Resumed.cs +++ b/src/Discord.Net/API/Client/GatewaySocket/Events/Resumed.cs @@ -2,7 +2,7 @@ namespace Discord.API.Client.GatewaySocket { - public sealed class ResumedEvent + public class ResumedEvent { [JsonProperty("heartbeat_interval")] public int HeartbeatInterval { get; set; } diff --git a/src/Discord.Net/API/Client/GatewaySocket/Events/TypingStart.cs b/src/Discord.Net/API/Client/GatewaySocket/Events/TypingStart.cs index ee8abebe6..484cec1bc 100644 --- a/src/Discord.Net/API/Client/GatewaySocket/Events/TypingStart.cs +++ b/src/Discord.Net/API/Client/GatewaySocket/Events/TypingStart.cs @@ -3,7 +3,7 @@ using Newtonsoft.Json; namespace Discord.API.Client.GatewaySocket { - public sealed class TypingStartEvent + public class TypingStartEvent { [JsonProperty("user_id"), JsonConverter(typeof(LongStringConverter))] public ulong UserId { get; set; } diff --git a/src/Discord.Net/API/Client/GatewaySocket/Events/UserSettingsUpdate.cs b/src/Discord.Net/API/Client/GatewaySocket/Events/UserSettingsUpdate.cs index 04effff5e..aad938157 100644 --- a/src/Discord.Net/API/Client/GatewaySocket/Events/UserSettingsUpdate.cs +++ b/src/Discord.Net/API/Client/GatewaySocket/Events/UserSettingsUpdate.cs @@ -1,4 +1,4 @@ namespace Discord.API.Client.GatewaySocket { - //public sealed class UserSettingsUpdateEvent { } + //public class UserSettingsUpdateEvent { } } diff --git a/src/Discord.Net/API/Client/GatewaySocket/Events/UserUpdate.cs b/src/Discord.Net/API/Client/GatewaySocket/Events/UserUpdate.cs index 6b43b34b6..3c366310a 100644 --- a/src/Discord.Net/API/Client/GatewaySocket/Events/UserUpdate.cs +++ b/src/Discord.Net/API/Client/GatewaySocket/Events/UserUpdate.cs @@ -1,4 +1,4 @@ namespace Discord.API.Client.GatewaySocket { - public sealed class UserUpdateEvent : User { } + public class UserUpdateEvent : User { } } diff --git a/src/Discord.Net/API/Client/GatewaySocket/Events/VoiceServerUpdate.cs b/src/Discord.Net/API/Client/GatewaySocket/Events/VoiceServerUpdate.cs index 0725cc5d8..d305642a1 100644 --- a/src/Discord.Net/API/Client/GatewaySocket/Events/VoiceServerUpdate.cs +++ b/src/Discord.Net/API/Client/GatewaySocket/Events/VoiceServerUpdate.cs @@ -3,7 +3,7 @@ using Newtonsoft.Json; namespace Discord.API.Client.GatewaySocket { - public sealed class VoiceServerUpdateEvent + public class VoiceServerUpdateEvent { [JsonProperty("guild_id"), JsonConverter(typeof(LongStringConverter))] public ulong GuildId { get; set; } diff --git a/src/Discord.Net/API/Client/GatewaySocket/Events/VoiceStateUpdate.cs b/src/Discord.Net/API/Client/GatewaySocket/Events/VoiceStateUpdate.cs index 448920b42..f3ba96b17 100644 --- a/src/Discord.Net/API/Client/GatewaySocket/Events/VoiceStateUpdate.cs +++ b/src/Discord.Net/API/Client/GatewaySocket/Events/VoiceStateUpdate.cs @@ -1,4 +1,4 @@ namespace Discord.API.Client.GatewaySocket { - public sealed class VoiceStateUpdateEvent : MemberVoiceState { } + public class VoiceStateUpdateEvent : MemberVoiceState { } } diff --git a/src/Discord.Net/API/Client/IWebSocketMessage.cs b/src/Discord.Net/API/Client/IWebSocketMessage.cs index 715110e51..6f6de535a 100644 --- a/src/Discord.Net/API/Client/IWebSocketMessage.cs +++ b/src/Discord.Net/API/Client/IWebSocketMessage.cs @@ -8,7 +8,7 @@ namespace Discord.API.Client object Payload { get; } bool IsPrivate { get; } } - public sealed class WebSocketMessage + public class WebSocketMessage { [JsonProperty("op")] public int? Operation { get; set; } diff --git a/src/Discord.Net/API/Client/Rest/AcceptInvite.cs b/src/Discord.Net/API/Client/Rest/AcceptInvite.cs index 639a558ec..865e37c2d 100644 --- a/src/Discord.Net/API/Client/Rest/AcceptInvite.cs +++ b/src/Discord.Net/API/Client/Rest/AcceptInvite.cs @@ -3,7 +3,7 @@ namespace Discord.API.Client.Rest { [JsonObject(MemberSerialization.OptIn)] - public sealed class AcceptInviteRequest : IRestRequest + public class AcceptInviteRequest : IRestRequest { string IRestRequest.Method => "POST"; string IRestRequest.Endpoint => $"invite/{InviteId}"; diff --git a/src/Discord.Net/API/Client/Rest/AckMessage.cs b/src/Discord.Net/API/Client/Rest/AckMessage.cs index 55b885e52..1678ed34b 100644 --- a/src/Discord.Net/API/Client/Rest/AckMessage.cs +++ b/src/Discord.Net/API/Client/Rest/AckMessage.cs @@ -3,7 +3,7 @@ namespace Discord.API.Client.Rest { [JsonObject(MemberSerialization.OptIn)] - public sealed class AckMessageRequest : IRestRequest + public class AckMessageRequest : IRestRequest { string IRestRequest.Method => "POST"; string IRestRequest.Endpoint => $"channels/{ChannelId}/messages/{MessageId}/ack"; diff --git a/src/Discord.Net/API/Client/Rest/AddChannelPermission.cs b/src/Discord.Net/API/Client/Rest/AddChannelPermission.cs index 03be2f653..c7a9f57ac 100644 --- a/src/Discord.Net/API/Client/Rest/AddChannelPermission.cs +++ b/src/Discord.Net/API/Client/Rest/AddChannelPermission.cs @@ -4,7 +4,7 @@ using Newtonsoft.Json; namespace Discord.API.Client.Rest { [JsonObject(MemberSerialization.OptIn)] - public sealed class AddChannelPermissionsRequest : IRestRequest + public class AddChannelPermissionsRequest : IRestRequest { string IRestRequest.Method => "PUT"; string IRestRequest.Endpoint => $"channels/{ChannelId}/permissions/{TargetId}"; diff --git a/src/Discord.Net/API/Client/Rest/AddGuildBan.cs b/src/Discord.Net/API/Client/Rest/AddGuildBan.cs index 295e39259..7699a74e4 100644 --- a/src/Discord.Net/API/Client/Rest/AddGuildBan.cs +++ b/src/Discord.Net/API/Client/Rest/AddGuildBan.cs @@ -3,7 +3,7 @@ namespace Discord.API.Client.Rest { [JsonObject(MemberSerialization.OptIn)] - public sealed class AddGuildBanRequest : IRestRequest + public class AddGuildBanRequest : IRestRequest { string IRestRequest.Method => "PUT"; string IRestRequest.Endpoint => $"guilds/{GuildId}/bans/{UserId}?delete-message-days={PruneDays}"; diff --git a/src/Discord.Net/API/Client/Rest/CreateChannel.cs b/src/Discord.Net/API/Client/Rest/CreateChannel.cs index 6a083e315..0dc45bc43 100644 --- a/src/Discord.Net/API/Client/Rest/CreateChannel.cs +++ b/src/Discord.Net/API/Client/Rest/CreateChannel.cs @@ -3,7 +3,7 @@ namespace Discord.API.Client.Rest { [JsonObject(MemberSerialization.OptIn)] - public sealed class CreateChannelRequest : IRestRequest + public class CreateChannelRequest : IRestRequest { string IRestRequest.Method => "POST"; string IRestRequest.Endpoint => $"guilds/{GuildId}/channels"; diff --git a/src/Discord.Net/API/Client/Rest/CreateGuild.cs b/src/Discord.Net/API/Client/Rest/CreateGuild.cs index e7b00934b..baa1f455e 100644 --- a/src/Discord.Net/API/Client/Rest/CreateGuild.cs +++ b/src/Discord.Net/API/Client/Rest/CreateGuild.cs @@ -3,7 +3,7 @@ namespace Discord.API.Client.Rest { [JsonObject(MemberSerialization.OptIn)] - public sealed class CreateGuildRequest : IRestRequest + public class CreateGuildRequest : IRestRequest { string IRestRequest.Method => "POST"; string IRestRequest.Endpoint => $"guilds"; diff --git a/src/Discord.Net/API/Client/Rest/CreateInvite.cs b/src/Discord.Net/API/Client/Rest/CreateInvite.cs index 2d9ae6a50..a55b9c7e9 100644 --- a/src/Discord.Net/API/Client/Rest/CreateInvite.cs +++ b/src/Discord.Net/API/Client/Rest/CreateInvite.cs @@ -3,7 +3,7 @@ namespace Discord.API.Client.Rest { [JsonObject(MemberSerialization.OptIn)] - public sealed class CreateInviteRequest : IRestRequest + public class CreateInviteRequest : IRestRequest { string IRestRequest.Method => "POST"; string IRestRequest.Endpoint => $"channels/{ChannelId}/invites"; diff --git a/src/Discord.Net/API/Client/Rest/CreatePrivateChannel.cs b/src/Discord.Net/API/Client/Rest/CreatePrivateChannel.cs index 526267590..2d413a8d9 100644 --- a/src/Discord.Net/API/Client/Rest/CreatePrivateChannel.cs +++ b/src/Discord.Net/API/Client/Rest/CreatePrivateChannel.cs @@ -4,7 +4,7 @@ using Newtonsoft.Json; namespace Discord.API.Client.Rest { [JsonObject(MemberSerialization.OptIn)] - public sealed class CreatePrivateChannelRequest : IRestRequest + public class CreatePrivateChannelRequest : IRestRequest { string IRestRequest.Method => "POST"; string IRestRequest.Endpoint => $"users/@me/channels"; diff --git a/src/Discord.Net/API/Client/Rest/CreateRole.cs b/src/Discord.Net/API/Client/Rest/CreateRole.cs index 8daa8145c..87715490d 100644 --- a/src/Discord.Net/API/Client/Rest/CreateRole.cs +++ b/src/Discord.Net/API/Client/Rest/CreateRole.cs @@ -3,7 +3,7 @@ namespace Discord.API.Client.Rest { [JsonObject(MemberSerialization.OptIn)] - public sealed class CreateRoleRequest : IRestRequest + public class CreateRoleRequest : IRestRequest { string IRestRequest.Method => "POST"; string IRestRequest.Endpoint => $"guilds/{GuildId}/roles"; diff --git a/src/Discord.Net/API/Client/Rest/DeleteChannel.cs b/src/Discord.Net/API/Client/Rest/DeleteChannel.cs index 0a44d3b3c..6443c2387 100644 --- a/src/Discord.Net/API/Client/Rest/DeleteChannel.cs +++ b/src/Discord.Net/API/Client/Rest/DeleteChannel.cs @@ -3,7 +3,7 @@ namespace Discord.API.Client.Rest { [JsonObject(MemberSerialization.OptIn)] - public sealed class DeleteChannelRequest : IRestRequest + public class DeleteChannelRequest : IRestRequest { string IRestRequest.Method => "DELETE"; string IRestRequest.Endpoint => $"channels/{ChannelId}"; diff --git a/src/Discord.Net/API/Client/Rest/DeleteInvite.cs b/src/Discord.Net/API/Client/Rest/DeleteInvite.cs index b469caa00..5de8b348b 100644 --- a/src/Discord.Net/API/Client/Rest/DeleteInvite.cs +++ b/src/Discord.Net/API/Client/Rest/DeleteInvite.cs @@ -3,7 +3,7 @@ namespace Discord.API.Client.Rest { [JsonObject(MemberSerialization.OptIn)] - public sealed class DeleteInviteRequest : IRestRequest + public class DeleteInviteRequest : IRestRequest { string IRestRequest.Method => "DELETE"; string IRestRequest.Endpoint => $"invite/{InviteCode}"; diff --git a/src/Discord.Net/API/Client/Rest/DeleteMessage.cs b/src/Discord.Net/API/Client/Rest/DeleteMessage.cs index c209baa3a..33921cd1a 100644 --- a/src/Discord.Net/API/Client/Rest/DeleteMessage.cs +++ b/src/Discord.Net/API/Client/Rest/DeleteMessage.cs @@ -3,7 +3,7 @@ namespace Discord.API.Client.Rest { [JsonObject(MemberSerialization.OptIn)] - public sealed class DeleteMessageRequest : IRestRequest + public class DeleteMessageRequest : IRestRequest { string IRestRequest.Method => "DELETE"; string IRestRequest.Endpoint => $"channels/{ChannelId}/messages/{MessageId}"; diff --git a/src/Discord.Net/API/Client/Rest/DeleteRole.cs b/src/Discord.Net/API/Client/Rest/DeleteRole.cs index 3ad327121..650ece9f2 100644 --- a/src/Discord.Net/API/Client/Rest/DeleteRole.cs +++ b/src/Discord.Net/API/Client/Rest/DeleteRole.cs @@ -3,7 +3,7 @@ namespace Discord.API.Client.Rest { [JsonObject(MemberSerialization.OptIn)] - public sealed class DeleteRoleRequest : IRestRequest + public class DeleteRoleRequest : IRestRequest { string IRestRequest.Method => "DELETE"; string IRestRequest.Endpoint => $"guilds/{GuildId}/roles/{RoleId}"; diff --git a/src/Discord.Net/API/Client/Rest/Gateway.cs b/src/Discord.Net/API/Client/Rest/Gateway.cs index e728b46a0..ef9486ca1 100644 --- a/src/Discord.Net/API/Client/Rest/Gateway.cs +++ b/src/Discord.Net/API/Client/Rest/Gateway.cs @@ -3,7 +3,7 @@ namespace Discord.API.Client.Rest { [JsonObject(MemberSerialization.OptIn)] - public sealed class GatewayRequest : IRestRequest + public class GatewayRequest : IRestRequest { string IRestRequest.Method => "GET"; string IRestRequest.Endpoint => $"gateway"; @@ -11,7 +11,7 @@ namespace Discord.API.Client.Rest bool IRestRequest.IsPrivate => false; } - public sealed class GatewayResponse + public class GatewayResponse { [JsonProperty("url")] public string Url { get; set; } diff --git a/src/Discord.Net/API/Client/Rest/GetBans.cs b/src/Discord.Net/API/Client/Rest/GetBans.cs index e4638ce32..ee07cb242 100644 --- a/src/Discord.Net/API/Client/Rest/GetBans.cs +++ b/src/Discord.Net/API/Client/Rest/GetBans.cs @@ -3,7 +3,7 @@ namespace Discord.API.Client.Rest { [JsonObject(MemberSerialization.OptIn)] - public sealed class GetBansRequest : IRestRequest + public class GetBansRequest : IRestRequest { string IRestRequest.Method => "GET"; string IRestRequest.Endpoint => $"guilds/{GuildId}/bans"; diff --git a/src/Discord.Net/API/Client/Rest/GetInvite.cs b/src/Discord.Net/API/Client/Rest/GetInvite.cs index 708a99e46..27de264f0 100644 --- a/src/Discord.Net/API/Client/Rest/GetInvite.cs +++ b/src/Discord.Net/API/Client/Rest/GetInvite.cs @@ -3,7 +3,7 @@ namespace Discord.API.Client.Rest { [JsonObject(MemberSerialization.OptIn)] - public sealed class GetInviteRequest : IRestRequest + public class GetInviteRequest : IRestRequest { string IRestRequest.Method => "GET"; string IRestRequest.Endpoint => $"invite/{InviteCode}"; diff --git a/src/Discord.Net/API/Client/Rest/GetInvites.cs b/src/Discord.Net/API/Client/Rest/GetInvites.cs index dc056ad5a..079c54ef5 100644 --- a/src/Discord.Net/API/Client/Rest/GetInvites.cs +++ b/src/Discord.Net/API/Client/Rest/GetInvites.cs @@ -3,7 +3,7 @@ namespace Discord.API.Client.Rest { [JsonObject(MemberSerialization.OptIn)] - public sealed class GetInvitesRequest : IRestRequest + public class GetInvitesRequest : IRestRequest { string IRestRequest.Method => "GET"; string IRestRequest.Endpoint => $"guilds/{GuildId}/invites"; diff --git a/src/Discord.Net/API/Client/Rest/GetMessages.cs b/src/Discord.Net/API/Client/Rest/GetMessages.cs index c5809ded1..b72b05c8b 100644 --- a/src/Discord.Net/API/Client/Rest/GetMessages.cs +++ b/src/Discord.Net/API/Client/Rest/GetMessages.cs @@ -4,7 +4,7 @@ using System.Text; namespace Discord.API.Client.Rest { [JsonObject(MemberSerialization.OptIn)] - public sealed class GetMessagesRequest : IRestRequest + public class GetMessagesRequest : IRestRequest { string IRestRequest.Method => "GET"; string IRestRequest.Endpoint diff --git a/src/Discord.Net/API/Client/Rest/GetVoiceRegions.cs b/src/Discord.Net/API/Client/Rest/GetVoiceRegions.cs index 307fd01eb..7dc97ef31 100644 --- a/src/Discord.Net/API/Client/Rest/GetVoiceRegions.cs +++ b/src/Discord.Net/API/Client/Rest/GetVoiceRegions.cs @@ -3,7 +3,7 @@ namespace Discord.API.Client.Rest { [JsonObject(MemberSerialization.OptIn)] - public sealed class GetVoiceRegionsRequest : IRestRequest + public class GetVoiceRegionsRequest : IRestRequest { string IRestRequest.Method => "GET"; string IRestRequest.Endpoint => $"voice/regions"; @@ -11,7 +11,7 @@ namespace Discord.API.Client.Rest bool IRestRequest.IsPrivate => false; } - public sealed class GetVoiceRegionsResponse + public class GetVoiceRegionsResponse { [JsonProperty("sample_hostname")] public string Hostname { get; set; } diff --git a/src/Discord.Net/API/Client/Rest/GetWidget.cs b/src/Discord.Net/API/Client/Rest/GetWidget.cs index bdb9b3a99..3b1006358 100644 --- a/src/Discord.Net/API/Client/Rest/GetWidget.cs +++ b/src/Discord.Net/API/Client/Rest/GetWidget.cs @@ -4,7 +4,7 @@ using Newtonsoft.Json; namespace Discord.API.Client.Rest { [JsonObject(MemberSerialization.OptIn)] - public sealed class GetWidgetRequest : IRestRequest + public class GetWidgetRequest : IRestRequest { string IRestRequest.Method => "GET"; string IRestRequest.Endpoint => $"servers/{GuildId}/widget.json"; @@ -19,9 +19,9 @@ namespace Discord.API.Client.Rest } } - public sealed class GetWidgetResponse + public class GetWidgetResponse { - public sealed class Channel + public class Channel { [JsonProperty("id"), JsonConverter(typeof(LongStringConverter))] public ulong Id { get; set; } @@ -30,7 +30,7 @@ namespace Discord.API.Client.Rest [JsonProperty("position")] public int Position { get; set; } } - public sealed class User : UserReference + public class User : UserReference { [JsonProperty("avatar_url")] public string AvatarUrl { get; set; } @@ -39,7 +39,7 @@ namespace Discord.API.Client.Rest [JsonProperty("game")] public UserGame Game { get; set; } } - public sealed class UserGame + public class UserGame { [JsonProperty("id")] public int Id { get; set; } diff --git a/src/Discord.Net/API/Client/Rest/KickMember.cs b/src/Discord.Net/API/Client/Rest/KickMember.cs index db69fbcd9..96804ff6b 100644 --- a/src/Discord.Net/API/Client/Rest/KickMember.cs +++ b/src/Discord.Net/API/Client/Rest/KickMember.cs @@ -3,7 +3,7 @@ namespace Discord.API.Client.Rest { [JsonObject(MemberSerialization.OptIn)] - public sealed class KickMemberRequest : IRestRequest + public class KickMemberRequest : IRestRequest { string IRestRequest.Method => "DELETE"; string IRestRequest.Endpoint => $"guilds/{GuildId}/members/{UserId}"; diff --git a/src/Discord.Net/API/Client/Rest/LeaveGuild.cs b/src/Discord.Net/API/Client/Rest/LeaveGuild.cs index 01dec28e0..6a8b3c0cf 100644 --- a/src/Discord.Net/API/Client/Rest/LeaveGuild.cs +++ b/src/Discord.Net/API/Client/Rest/LeaveGuild.cs @@ -3,7 +3,7 @@ namespace Discord.API.Client.Rest { [JsonObject(MemberSerialization.OptIn)] - public sealed class LeaveGuildRequest : IRestRequest + public class LeaveGuildRequest : IRestRequest { string IRestRequest.Method => "DELETE"; string IRestRequest.Endpoint => $"guilds/{GuildId}"; diff --git a/src/Discord.Net/API/Client/Rest/Login.cs b/src/Discord.Net/API/Client/Rest/Login.cs index f4b0b0c92..ab7efc31b 100644 --- a/src/Discord.Net/API/Client/Rest/Login.cs +++ b/src/Discord.Net/API/Client/Rest/Login.cs @@ -3,7 +3,7 @@ namespace Discord.API.Client.Rest { [JsonObject(MemberSerialization.OptIn)] - public sealed class LoginRequest : IRestRequest + public class LoginRequest : IRestRequest { string IRestRequest.Method => Email != null ? "POST" : "GET"; string IRestRequest.Endpoint => $"auth/login"; @@ -16,7 +16,7 @@ namespace Discord.API.Client.Rest public string Password { get; set; } } - public sealed class LoginResponse + public class LoginResponse { [JsonProperty("token")] public string Token { get; set; } diff --git a/src/Discord.Net/API/Client/Rest/Logout.cs b/src/Discord.Net/API/Client/Rest/Logout.cs index 5df18dbe1..78f8059e5 100644 --- a/src/Discord.Net/API/Client/Rest/Logout.cs +++ b/src/Discord.Net/API/Client/Rest/Logout.cs @@ -3,7 +3,7 @@ namespace Discord.API.Client.Rest { [JsonObject(MemberSerialization.OptIn)] - public sealed class LogoutRequest : IRestRequest + public class LogoutRequest : IRestRequest { string IRestRequest.Method => "POST"; string IRestRequest.Endpoint => $"auth/logout"; diff --git a/src/Discord.Net/API/Client/Rest/PruneMembers.cs b/src/Discord.Net/API/Client/Rest/PruneMembers.cs index ea0b86e41..41771f7d6 100644 --- a/src/Discord.Net/API/Client/Rest/PruneMembers.cs +++ b/src/Discord.Net/API/Client/Rest/PruneMembers.cs @@ -3,7 +3,7 @@ namespace Discord.API.Client.Rest { [JsonObject(MemberSerialization.OptIn)] - public sealed class PruneMembersRequest : IRestRequest + public class PruneMembersRequest : IRestRequest { string IRestRequest.Method => IsSimulation ? "GET" : "POST"; string IRestRequest.Endpoint => $"guilds/{GuildId}/prune?days={Days}"; @@ -21,7 +21,7 @@ namespace Discord.API.Client.Rest } } - public sealed class PruneMembersResponse + public class PruneMembersResponse { [JsonProperty("pruned")] public int Pruned { get; set; } diff --git a/src/Discord.Net/API/Client/Rest/RemoveChannelPermission.cs b/src/Discord.Net/API/Client/Rest/RemoveChannelPermission.cs index 3e6e06e90..c704eadbc 100644 --- a/src/Discord.Net/API/Client/Rest/RemoveChannelPermission.cs +++ b/src/Discord.Net/API/Client/Rest/RemoveChannelPermission.cs @@ -3,7 +3,7 @@ namespace Discord.API.Client.Rest { [JsonObject(MemberSerialization.OptIn)] - public sealed class RemoveChannelPermissionsRequest : IRestRequest + public class RemoveChannelPermissionsRequest : IRestRequest { string IRestRequest.Method => "DELETE"; string IRestRequest.Endpoint => $"channels/{ChannelId}/permissions/{TargetId}"; diff --git a/src/Discord.Net/API/Client/Rest/RemoveGuildBan.cs b/src/Discord.Net/API/Client/Rest/RemoveGuildBan.cs index e126f6bc2..c6d48c944 100644 --- a/src/Discord.Net/API/Client/Rest/RemoveGuildBan.cs +++ b/src/Discord.Net/API/Client/Rest/RemoveGuildBan.cs @@ -3,7 +3,7 @@ namespace Discord.API.Client.Rest { [JsonObject(MemberSerialization.OptIn)] - public sealed class RemoveGuildBanRequest : IRestRequest + public class RemoveGuildBanRequest : IRestRequest { string IRestRequest.Method => "DELETE"; string IRestRequest.Endpoint => $"guilds/{GuildId}/bans/{UserId}"; diff --git a/src/Discord.Net/API/Client/Rest/ReorderChannels.cs b/src/Discord.Net/API/Client/Rest/ReorderChannels.cs index 3f768cf07..c481eda43 100644 --- a/src/Discord.Net/API/Client/Rest/ReorderChannels.cs +++ b/src/Discord.Net/API/Client/Rest/ReorderChannels.cs @@ -5,7 +5,7 @@ using System.Linq; namespace Discord.API.Client.Rest { [JsonObject(MemberSerialization.OptIn)] - public sealed class ReorderChannelsRequest : IRestRequest + public class ReorderChannelsRequest : IRestRequest { string IRestRequest.Method => "PATCH"; string IRestRequest.Endpoint => $"guilds/{GuildId}/channels"; @@ -19,7 +19,7 @@ namespace Discord.API.Client.Rest } bool IRestRequest.IsPrivate => false; - public sealed class Channel + public class Channel { [JsonProperty("id"), JsonConverter(typeof(LongStringConverter))] public ulong Id { get; set; } diff --git a/src/Discord.Net/API/Client/Rest/ReorderRoles.cs b/src/Discord.Net/API/Client/Rest/ReorderRoles.cs index 5eb9a9d11..23d73541f 100644 --- a/src/Discord.Net/API/Client/Rest/ReorderRoles.cs +++ b/src/Discord.Net/API/Client/Rest/ReorderRoles.cs @@ -5,7 +5,7 @@ using System.Linq; namespace Discord.API.Client.Rest { [JsonObject(MemberSerialization.OptIn)] - public sealed class ReorderRolesRequest : IRestRequest + public class ReorderRolesRequest : IRestRequest { string IRestRequest.Method => "PATCH"; string IRestRequest.Endpoint => $"guilds/{GuildId}/roles"; @@ -19,7 +19,7 @@ namespace Discord.API.Client.Rest } bool IRestRequest.IsPrivate => false; - public sealed class Role + public class Role { [JsonProperty("id"), JsonConverter(typeof(LongStringConverter))] public ulong Id { get; set; } diff --git a/src/Discord.Net/API/Client/Rest/SendFile.cs b/src/Discord.Net/API/Client/Rest/SendFile.cs index 7b1a6b084..8d072d0e3 100644 --- a/src/Discord.Net/API/Client/Rest/SendFile.cs +++ b/src/Discord.Net/API/Client/Rest/SendFile.cs @@ -4,7 +4,7 @@ using System.IO; namespace Discord.API.Client.Rest { [JsonObject(MemberSerialization.OptIn)] - public sealed class SendFileRequest : IRestFileRequest + public class SendFileRequest : IRestFileRequest { string IRestRequest.Method => "POST"; string IRestRequest.Endpoint => $"channels/{ChannelId}/messages"; diff --git a/src/Discord.Net/API/Client/Rest/SendIsTyping.cs b/src/Discord.Net/API/Client/Rest/SendIsTyping.cs index abaceb96c..aab017c67 100644 --- a/src/Discord.Net/API/Client/Rest/SendIsTyping.cs +++ b/src/Discord.Net/API/Client/Rest/SendIsTyping.cs @@ -3,7 +3,7 @@ namespace Discord.API.Client.Rest { [JsonObject(MemberSerialization.OptIn)] - public sealed class SendIsTypingRequest : IRestRequest + public class SendIsTypingRequest : IRestRequest { string IRestRequest.Method => "POST"; string IRestRequest.Endpoint => $"channels/{ChannelId}/typing"; diff --git a/src/Discord.Net/API/Client/Rest/SendMessage.cs b/src/Discord.Net/API/Client/Rest/SendMessage.cs index c58d00d9e..6c6d1ae10 100644 --- a/src/Discord.Net/API/Client/Rest/SendMessage.cs +++ b/src/Discord.Net/API/Client/Rest/SendMessage.cs @@ -3,7 +3,7 @@ namespace Discord.API.Client.Rest { [JsonObject(MemberSerialization.OptIn)] - public sealed class SendMessageRequest : IRestRequest + public class SendMessageRequest : IRestRequest { string IRestRequest.Method => "POST"; string IRestRequest.Endpoint => $"channels/{ChannelId}/messages"; diff --git a/src/Discord.Net/API/Client/Rest/UpdateChannel.cs b/src/Discord.Net/API/Client/Rest/UpdateChannel.cs index f09c8ba87..cccd4b096 100644 --- a/src/Discord.Net/API/Client/Rest/UpdateChannel.cs +++ b/src/Discord.Net/API/Client/Rest/UpdateChannel.cs @@ -3,7 +3,7 @@ namespace Discord.API.Client.Rest { [JsonObject(MemberSerialization.OptIn)] - public sealed class UpdateChannelRequest : IRestRequest + public class UpdateChannelRequest : IRestRequest { string IRestRequest.Method => "PATCH"; string IRestRequest.Endpoint => $"channels/{ChannelId}"; diff --git a/src/Discord.Net/API/Client/Rest/UpdateGuild.cs b/src/Discord.Net/API/Client/Rest/UpdateGuild.cs index 163dc437e..4ff530554 100644 --- a/src/Discord.Net/API/Client/Rest/UpdateGuild.cs +++ b/src/Discord.Net/API/Client/Rest/UpdateGuild.cs @@ -4,7 +4,7 @@ using Newtonsoft.Json; namespace Discord.API.Client.Rest { [JsonObject(MemberSerialization.OptIn)] - public sealed class UpdateGuildRequest : IRestRequest + public class UpdateGuildRequest : IRestRequest { string IRestRequest.Method => "PATCH"; string IRestRequest.Endpoint => $"guilds/{GuildId}"; diff --git a/src/Discord.Net/API/Client/Rest/UpdateMember.cs b/src/Discord.Net/API/Client/Rest/UpdateMember.cs index 1c90560ac..0bc5274d0 100644 --- a/src/Discord.Net/API/Client/Rest/UpdateMember.cs +++ b/src/Discord.Net/API/Client/Rest/UpdateMember.cs @@ -5,7 +5,7 @@ using System.Collections.Generic; namespace Discord.API.Client.Rest { [JsonObject(MemberSerialization.OptIn)] - public sealed class UpdateMemberRequest : IRestRequest + public class UpdateMemberRequest : IRestRequest { string IRestRequest.Method => "PATCH"; string IRestRequest.Endpoint => $"guilds/{GuildId}/members/{UserId}"; diff --git a/src/Discord.Net/API/Client/Rest/UpdateMessage.cs b/src/Discord.Net/API/Client/Rest/UpdateMessage.cs index ede0a0797..5b4480a4b 100644 --- a/src/Discord.Net/API/Client/Rest/UpdateMessage.cs +++ b/src/Discord.Net/API/Client/Rest/UpdateMessage.cs @@ -4,7 +4,7 @@ using Newtonsoft.Json; namespace Discord.API.Client.Rest { [JsonObject(MemberSerialization.OptIn)] - public sealed class UpdateMessageRequest : IRestRequest + public class UpdateMessageRequest : IRestRequest { string IRestRequest.Method => "PATCH"; string IRestRequest.Endpoint => $"channels/{ChannelId}/messages/{MessageId}"; diff --git a/src/Discord.Net/API/Client/Rest/UpdateProfile.cs b/src/Discord.Net/API/Client/Rest/UpdateProfile.cs index d89e60983..08f28d868 100644 --- a/src/Discord.Net/API/Client/Rest/UpdateProfile.cs +++ b/src/Discord.Net/API/Client/Rest/UpdateProfile.cs @@ -3,7 +3,7 @@ namespace Discord.API.Client.Rest { [JsonObject(MemberSerialization.OptIn)] - public sealed class UpdateProfileRequest : IRestRequest + public class UpdateProfileRequest : IRestRequest { string IRestRequest.Method => "PATCH"; string IRestRequest.Endpoint => $"users/@me"; diff --git a/src/Discord.Net/API/Client/Rest/UpdateRole.cs b/src/Discord.Net/API/Client/Rest/UpdateRole.cs index 9ebc1e76f..7aac774b7 100644 --- a/src/Discord.Net/API/Client/Rest/UpdateRole.cs +++ b/src/Discord.Net/API/Client/Rest/UpdateRole.cs @@ -3,7 +3,7 @@ namespace Discord.API.Client.Rest { [JsonObject(MemberSerialization.OptIn)] - public sealed class UpdateRoleRequest : IRestRequest + public class UpdateRoleRequest : IRestRequest { string IRestRequest.Method => "PATCH"; string IRestRequest.Endpoint => $"guilds/{GuildId}/roles/{RoleId}"; diff --git a/src/Discord.Net/API/Client/VoiceSocket/Commands/Heartbeat.cs b/src/Discord.Net/API/Client/VoiceSocket/Commands/Heartbeat.cs index a9727dd91..349a8a28b 100644 --- a/src/Discord.Net/API/Client/VoiceSocket/Commands/Heartbeat.cs +++ b/src/Discord.Net/API/Client/VoiceSocket/Commands/Heartbeat.cs @@ -1,6 +1,6 @@ namespace Discord.API.Client.VoiceSocket { - public sealed class HeartbeatCommand : IWebSocketMessage + public class HeartbeatCommand : IWebSocketMessage { int IWebSocketMessage.OpCode => (int)OpCodes.Heartbeat; object IWebSocketMessage.Payload => EpochTime.GetMilliseconds(); diff --git a/src/Discord.Net/API/Client/VoiceSocket/Commands/Identify.cs b/src/Discord.Net/API/Client/VoiceSocket/Commands/Identify.cs index 1836b234c..fbb38b9d0 100644 --- a/src/Discord.Net/API/Client/VoiceSocket/Commands/Identify.cs +++ b/src/Discord.Net/API/Client/VoiceSocket/Commands/Identify.cs @@ -3,7 +3,7 @@ using Newtonsoft.Json; namespace Discord.API.Client.VoiceSocket { - public sealed class IdentifyCommand : IWebSocketMessage + public class IdentifyCommand : IWebSocketMessage { int IWebSocketMessage.OpCode => (int)OpCodes.Identify; object IWebSocketMessage.Payload => this; diff --git a/src/Discord.Net/API/Client/VoiceSocket/Commands/SelectProtocol.cs b/src/Discord.Net/API/Client/VoiceSocket/Commands/SelectProtocol.cs index aa8e8127b..d860efe45 100644 --- a/src/Discord.Net/API/Client/VoiceSocket/Commands/SelectProtocol.cs +++ b/src/Discord.Net/API/Client/VoiceSocket/Commands/SelectProtocol.cs @@ -2,13 +2,13 @@ namespace Discord.API.Client.VoiceSocket { - public sealed class SelectProtocolCommand : IWebSocketMessage + public class SelectProtocolCommand : IWebSocketMessage { int IWebSocketMessage.OpCode => (int)OpCodes.SelectProtocol; object IWebSocketMessage.Payload => this; bool IWebSocketMessage.IsPrivate => false; - public sealed class Data + public class Data { [JsonProperty("address")] public string Address { get; set; } diff --git a/src/Discord.Net/API/Client/VoiceSocket/Commands/SetSpeaking.cs b/src/Discord.Net/API/Client/VoiceSocket/Commands/SetSpeaking.cs index 13ab00524..6022c4d58 100644 --- a/src/Discord.Net/API/Client/VoiceSocket/Commands/SetSpeaking.cs +++ b/src/Discord.Net/API/Client/VoiceSocket/Commands/SetSpeaking.cs @@ -2,7 +2,7 @@ namespace Discord.API.Client.VoiceSocket { - public sealed class SetSpeakingCommand : IWebSocketMessage + public class SetSpeakingCommand : IWebSocketMessage { int IWebSocketMessage.OpCode => (int)OpCodes.Speaking; object IWebSocketMessage.Payload => this; diff --git a/src/Discord.Net/API/Client/VoiceSocket/Events/Ready.cs b/src/Discord.Net/API/Client/VoiceSocket/Events/Ready.cs index b0fa34c1d..6fdced897 100644 --- a/src/Discord.Net/API/Client/VoiceSocket/Events/Ready.cs +++ b/src/Discord.Net/API/Client/VoiceSocket/Events/Ready.cs @@ -2,7 +2,7 @@ namespace Discord.API.Client.VoiceSocket { - public sealed class ReadyEvent + public class ReadyEvent { [JsonProperty("ssrc")] public uint SSRC { get; set; } diff --git a/src/Discord.Net/API/Client/VoiceSocket/Events/SessionDescription.cs b/src/Discord.Net/API/Client/VoiceSocket/Events/SessionDescription.cs index 13f190cfc..042c5278d 100644 --- a/src/Discord.Net/API/Client/VoiceSocket/Events/SessionDescription.cs +++ b/src/Discord.Net/API/Client/VoiceSocket/Events/SessionDescription.cs @@ -2,7 +2,7 @@ namespace Discord.API.Client.VoiceSocket { - public sealed class SessionDescriptionEvent + public class SessionDescriptionEvent { [JsonProperty("secret_key")] public byte[] SecretKey { get; set; } diff --git a/src/Discord.Net/API/Client/VoiceSocket/Events/Speaking.cs b/src/Discord.Net/API/Client/VoiceSocket/Events/Speaking.cs index b3de0f800..59268c4e6 100644 --- a/src/Discord.Net/API/Client/VoiceSocket/Events/Speaking.cs +++ b/src/Discord.Net/API/Client/VoiceSocket/Events/Speaking.cs @@ -3,7 +3,7 @@ using Newtonsoft.Json; namespace Discord.API.Client.VoiceSocket { - public sealed class SpeakingEvent + public class SpeakingEvent { [JsonProperty("user_id"), JsonConverter(typeof(LongStringConverter))] public ulong UserId { get; set; } diff --git a/src/Discord.Net/API/Converters.cs b/src/Discord.Net/API/Converters.cs index 1142e5755..5d80ca99f 100644 --- a/src/Discord.Net/API/Converters.cs +++ b/src/Discord.Net/API/Converters.cs @@ -4,7 +4,7 @@ using System.Collections.Generic; namespace Discord.API.Converters { - public sealed class LongStringConverter : JsonConverter + public class LongStringConverter : JsonConverter { public override bool CanConvert(Type objectType) => objectType == typeof(ulong); @@ -14,7 +14,7 @@ namespace Discord.API.Converters => writer.WriteValue(((ulong)value).ToIdString()); } - public sealed class NullableLongStringConverter : JsonConverter + public class NullableLongStringConverter : JsonConverter { public override bool CanConvert(Type objectType) => objectType == typeof(ulong?); @@ -24,7 +24,7 @@ namespace Discord.API.Converters => writer.WriteValue(((ulong?)value).ToIdString()); } - /*public sealed class LongStringEnumerableConverter : JsonConverter + /*public class LongStringEnumerableConverter : JsonConverter { public override bool CanConvert(Type objectType) => objectType == typeof(IEnumerable); public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) @@ -55,7 +55,7 @@ namespace Discord.API.Converters } }*/ - internal sealed class LongStringArrayConverter : JsonConverter + internal class LongStringArrayConverter : JsonConverter { public override bool CanConvert(Type objectType) => objectType == typeof(IEnumerable); public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) diff --git a/src/Discord.Net/API/Status/Common/StatusResult.cs b/src/Discord.Net/API/Status/Common/StatusResult.cs index 314a180c7..74728c578 100644 --- a/src/Discord.Net/API/Status/Common/StatusResult.cs +++ b/src/Discord.Net/API/Status/Common/StatusResult.cs @@ -5,7 +5,7 @@ namespace Discord.API.Status { public class StatusResult { - public sealed class PageData + public class PageData { [JsonProperty("id")] public string Id { get; set; } @@ -17,7 +17,7 @@ namespace Discord.API.Status public DateTime? UpdatedAt { get; set; } } - public sealed class IncidentData + public class IncidentData { [JsonProperty("id")] public string Id { get; set; } @@ -50,7 +50,7 @@ namespace Discord.API.Status public IncidentUpdateData[] Updates { get; set; } } - public sealed class IncidentUpdateData + public class IncidentUpdateData { [JsonProperty("id")] public string Id { get; set; } diff --git a/src/Discord.Net/API/Status/Rest/ActiveMaintenances.cs b/src/Discord.Net/API/Status/Rest/ActiveMaintenances.cs index 5a8412f96..638c176a5 100644 --- a/src/Discord.Net/API/Status/Rest/ActiveMaintenances.cs +++ b/src/Discord.Net/API/Status/Rest/ActiveMaintenances.cs @@ -3,7 +3,7 @@ namespace Discord.API.Status.Rest { [JsonObject(MemberSerialization.OptIn)] - public sealed class GetActiveMaintenancesRequest : IRestRequest + public class GetActiveMaintenancesRequest : IRestRequest { string IRestRequest.Method => "GET"; string IRestRequest.Endpoint => $"scheduled-maintenances/active.json"; diff --git a/src/Discord.Net/API/Status/Rest/AllIncidents.cs b/src/Discord.Net/API/Status/Rest/AllIncidents.cs index 13f43e022..81a82ce51 100644 --- a/src/Discord.Net/API/Status/Rest/AllIncidents.cs +++ b/src/Discord.Net/API/Status/Rest/AllIncidents.cs @@ -3,7 +3,7 @@ namespace Discord.API.Status.Rest { [JsonObject(MemberSerialization.OptIn)] - public sealed class GetAllIncidentsRequest : IRestRequest + public class GetAllIncidentsRequest : IRestRequest { string IRestRequest.Method => "GET"; string IRestRequest.Endpoint => $"incidents.json"; diff --git a/src/Discord.Net/API/Status/Rest/UnresolvedIncidents.cs b/src/Discord.Net/API/Status/Rest/UnresolvedIncidents.cs index f07de061c..1665dde75 100644 --- a/src/Discord.Net/API/Status/Rest/UnresolvedIncidents.cs +++ b/src/Discord.Net/API/Status/Rest/UnresolvedIncidents.cs @@ -3,7 +3,7 @@ namespace Discord.API.Status.Rest { [JsonObject(MemberSerialization.OptIn)] - public sealed class GetUnresolvedIncidentsRequest : IRestRequest + public class GetUnresolvedIncidentsRequest : IRestRequest { string IRestRequest.Method => "GET"; string IRestRequest.Endpoint => $"incidents/unresolved.json"; diff --git a/src/Discord.Net/API/Status/Rest/UpcomingMaintenances.cs b/src/Discord.Net/API/Status/Rest/UpcomingMaintenances.cs index 769602381..afc812cc9 100644 --- a/src/Discord.Net/API/Status/Rest/UpcomingMaintenances.cs +++ b/src/Discord.Net/API/Status/Rest/UpcomingMaintenances.cs @@ -3,7 +3,7 @@ namespace Discord.API.Status.Rest { [JsonObject(MemberSerialization.OptIn)] - public sealed class GetUpcomingMaintenancesRequest : IRestRequest + public class GetUpcomingMaintenancesRequest : IRestRequest { string IRestRequest.Method => "GET"; string IRestRequest.Endpoint => $"scheduled-maintenances/upcoming.json"; diff --git a/src/Discord.Net/DiscordClient.cs b/src/Discord.Net/DiscordClient.cs index 5a8cef107..dc0e4b807 100644 --- a/src/Discord.Net/DiscordClient.cs +++ b/src/Discord.Net/DiscordClient.cs @@ -31,7 +31,6 @@ namespace Discord private readonly ConcurrentDictionary _channels; private readonly ConcurrentDictionary _privateChannels; //Key = RecipientId private Dictionary _regions; - private CancellationTokenSource _cancelTokenSource; internal Logger Logger { get; } @@ -138,7 +137,7 @@ namespace Discord //Networking ClientAPI = new RestClient(Config, DiscordConfig.ClientAPIUrl, Log.CreateLogger("ClientAPI")); StatusAPI = new RestClient(Config, DiscordConfig.StatusAPIUrl, Log.CreateLogger("StatusAPI")); - GatewaySocket = new GatewaySocket(this, Log.CreateLogger("Gateway")); + GatewaySocket = new GatewaySocket(Config, Serializer, Log.CreateLogger("Gateway")); GatewaySocket.Connected += (s, e) => { if (State == ConnectionState.Connecting) @@ -148,7 +147,7 @@ namespace Discord GatewaySocket.ReceivedDispatch += (s, e) => OnReceivedEvent(e); if (Config.UseMessageQueue) - MessageQueue = new MessageQueue(this, Log.CreateLogger("MessageQueue")); + MessageQueue = new MessageQueue(ClientAPI, Log.CreateLogger("MessageQueue")); //Extensibility Services = new ServiceManager(this); @@ -182,9 +181,7 @@ namespace Discord { using (await _connectionLock.LockAsync().ConfigureAwait(false)) { - if (State != ConnectionState.Disconnected) - await Disconnect().ConfigureAwait(false); - await _taskManager.Stop().ConfigureAwait(false); + await Disconnect().ConfigureAwait(false); _taskManager.ClearException(); Stopwatch stopwatch = null; @@ -193,19 +190,20 @@ namespace Discord State = ConnectionState.Connecting; _disconnectedEvent.Reset(); - _cancelTokenSource = new CancellationTokenSource(); - CancelToken = _cancelTokenSource.Token; - GatewaySocket.ParentCancelToken = CancelToken; + var cancelSource = new CancellationTokenSource(); + CancelToken = cancelSource.Token; + ClientAPI.CancelToken = CancelToken; + StatusAPI.CancelToken = CancelToken; await Login(email, password, token).ConfigureAwait(false); - await GatewaySocket.Connect().ConfigureAwait(false); + await GatewaySocket.Connect(ClientAPI, CancelToken).ConfigureAwait(false); List tasks = new List(); tasks.Add(CancelToken.Wait()); if (Config.UseMessageQueue) tasks.Add(MessageQueue.Run(CancelToken, Config.MessageQueueInterval)); - await _taskManager.Start(tasks, _cancelTokenSource).ConfigureAwait(false); + await _taskManager.Start(tasks, cancelSource).ConfigureAwait(false); GatewaySocket.WaitForConnection(CancelToken); if (Config.LogLevel >= LogSeverity.Verbose) @@ -248,8 +246,6 @@ namespace Discord SaveToken(tokenPath, cacheKey, token); ClientAPI.Token = token; - GatewaySocket.Token = token; - GatewaySocket.SessionId = null; //Cache other stuff var regionsResponse = (await ClientAPI.Send(new GetVoiceRegionsRequest()).ConfigureAwait(false)); @@ -261,7 +257,6 @@ namespace Discord State = ConnectionState.Connected; _connectedEvent.Set(); - ClientAPI.CancelToken = CancelToken; SendStatus(); OnConnected(); } @@ -274,7 +269,10 @@ namespace Discord State = ConnectionState.Disconnecting; if (oldState == ConnectionState.Connected) - await ClientAPI.Send(new LogoutRequest()).ConfigureAwait(false); + { + try { await ClientAPI.Send(new LogoutRequest()).ConfigureAwait(false); } + catch (OperationCanceledException) { } + } if (Config.UseMessageQueue) MessageQueue.Clear(); @@ -282,8 +280,6 @@ namespace Discord await GatewaySocket.Disconnect().ConfigureAwait(false); ClientAPI.Token = null; - GatewaySocket.Token = null; - GatewaySocket.SessionId = null; _servers.Clear(); _channels.Clear(); @@ -481,8 +477,6 @@ namespace Discord if (Config.LogLevel >= LogSeverity.Verbose) stopwatch = Stopwatch.StartNew(); var data = e.Payload.ToObject(Serializer); - GatewaySocket.StartHeartbeat(data.HeartbeatInterval); - GatewaySocket.SessionId = data.SessionId; SessionId = data.SessionId; PrivateUser = new User(this, data.User.Id, null); PrivateUser.Update(data.User); @@ -509,12 +503,6 @@ namespace Discord } } break; - case "RESUMED": - { - var data = e.Payload.ToObject(Serializer); - GatewaySocket.StartHeartbeat(data.HeartbeatInterval); - } - break; //Servers case "GUILD_CREATE": @@ -1018,6 +1006,10 @@ namespace Discord } break; + //Handled in GatewaySocket + case "RESUMED": + break; + //Ignored case "USER_SETTINGS_UPDATE": case "GUILD_INTEGRATIONS_UPDATE": diff --git a/src/Discord.Net/DiscordConfig.cs b/src/Discord.Net/DiscordConfig.cs index cf2f59f0d..89f746fcd 100644 --- a/src/Discord.Net/DiscordConfig.cs +++ b/src/Discord.Net/DiscordConfig.cs @@ -57,7 +57,6 @@ namespace Discord private DiscordMode _mode = DiscordMode.Bot; /// User Agent string to use when connecting to Discord. - [JsonIgnore] public string UserAgent { get; private set; } //Rest diff --git a/src/Discord.Net/Enums/ChannelType.cs b/src/Discord.Net/Enums/ChannelType.cs index b3d6ce6e8..9ed49a701 100644 --- a/src/Discord.Net/Enums/ChannelType.cs +++ b/src/Discord.Net/Enums/ChannelType.cs @@ -2,7 +2,7 @@ namespace Discord { - public sealed class ChannelType : StringEnum, IEquatable + public class ChannelType : StringEnum, IEquatable { /// A text-only channel. public static ChannelType Text { get; } = new ChannelType("text"); diff --git a/src/Discord.Net/Enums/PermissionTarget.cs b/src/Discord.Net/Enums/PermissionTarget.cs index 2da27cabc..38a70e013 100644 --- a/src/Discord.Net/Enums/PermissionTarget.cs +++ b/src/Discord.Net/Enums/PermissionTarget.cs @@ -1,6 +1,6 @@ namespace Discord { - public sealed class PermissionTarget : StringEnum + public class PermissionTarget : StringEnum { /// A text-only channel. public static PermissionTarget Role { get; } = new PermissionTarget("role"); diff --git a/src/Discord.Net/Enums/UserStatus.cs b/src/Discord.Net/Enums/UserStatus.cs index da126a261..80def4234 100644 --- a/src/Discord.Net/Enums/UserStatus.cs +++ b/src/Discord.Net/Enums/UserStatus.cs @@ -1,6 +1,6 @@ namespace Discord { - public sealed class UserStatus : StringEnum + public class UserStatus : StringEnum { /// User is currently online and active. public static UserStatus Online { get; } = new UserStatus("online"); diff --git a/src/Discord.Net/Logging/LogManager.cs b/src/Discord.Net/Logging/LogManager.cs index f52031df9..576ec7679 100644 --- a/src/Discord.Net/Logging/LogManager.cs +++ b/src/Discord.Net/Logging/LogManager.cs @@ -2,7 +2,7 @@ namespace Discord.Logging { - public sealed class LogManager + public class LogManager { private readonly DiscordClient _client; diff --git a/src/Discord.Net/Logging/Logger.cs b/src/Discord.Net/Logging/Logger.cs index 6c1cc1a4b..9d7a6111b 100644 --- a/src/Discord.Net/Logging/Logger.cs +++ b/src/Discord.Net/Logging/Logger.cs @@ -2,7 +2,7 @@ namespace Discord.Logging { - public sealed class Logger + public class Logger { private readonly LogManager _manager; diff --git a/src/Discord.Net/MessageQueue.cs b/src/Discord.Net/MessageQueue.cs index 284e967c7..2759f458e 100644 --- a/src/Discord.Net/MessageQueue.cs +++ b/src/Discord.Net/MessageQueue.cs @@ -1,5 +1,6 @@ using Discord.API.Client.Rest; using Discord.Logging; +using Discord.Net.Rest; using System; using System.Collections.Concurrent; using System.Net; @@ -9,7 +10,7 @@ using System.Threading.Tasks; namespace Discord.Net { /// Manages an outgoing message queue for DiscordClient. - public sealed class MessageQueue + public class MessageQueue { private interface IQueuedAction { @@ -52,7 +53,7 @@ namespace Discord.Net private const int WarningStart = 30; private readonly Random _nonceRand; - private readonly DiscordClient _client; + private readonly RestClient _rest; private readonly Logger _logger; private readonly ConcurrentQueue _pendingActions; private readonly ConcurrentDictionary _pendingSends; @@ -61,9 +62,9 @@ namespace Discord.Net /// Gets the current number of queued actions. public int Count { get; private set; } - internal MessageQueue(DiscordClient client, Logger logger) + internal MessageQueue(RestClient rest, Logger logger) { - _client = client; + _rest = rest; _logger = logger; _nonceRand = new Random(); @@ -73,7 +74,7 @@ namespace Discord.Net internal Message QueueSend(Channel channel, string text, bool isTTS) { - Message msg = new Message(0, channel, channel.IsPrivate ? _client.PrivateUser : channel.Server.CurrentUser); + Message msg = new Message(0, channel, channel.IsPrivate ? channel.Client.PrivateUser : channel.Server.CurrentUser); msg.RawText = text; msg.Text = msg.Resolve(text); msg.Nonce = GenerateNonce(); @@ -135,7 +136,7 @@ namespace Discord.Net Nonce = msg.Nonce.ToString(), IsTTS = msg.IsTTS }; - var response = await _client.ClientAPI.Send(request).ConfigureAwait(false); + var response = await _rest.Send(request).ConfigureAwait(false); msg.Id = response.Id; msg.Update(response); msg.State = MessageState.Normal; @@ -153,7 +154,7 @@ namespace Discord.Net { Content = text }; - await _client.ClientAPI.Send(request).ConfigureAwait(false); + await _rest.Send(request).ConfigureAwait(false); } catch (Exception ex) { _logger.Error("Failed to edit message", ex); } } @@ -165,7 +166,7 @@ namespace Discord.Net try { var request = new DeleteMessageRequest(msg.Channel.Id, msg.Id); - await _client.ClientAPI.Send(request).ConfigureAwait(false); + await _rest.Send(request).ConfigureAwait(false); } catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } //Ignore catch (Exception ex) { _logger.Error("Failed to delete message", ex); } diff --git a/src/Discord.Net/Models/Channel.cs b/src/Discord.Net/Models/Channel.cs index b1cfb0e6b..60b10e12f 100644 --- a/src/Discord.Net/Models/Channel.cs +++ b/src/Discord.Net/Models/Channel.cs @@ -12,7 +12,7 @@ using APIChannel = Discord.API.Client.Channel; namespace Discord { - public sealed class Channel : IMentionable + public class Channel : IMentionable { private readonly static Action _cloner = DynamicIL.CreateCloner(); @@ -28,7 +28,7 @@ namespace Discord } } - public sealed class PermissionOverwrite + public class PermissionOverwrite { public PermissionTarget TargetType { get; } public ulong TargetId { get; } diff --git a/src/Discord.Net/Models/Color.cs b/src/Discord.Net/Models/Color.cs index c62bfecbb..c30f9737c 100644 --- a/src/Discord.Net/Models/Color.cs +++ b/src/Discord.Net/Models/Color.cs @@ -2,7 +2,7 @@ namespace Discord { - public sealed class Color : IEquatable + public class Color : IEquatable { private readonly static Action _cloner = DynamicIL.CreateCloner(); diff --git a/src/Discord.Net/Models/Invite.cs b/src/Discord.Net/Models/Invite.cs index 3bd300098..eac38bdfb 100644 --- a/src/Discord.Net/Models/Invite.cs +++ b/src/Discord.Net/Models/Invite.cs @@ -8,11 +8,11 @@ using APIInvite = Discord.API.Client.Invite; namespace Discord { - public sealed class Invite + public class Invite { private readonly static Action _cloner = DynamicIL.CreateCloner(); - public sealed class ServerInfo + public class ServerInfo { /// Returns the unique identifier of this server. public ulong Id { get; } @@ -25,7 +25,7 @@ namespace Discord Name = name; } } - public sealed class ChannelInfo + public class ChannelInfo { /// Returns the unique identifier of this channel. public ulong Id { get; } @@ -38,7 +38,7 @@ namespace Discord Name = name; } } - public sealed class InviterInfo + public class InviterInfo { /// Returns the unique identifier for this user. public ulong Id { get; } diff --git a/src/Discord.Net/Models/Message.cs b/src/Discord.Net/Models/Message.cs index 2568806eb..b6ef02a6c 100644 --- a/src/Discord.Net/Models/Message.cs +++ b/src/Discord.Net/Models/Message.cs @@ -22,7 +22,7 @@ namespace Discord Failed } - public sealed class Message + public class Message { private readonly static Action _cloner = DynamicIL.CreateCloner(); @@ -111,7 +111,7 @@ namespace Discord } }*/ - public sealed class Attachment : File + public class Attachment : File { /// Unique identifier for this file. public string Id { get; internal set; } @@ -123,7 +123,7 @@ namespace Discord internal Attachment() { } } - public sealed class Embed + public class Embed { /// URL of this embed. public string Url { get; internal set; } @@ -143,7 +143,7 @@ namespace Discord internal Embed() { } } - public sealed class EmbedLink + public class EmbedLink { /// URL of this embed provider. public string Url { get; internal set; } diff --git a/src/Discord.Net/Models/Permissions.cs b/src/Discord.Net/Models/Permissions.cs index 4d4d148ff..e992b748e 100644 --- a/src/Discord.Net/Models/Permissions.cs +++ b/src/Discord.Net/Models/Permissions.cs @@ -31,7 +31,7 @@ namespace Discord UseVoiceActivation = 25 } - public sealed class ServerPermissions : Permissions + public class ServerPermissions : Permissions { private readonly static Action _cloner = DynamicIL.CreateCloner(); @@ -60,7 +60,7 @@ namespace Discord public bool ManageServer { get { return GetBit(PermissionsBits.ManageServer); } set { SetBit(PermissionsBits.ManageServer, value); } } } - public sealed class ChannelPermissions : Permissions + public class ChannelPermissions : Permissions { private readonly static Action _cloner = DynamicIL.CreateCloner(); @@ -167,7 +167,7 @@ namespace Discord public bool Equals(Permissions permission) => permission?._rawValue == _rawValue; } - public sealed class DualChannelPermissions + public class DualChannelPermissions { private readonly static Action _cloner = DynamicIL.CreateCloner(); diff --git a/src/Discord.Net/Models/Profile.cs b/src/Discord.Net/Models/Profile.cs index 08e462f1d..077e0731c 100644 --- a/src/Discord.Net/Models/Profile.cs +++ b/src/Discord.Net/Models/Profile.cs @@ -6,7 +6,7 @@ using APIUser = Discord.API.Client.User; namespace Discord { - public sealed class Profile + public class Profile { private readonly static Action _cloner = DynamicIL.CreateCloner(); @@ -72,8 +72,6 @@ namespace Discord }; var loginResponse = await Client.ClientAPI.Send(loginRequest).ConfigureAwait(false); Client.ClientAPI.Token = loginResponse.Token; - Client.GatewaySocket.Token = loginResponse.Token; - Client.GatewaySocket.SessionId = null; } } diff --git a/src/Discord.Net/Models/Region.cs b/src/Discord.Net/Models/Region.cs index dcb8de12b..5b0354048 100644 --- a/src/Discord.Net/Models/Region.cs +++ b/src/Discord.Net/Models/Region.cs @@ -1,6 +1,6 @@ namespace Discord { - public sealed class Region + public class Region { public string Id { get; } public string Name { get; } diff --git a/src/Discord.Net/Models/Role.cs b/src/Discord.Net/Models/Role.cs index a7aa805d2..8334baa63 100644 --- a/src/Discord.Net/Models/Role.cs +++ b/src/Discord.Net/Models/Role.cs @@ -9,7 +9,7 @@ using APIRole = Discord.API.Client.Role; namespace Discord { - public sealed class Role : IMentionable + public class Role : IMentionable { private readonly static Action _cloner = DynamicIL.CreateCloner(); diff --git a/src/Discord.Net/Models/Server.cs b/src/Discord.Net/Models/Server.cs index f0ef11bcd..13f80572e 100644 --- a/src/Discord.Net/Models/Server.cs +++ b/src/Discord.Net/Models/Server.cs @@ -12,7 +12,7 @@ using System.Threading.Tasks; namespace Discord { /// Represents a Discord server (also known as a guild). - public sealed class Server + public class Server { private readonly static Action _cloner = DynamicIL.CreateCloner(); @@ -21,7 +21,7 @@ namespace Discord internal static string GetSplashUrl(ulong serverId, string splashId) => splashId != null ? $"{DiscordConfig.ClientAPIUrl}guilds/{serverId}/splashes/{splashId}.jpg" : null; - public sealed class Emoji + public class Emoji { public string Id { get; } diff --git a/src/Discord.Net/Models/User.cs b/src/Discord.Net/Models/User.cs index ef9200b10..2ae7223f5 100644 --- a/src/Discord.Net/Models/User.cs +++ b/src/Discord.Net/Models/User.cs @@ -9,7 +9,7 @@ using APIMember = Discord.API.Client.Member; namespace Discord { - public sealed class User + public class User { private readonly static Action _cloner = DynamicIL.CreateCloner(); diff --git a/src/Discord.Net/Net/HttpException.cs b/src/Discord.Net/Net/HttpException.cs index afb17bcaf..8bfdbf73b 100644 --- a/src/Discord.Net/Net/HttpException.cs +++ b/src/Discord.Net/Net/HttpException.cs @@ -7,7 +7,7 @@ namespace Discord.Net #if NET46 [Serializable] #endif - public sealed class HttpException : Exception + public class HttpException : Exception { public HttpStatusCode StatusCode { get; } diff --git a/src/Discord.Net/Net/Rest/BuiltInEngine.cs b/src/Discord.Net/Net/Rest/BuiltInEngine.cs index cb37d6648..7e422b49a 100644 --- a/src/Discord.Net/Net/Rest/BuiltInEngine.cs +++ b/src/Discord.Net/Net/Rest/BuiltInEngine.cs @@ -13,7 +13,7 @@ using Nito.AsyncEx; namespace Discord.Net.Rest { - internal sealed class BuiltInEngine : IRestEngine + internal class BuiltInEngine : IRestEngine { private const int HR_SECURECHANNELFAILED = -2146233079; diff --git a/src/Discord.Net/Net/Rest/RestClient.cs b/src/Discord.Net/Net/Rest/RestClient.cs index 727182037..8e5ce180c 100644 --- a/src/Discord.Net/Net/Rest/RestClient.cs +++ b/src/Discord.Net/Net/Rest/RestClient.cs @@ -8,7 +8,7 @@ using System.Threading.Tasks; namespace Discord.Net.Rest { - public sealed partial class RestClient + public partial class RestClient { private struct RestResults { diff --git a/src/Discord.Net/Net/Rest/SharpRestEngine.cs b/src/Discord.Net/Net/Rest/SharpRestEngine.cs index 8ec300c11..325808702 100644 --- a/src/Discord.Net/Net/Rest/SharpRestEngine.cs +++ b/src/Discord.Net/Net/Rest/SharpRestEngine.cs @@ -11,7 +11,7 @@ using RestSharpClient = RestSharp.RestClient; namespace Discord.Net.Rest { - internal sealed class RestSharpEngine : IRestEngine + internal class RestSharpEngine : IRestEngine { private const int HR_SECURECHANNELFAILED = -2146233079; diff --git a/src/Discord.Net/Net/TimeoutException.cs b/src/Discord.Net/Net/TimeoutException.cs index 542ee542a..051eeb263 100644 --- a/src/Discord.Net/Net/TimeoutException.cs +++ b/src/Discord.Net/Net/TimeoutException.cs @@ -5,7 +5,7 @@ namespace Discord.Net #if NET46 [Serializable] #endif - public sealed class TimeoutException : OperationCanceledException + public class TimeoutException : OperationCanceledException { public TimeoutException() : base("An operation has timed out.") diff --git a/src/Discord.Net/Net/WebSocketException.cs b/src/Discord.Net/Net/WebSocketException.cs index d2c61b644..b845d90c4 100644 --- a/src/Discord.Net/Net/WebSocketException.cs +++ b/src/Discord.Net/Net/WebSocketException.cs @@ -2,7 +2,7 @@ namespace Discord.Net { - public sealed class WebSocketException : Exception + public class WebSocketException : Exception { public int Code { get; } public string Reason { get; } diff --git a/src/Discord.Net/Net/WebSockets/BuiltInEngine.cs b/src/Discord.Net/Net/WebSockets/BuiltInEngine.cs index 7c454715d..34224720f 100644 --- a/src/Discord.Net/Net/WebSockets/BuiltInEngine.cs +++ b/src/Discord.Net/Net/WebSockets/BuiltInEngine.cs @@ -12,7 +12,7 @@ using WebSocketClient = System.Net.WebSockets.ClientWebSocket; namespace Discord.Net.WebSockets { - internal sealed class BuiltInEngine : IWebSocketEngine + internal class BuiltInEngine : IWebSocketEngine { private const int ReceiveChunkSize = 12 * 1024; //12KB private const int SendChunkSize = 4 * 1024; //4KB @@ -81,7 +81,7 @@ namespace Discord.Net.WebSockets try { - result = await _webSocket.ReceiveAsync(new ArraySegment(buffer), cancelToken);//.ConfigureAwait(false); + result = await _webSocket.ReceiveAsync(new ArraySegment(buffer), cancelToken).ConfigureAwait(false); } catch (Win32Exception ex) when (ex.HResult == HR_TIMEOUT) { diff --git a/src/Discord.Net/Net/WebSockets/GatewaySocket.cs b/src/Discord.Net/Net/WebSockets/GatewaySocket.cs index bd2a8ab6a..1bbe3296d 100644 --- a/src/Discord.Net/Net/WebSockets/GatewaySocket.cs +++ b/src/Discord.Net/Net/WebSockets/GatewaySocket.cs @@ -2,6 +2,7 @@ using Discord.API.Client.GatewaySocket; using Discord.API.Client.Rest; using Discord.Logging; +using Discord.Net.Rest; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System; @@ -11,20 +12,21 @@ using System.Threading.Tasks; namespace Discord.Net.WebSockets { - public sealed class GatewaySocket : WebSocket + public class GatewaySocket : WebSocket { + private RestClient _rest; private uint _lastSequence; private int _reconnects; - public string Token { get; internal set; } - public string SessionId { get; internal set; } + //public string Token { get; private set; } + public string SessionId { get; private set; } public event EventHandler ReceivedDispatch = delegate { }; private void OnReceivedDispatch(string type, JToken payload) => ReceivedDispatch(this, new WebSocketEventEventArgs(type, payload)); - public GatewaySocket(DiscordClient client, Logger logger) - : base(client, logger) + public GatewaySocket(DiscordConfig config, JsonSerializer serializer, Logger logger) + : base(config, serializer, logger) { Disconnected += async (s, e) => { @@ -32,17 +34,19 @@ namespace Discord.Net.WebSockets await Reconnect().ConfigureAwait(false); }; } - - public async Task Connect() + + public async Task Connect(RestClient rest, CancellationToken parentCancelToken) { - var gatewayResponse = await _client.ClientAPI.Send(new GatewayRequest()).ConfigureAwait(false); - Host = gatewayResponse.Url; - if (Logger.Level >= LogSeverity.Verbose) - Logger.Verbose($"Login successful, gateway: {gatewayResponse.Url}"); + _rest = rest; + //Token = rest.Token; + + var gatewayResponse = await rest.Send(new GatewayRequest()).ConfigureAwait(false); + Logger.Verbose($"Login successful, gateway: {gatewayResponse.Url}"); - await BeginConnect().ConfigureAwait(false); + Host = gatewayResponse.Url; + await BeginConnect(parentCancelToken).ConfigureAwait(false); if (SessionId == null) - SendIdentify(Token); + SendIdentify(_rest.Token); else SendResume(); } @@ -50,17 +54,17 @@ namespace Discord.Net.WebSockets { try { - var cancelToken = ParentCancelToken.Value; + var cancelToken = _parentCancelToken; if (_reconnects++ == 0) - await Task.Delay(_client.Config.ReconnectDelay, cancelToken).ConfigureAwait(false); + await Task.Delay(_config.ReconnectDelay, cancelToken).ConfigureAwait(false); else - await Task.Delay(_client.Config.FailedReconnectDelay, cancelToken).ConfigureAwait(false); + await Task.Delay(_config.FailedReconnectDelay, cancelToken).ConfigureAwait(false); while (!cancelToken.IsCancellationRequested) { try { - await Connect().ConfigureAwait(false); + await Connect(_rest, _parentCancelToken).ConfigureAwait(false); break; } catch (OperationCanceledException) { throw; } @@ -68,20 +72,25 @@ namespace Discord.Net.WebSockets { Logger.Error("Reconnect failed", ex); //Net is down? We can keep trying to reconnect until the user runs Disconnect() - await Task.Delay(_client.Config.FailedReconnectDelay, cancelToken).ConfigureAwait(false); + await Task.Delay(_config.FailedReconnectDelay, cancelToken).ConfigureAwait(false); } } } catch (OperationCanceledException) { } } - public Task Disconnect() => _taskManager.Stop(true); + public async Task Disconnect() + { + await _taskManager.Stop(true).ConfigureAwait(false); + //Token = null; + SessionId = null; + } protected override async Task Run() { List tasks = new List(); tasks.AddRange(_engine.GetTasks(CancelToken)); tasks.Add(HeartbeatAsync(CancelToken)); - await _taskManager.Start(tasks, _cancelTokenSource).ConfigureAwait(false); + await _taskManager.Start(tasks, _cancelSource).ConfigureAwait(false); } protected override Task Cleanup() { @@ -103,11 +112,16 @@ namespace Discord.Net.WebSockets { case OpCodes.Dispatch: { - OnReceivedDispatch(msg.Type, msg.Payload as JToken); + if (msg.Type == "READY") + SessionId = (msg.Payload as JToken).Value("session_id"); + + OnReceivedDispatch(msg.Type, msg.Payload as JToken); + if (msg.Type == "READY" || msg.Type == "RESUMED") { + _heartbeatInterval = (msg.Payload as JToken).Value("heartbeat_interval"); _reconnects = 0; - await EndConnect(); //Complete the connect + await EndConnect().ConfigureAwait(false); //Complete the connect } } break; @@ -142,7 +156,7 @@ namespace Discord.Net.WebSockets Version = 3, Token = token, Properties = props, - LargeThreshold = _client.Config.UseLargeThreshold ? 100 : (int?)null, + LargeThreshold = _config.UseLargeThreshold ? 100 : (int?)null, UseCompression = true }; QueueMessage(msg); @@ -163,11 +177,6 @@ namespace Discord.Net.WebSockets public void SendRequestMembers(ulong serverId, string query, int limit) => QueueMessage(new RequestMembersCommand { GuildId = serverId, Query = query, Limit = limit }); - internal void StartHeartbeat(int interval) - { - _heartbeatInterval = interval; - } - //Cancel if either DiscordClient.Disconnect is called, data socket errors or timeout is reached public override void WaitForConnection(CancellationToken cancelToken) => base.WaitForConnection(CancellationTokenSource.CreateLinkedTokenSource(cancelToken, CancelToken).Token); diff --git a/src/Discord.Net/Net/WebSockets/WS4NetEngine.cs b/src/Discord.Net/Net/WebSockets/WS4NetEngine.cs index 785e4f813..98eb7db02 100644 --- a/src/Discord.Net/Net/WebSockets/WS4NetEngine.cs +++ b/src/Discord.Net/Net/WebSockets/WS4NetEngine.cs @@ -10,7 +10,7 @@ using WebSocketClient = WebSocket4Net.WebSocket; namespace Discord.Net.WebSockets { - internal sealed class WS4NetEngine : IWebSocketEngine + internal class WS4NetEngine : IWebSocketEngine { private readonly DiscordConfig _config; private readonly ConcurrentQueue _sendQueue; diff --git a/src/Discord.Net/Net/WebSockets/WebSocket.cs b/src/Discord.Net/Net/WebSockets/WebSocket.cs index accf31a07..1057c8d9e 100644 --- a/src/Discord.Net/Net/WebSockets/WebSocket.cs +++ b/src/Discord.Net/Net/WebSockets/WebSocket.cs @@ -14,18 +14,18 @@ namespace Discord.Net.WebSockets { private readonly AsyncLock _lock; protected readonly IWebSocketEngine _engine; - protected readonly DiscordClient _client; + protected readonly DiscordConfig _config; protected readonly ManualResetEventSlim _connectedEvent; protected readonly TaskManager _taskManager; protected readonly JsonSerializer _serializer; - protected CancellationTokenSource _cancelTokenSource; + protected CancellationTokenSource _cancelSource; + protected CancellationToken _parentCancelToken; protected int _heartbeatInterval; private DateTime _lastHeartbeat; - + /// Gets the logger used for this client. protected internal Logger Logger { get; } public CancellationToken CancelToken { get; private set; } - public CancellationToken? ParentCancelToken { get; set; } public string Host { get; set; } /// Gets the current connection state of this client. @@ -38,11 +38,11 @@ namespace Discord.Net.WebSockets private void OnDisconnected(bool wasUnexpected, Exception error) => Disconnected(this, new DisconnectedEventArgs(wasUnexpected, error)); - public WebSocket(DiscordClient client, Logger logger) + public WebSocket(DiscordConfig config, JsonSerializer serializer, Logger logger) { - _client = client; + _config = config; + _serializer = serializer; Logger = logger; - _serializer = client.Serializer; _lock = new AsyncLock(); _taskManager = new TaskManager(Cleanup); @@ -50,9 +50,9 @@ namespace Discord.Net.WebSockets _connectedEvent = new ManualResetEventSlim(false); #if !DOTNET5_4 - _engine = new WS4NetEngine(client.Config, _taskManager); + _engine = new WS4NetEngine(config, _taskManager); #else - _engine = new BuiltInEngine(client.Config); + _engine = new BuiltInEngine(config); #endif _engine.BinaryMessage += (s, e) => { @@ -69,18 +69,20 @@ namespace Discord.Net.WebSockets _engine.TextMessage += (s, e) => ProcessMessage(e.Message).Wait(); } - protected async Task BeginConnect() + protected async Task BeginConnect(CancellationToken parentCancelToken) { try { using (await _lock.LockAsync().ConfigureAwait(false)) { + _parentCancelToken = parentCancelToken; + await _taskManager.Stop().ConfigureAwait(false); _taskManager.ClearException(); State = ConnectionState.Connecting; - _cancelTokenSource = new CancellationTokenSource(); - CancelToken = CancellationTokenSource.CreateLinkedTokenSource(_cancelTokenSource.Token, ParentCancelToken.Value).Token; + _cancelSource = new CancellationTokenSource(); + CancelToken = CancellationTokenSource.CreateLinkedTokenSource(_cancelSource.Token, parentCancelToken).Token; _lastHeartbeat = DateTime.UtcNow; await _engine.Connect(Host, CancelToken).ConfigureAwait(false); @@ -117,7 +119,7 @@ namespace Discord.Net.WebSockets State = ConnectionState.Disconnecting; await _engine.Disconnect().ConfigureAwait(false); - _cancelTokenSource = null; + _cancelSource = null; _connectedEvent.Reset(); if (oldState == ConnectionState.Connecting || oldState == ConnectionState.Connected) @@ -154,7 +156,7 @@ namespace Discord.Net.WebSockets { while (!cancelToken.IsCancellationRequested) { - if (this.State == ConnectionState.Connected) + if (this.State == ConnectionState.Connected && _heartbeatInterval > 0) { SendHeartbeat(); await Task.Delay(_heartbeatInterval, cancelToken).ConfigureAwait(false); @@ -172,7 +174,7 @@ namespace Discord.Net.WebSockets { try { - if (!_connectedEvent.Wait(_client.Config.ConnectionTimeout, cancelToken)) + if (!_connectedEvent.Wait(_config.ConnectionTimeout, cancelToken)) { if (State != ConnectionState.Connected) throw new TimeoutException(); diff --git a/src/Discord.Net/ServiceManager.cs b/src/Discord.Net/ServiceManager.cs index cb1e8e89a..0e6533b90 100644 --- a/src/Discord.Net/ServiceManager.cs +++ b/src/Discord.Net/ServiceManager.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; namespace Discord { - public sealed class ServiceManager + public class ServiceManager { private readonly Dictionary _services; diff --git a/src/Discord.Net/TaskManager.cs b/src/Discord.Net/TaskManager.cs index 4934e53d9..7c9a667ce 100644 --- a/src/Discord.Net/TaskManager.cs +++ b/src/Discord.Net/TaskManager.cs @@ -9,37 +9,41 @@ using System.Threading.Tasks; namespace Discord { /// Helper class used to manage several tasks and keep them in sync. If any single task errors or stops, all other tasks will also be stopped. - public sealed class TaskManager + public class TaskManager { private readonly AsyncLock _lock; private readonly Func _stopAction; + private ExceptionDispatchInfo _stopReason; private CancellationTokenSource _cancelSource; private Task _task; - public bool WasStopExpected => _wasStopExpected; - private bool _wasStopExpected; + public bool StopOnCompletion { get; } + public bool WasStopExpected { get; private set; } public Exception Exception => _stopReason?.SourceException; - private ExceptionDispatchInfo _stopReason; - internal TaskManager() + internal TaskManager(bool stopOnCompletion) { _lock = new AsyncLock(); + StopOnCompletion = stopOnCompletion; } - public TaskManager(Action stopAction) - : this() + public TaskManager(Action stopAction, bool stopOnCompletion = true) + : this(stopOnCompletion) { _stopAction = TaskHelper.ToAsync(stopAction); } - public TaskManager(Func stopAction) - : this() + public TaskManager(Func stopAction, bool stopOnCompletion = true) + : this(stopOnCompletion) { _stopAction = stopAction; } public async Task Start(IEnumerable tasks, CancellationTokenSource cancelSource) { + if (tasks == null) throw new ArgumentNullException(nameof(tasks)); + if (cancelSource == null) throw new ArgumentNullException(nameof(cancelSource)); + while (true) { var task = _task; @@ -54,27 +58,36 @@ namespace Discord continue; //Another thread sneaked in and started this manager before we got a lock, loop and try again _stopReason = null; - _wasStopExpected = false; + WasStopExpected = false; Task[] tasksArray = tasks.ToArray(); - Task anyTask = Task.WhenAny(tasksArray); - Task allTasks = Task.WhenAll(tasksArray); _task = Task.Run(async () => { - //Wait for the first task to stop or error - Task firstTask = await anyTask.ConfigureAwait(false); - - //Signal the rest of the tasks to stop - if (firstTask.Exception != null) - await SignalError(firstTask.Exception).ConfigureAwait(false); - else - await SignalStop().ConfigureAwait(false); - - //Wait for the other tasks, and signal their errors too just in case - try { await allTasks.ConfigureAwait(false); } - catch (AggregateException ex) { await SignalError(ex.InnerExceptions.First()).ConfigureAwait(false); } - catch (Exception ex) { await SignalError(ex).ConfigureAwait(false); } + if (tasksArray.Length > 0) + { + Task anyTask = tasksArray.Length > 0 ? Task.WhenAny(tasksArray) : null; + Task allTasks = tasksArray.Length > 0 ? Task.WhenAll(tasksArray) : null; + //Wait for the first task to stop or error + Task firstTask = await anyTask.ConfigureAwait(false); + + //Signal the rest of the tasks to stop + if (firstTask.Exception != null) + await SignalError(firstTask.Exception).ConfigureAwait(false); + else if (StopOnCompletion) //Unless we allow for natural completions + await SignalStop().ConfigureAwait(false); + + //Wait for the other tasks, and signal their errors too just in case + try { await allTasks.ConfigureAwait(false); } + catch (AggregateException ex) { await SignalError(ex.InnerExceptions.First()).ConfigureAwait(false); } + catch (Exception ex) { await SignalError(ex).ConfigureAwait(false); } + } + + if (!StopOnCompletion && !_cancelSource.IsCancellationRequested) + { + try { await Task.Delay(-1, _cancelSource.Token).ConfigureAwait(false); } //Pause until TaskManager is stopped + catch (OperationCanceledException) { } + } //Run the cleanup function within our lock if (_stopAction != null) @@ -92,13 +105,9 @@ namespace Discord using (await _lock.LockAsync().ConfigureAwait(false)) { if (isExpected) - _wasStopExpected = true; - - if (_task == null) return; //Are we running? - if (_cancelSource.IsCancellationRequested) return; + WasStopExpected = true; - if (_cancelSource != null) - _cancelSource.Cancel(); + Cancel(); } } public async Task Stop(bool isExpected = false) @@ -107,14 +116,11 @@ namespace Discord using (await _lock.LockAsync().ConfigureAwait(false)) { if (isExpected) - _wasStopExpected = true; + WasStopExpected = true; //Cache the task so we still have something to await if Cleanup is run really quickly - task = _task; - if (task == null) return; //Are we running? - - if (!_cancelSource.IsCancellationRequested && _cancelSource != null) - _cancelSource.Cancel(); + task = _task ?? TaskHelper.CompletedTask; + Cancel(); } await task.ConfigureAwait(false); } @@ -125,9 +131,7 @@ namespace Discord { if (_stopReason != null) return; - _stopReason = ExceptionDispatchInfo.Capture(ex); - if (_cancelSource != null) - _cancelSource.Cancel(); + Cancel(ex); } } public async Task Error(Exception ex) @@ -139,15 +143,20 @@ namespace Discord //Cache the task so we still have something to await if Cleanup is run really quickly task = _task ?? TaskHelper.CompletedTask; - if (!_cancelSource.IsCancellationRequested) - { - _stopReason = ExceptionDispatchInfo.Capture(ex); - if (_cancelSource != null) - _cancelSource.Cancel(); - } + Cancel(ex); } await task.ConfigureAwait(false); } + private void Cancel(Exception ex = null) + { + var source = _cancelSource; + if (source != null && !source.IsCancellationRequested) + { + if (ex != null) + _stopReason = ExceptionDispatchInfo.Capture(ex); + _cancelSource.Cancel(); + } + } /// Throws an exception if one was captured. public void ThrowException() @@ -160,7 +169,7 @@ namespace Discord using (_lock.Lock()) { _stopReason = null; - _wasStopExpected = false; + WasStopExpected = false; } } }