| @@ -3,7 +3,7 @@ | |||||
| public static class Format | public static class Format | ||||
| { | { | ||||
| // Characters which need escaping | // Characters which need escaping | ||||
| private static string[] SensitiveCharacters = { "\\", "*", "_", "~", "`" }; | |||||
| private static string[] _sensitiveCharacters = { "\\", "*", "_", "~", "`" }; | |||||
| /// <summary> Returns a markdown-formatted string with bold formatting. </summary> | /// <summary> Returns a markdown-formatted string with bold formatting. </summary> | ||||
| public static string Bold(string text) => $"**{text}**"; | public static string Bold(string text) => $"**{text}**"; | ||||
| @@ -26,7 +26,7 @@ | |||||
| /// <summary> Sanitizes the string, safely escaping any Markdown sequences. </summary> | /// <summary> Sanitizes the string, safely escaping any Markdown sequences. </summary> | ||||
| public static string Sanitize(string text) | public static string Sanitize(string text) | ||||
| { | { | ||||
| foreach (string unsafeChar in SensitiveCharacters) | |||||
| foreach (string unsafeChar in _sensitiveCharacters) | |||||
| text = text.Replace(unsafeChar, $"\\{unsafeChar}"); | text = text.Replace(unsafeChar, $"\\{unsafeChar}"); | ||||
| return text; | return text; | ||||
| } | } | ||||
| @@ -22,23 +22,25 @@ namespace Discord.Rest | |||||
| private readonly SemaphoreSlim _stateLock; | private readonly SemaphoreSlim _stateLock; | ||||
| private bool _isFirstLogin, _isDisposed; | private bool _isFirstLogin, _isDisposed; | ||||
| internal API.DiscordRestApiClient ApiClient { get; } | |||||
| internal LogManager LogManager { get; } | internal LogManager LogManager { get; } | ||||
| internal API.DiscordRestApiClient ApiClient { get; private set; } | |||||
| public LoginState LoginState { get; private set; } | public LoginState LoginState { get; private set; } | ||||
| public ISelfUser CurrentUser { get; protected set; } | public ISelfUser CurrentUser { get; protected set; } | ||||
| public TokenType TokenType => ApiClient.AuthTokenType; | public TokenType TokenType => ApiClient.AuthTokenType; | ||||
| /// <summary> Creates a new REST-only discord client. </summary> | /// <summary> Creates a new REST-only discord client. </summary> | ||||
| internal BaseDiscordClient(DiscordRestConfig config, API.DiscordRestApiClient client) | |||||
| internal BaseDiscordClient(DiscordRestConfig config) | |||||
| { | { | ||||
| ApiClient = client; | |||||
| LogManager = new LogManager(config.LogLevel); | LogManager = new LogManager(config.LogLevel); | ||||
| LogManager.Message += async msg => await _logEvent.InvokeAsync(msg).ConfigureAwait(false); | LogManager.Message += async msg => await _logEvent.InvokeAsync(msg).ConfigureAwait(false); | ||||
| _stateLock = new SemaphoreSlim(1, 1); | _stateLock = new SemaphoreSlim(1, 1); | ||||
| _restLogger = LogManager.CreateLogger("Rest"); | _restLogger = LogManager.CreateLogger("Rest"); | ||||
| _isFirstLogin = config.DisplayInitialLog; | _isFirstLogin = config.DisplayInitialLog; | ||||
| } | |||||
| internal void SetApiClient(API.DiscordRestApiClient client) | |||||
| { | |||||
| ApiClient = client; | |||||
| ApiClient.RequestQueue.RateLimitTriggered += async (id, info) => | ApiClient.RequestQueue.RateLimitTriggered += async (id, info) => | ||||
| { | { | ||||
| if (info == null) | if (info == null) | ||||
| @@ -1,10 +1,9 @@ | |||||
| #pragma warning disable CS1591 | #pragma warning disable CS1591 | ||||
| using Discord.API.Rest; | using Discord.API.Rest; | ||||
| using Discord.Net; | using Discord.Net; | ||||
| using Discord.Net.Converters; | |||||
| using Discord.Net.Queue; | using Discord.Net.Queue; | ||||
| using Discord.Net.Rest; | using Discord.Net.Rest; | ||||
| using Newtonsoft.Json; | |||||
| using Discord.Serialization; | |||||
| using System; | using System; | ||||
| using System.Collections.Concurrent; | using System.Collections.Concurrent; | ||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||
| @@ -27,9 +26,9 @@ namespace Discord.API | |||||
| public event Func<string, string, double, Task> SentRequest { add { _sentRequestEvent.Add(value); } remove { _sentRequestEvent.Remove(value); } } | public event Func<string, string, double, Task> SentRequest { add { _sentRequestEvent.Add(value); } remove { _sentRequestEvent.Remove(value); } } | ||||
| private readonly AsyncEvent<Func<string, string, double, Task>> _sentRequestEvent = new AsyncEvent<Func<string, string, double, Task>>(); | private readonly AsyncEvent<Func<string, string, double, Task>> _sentRequestEvent = new AsyncEvent<Func<string, string, double, Task>>(); | ||||
| protected readonly JsonSerializer _serializer; | |||||
| protected readonly SemaphoreSlim _stateLock; | protected readonly SemaphoreSlim _stateLock; | ||||
| protected readonly ScopedSerializer _serializer; | |||||
| private readonly RestClientProvider _restClientProvider; | private readonly RestClientProvider _restClientProvider; | ||||
| protected bool _isDisposed; | protected bool _isDisposed; | ||||
| @@ -45,13 +44,12 @@ namespace Discord.API | |||||
| internal IRestClient RestClient { get; private set; } | internal IRestClient RestClient { get; private set; } | ||||
| internal ulong? CurrentUserId { get; set;} | internal ulong? CurrentUserId { get; set;} | ||||
| public DiscordRestApiClient(RestClientProvider restClientProvider, string userAgent, RetryMode defaultRetryMode = RetryMode.AlwaysRetry, | |||||
| JsonSerializer serializer = null) | |||||
| public DiscordRestApiClient(RestClientProvider restClientProvider, string userAgent, ScopedSerializer serializer, RetryMode defaultRetryMode = RetryMode.AlwaysRetry) | |||||
| { | { | ||||
| _restClientProvider = restClientProvider; | _restClientProvider = restClientProvider; | ||||
| UserAgent = userAgent; | UserAgent = userAgent; | ||||
| _serializer = serializer; | |||||
| DefaultRetryMode = defaultRetryMode; | DefaultRetryMode = defaultRetryMode; | ||||
| _serializer = serializer ?? new JsonSerializer { DateFormatString = "yyyy-MM-ddTHH:mm:ssZ", ContractResolver = new DiscordContractResolver() }; | |||||
| RequestQueue = new RequestQueue(); | RequestQueue = new RequestQueue(); | ||||
| _stateLock = new SemaphoreSlim(1, 1); | _stateLock = new SemaphoreSlim(1, 1); | ||||
| @@ -1159,16 +1157,14 @@ namespace Discord.API | |||||
| protected string SerializeJson(object value) | protected string SerializeJson(object value) | ||||
| { | { | ||||
| var sb = new StringBuilder(256); | var sb = new StringBuilder(256); | ||||
| using (TextWriter text = new StringWriter(sb, CultureInfo.InvariantCulture)) | |||||
| using (JsonWriter writer = new JsonTextWriter(text)) | |||||
| _serializer.Serialize(writer, value); | |||||
| using (var writer = new StringWriter(sb, CultureInfo.InvariantCulture)) | |||||
| _serializer.ToJson(writer, value); | |||||
| return sb.ToString(); | return sb.ToString(); | ||||
| } | } | ||||
| protected T DeserializeJson<T>(Stream jsonStream) | protected T DeserializeJson<T>(Stream jsonStream) | ||||
| { | { | ||||
| using (TextReader text = new StreamReader(jsonStream)) | |||||
| using (JsonReader reader = new JsonTextReader(text)) | |||||
| return _serializer.Deserialize<T>(reader); | |||||
| using (var reader = new StreamReader(jsonStream)) | |||||
| return _serializer.FromJson<T>(reader); | |||||
| } | } | ||||
| internal class BucketIds | internal class BucketIds | ||||
| @@ -1,4 +1,5 @@ | |||||
| using System.Collections.Generic; | |||||
| using Discord.Serialization; | |||||
| using System.Collections.Generic; | |||||
| using System.Collections.Immutable; | using System.Collections.Immutable; | ||||
| using System.IO; | using System.IO; | ||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
| @@ -7,15 +8,23 @@ namespace Discord.Rest | |||||
| { | { | ||||
| public class DiscordRestClient : BaseDiscordClient, IDiscordClient | public class DiscordRestClient : BaseDiscordClient, IDiscordClient | ||||
| { | { | ||||
| private readonly ScopedSerializer _serializer; | |||||
| private RestApplication _applicationInfo; | private RestApplication _applicationInfo; | ||||
| public new RestSelfUser CurrentUser => base.CurrentUser as RestSelfUser; | public new RestSelfUser CurrentUser => base.CurrentUser as RestSelfUser; | ||||
| public DiscordRestClient() : this(new DiscordRestConfig()) { } | public DiscordRestClient() : this(new DiscordRestConfig()) { } | ||||
| public DiscordRestClient(DiscordRestConfig config) : base(config, CreateApiClient(config)) { } | |||||
| public DiscordRestClient(DiscordRestConfig config) : base(config) | |||||
| { | |||||
| _serializer = Serializer.CreateScope(); | |||||
| _serializer.Error += async ex => | |||||
| { | |||||
| await _restLogger.WarningAsync("Serializer Error", ex); | |||||
| }; | |||||
| SetApiClient(new API.DiscordRestApiClient(config.RestClientProvider, DiscordConfig.UserAgent, _serializer, config.DefaultRetryMode)); | |||||
| } | |||||
| private static API.DiscordRestApiClient CreateApiClient(DiscordRestConfig config) | |||||
| => new API.DiscordRestApiClient(config.RestClientProvider, DiscordRestConfig.UserAgent); | |||||
| internal override void Dispose(bool disposing) | internal override void Dispose(bool disposing) | ||||
| { | { | ||||
| if (disposing) | if (disposing) | ||||
| @@ -1,5 +1,4 @@ | |||||
| using Newtonsoft.Json; | |||||
| using System; | |||||
| using System; | |||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||
| using System.Globalization; | using System.Globalization; | ||||
| using System.IO; | using System.IO; | ||||
| @@ -18,7 +17,6 @@ namespace Discord.Net.Rest | |||||
| private readonly HttpClient _client; | private readonly HttpClient _client; | ||||
| private readonly string _baseUrl; | private readonly string _baseUrl; | ||||
| private readonly JsonSerializer _errorDeserializer; | |||||
| private CancellationToken _cancelToken; | private CancellationToken _cancelToken; | ||||
| private bool _isDisposed; | private bool _isDisposed; | ||||
| @@ -35,7 +33,6 @@ namespace Discord.Net.Rest | |||||
| SetHeader("accept-encoding", "gzip, deflate"); | SetHeader("accept-encoding", "gzip, deflate"); | ||||
| _cancelToken = CancellationToken.None; | _cancelToken = CancellationToken.None; | ||||
| _errorDeserializer = new JsonSerializer(); | |||||
| } | } | ||||
| private void Dispose(bool disposing) | private void Dispose(bool disposing) | ||||
| { | { | ||||
| @@ -2,7 +2,7 @@ | |||||
| using System; | using System; | ||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||
| namespace Discord.Net.Converters | |||||
| namespace Discord.Serialization.JsonConverters | |||||
| { | { | ||||
| internal class ArrayConverter<T> : JsonConverter | internal class ArrayConverter<T> : JsonConverter | ||||
| { | { | ||||
| @@ -6,7 +6,7 @@ using System.Collections.Generic; | |||||
| using System.Linq; | using System.Linq; | ||||
| using System.Reflection; | using System.Reflection; | ||||
| namespace Discord.Net.Converters | |||||
| namespace Discord.Serialization.JsonConverters | |||||
| { | { | ||||
| internal class DiscordContractResolver : DefaultContractResolver | internal class DiscordContractResolver : DefaultContractResolver | ||||
| { | { | ||||
| @@ -2,7 +2,7 @@ | |||||
| using System; | using System; | ||||
| using Model = Discord.API.Image; | using Model = Discord.API.Image; | ||||
| namespace Discord.Net.Converters | |||||
| namespace Discord.Serialization.JsonConverters | |||||
| { | { | ||||
| internal class ImageConverter : JsonConverter | internal class ImageConverter : JsonConverter | ||||
| { | { | ||||
| @@ -1,7 +1,7 @@ | |||||
| using Newtonsoft.Json; | using Newtonsoft.Json; | ||||
| using System; | using System; | ||||
| namespace Discord.Net.Converters | |||||
| namespace Discord.Serialization.JsonConverters | |||||
| { | { | ||||
| internal class NullableConverter<T> : JsonConverter | internal class NullableConverter<T> : JsonConverter | ||||
| where T : struct | where T : struct | ||||
| @@ -1,7 +1,7 @@ | |||||
| using Newtonsoft.Json; | using Newtonsoft.Json; | ||||
| using System; | using System; | ||||
| namespace Discord.Net.Converters | |||||
| namespace Discord.Serialization.JsonConverters | |||||
| { | { | ||||
| internal class OptionalConverter<T> : JsonConverter | internal class OptionalConverter<T> : JsonConverter | ||||
| { | { | ||||
| @@ -1,7 +1,7 @@ | |||||
| using Newtonsoft.Json; | using Newtonsoft.Json; | ||||
| using System; | using System; | ||||
| namespace Discord.Net.Converters | |||||
| namespace Discord.Serialization.JsonConverters | |||||
| { | { | ||||
| internal class PermissionTargetConverter : JsonConverter | internal class PermissionTargetConverter : JsonConverter | ||||
| { | { | ||||
| @@ -1,7 +1,7 @@ | |||||
| using Newtonsoft.Json; | using Newtonsoft.Json; | ||||
| using System; | using System; | ||||
| namespace Discord.Net.Converters | |||||
| namespace Discord.Serialization.JsonConverters | |||||
| { | { | ||||
| internal class StringEntityConverter : JsonConverter | internal class StringEntityConverter : JsonConverter | ||||
| { | { | ||||
| @@ -2,7 +2,7 @@ | |||||
| using System; | using System; | ||||
| using System.Globalization; | using System.Globalization; | ||||
| namespace Discord.Net.Converters | |||||
| namespace Discord.Serialization.JsonConverters | |||||
| { | { | ||||
| internal class UInt64Converter : JsonConverter | internal class UInt64Converter : JsonConverter | ||||
| { | { | ||||
| @@ -2,7 +2,7 @@ | |||||
| using System; | using System; | ||||
| using System.Globalization; | using System.Globalization; | ||||
| namespace Discord.Net.Converters | |||||
| namespace Discord.Serialization.JsonConverters | |||||
| { | { | ||||
| internal class UInt64EntityConverter : JsonConverter | internal class UInt64EntityConverter : JsonConverter | ||||
| { | { | ||||
| @@ -2,7 +2,7 @@ | |||||
| using Newtonsoft.Json; | using Newtonsoft.Json; | ||||
| using System; | using System; | ||||
| namespace Discord.Net.Converters | |||||
| namespace Discord.Serialization.JsonConverters | |||||
| { | { | ||||
| internal class UInt64EntityOrIdConverter<T> : JsonConverter | internal class UInt64EntityOrIdConverter<T> : JsonConverter | ||||
| { | { | ||||
| @@ -1,7 +1,7 @@ | |||||
| using Newtonsoft.Json; | using Newtonsoft.Json; | ||||
| using System; | using System; | ||||
| namespace Discord.Net.Converters | |||||
| namespace Discord.Serialization.JsonConverters | |||||
| { | { | ||||
| internal class UserStatusConverter : JsonConverter | internal class UserStatusConverter : JsonConverter | ||||
| { | { | ||||
| @@ -0,0 +1,80 @@ | |||||
| using Discord.Serialization.JsonConverters; | |||||
| using Newtonsoft.Json; | |||||
| using Newtonsoft.Json.Linq; | |||||
| using System; | |||||
| using System.IO; | |||||
| using System.Text; | |||||
| using System.Threading.Tasks; | |||||
| namespace Discord.Serialization | |||||
| { | |||||
| internal class Serializer | |||||
| { | |||||
| public static ScopedSerializer Global { get; } = new ScopedSerializer(); | |||||
| public static T FromJson<T>(Stream stream) => Global.FromJson<T>(stream); | |||||
| public static T FromJson<T>(StreamReader reader) => Global.FromJson<T>(reader); | |||||
| public static T FromJson<T>(JsonTextReader reader) => Global.FromJson<T>(reader); | |||||
| public static T FromJson<T>(JToken token) => Global.FromJson<T>(token); | |||||
| public static void ToJson<T>(Stream stream, T obj) => Global.ToJson(stream, obj); | |||||
| public static void ToJson<T>(StreamWriter writer, T obj) => Global.ToJson(writer, obj); | |||||
| public static void ToJson<T>(JsonTextWriter writer, T obj) => Global.ToJson(writer, obj); | |||||
| public static ScopedSerializer CreateScope() => new ScopedSerializer(); | |||||
| } | |||||
| internal class ScopedSerializer | |||||
| { | |||||
| private readonly JsonSerializer _serializer; | |||||
| private readonly AsyncEvent<Func<Exception, Task>> _errorEvent = new AsyncEvent<Func<Exception, Task>>(); | |||||
| public event Func<Exception, Task> Error | |||||
| { | |||||
| add { _errorEvent.Add(value); } | |||||
| remove { _errorEvent.Remove(value); } | |||||
| } | |||||
| internal ScopedSerializer() | |||||
| { | |||||
| _serializer = new JsonSerializer | |||||
| { | |||||
| DateFormatString = "yyyy-MM-ddTHH:mm:ssZ", | |||||
| ContractResolver = new DiscordContractResolver() | |||||
| }; | |||||
| _serializer.Error += (s, e) => | |||||
| { | |||||
| _errorEvent.InvokeAsync(e.ErrorContext.Error).GetAwaiter().GetResult(); | |||||
| e.ErrorContext.Handled = true; | |||||
| }; | |||||
| } | |||||
| public T FromJson<T>(Stream stream) | |||||
| { | |||||
| using (var reader = new StreamReader(stream, Encoding.UTF8, false, 1024, true)) //1KB buffer | |||||
| return FromJson<T>(reader); | |||||
| } | |||||
| public T FromJson<T>(TextReader reader) | |||||
| { | |||||
| using (var jsonReader = new JsonTextReader(reader) { CloseInput = false }) | |||||
| return FromJson<T>(jsonReader); | |||||
| } | |||||
| public T FromJson<T>(JsonTextReader reader) | |||||
| => _serializer.Deserialize<T>(reader); | |||||
| public T FromJson<T>(JToken token) | |||||
| => token.ToObject<T>(_serializer); | |||||
| public void ToJson<T>(Stream stream, T obj) | |||||
| { | |||||
| using (var writer = new StreamWriter(stream, Encoding.UTF8, 1024, false)) //1KB buffer | |||||
| ToJson(writer, obj); | |||||
| } | |||||
| public void ToJson<T>(TextWriter writer, T obj) | |||||
| { | |||||
| using (var jsonWriter = new JsonTextWriter(writer) { CloseOutput = false }) | |||||
| ToJson(jsonWriter, obj); | |||||
| } | |||||
| public void ToJson<T>(JsonTextWriter writer, T obj) | |||||
| => _serializer.Serialize(writer, obj); | |||||
| } | |||||
| } | |||||
| @@ -4,6 +4,7 @@ using Discord.Net.Queue; | |||||
| using Discord.Net.Rest; | using Discord.Net.Rest; | ||||
| using Discord.Net.WebSockets; | using Discord.Net.WebSockets; | ||||
| using Discord.Rpc; | using Discord.Rpc; | ||||
| using Discord.Serialization; | |||||
| using Newtonsoft.Json; | using Newtonsoft.Json; | ||||
| using Newtonsoft.Json.Linq; | using Newtonsoft.Json.Linq; | ||||
| using System; | using System; | ||||
| @@ -19,12 +20,13 @@ namespace Discord.API | |||||
| { | { | ||||
| internal class DiscordRpcApiClient : DiscordRestApiClient, IDisposable | internal class DiscordRpcApiClient : DiscordRestApiClient, IDisposable | ||||
| { | { | ||||
| private abstract class RpcRequest | |||||
| private interface IRpcRequest | |||||
| { | { | ||||
| public abstract Task SetResultAsync(JToken data, JsonSerializer serializer); | |||||
| public abstract Task SetExceptionAsync(JToken data, JsonSerializer serializer); | |||||
| Task SetResultAsync(JToken data); | |||||
| Task SetExceptionAsync(JToken data); | |||||
| } | } | ||||
| private class RpcRequest<T> : RpcRequest | |||||
| private class RpcRequest<T> : IRpcRequest | |||||
| { | { | ||||
| public TaskCompletionSource<T> Promise { get; set; } | public TaskCompletionSource<T> Promise { get; set; } | ||||
| @@ -37,13 +39,13 @@ namespace Discord.API | |||||
| Promise.TrySetCanceled(); //Doesn't need to be async, we're already in a separate task | Promise.TrySetCanceled(); //Doesn't need to be async, we're already in a separate task | ||||
| }); | }); | ||||
| } | } | ||||
| public override Task SetResultAsync(JToken data, JsonSerializer serializer) | |||||
| public Task SetResultAsync(JToken data) | |||||
| { | { | ||||
| return Promise.TrySetResultAsync(data.ToObject<T>(serializer)); | |||||
| return Promise.TrySetResultAsync(Serializer.FromJson<T>(data)); | |||||
| } | } | ||||
| public override Task SetExceptionAsync(JToken data, JsonSerializer serializer) | |||||
| public Task SetExceptionAsync(JToken data) | |||||
| { | { | ||||
| var error = data.ToObject<ErrorEvent>(serializer); | |||||
| var error = Serializer.FromJson<ErrorEvent>(data); | |||||
| return Promise.TrySetExceptionAsync(new RpcException(error.Code, error.Message)); | return Promise.TrySetExceptionAsync(new RpcException(error.Code, error.Message)); | ||||
| } | } | ||||
| } | } | ||||
| @@ -58,40 +60,46 @@ namespace Discord.API | |||||
| public event Func<Exception, Task> Disconnected { add { _disconnectedEvent.Add(value); } remove { _disconnectedEvent.Remove(value); } } | public event Func<Exception, Task> Disconnected { add { _disconnectedEvent.Add(value); } remove { _disconnectedEvent.Remove(value); } } | ||||
| private readonly AsyncEvent<Func<Exception, Task>> _disconnectedEvent = new AsyncEvent<Func<Exception, Task>>(); | private readonly AsyncEvent<Func<Exception, Task>> _disconnectedEvent = new AsyncEvent<Func<Exception, Task>>(); | ||||
| private readonly ConcurrentDictionary<Guid, RpcRequest> _requests; | |||||
| private readonly string _clientId; | |||||
| private readonly ConcurrentDictionary<Guid, IRpcRequest> _requests; | |||||
| private readonly IWebSocketClient _webSocketClient; | private readonly IWebSocketClient _webSocketClient; | ||||
| private readonly SemaphoreSlim _connectionLock; | private readonly SemaphoreSlim _connectionLock; | ||||
| private readonly string _clientId; | |||||
| private readonly MemoryStream _decompressionStream; | |||||
| private readonly StreamReader _decompressionReader; | |||||
| private CancellationTokenSource _stateCancelToken; | private CancellationTokenSource _stateCancelToken; | ||||
| private string _origin; | private string _origin; | ||||
| public ConnectionState ConnectionState { get; private set; } | public ConnectionState ConnectionState { get; private set; } | ||||
| public DiscordRpcApiClient(string clientId, string userAgent, string origin, RestClientProvider restClientProvider, WebSocketProvider webSocketProvider, | public DiscordRpcApiClient(string clientId, string userAgent, string origin, RestClientProvider restClientProvider, WebSocketProvider webSocketProvider, | ||||
| RetryMode defaultRetryMode = RetryMode.AlwaysRetry, JsonSerializer serializer = null) | |||||
| : base(restClientProvider, userAgent, defaultRetryMode, serializer) | |||||
| RetryMode defaultRetryMode = RetryMode.AlwaysRetry) | |||||
| : base(restClientProvider, userAgent, defaultRetryMode) | |||||
| { | { | ||||
| _connectionLock = new SemaphoreSlim(1, 1); | _connectionLock = new SemaphoreSlim(1, 1); | ||||
| _clientId = clientId; | _clientId = clientId; | ||||
| _origin = origin; | _origin = origin; | ||||
| _requests = new ConcurrentDictionary<Guid, RpcRequest>(); | |||||
| _requests = new ConcurrentDictionary<Guid, IRpcRequest>(); | |||||
| _decompressionStream = new MemoryStream(10 * 1024); //10 KB | |||||
| _decompressionReader = new StreamReader(_decompressionStream); | |||||
| _webSocketClient = webSocketProvider(); | _webSocketClient = webSocketProvider(); | ||||
| //_webSocketClient.SetHeader("user-agent", DiscordConfig.UserAgent); (Causes issues in .Net 4.6+) | //_webSocketClient.SetHeader("user-agent", DiscordConfig.UserAgent); (Causes issues in .Net 4.6+) | ||||
| _webSocketClient.SetHeader("origin", _origin); | _webSocketClient.SetHeader("origin", _origin); | ||||
| _webSocketClient.BinaryMessage += async (data, index, count) => | _webSocketClient.BinaryMessage += async (data, index, count) => | ||||
| { | { | ||||
| using (var compressed = new MemoryStream(data, index + 2, count - 2)) | using (var compressed = new MemoryStream(data, index + 2, count - 2)) | ||||
| using (var decompressed = new MemoryStream()) | |||||
| { | { | ||||
| _decompressionStream.Position = 0; | |||||
| using (var zlib = new DeflateStream(compressed, CompressionMode.Decompress)) | using (var zlib = new DeflateStream(compressed, CompressionMode.Decompress)) | ||||
| zlib.CopyTo(decompressed); | |||||
| decompressed.Position = 0; | |||||
| using (var reader = new StreamReader(decompressed)) | |||||
| using (var jsonReader = new JsonTextReader(reader)) | |||||
| zlib.CopyTo(_decompressionStream); | |||||
| _decompressionStream.SetLength(_decompressionStream.Position); | |||||
| _decompressionStream.Position = 0; | |||||
| var msg = _serializer.FromJson<API.Rpc.RpcFrame>(_decompressionReader); | |||||
| if (msg != null) | |||||
| { | { | ||||
| var msg = _serializer.Deserialize<API.Rpc.RpcFrame>(jsonReader); | |||||
| await _receivedRpcEvent.InvokeAsync(msg.Cmd, msg.Event, msg.Data).ConfigureAwait(false); | await _receivedRpcEvent.InvokeAsync(msg.Cmd, msg.Event, msg.Data).ConfigureAwait(false); | ||||
| if (msg.Nonce.IsSpecified && msg.Nonce.Value.HasValue) | if (msg.Nonce.IsSpecified && msg.Nonce.Value.HasValue) | ||||
| ProcessMessage(msg); | ProcessMessage(msg); | ||||
| @@ -101,12 +109,14 @@ namespace Discord.API | |||||
| _webSocketClient.TextMessage += async text => | _webSocketClient.TextMessage += async text => | ||||
| { | { | ||||
| using (var reader = new StringReader(text)) | using (var reader = new StringReader(text)) | ||||
| using (var jsonReader = new JsonTextReader(reader)) | |||||
| { | { | ||||
| var msg = _serializer.Deserialize<API.Rpc.RpcFrame>(jsonReader); | |||||
| await _receivedRpcEvent.InvokeAsync(msg.Cmd, msg.Event, msg.Data).ConfigureAwait(false); | |||||
| if (msg.Nonce.IsSpecified && msg.Nonce.Value.HasValue) | |||||
| ProcessMessage(msg); | |||||
| var msg = _serializer.FromJson<API.Rpc.RpcFrame>(reader); | |||||
| if (msg != null) | |||||
| { | |||||
| await _receivedRpcEvent.InvokeAsync(msg.Cmd, msg.Event, msg.Data).ConfigureAwait(false); | |||||
| if (msg.Nonce.IsSpecified && msg.Nonce.Value.HasValue) | |||||
| ProcessMessage(msg); | |||||
| } | |||||
| } | } | ||||
| }; | }; | ||||
| _webSocketClient.Closed += async ex => | _webSocketClient.Closed += async ex => | ||||
| @@ -219,7 +229,7 @@ namespace Discord.API | |||||
| payload = new API.Rpc.RpcFrame { Cmd = cmd, Event = evt, Args = payload, Nonce = guid }; | payload = new API.Rpc.RpcFrame { Cmd = cmd, Event = evt, Args = payload, Nonce = guid }; | ||||
| if (payload != null) | if (payload != null) | ||||
| { | { | ||||
| var json = SerializeJson(payload); | |||||
| string json = SerializeJson(payload); | |||||
| bytes = Encoding.UTF8.GetBytes(json); | bytes = Encoding.UTF8.GetBytes(json); | ||||
| } | } | ||||
| @@ -249,7 +259,7 @@ namespace Discord.API | |||||
| { | { | ||||
| ClientId = _clientId, | ClientId = _clientId, | ||||
| Scopes = scopes, | Scopes = scopes, | ||||
| RpcToken = rpcToken != null ? rpcToken : Optional.Create<string>() | |||||
| RpcToken = rpcToken ?? Optional.Create<string>() | |||||
| }; | }; | ||||
| if (options.Timeout == null) | if (options.Timeout == null) | ||||
| options.Timeout = 60000; //This requires manual input on the user's end, lets give them more time | options.Timeout = 60000; //This requires manual input on the user's end, lets give them more time | ||||
| @@ -378,15 +388,15 @@ namespace Discord.API | |||||
| private bool ProcessMessage(API.Rpc.RpcFrame msg) | private bool ProcessMessage(API.Rpc.RpcFrame msg) | ||||
| { | { | ||||
| if (_requests.TryGetValue(msg.Nonce.Value.Value, out RpcRequest requestTracker)) | |||||
| if (_requests.TryGetValue(msg.Nonce.Value.Value, out IRpcRequest requestTracker)) | |||||
| { | { | ||||
| if (msg.Event.GetValueOrDefault("") == "ERROR") | if (msg.Event.GetValueOrDefault("") == "ERROR") | ||||
| { | { | ||||
| var _ = requestTracker.SetExceptionAsync(msg.Data.GetValueOrDefault() as JToken, _serializer); | |||||
| var _ = requestTracker.SetExceptionAsync(msg.Data.GetValueOrDefault() as JToken); | |||||
| } | } | ||||
| else | else | ||||
| { | { | ||||
| var _ = requestTracker.SetResultAsync(msg.Data.GetValueOrDefault() as JToken, _serializer); | |||||
| var _ = requestTracker.SetResultAsync(msg.Data.GetValueOrDefault() as JToken); | |||||
| } | } | ||||
| return true; | return true; | ||||
| } | } | ||||
| @@ -1,8 +1,6 @@ | |||||
| using Discord.API.Rpc; | using Discord.API.Rpc; | ||||
| using Discord.Logging; | using Discord.Logging; | ||||
| using Discord.Net.Converters; | |||||
| using Discord.Rest; | using Discord.Rest; | ||||
| using Newtonsoft.Json; | |||||
| using Newtonsoft.Json.Linq; | using Newtonsoft.Json.Linq; | ||||
| using System; | using System; | ||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||
| @@ -10,22 +8,23 @@ using System.Collections.Immutable; | |||||
| using System.Linq; | using System.Linq; | ||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
| using System.Threading; | using System.Threading; | ||||
| using Discord.Serialization; | |||||
| namespace Discord.Rpc | namespace Discord.Rpc | ||||
| { | { | ||||
| public partial class DiscordRpcClient : BaseDiscordClient, IDiscordClient | public partial class DiscordRpcClient : BaseDiscordClient, IDiscordClient | ||||
| { | { | ||||
| private readonly JsonSerializer _serializer; | |||||
| private readonly ConnectionManager _connection; | private readonly ConnectionManager _connection; | ||||
| private readonly Logger _rpcLogger; | private readonly Logger _rpcLogger; | ||||
| private readonly SemaphoreSlim _stateLock, _authorizeLock; | private readonly SemaphoreSlim _stateLock, _authorizeLock; | ||||
| private readonly ScopedSerializer _serializer; | |||||
| public ConnectionState ConnectionState { get; private set; } | public ConnectionState ConnectionState { get; private set; } | ||||
| public IReadOnlyCollection<string> Scopes { get; private set; } | public IReadOnlyCollection<string> Scopes { get; private set; } | ||||
| public DateTimeOffset TokenExpiresAt { get; private set; } | public DateTimeOffset TokenExpiresAt { get; private set; } | ||||
| internal new API.DiscordRpcApiClient ApiClient => base.ApiClient as API.DiscordRpcApiClient; | internal new API.DiscordRpcApiClient ApiClient => base.ApiClient as API.DiscordRpcApiClient; | ||||
| public new RestSelfUser CurrentUser { get { return base.CurrentUser as RestSelfUser; } private set { base.CurrentUser = value; } } | |||||
| public new RestSelfUser CurrentUser { get => base.CurrentUser as RestSelfUser; private set => base.CurrentUser = value; } | |||||
| public RestApplication ApplicationInfo { get; private set; } | public RestApplication ApplicationInfo { get; private set; } | ||||
| /// <summary> Creates a new RPC discord client. </summary> | /// <summary> Creates a new RPC discord client. </summary> | ||||
| @@ -38,18 +37,18 @@ namespace Discord.Rpc | |||||
| _stateLock = new SemaphoreSlim(1, 1); | _stateLock = new SemaphoreSlim(1, 1); | ||||
| _authorizeLock = new SemaphoreSlim(1, 1); | _authorizeLock = new SemaphoreSlim(1, 1); | ||||
| _rpcLogger = LogManager.CreateLogger("RPC"); | _rpcLogger = LogManager.CreateLogger("RPC"); | ||||
| _serializer = Serializer.CreateScope(); | |||||
| _serializer.Error += async ex => | |||||
| { | |||||
| await _rpcLogger.WarningAsync("Serializer Error", ex); | |||||
| }; | |||||
| _connection = new ConnectionManager(_stateLock, _rpcLogger, config.ConnectionTimeout, | _connection = new ConnectionManager(_stateLock, _rpcLogger, config.ConnectionTimeout, | ||||
| OnConnectingAsync, OnDisconnectingAsync, x => ApiClient.Disconnected += x); | OnConnectingAsync, OnDisconnectingAsync, x => ApiClient.Disconnected += x); | ||||
| _connection.Connected += () => _connectedEvent.InvokeAsync(); | _connection.Connected += () => _connectedEvent.InvokeAsync(); | ||||
| _connection.Disconnected += (ex, recon) => _disconnectedEvent.InvokeAsync(ex); | _connection.Disconnected += (ex, recon) => _disconnectedEvent.InvokeAsync(ex); | ||||
| _serializer = new JsonSerializer { ContractResolver = new DiscordContractResolver() }; | |||||
| _serializer.Error += (s, e) => | |||||
| { | |||||
| _rpcLogger.WarningAsync(e.ErrorContext.Error).GetAwaiter().GetResult(); | |||||
| e.ErrorContext.Handled = true; | |||||
| }; | |||||
| ApiClient.SentRpcMessage += async opCode => await _rpcLogger.DebugAsync($"Sent {opCode}").ConfigureAwait(false); | ApiClient.SentRpcMessage += async opCode => await _rpcLogger.DebugAsync($"Sent {opCode}").ConfigureAwait(false); | ||||
| ApiClient.ReceivedRpcEvent += ProcessMessageAsync; | ApiClient.ReceivedRpcEvent += ProcessMessageAsync; | ||||
| } | } | ||||
| @@ -185,10 +184,12 @@ namespace Discord.Rpc | |||||
| { | { | ||||
| if (func == null) throw new NullReferenceException(nameof(func)); | if (func == null) throw new NullReferenceException(nameof(func)); | ||||
| var settings = new VoiceProperties(); | |||||
| settings.Input = new VoiceDeviceProperties(); | |||||
| settings.Output = new VoiceDeviceProperties(); | |||||
| settings.Mode = new VoiceModeProperties(); | |||||
| var settings = new VoiceProperties | |||||
| { | |||||
| Input = new VoiceDeviceProperties(), | |||||
| Output = new VoiceDeviceProperties(), | |||||
| Mode = new VoiceModeProperties() | |||||
| }; | |||||
| func(settings); | func(settings); | ||||
| var model = new API.Rpc.VoiceSettings | var model = new API.Rpc.VoiceSettings | ||||
| @@ -294,9 +295,9 @@ namespace Discord.Rpc | |||||
| case "READY": | case "READY": | ||||
| { | { | ||||
| await _rpcLogger.DebugAsync("Received Dispatch (READY)").ConfigureAwait(false); | await _rpcLogger.DebugAsync("Received Dispatch (READY)").ConfigureAwait(false); | ||||
| var data = (payload.Value as JToken).ToObject<ReadyEvent>(_serializer); | |||||
| var data = _serializer.FromJson<ReadyEvent>(payload.Value as JToken); | |||||
| RequestOptions options = new RequestOptions | |||||
| var options = new RequestOptions | |||||
| { | { | ||||
| //CancellationToken = _cancelToken //TODO: Implement | //CancellationToken = _cancelToken //TODO: Implement | ||||
| }; | }; | ||||
| @@ -336,7 +337,7 @@ namespace Discord.Rpc | |||||
| case "CHANNEL_CREATE": | case "CHANNEL_CREATE": | ||||
| { | { | ||||
| await _rpcLogger.DebugAsync("Received Dispatch (CHANNEL_CREATE)").ConfigureAwait(false); | await _rpcLogger.DebugAsync("Received Dispatch (CHANNEL_CREATE)").ConfigureAwait(false); | ||||
| var data = (payload.Value as JToken).ToObject<ChannelSummary>(_serializer); | |||||
| var data = _serializer.FromJson<ChannelSummary>(payload.Value as JToken); | |||||
| var channel = RpcChannelSummary.Create(data); | var channel = RpcChannelSummary.Create(data); | ||||
| await _channelCreatedEvent.InvokeAsync(channel).ConfigureAwait(false); | await _channelCreatedEvent.InvokeAsync(channel).ConfigureAwait(false); | ||||
| @@ -347,7 +348,7 @@ namespace Discord.Rpc | |||||
| case "GUILD_CREATE": | case "GUILD_CREATE": | ||||
| { | { | ||||
| await _rpcLogger.DebugAsync("Received Dispatch (GUILD_CREATE)").ConfigureAwait(false); | await _rpcLogger.DebugAsync("Received Dispatch (GUILD_CREATE)").ConfigureAwait(false); | ||||
| var data = (payload.Value as JToken).ToObject<GuildSummary>(_serializer); | |||||
| var data = _serializer.FromJson<GuildSummary>(payload.Value as JToken); | |||||
| var guild = RpcGuildSummary.Create(data); | var guild = RpcGuildSummary.Create(data); | ||||
| await _guildCreatedEvent.InvokeAsync(guild).ConfigureAwait(false); | await _guildCreatedEvent.InvokeAsync(guild).ConfigureAwait(false); | ||||
| @@ -356,7 +357,7 @@ namespace Discord.Rpc | |||||
| case "GUILD_STATUS": | case "GUILD_STATUS": | ||||
| { | { | ||||
| await _rpcLogger.DebugAsync("Received Dispatch (GUILD_STATUS)").ConfigureAwait(false); | await _rpcLogger.DebugAsync("Received Dispatch (GUILD_STATUS)").ConfigureAwait(false); | ||||
| var data = (payload.Value as JToken).ToObject<GuildStatusEvent>(_serializer); | |||||
| var data = _serializer.FromJson<GuildStatusEvent>(payload.Value as JToken); | |||||
| var guildStatus = RpcGuildStatus.Create(data); | var guildStatus = RpcGuildStatus.Create(data); | ||||
| await _guildStatusUpdatedEvent.InvokeAsync(guildStatus).ConfigureAwait(false); | await _guildStatusUpdatedEvent.InvokeAsync(guildStatus).ConfigureAwait(false); | ||||
| @@ -367,7 +368,7 @@ namespace Discord.Rpc | |||||
| case "VOICE_STATE_CREATE": | case "VOICE_STATE_CREATE": | ||||
| { | { | ||||
| await _rpcLogger.DebugAsync("Received Dispatch (VOICE_STATE_CREATE)").ConfigureAwait(false); | await _rpcLogger.DebugAsync("Received Dispatch (VOICE_STATE_CREATE)").ConfigureAwait(false); | ||||
| var data = (payload.Value as JToken).ToObject<ExtendedVoiceState>(_serializer); | |||||
| var data = _serializer.FromJson<ExtendedVoiceState>(payload.Value as JToken); | |||||
| var voiceState = RpcVoiceState.Create(this, data); | var voiceState = RpcVoiceState.Create(this, data); | ||||
| await _voiceStateCreatedEvent.InvokeAsync(voiceState).ConfigureAwait(false); | await _voiceStateCreatedEvent.InvokeAsync(voiceState).ConfigureAwait(false); | ||||
| @@ -376,7 +377,7 @@ namespace Discord.Rpc | |||||
| case "VOICE_STATE_UPDATE": | case "VOICE_STATE_UPDATE": | ||||
| { | { | ||||
| await _rpcLogger.DebugAsync("Received Dispatch (VOICE_STATE_UPDATE)").ConfigureAwait(false); | await _rpcLogger.DebugAsync("Received Dispatch (VOICE_STATE_UPDATE)").ConfigureAwait(false); | ||||
| var data = (payload.Value as JToken).ToObject<ExtendedVoiceState>(_serializer); | |||||
| var data = _serializer.FromJson<ExtendedVoiceState>(payload.Value as JToken); | |||||
| var voiceState = RpcVoiceState.Create(this, data); | var voiceState = RpcVoiceState.Create(this, data); | ||||
| await _voiceStateUpdatedEvent.InvokeAsync(voiceState).ConfigureAwait(false); | await _voiceStateUpdatedEvent.InvokeAsync(voiceState).ConfigureAwait(false); | ||||
| @@ -385,7 +386,7 @@ namespace Discord.Rpc | |||||
| case "VOICE_STATE_DELETE": | case "VOICE_STATE_DELETE": | ||||
| { | { | ||||
| await _rpcLogger.DebugAsync("Received Dispatch (VOICE_STATE_DELETE)").ConfigureAwait(false); | await _rpcLogger.DebugAsync("Received Dispatch (VOICE_STATE_DELETE)").ConfigureAwait(false); | ||||
| var data = (payload.Value as JToken).ToObject<ExtendedVoiceState>(_serializer); | |||||
| var data = _serializer.FromJson<ExtendedVoiceState>(payload.Value as JToken); | |||||
| var voiceState = RpcVoiceState.Create(this, data); | var voiceState = RpcVoiceState.Create(this, data); | ||||
| await _voiceStateDeletedEvent.InvokeAsync(voiceState).ConfigureAwait(false); | await _voiceStateDeletedEvent.InvokeAsync(voiceState).ConfigureAwait(false); | ||||
| @@ -395,7 +396,7 @@ namespace Discord.Rpc | |||||
| case "SPEAKING_START": | case "SPEAKING_START": | ||||
| { | { | ||||
| await _rpcLogger.DebugAsync("Received Dispatch (SPEAKING_START)").ConfigureAwait(false); | await _rpcLogger.DebugAsync("Received Dispatch (SPEAKING_START)").ConfigureAwait(false); | ||||
| var data = (payload.Value as JToken).ToObject<SpeakingEvent>(_serializer); | |||||
| var data = _serializer.FromJson<SpeakingEvent>(payload.Value as JToken); | |||||
| await _speakingStartedEvent.InvokeAsync(data.UserId).ConfigureAwait(false); | await _speakingStartedEvent.InvokeAsync(data.UserId).ConfigureAwait(false); | ||||
| } | } | ||||
| @@ -403,7 +404,7 @@ namespace Discord.Rpc | |||||
| case "SPEAKING_STOP": | case "SPEAKING_STOP": | ||||
| { | { | ||||
| await _rpcLogger.DebugAsync("Received Dispatch (SPEAKING_STOP)").ConfigureAwait(false); | await _rpcLogger.DebugAsync("Received Dispatch (SPEAKING_STOP)").ConfigureAwait(false); | ||||
| var data = (payload.Value as JToken).ToObject<SpeakingEvent>(_serializer); | |||||
| var data = _serializer.FromJson<SpeakingEvent>(payload.Value as JToken); | |||||
| await _speakingStoppedEvent.InvokeAsync(data.UserId).ConfigureAwait(false); | await _speakingStoppedEvent.InvokeAsync(data.UserId).ConfigureAwait(false); | ||||
| } | } | ||||
| @@ -411,7 +412,7 @@ namespace Discord.Rpc | |||||
| case "VOICE_SETTINGS_UPDATE": | case "VOICE_SETTINGS_UPDATE": | ||||
| { | { | ||||
| await _rpcLogger.DebugAsync("Received Dispatch (VOICE_SETTINGS_UPDATE)").ConfigureAwait(false); | await _rpcLogger.DebugAsync("Received Dispatch (VOICE_SETTINGS_UPDATE)").ConfigureAwait(false); | ||||
| var data = (payload.Value as JToken).ToObject<API.Rpc.VoiceSettings>(_serializer); | |||||
| var data = _serializer.FromJson<API.Rpc.VoiceSettings>(payload.Value as JToken); | |||||
| var settings = VoiceSettings.Create(data); | var settings = VoiceSettings.Create(data); | ||||
| await _voiceSettingsUpdated.InvokeAsync(settings).ConfigureAwait(false); | await _voiceSettingsUpdated.InvokeAsync(settings).ConfigureAwait(false); | ||||
| @@ -422,7 +423,7 @@ namespace Discord.Rpc | |||||
| case "MESSAGE_CREATE": | case "MESSAGE_CREATE": | ||||
| { | { | ||||
| await _rpcLogger.DebugAsync("Received Dispatch (MESSAGE_CREATE)").ConfigureAwait(false); | await _rpcLogger.DebugAsync("Received Dispatch (MESSAGE_CREATE)").ConfigureAwait(false); | ||||
| var data = (payload.Value as JToken).ToObject<MessageEvent>(_serializer); | |||||
| var data = _serializer.FromJson<MessageEvent>(payload.Value as JToken); | |||||
| var msg = RpcMessage.Create(this, data.ChannelId, data.Message); | var msg = RpcMessage.Create(this, data.ChannelId, data.Message); | ||||
| await _messageReceivedEvent.InvokeAsync(msg).ConfigureAwait(false); | await _messageReceivedEvent.InvokeAsync(msg).ConfigureAwait(false); | ||||
| @@ -431,7 +432,7 @@ namespace Discord.Rpc | |||||
| case "MESSAGE_UPDATE": | case "MESSAGE_UPDATE": | ||||
| { | { | ||||
| await _rpcLogger.DebugAsync("Received Dispatch (MESSAGE_UPDATE)").ConfigureAwait(false); | await _rpcLogger.DebugAsync("Received Dispatch (MESSAGE_UPDATE)").ConfigureAwait(false); | ||||
| var data = (payload.Value as JToken).ToObject<MessageEvent>(_serializer); | |||||
| var data = _serializer.FromJson<MessageEvent>(payload.Value as JToken); | |||||
| var msg = RpcMessage.Create(this, data.ChannelId, data.Message); | var msg = RpcMessage.Create(this, data.ChannelId, data.Message); | ||||
| await _messageUpdatedEvent.InvokeAsync(msg).ConfigureAwait(false); | await _messageUpdatedEvent.InvokeAsync(msg).ConfigureAwait(false); | ||||
| @@ -440,7 +441,7 @@ namespace Discord.Rpc | |||||
| case "MESSAGE_DELETE": | case "MESSAGE_DELETE": | ||||
| { | { | ||||
| await _rpcLogger.DebugAsync("Received Dispatch (MESSAGE_DELETE)").ConfigureAwait(false); | await _rpcLogger.DebugAsync("Received Dispatch (MESSAGE_DELETE)").ConfigureAwait(false); | ||||
| var data = (payload.Value as JToken).ToObject<MessageEvent>(_serializer); | |||||
| var data = _serializer.FromJson<MessageEvent>(payload.Value as JToken); | |||||
| await _messageDeletedEvent.InvokeAsync(data.ChannelId, data.Message.Id).ConfigureAwait(false); | await _messageDeletedEvent.InvokeAsync(data.ChannelId, data.Message.Id).ConfigureAwait(false); | ||||
| } | } | ||||
| @@ -1,9 +1,7 @@ | |||||
| using Discord.API.Voice; | using Discord.API.Voice; | ||||
| using Discord.Audio.Streams; | using Discord.Audio.Streams; | ||||
| using Discord.Logging; | using Discord.Logging; | ||||
| using Discord.Net.Converters; | |||||
| using Discord.WebSocket; | using Discord.WebSocket; | ||||
| using Newtonsoft.Json; | |||||
| using Newtonsoft.Json.Linq; | using Newtonsoft.Json.Linq; | ||||
| using System; | using System; | ||||
| using System.Collections.Concurrent; | using System.Collections.Concurrent; | ||||
| @@ -13,6 +11,7 @@ using System.Threading; | |||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||
| using System.Runtime.InteropServices; | using System.Runtime.InteropServices; | ||||
| using Discord.Serialization; | |||||
| namespace Discord.Audio | namespace Discord.Audio | ||||
| { | { | ||||
| @@ -32,7 +31,7 @@ namespace Discord.Audio | |||||
| } | } | ||||
| private readonly Logger _audioLogger; | private readonly Logger _audioLogger; | ||||
| private readonly JsonSerializer _serializer; | |||||
| private readonly ScopedSerializer _serializer; | |||||
| private readonly ConnectionManager _connection; | private readonly ConnectionManager _connection; | ||||
| private readonly SemaphoreSlim _stateLock; | private readonly SemaphoreSlim _stateLock; | ||||
| private readonly ConcurrentQueue<long> _heartbeatTimes; | private readonly ConcurrentQueue<long> _heartbeatTimes; | ||||
| @@ -68,7 +67,13 @@ namespace Discord.Audio | |||||
| ChannelId = channelId; | ChannelId = channelId; | ||||
| _audioLogger = Discord.LogManager.CreateLogger($"Audio #{clientId}"); | _audioLogger = Discord.LogManager.CreateLogger($"Audio #{clientId}"); | ||||
| ApiClient = new DiscordVoiceAPIClient(guild.Id, Discord.WebSocketProvider, Discord.UdpSocketProvider); | |||||
| _serializer = Serializer.CreateScope(); | |||||
| _serializer.Error += async ex => | |||||
| { | |||||
| await _audioLogger.WarningAsync("Serializer Error", ex); | |||||
| }; | |||||
| ApiClient = new DiscordVoiceAPIClient(guild.Id, Discord.WebSocketProvider, Discord.UdpSocketProvider, _serializer); | |||||
| ApiClient.SentGatewayMessage += async opCode => await _audioLogger.DebugAsync($"Sent {opCode}").ConfigureAwait(false); | ApiClient.SentGatewayMessage += async opCode => await _audioLogger.DebugAsync($"Sent {opCode}").ConfigureAwait(false); | ||||
| ApiClient.SentDiscovery += async () => await _audioLogger.DebugAsync($"Sent Discovery").ConfigureAwait(false); | ApiClient.SentDiscovery += async () => await _audioLogger.DebugAsync($"Sent Discovery").ConfigureAwait(false); | ||||
| //ApiClient.SentData += async bytes => await _audioLogger.DebugAsync($"Sent {bytes} Bytes").ConfigureAwait(false); | //ApiClient.SentData += async bytes => await _audioLogger.DebugAsync($"Sent {bytes} Bytes").ConfigureAwait(false); | ||||
| @@ -85,13 +90,6 @@ namespace Discord.Audio | |||||
| _ssrcMap = new ConcurrentDictionary<uint, ulong>(); | _ssrcMap = new ConcurrentDictionary<uint, ulong>(); | ||||
| _streams = new ConcurrentDictionary<ulong, StreamPair>(); | _streams = new ConcurrentDictionary<ulong, StreamPair>(); | ||||
| _frameBuffers = new ConcurrentQueue<byte[]>(); | _frameBuffers = new ConcurrentQueue<byte[]>(); | ||||
| _serializer = new JsonSerializer { ContractResolver = new DiscordContractResolver() }; | |||||
| _serializer.Error += (s, e) => | |||||
| { | |||||
| _audioLogger.WarningAsync(e.ErrorContext.Error).GetAwaiter().GetResult(); | |||||
| e.ErrorContext.Handled = true; | |||||
| }; | |||||
| LatencyUpdated += async (old, val) => await _audioLogger.DebugAsync($"Latency = {val} ms").ConfigureAwait(false); | LatencyUpdated += async (old, val) => await _audioLogger.DebugAsync($"Latency = {val} ms").ConfigureAwait(false); | ||||
| UdpLatencyUpdated += async (old, val) => await _audioLogger.DebugAsync($"UDP Latency = {val} ms").ConfigureAwait(false); | UdpLatencyUpdated += async (old, val) => await _audioLogger.DebugAsync($"UDP Latency = {val} ms").ConfigureAwait(false); | ||||
| @@ -239,7 +237,7 @@ namespace Discord.Audio | |||||
| case VoiceOpCode.Ready: | case VoiceOpCode.Ready: | ||||
| { | { | ||||
| await _audioLogger.DebugAsync("Received Ready").ConfigureAwait(false); | await _audioLogger.DebugAsync("Received Ready").ConfigureAwait(false); | ||||
| var data = (payload as JToken).ToObject<ReadyEvent>(_serializer); | |||||
| var data = _serializer.FromJson<ReadyEvent>(payload as JToken); | |||||
| _ssrc = data.SSRC; | _ssrc = data.SSRC; | ||||
| @@ -255,7 +253,7 @@ namespace Discord.Audio | |||||
| case VoiceOpCode.SessionDescription: | case VoiceOpCode.SessionDescription: | ||||
| { | { | ||||
| await _audioLogger.DebugAsync("Received SessionDescription").ConfigureAwait(false); | await _audioLogger.DebugAsync("Received SessionDescription").ConfigureAwait(false); | ||||
| var data = (payload as JToken).ToObject<SessionDescriptionEvent>(_serializer); | |||||
| var data = _serializer.FromJson<SessionDescriptionEvent>(payload as JToken); | |||||
| if (data.Mode != DiscordVoiceAPIClient.Mode) | if (data.Mode != DiscordVoiceAPIClient.Mode) | ||||
| throw new InvalidOperationException($"Discord selected an unexpected mode: {data.Mode}"); | throw new InvalidOperationException($"Discord selected an unexpected mode: {data.Mode}"); | ||||
| @@ -291,7 +289,7 @@ namespace Discord.Audio | |||||
| { | { | ||||
| await _audioLogger.DebugAsync("Received Speaking").ConfigureAwait(false); | await _audioLogger.DebugAsync("Received Speaking").ConfigureAwait(false); | ||||
| var data = (payload as JToken).ToObject<SpeakingEvent>(_serializer); | |||||
| var data = _serializer.FromJson<SpeakingEvent>(payload as JToken); | |||||
| _ssrcMap[data.Ssrc] = data.UserId; //TODO: Memory Leak: SSRCs are never cleaned up | _ssrcMap[data.Ssrc] = data.UserId; //TODO: Memory Leak: SSRCs are never cleaned up | ||||
| await _speakingUpdatedEvent.InvokeAsync(data.UserId, data.Speaking); | await _speakingUpdatedEvent.InvokeAsync(data.UserId, data.Speaking); | ||||
| @@ -6,6 +6,7 @@ using System.IO; | |||||
| using System.Linq; | using System.Linq; | ||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
| using System.Threading; | using System.Threading; | ||||
| using Discord.Serialization; | |||||
| namespace Discord.WebSocket | namespace Discord.WebSocket | ||||
| { | { | ||||
| @@ -13,6 +14,8 @@ namespace Discord.WebSocket | |||||
| { | { | ||||
| private readonly DiscordSocketConfig _baseConfig; | private readonly DiscordSocketConfig _baseConfig; | ||||
| private readonly SemaphoreSlim _connectionGroupLock; | private readonly SemaphoreSlim _connectionGroupLock; | ||||
| private readonly ScopedSerializer _serializer; | |||||
| private int[] _shardIds; | private int[] _shardIds; | ||||
| private Dictionary<int, int> _shardIdsToIndex; | private Dictionary<int, int> _shardIdsToIndex; | ||||
| private DiscordSocketClient[] _shards; | private DiscordSocketClient[] _shards; | ||||
| @@ -25,7 +28,7 @@ namespace Discord.WebSocket | |||||
| public Game? Game => _shards[0].Game; | public Game? Game => _shards[0].Game; | ||||
| internal new DiscordSocketApiClient ApiClient => base.ApiClient as DiscordSocketApiClient; | internal new DiscordSocketApiClient ApiClient => base.ApiClient as DiscordSocketApiClient; | ||||
| public new SocketSelfUser CurrentUser { get { return base.CurrentUser as SocketSelfUser; } private set { base.CurrentUser = value; } } | |||||
| public new SocketSelfUser CurrentUser { get => base.CurrentUser as SocketSelfUser; private set => base.CurrentUser = value; } | |||||
| public IReadOnlyCollection<SocketGuild> Guilds => GetGuilds().ToReadOnlyCollection(() => GetGuildCount()); | public IReadOnlyCollection<SocketGuild> Guilds => GetGuilds().ToReadOnlyCollection(() => GetGuildCount()); | ||||
| public IReadOnlyCollection<ISocketPrivateChannel> PrivateChannels => GetPrivateChannels().ToReadOnlyCollection(() => GetPrivateChannelCount()); | public IReadOnlyCollection<ISocketPrivateChannel> PrivateChannels => GetPrivateChannels().ToReadOnlyCollection(() => GetPrivateChannelCount()); | ||||
| public IReadOnlyCollection<DiscordSocketClient> Shards => _shards; | public IReadOnlyCollection<DiscordSocketClient> Shards => _shards; | ||||
| @@ -34,13 +37,12 @@ namespace Discord.WebSocket | |||||
| /// <summary> Creates a new REST/WebSocket discord client. </summary> | /// <summary> Creates a new REST/WebSocket discord client. </summary> | ||||
| public DiscordShardedClient() : this(null, new DiscordSocketConfig()) { } | public DiscordShardedClient() : this(null, new DiscordSocketConfig()) { } | ||||
| /// <summary> Creates a new REST/WebSocket discord client. </summary> | /// <summary> Creates a new REST/WebSocket discord client. </summary> | ||||
| public DiscordShardedClient(DiscordSocketConfig config) : this(null, config, CreateApiClient(config)) { } | |||||
| public DiscordShardedClient(DiscordSocketConfig config) : this(null, config) { } | |||||
| /// <summary> Creates a new REST/WebSocket discord client. </summary> | /// <summary> Creates a new REST/WebSocket discord client. </summary> | ||||
| public DiscordShardedClient(int[] ids) : this(ids, new DiscordSocketConfig()) { } | public DiscordShardedClient(int[] ids) : this(ids, new DiscordSocketConfig()) { } | ||||
| /// <summary> Creates a new REST/WebSocket discord client. </summary> | /// <summary> Creates a new REST/WebSocket discord client. </summary> | ||||
| public DiscordShardedClient(int[] ids, DiscordSocketConfig config) : this(ids, config, CreateApiClient(config)) { } | |||||
| private DiscordShardedClient(int[] ids, DiscordSocketConfig config, API.DiscordSocketApiClient client) | |||||
| : base(config, client) | |||||
| public DiscordShardedClient(int[] ids, DiscordSocketConfig config) | |||||
| : base(config) | |||||
| { | { | ||||
| if (config.ShardId != null) | if (config.ShardId != null) | ||||
| throw new ArgumentException($"{nameof(config.ShardId)} must not be set."); | throw new ArgumentException($"{nameof(config.ShardId)} must not be set."); | ||||
| @@ -52,6 +54,14 @@ namespace Discord.WebSocket | |||||
| _baseConfig = config; | _baseConfig = config; | ||||
| _connectionGroupLock = new SemaphoreSlim(1, 1); | _connectionGroupLock = new SemaphoreSlim(1, 1); | ||||
| _serializer = Serializer.CreateScope(); | |||||
| _serializer.Error += async ex => | |||||
| { | |||||
| await _restLogger.WarningAsync("Serializer Error", ex); | |||||
| }; | |||||
| SetApiClient(new API.DiscordSocketApiClient(config.RestClientProvider, config.WebSocketProvider, DiscordConfig.UserAgent, _serializer)); | |||||
| if (config.TotalShards == null) | if (config.TotalShards == null) | ||||
| _automaticShards = true; | _automaticShards = true; | ||||
| else | else | ||||
| @@ -69,8 +79,6 @@ namespace Discord.WebSocket | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| private static API.DiscordSocketApiClient CreateApiClient(DiscordSocketConfig config) | |||||
| => new API.DiscordSocketApiClient(config.RestClientProvider, config.WebSocketProvider, DiscordRestConfig.UserAgent); | |||||
| internal override async Task OnLoginAsync(TokenType tokenType, string token) | internal override async Task OnLoginAsync(TokenType tokenType, string token) | ||||
| { | { | ||||
| @@ -4,8 +4,8 @@ using Discord.API.Rest; | |||||
| using Discord.Net.Queue; | using Discord.Net.Queue; | ||||
| using Discord.Net.Rest; | using Discord.Net.Rest; | ||||
| using Discord.Net.WebSockets; | using Discord.Net.WebSockets; | ||||
| using Discord.Serialization; | |||||
| using Discord.WebSocket; | using Discord.WebSocket; | ||||
| using Newtonsoft.Json; | |||||
| using System; | using System; | ||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||
| using System.IO; | using System.IO; | ||||
| @@ -36,13 +36,14 @@ namespace Discord.API | |||||
| public ConnectionState ConnectionState { get; private set; } | public ConnectionState ConnectionState { get; private set; } | ||||
| public DiscordSocketApiClient(RestClientProvider restClientProvider, WebSocketProvider webSocketProvider, string userAgent, | |||||
| string url = null, RetryMode defaultRetryMode = RetryMode.AlwaysRetry, JsonSerializer serializer = null) | |||||
| : base(restClientProvider, userAgent, defaultRetryMode, serializer) | |||||
| public DiscordSocketApiClient(RestClientProvider restClientProvider, WebSocketProvider webSocketProvider, string userAgent, ScopedSerializer serializer, | |||||
| string url = null, RetryMode defaultRetryMode = RetryMode.AlwaysRetry) | |||||
| : base(restClientProvider, userAgent, serializer, defaultRetryMode) | |||||
| { | { | ||||
| _gatewayUrl = url; | _gatewayUrl = url; | ||||
| if (url != null) | if (url != null) | ||||
| _isExplicitUrl = true; | _isExplicitUrl = true; | ||||
| _decompressionStream = new MemoryStream(10 * 1024); //10 KB | _decompressionStream = new MemoryStream(10 * 1024); //10 KB | ||||
| _decompressionReader = new StreamReader(_decompressionStream); | _decompressionReader = new StreamReader(_decompressionStream); | ||||
| @@ -58,20 +59,16 @@ namespace Discord.API | |||||
| _decompressionStream.SetLength(_decompressionStream.Position); | _decompressionStream.SetLength(_decompressionStream.Position); | ||||
| _decompressionStream.Position = 0; | _decompressionStream.Position = 0; | ||||
| using (var jsonReader = new JsonTextReader(_decompressionReader) { CloseInput = false }) | |||||
| { | |||||
| var msg = _serializer.Deserialize<SocketFrame>(jsonReader); | |||||
| if (msg != null) | |||||
| await _receivedGatewayEvent.InvokeAsync((GatewayOpCode)msg.Operation, msg.Sequence, msg.Type, msg.Payload).ConfigureAwait(false); | |||||
| } | |||||
| var msg = _serializer.FromJson<SocketFrame>(_decompressionReader); | |||||
| if (msg != null) | |||||
| await _receivedGatewayEvent.InvokeAsync((GatewayOpCode)msg.Operation, msg.Sequence, msg.Type, msg.Payload).ConfigureAwait(false); | |||||
| } | } | ||||
| }; | }; | ||||
| WebSocketClient.TextMessage += async text => | WebSocketClient.TextMessage += async text => | ||||
| { | { | ||||
| using (var reader = new StringReader(text)) | using (var reader = new StringReader(text)) | ||||
| using (var jsonReader = new JsonTextReader(reader)) | |||||
| { | { | ||||
| var msg = _serializer.Deserialize<SocketFrame>(jsonReader); | |||||
| var msg = _serializer.FromJson<SocketFrame>(reader); | |||||
| if (msg != null) | if (msg != null) | ||||
| await _receivedGatewayEvent.InvokeAsync((GatewayOpCode)msg.Operation, msg.Sequence, msg.Type, msg.Payload).ConfigureAwait(false); | await _receivedGatewayEvent.InvokeAsync((GatewayOpCode)msg.Operation, msg.Sequence, msg.Type, msg.Payload).ConfigureAwait(false); | ||||
| } | } | ||||
| @@ -1,11 +1,10 @@ | |||||
| using Discord.API; | using Discord.API; | ||||
| using Discord.API.Gateway; | using Discord.API.Gateway; | ||||
| using Discord.Logging; | using Discord.Logging; | ||||
| using Discord.Net.Converters; | |||||
| using Discord.Net.Udp; | using Discord.Net.Udp; | ||||
| using Discord.Net.WebSockets; | using Discord.Net.WebSockets; | ||||
| using Discord.Rest; | using Discord.Rest; | ||||
| using Newtonsoft.Json; | |||||
| using Discord.Serialization; | |||||
| using Newtonsoft.Json.Linq; | using Newtonsoft.Json.Linq; | ||||
| using System; | using System; | ||||
| using System.Collections.Concurrent; | using System.Collections.Concurrent; | ||||
| @@ -22,13 +21,12 @@ namespace Discord.WebSocket | |||||
| public partial class DiscordSocketClient : BaseDiscordClient, IDiscordClient | public partial class DiscordSocketClient : BaseDiscordClient, IDiscordClient | ||||
| { | { | ||||
| private readonly ConcurrentQueue<ulong> _largeGuilds; | private readonly ConcurrentQueue<ulong> _largeGuilds; | ||||
| private readonly JsonSerializer _serializer; | |||||
| private readonly SemaphoreSlim _connectionGroupLock; | |||||
| private readonly SemaphoreSlim _connectionGroupLock, _stateLock; | |||||
| private readonly ScopedSerializer _serializer; | |||||
| private readonly DiscordSocketClient _parentClient; | private readonly DiscordSocketClient _parentClient; | ||||
| private readonly ConcurrentQueue<long> _heartbeatTimes; | private readonly ConcurrentQueue<long> _heartbeatTimes; | ||||
| private readonly ConnectionManager _connection; | private readonly ConnectionManager _connection; | ||||
| private readonly Logger _gatewayLogger; | private readonly Logger _gatewayLogger; | ||||
| private readonly SemaphoreSlim _stateLock; | |||||
| private string _sessionId; | private string _sessionId; | ||||
| private int _lastSeq; | private int _lastSeq; | ||||
| @@ -72,10 +70,9 @@ namespace Discord.WebSocket | |||||
| /// <summary> Creates a new REST/WebSocket discord client. </summary> | /// <summary> Creates a new REST/WebSocket discord client. </summary> | ||||
| public DiscordSocketClient() : this(new DiscordSocketConfig()) { } | public DiscordSocketClient() : this(new DiscordSocketConfig()) { } | ||||
| /// <summary> Creates a new REST/WebSocket discord client. </summary> | /// <summary> Creates a new REST/WebSocket discord client. </summary> | ||||
| public DiscordSocketClient(DiscordSocketConfig config) : this(config, CreateApiClient(config), null, null) { } | |||||
| internal DiscordSocketClient(DiscordSocketConfig config, SemaphoreSlim groupLock, DiscordSocketClient parentClient) : this(config, CreateApiClient(config), groupLock, parentClient) { } | |||||
| private DiscordSocketClient(DiscordSocketConfig config, API.DiscordSocketApiClient client, SemaphoreSlim groupLock, DiscordSocketClient parentClient) | |||||
| : base(config, client) | |||||
| public DiscordSocketClient(DiscordSocketConfig config) : this(config, null, null) { } | |||||
| internal DiscordSocketClient(DiscordSocketConfig config, SemaphoreSlim groupLock, DiscordSocketClient parentClient) | |||||
| : base(config) | |||||
| { | { | ||||
| ShardId = config.ShardId ?? 0; | ShardId = config.ShardId ?? 0; | ||||
| TotalShards = config.TotalShards ?? 1; | TotalShards = config.TotalShards ?? 1; | ||||
| @@ -87,9 +84,17 @@ namespace Discord.WebSocket | |||||
| HandlerTimeout = config.HandlerTimeout; | HandlerTimeout = config.HandlerTimeout; | ||||
| State = new ClientState(0, 0); | State = new ClientState(0, 0); | ||||
| _heartbeatTimes = new ConcurrentQueue<long>(); | _heartbeatTimes = new ConcurrentQueue<long>(); | ||||
| _stateLock = new SemaphoreSlim(1, 1); | _stateLock = new SemaphoreSlim(1, 1); | ||||
| _gatewayLogger = LogManager.CreateLogger(ShardId == 0 && TotalShards == 1 ? "Gateway" : $"Shard #{ShardId}"); | _gatewayLogger = LogManager.CreateLogger(ShardId == 0 && TotalShards == 1 ? "Gateway" : $"Shard #{ShardId}"); | ||||
| _serializer = Serializer.CreateScope(); | |||||
| _serializer.Error += async ex => | |||||
| { | |||||
| await _restLogger.WarningAsync("Serializer Error", ex); | |||||
| }; | |||||
| SetApiClient(new API.DiscordSocketApiClient(config.RestClientProvider, config.WebSocketProvider, DiscordConfig.UserAgent, _serializer, config.GatewayHost, config.DefaultRetryMode)); | |||||
| _connection = new ConnectionManager(_stateLock, _gatewayLogger, config.ConnectionTimeout, | _connection = new ConnectionManager(_stateLock, _gatewayLogger, config.ConnectionTimeout, | ||||
| OnConnectingAsync, OnDisconnectingAsync, x => ApiClient.Disconnected += x); | OnConnectingAsync, OnDisconnectingAsync, x => ApiClient.Disconnected += x); | ||||
| _connection.Connected += () => TimedInvokeAsync(_connectedEvent, nameof(Connected)); | _connection.Connected += () => TimedInvokeAsync(_connectedEvent, nameof(Connected)); | ||||
| @@ -98,13 +103,6 @@ namespace Discord.WebSocket | |||||
| _nextAudioId = 1; | _nextAudioId = 1; | ||||
| _connectionGroupLock = groupLock; | _connectionGroupLock = groupLock; | ||||
| _parentClient = parentClient; | _parentClient = parentClient; | ||||
| _serializer = new JsonSerializer { ContractResolver = new DiscordContractResolver() }; | |||||
| _serializer.Error += (s, e) => | |||||
| { | |||||
| _gatewayLogger.WarningAsync("Serializer Error", e.ErrorContext.Error).GetAwaiter().GetResult(); | |||||
| e.ErrorContext.Handled = true; | |||||
| }; | |||||
| ApiClient.SentGatewayMessage += async opCode => await _gatewayLogger.DebugAsync($"Sent {opCode}").ConfigureAwait(false); | ApiClient.SentGatewayMessage += async opCode => await _gatewayLogger.DebugAsync($"Sent {opCode}").ConfigureAwait(false); | ||||
| ApiClient.ReceivedGatewayEvent += ProcessMessageAsync; | ApiClient.ReceivedGatewayEvent += ProcessMessageAsync; | ||||
| @@ -127,8 +125,6 @@ namespace Discord.WebSocket | |||||
| _voiceRegions = ImmutableDictionary.Create<string, RestVoiceRegion>(); | _voiceRegions = ImmutableDictionary.Create<string, RestVoiceRegion>(); | ||||
| _largeGuilds = new ConcurrentQueue<ulong>(); | _largeGuilds = new ConcurrentQueue<ulong>(); | ||||
| } | } | ||||
| private static API.DiscordSocketApiClient CreateApiClient(DiscordSocketConfig config) | |||||
| => new API.DiscordSocketApiClient(config.RestClientProvider, config.WebSocketProvider, DiscordRestConfig.UserAgent, config.GatewayHost); | |||||
| internal override void Dispose(bool disposing) | internal override void Dispose(bool disposing) | ||||
| { | { | ||||
| if (disposing) | if (disposing) | ||||
| @@ -399,7 +395,7 @@ namespace Discord.WebSocket | |||||
| case GatewayOpCode.Hello: | case GatewayOpCode.Hello: | ||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Hello").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Hello").ConfigureAwait(false); | ||||
| var data = (payload as JToken).ToObject<HelloEvent>(_serializer); | |||||
| var data = _serializer.FromJson<HelloEvent>(payload as JToken); | |||||
| _heartbeatTask = RunHeartbeatAsync(data.HeartbeatInterval, _connection.CancelToken); | _heartbeatTask = RunHeartbeatAsync(data.HeartbeatInterval, _connection.CancelToken); | ||||
| } | } | ||||
| @@ -455,7 +451,7 @@ namespace Discord.WebSocket | |||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (READY)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (READY)").ConfigureAwait(false); | ||||
| var data = (payload as JToken).ToObject<ReadyEvent>(_serializer); | |||||
| var data = _serializer.FromJson<ReadyEvent>(payload as JToken); | |||||
| var state = new ClientState(data.Guilds.Length, data.PrivateChannels.Length); | var state = new ClientState(data.Guilds.Length, data.PrivateChannels.Length); | ||||
| var currentUser = SocketSelfUser.Create(this, state, data.User); | var currentUser = SocketSelfUser.Create(this, state, data.User); | ||||
| @@ -525,7 +521,7 @@ namespace Discord.WebSocket | |||||
| //Guilds | //Guilds | ||||
| case "GUILD_CREATE": | case "GUILD_CREATE": | ||||
| { | { | ||||
| var data = (payload as JToken).ToObject<ExtendedGuild>(_serializer); | |||||
| var data = _serializer.FromJson<ExtendedGuild>(payload as JToken); | |||||
| if (data.Unavailable == false) | if (data.Unavailable == false) | ||||
| { | { | ||||
| @@ -577,7 +573,7 @@ namespace Discord.WebSocket | |||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_UPDATE)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_UPDATE)").ConfigureAwait(false); | ||||
| var data = (payload as JToken).ToObject<API.Guild>(_serializer); | |||||
| var data = _serializer.FromJson<API.Guild>(payload as JToken); | |||||
| var guild = State.GetGuild(data.Id); | var guild = State.GetGuild(data.Id); | ||||
| if (guild != null) | if (guild != null) | ||||
| { | { | ||||
| @@ -596,7 +592,7 @@ namespace Discord.WebSocket | |||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_EMOJIS_UPDATE)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_EMOJIS_UPDATE)").ConfigureAwait(false); | ||||
| var data = (payload as JToken).ToObject<API.Gateway.GuildEmojiUpdateEvent>(_serializer); | |||||
| var data = _serializer.FromJson<API.Gateway.GuildEmojiUpdateEvent>(payload as JToken); | |||||
| var guild = State.GetGuild(data.GuildId); | var guild = State.GetGuild(data.GuildId); | ||||
| if (guild != null) | if (guild != null) | ||||
| { | { | ||||
| @@ -614,7 +610,7 @@ namespace Discord.WebSocket | |||||
| case "GUILD_SYNC": | case "GUILD_SYNC": | ||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_SYNC)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_SYNC)").ConfigureAwait(false); | ||||
| var data = (payload as JToken).ToObject<GuildSyncEvent>(_serializer); | |||||
| var data = _serializer.FromJson<GuildSyncEvent>(payload as JToken); | |||||
| var guild = State.GetGuild(data.Id); | var guild = State.GetGuild(data.Id); | ||||
| if (guild != null) | if (guild != null) | ||||
| { | { | ||||
| @@ -635,7 +631,7 @@ namespace Discord.WebSocket | |||||
| break; | break; | ||||
| case "GUILD_DELETE": | case "GUILD_DELETE": | ||||
| { | { | ||||
| var data = (payload as JToken).ToObject<ExtendedGuild>(_serializer); | |||||
| var data = _serializer.FromJson<ExtendedGuild>(payload as JToken); | |||||
| if (data.Unavailable == true) | if (data.Unavailable == true) | ||||
| { | { | ||||
| type = "GUILD_UNAVAILABLE"; | type = "GUILD_UNAVAILABLE"; | ||||
| @@ -677,7 +673,7 @@ namespace Discord.WebSocket | |||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (CHANNEL_CREATE)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (CHANNEL_CREATE)").ConfigureAwait(false); | ||||
| var data = (payload as JToken).ToObject<API.Channel>(_serializer); | |||||
| var data = _serializer.FromJson<API.Channel>(payload as JToken); | |||||
| SocketChannel channel = null; | SocketChannel channel = null; | ||||
| if (data.GuildId.IsSpecified) | if (data.GuildId.IsSpecified) | ||||
| { | { | ||||
| @@ -709,7 +705,7 @@ namespace Discord.WebSocket | |||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (CHANNEL_UPDATE)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (CHANNEL_UPDATE)").ConfigureAwait(false); | ||||
| var data = (payload as JToken).ToObject<API.Channel>(_serializer); | |||||
| var data = _serializer.FromJson<API.Channel>(payload as JToken); | |||||
| var channel = State.GetChannel(data.Id); | var channel = State.GetChannel(data.Id); | ||||
| if (channel != null) | if (channel != null) | ||||
| { | { | ||||
| @@ -737,7 +733,7 @@ namespace Discord.WebSocket | |||||
| await _gatewayLogger.DebugAsync("Received Dispatch (CHANNEL_DELETE)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (CHANNEL_DELETE)").ConfigureAwait(false); | ||||
| SocketChannel channel = null; | SocketChannel channel = null; | ||||
| var data = (payload as JToken).ToObject<API.Channel>(_serializer); | |||||
| var data = _serializer.FromJson<API.Channel>(payload as JToken); | |||||
| if (data.GuildId.IsSpecified) | if (data.GuildId.IsSpecified) | ||||
| { | { | ||||
| var guild = State.GetGuild(data.GuildId.Value); | var guild = State.GetGuild(data.GuildId.Value); | ||||
| @@ -775,7 +771,7 @@ namespace Discord.WebSocket | |||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_MEMBER_ADD)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_MEMBER_ADD)").ConfigureAwait(false); | ||||
| var data = (payload as JToken).ToObject<GuildMemberAddEvent>(_serializer); | |||||
| var data = _serializer.FromJson<GuildMemberAddEvent>(payload as JToken); | |||||
| var guild = State.GetGuild(data.GuildId); | var guild = State.GetGuild(data.GuildId); | ||||
| if (guild != null) | if (guild != null) | ||||
| { | { | ||||
| @@ -801,7 +797,7 @@ namespace Discord.WebSocket | |||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_MEMBER_UPDATE)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_MEMBER_UPDATE)").ConfigureAwait(false); | ||||
| var data = (payload as JToken).ToObject<GuildMemberUpdateEvent>(_serializer); | |||||
| var data = _serializer.FromJson<GuildMemberUpdateEvent>(payload as JToken); | |||||
| var guild = State.GetGuild(data.GuildId); | var guild = State.GetGuild(data.GuildId); | ||||
| if (guild != null) | if (guild != null) | ||||
| { | { | ||||
| @@ -839,7 +835,7 @@ namespace Discord.WebSocket | |||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_MEMBER_REMOVE)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_MEMBER_REMOVE)").ConfigureAwait(false); | ||||
| var data = (payload as JToken).ToObject<GuildMemberRemoveEvent>(_serializer); | |||||
| var data = _serializer.FromJson<GuildMemberRemoveEvent>(payload as JToken); | |||||
| var guild = State.GetGuild(data.GuildId); | var guild = State.GetGuild(data.GuildId); | ||||
| if (guild != null) | if (guild != null) | ||||
| { | { | ||||
| @@ -874,7 +870,7 @@ namespace Discord.WebSocket | |||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_MEMBERS_CHUNK)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_MEMBERS_CHUNK)").ConfigureAwait(false); | ||||
| var data = (payload as JToken).ToObject<GuildMembersChunkEvent>(_serializer); | |||||
| var data = _serializer.FromJson<GuildMembersChunkEvent>(payload as JToken); | |||||
| var guild = State.GetGuild(data.GuildId); | var guild = State.GetGuild(data.GuildId); | ||||
| if (guild != null) | if (guild != null) | ||||
| { | { | ||||
| @@ -898,7 +894,7 @@ namespace Discord.WebSocket | |||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (CHANNEL_RECIPIENT_ADD)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (CHANNEL_RECIPIENT_ADD)").ConfigureAwait(false); | ||||
| var data = (payload as JToken).ToObject<RecipientEvent>(_serializer); | |||||
| var data = _serializer.FromJson<RecipientEvent>(payload as JToken); | |||||
| if (State.GetChannel(data.ChannelId) is SocketGroupChannel channel) | if (State.GetChannel(data.ChannelId) is SocketGroupChannel channel) | ||||
| { | { | ||||
| var user = channel.GetOrAddUser(data.User); | var user = channel.GetOrAddUser(data.User); | ||||
| @@ -915,7 +911,7 @@ namespace Discord.WebSocket | |||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (CHANNEL_RECIPIENT_REMOVE)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (CHANNEL_RECIPIENT_REMOVE)").ConfigureAwait(false); | ||||
| var data = (payload as JToken).ToObject<RecipientEvent>(_serializer); | |||||
| var data = _serializer.FromJson<RecipientEvent>(payload as JToken); | |||||
| if (State.GetChannel(data.ChannelId) is SocketGroupChannel channel) | if (State.GetChannel(data.ChannelId) is SocketGroupChannel channel) | ||||
| { | { | ||||
| var user = channel.RemoveUser(data.User.Id); | var user = channel.RemoveUser(data.User.Id); | ||||
| @@ -940,7 +936,7 @@ namespace Discord.WebSocket | |||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_ROLE_CREATE)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_ROLE_CREATE)").ConfigureAwait(false); | ||||
| var data = (payload as JToken).ToObject<GuildRoleCreateEvent>(_serializer); | |||||
| var data = _serializer.FromJson<GuildRoleCreateEvent>(payload as JToken); | |||||
| var guild = State.GetGuild(data.GuildId); | var guild = State.GetGuild(data.GuildId); | ||||
| if (guild != null) | if (guild != null) | ||||
| { | { | ||||
| @@ -964,7 +960,7 @@ namespace Discord.WebSocket | |||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_ROLE_UPDATE)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_ROLE_UPDATE)").ConfigureAwait(false); | ||||
| var data = (payload as JToken).ToObject<GuildRoleUpdateEvent>(_serializer); | |||||
| var data = _serializer.FromJson<GuildRoleUpdateEvent>(payload as JToken); | |||||
| var guild = State.GetGuild(data.GuildId); | var guild = State.GetGuild(data.GuildId); | ||||
| if (guild != null) | if (guild != null) | ||||
| { | { | ||||
| @@ -999,7 +995,7 @@ namespace Discord.WebSocket | |||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_ROLE_DELETE)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_ROLE_DELETE)").ConfigureAwait(false); | ||||
| var data = (payload as JToken).ToObject<GuildRoleDeleteEvent>(_serializer); | |||||
| var data = _serializer.FromJson<GuildRoleDeleteEvent>(payload as JToken); | |||||
| var guild = State.GetGuild(data.GuildId); | var guild = State.GetGuild(data.GuildId); | ||||
| if (guild != null) | if (guild != null) | ||||
| { | { | ||||
| @@ -1033,7 +1029,7 @@ namespace Discord.WebSocket | |||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_BAN_ADD)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_BAN_ADD)").ConfigureAwait(false); | ||||
| var data = (payload as JToken).ToObject<GuildBanEvent>(_serializer); | |||||
| var data = _serializer.FromJson<GuildBanEvent>(payload as JToken); | |||||
| var guild = State.GetGuild(data.GuildId); | var guild = State.GetGuild(data.GuildId); | ||||
| if (guild != null) | if (guild != null) | ||||
| { | { | ||||
| @@ -1059,7 +1055,7 @@ namespace Discord.WebSocket | |||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_BAN_REMOVE)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_BAN_REMOVE)").ConfigureAwait(false); | ||||
| var data = (payload as JToken).ToObject<GuildBanEvent>(_serializer); | |||||
| var data = _serializer.FromJson<GuildBanEvent>(payload as JToken); | |||||
| var guild = State.GetGuild(data.GuildId); | var guild = State.GetGuild(data.GuildId); | ||||
| if (guild != null) | if (guild != null) | ||||
| { | { | ||||
| @@ -1087,7 +1083,7 @@ namespace Discord.WebSocket | |||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_CREATE)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_CREATE)").ConfigureAwait(false); | ||||
| var data = (payload as JToken).ToObject<API.Message>(_serializer); | |||||
| var data = _serializer.FromJson<API.Message>(payload as JToken); | |||||
| if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) | if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) | ||||
| { | { | ||||
| var guild = (channel as SocketGuildChannel)?.Guild; | var guild = (channel as SocketGuildChannel)?.Guild; | ||||
| @@ -1134,7 +1130,7 @@ namespace Discord.WebSocket | |||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_UPDATE)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_UPDATE)").ConfigureAwait(false); | ||||
| var data = (payload as JToken).ToObject<API.Message>(_serializer); | |||||
| var data = _serializer.FromJson<API.Message>(payload as JToken); | |||||
| if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) | if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) | ||||
| { | { | ||||
| var guild = (channel as SocketGuildChannel)?.Guild; | var guild = (channel as SocketGuildChannel)?.Guild; | ||||
| @@ -1181,7 +1177,7 @@ namespace Discord.WebSocket | |||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_DELETE)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_DELETE)").ConfigureAwait(false); | ||||
| var data = (payload as JToken).ToObject<API.Message>(_serializer); | |||||
| var data = _serializer.FromJson<API.Message>(payload as JToken); | |||||
| if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) | if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) | ||||
| { | { | ||||
| var guild = (channel as SocketGuildChannel)?.Guild; | var guild = (channel as SocketGuildChannel)?.Guild; | ||||
| @@ -1208,7 +1204,7 @@ namespace Discord.WebSocket | |||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_REACTION_ADD)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_REACTION_ADD)").ConfigureAwait(false); | ||||
| var data = (payload as JToken).ToObject<API.Gateway.Reaction>(_serializer); | |||||
| var data = _serializer.FromJson<API.Gateway.Reaction>(payload as JToken); | |||||
| if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) | if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) | ||||
| { | { | ||||
| var cachedMsg = channel.GetCachedMessage(data.MessageId) as SocketUserMessage; | var cachedMsg = channel.GetCachedMessage(data.MessageId) as SocketUserMessage; | ||||
| @@ -1232,7 +1228,7 @@ namespace Discord.WebSocket | |||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_REACTION_REMOVE)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_REACTION_REMOVE)").ConfigureAwait(false); | ||||
| var data = (payload as JToken).ToObject<API.Gateway.Reaction>(_serializer); | |||||
| var data = _serializer.FromJson<API.Gateway.Reaction>(payload as JToken); | |||||
| if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) | if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) | ||||
| { | { | ||||
| var cachedMsg = channel.GetCachedMessage(data.MessageId) as SocketUserMessage; | var cachedMsg = channel.GetCachedMessage(data.MessageId) as SocketUserMessage; | ||||
| @@ -1256,7 +1252,7 @@ namespace Discord.WebSocket | |||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_REACTION_REMOVE_ALL)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_REACTION_REMOVE_ALL)").ConfigureAwait(false); | ||||
| var data = (payload as JToken).ToObject<RemoveAllReactionsEvent>(_serializer); | |||||
| var data = _serializer.FromJson<RemoveAllReactionsEvent>(payload as JToken); | |||||
| if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) | if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) | ||||
| { | { | ||||
| var cachedMsg = channel.GetCachedMessage(data.MessageId) as SocketUserMessage; | var cachedMsg = channel.GetCachedMessage(data.MessageId) as SocketUserMessage; | ||||
| @@ -1278,7 +1274,7 @@ namespace Discord.WebSocket | |||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_DELETE_BULK)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_DELETE_BULK)").ConfigureAwait(false); | ||||
| var data = (payload as JToken).ToObject<MessageDeleteBulkEvent>(_serializer); | |||||
| var data = _serializer.FromJson<MessageDeleteBulkEvent>(payload as JToken); | |||||
| if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) | if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) | ||||
| { | { | ||||
| var guild = (channel as SocketGuildChannel)?.Guild; | var guild = (channel as SocketGuildChannel)?.Guild; | ||||
| @@ -1309,7 +1305,7 @@ namespace Discord.WebSocket | |||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (PRESENCE_UPDATE)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (PRESENCE_UPDATE)").ConfigureAwait(false); | ||||
| var data = (payload as JToken).ToObject<API.Presence>(_serializer); | |||||
| var data = _serializer.FromJson<API.Presence>(payload as JToken); | |||||
| if (data.GuildId.IsSpecified) | if (data.GuildId.IsSpecified) | ||||
| { | { | ||||
| @@ -1368,7 +1364,7 @@ namespace Discord.WebSocket | |||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (TYPING_START)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (TYPING_START)").ConfigureAwait(false); | ||||
| var data = (payload as JToken).ToObject<TypingStartEvent>(_serializer); | |||||
| var data = _serializer.FromJson<TypingStartEvent>(payload as JToken); | |||||
| if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) | if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) | ||||
| { | { | ||||
| var guild = (channel as SocketGuildChannel)?.Guild; | var guild = (channel as SocketGuildChannel)?.Guild; | ||||
| @@ -1390,7 +1386,7 @@ namespace Discord.WebSocket | |||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (USER_UPDATE)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (USER_UPDATE)").ConfigureAwait(false); | ||||
| var data = (payload as JToken).ToObject<API.User>(_serializer); | |||||
| var data = _serializer.FromJson<API.User>(payload as JToken); | |||||
| if (data.Id == CurrentUser.Id) | if (data.Id == CurrentUser.Id) | ||||
| { | { | ||||
| var before = CurrentUser.Clone(); | var before = CurrentUser.Clone(); | ||||
| @@ -1410,7 +1406,7 @@ namespace Discord.WebSocket | |||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (VOICE_STATE_UPDATE)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (VOICE_STATE_UPDATE)").ConfigureAwait(false); | ||||
| var data = (payload as JToken).ToObject<API.VoiceState>(_serializer); | |||||
| var data = _serializer.FromJson<API.VoiceState>(payload as JToken); | |||||
| SocketUser user; | SocketUser user; | ||||
| SocketVoiceState before, after; | SocketVoiceState before, after; | ||||
| if (data.GuildId != null) | if (data.GuildId != null) | ||||
| @@ -1482,7 +1478,7 @@ namespace Discord.WebSocket | |||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (VOICE_SERVER_UPDATE)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (VOICE_SERVER_UPDATE)").ConfigureAwait(false); | ||||
| var data = (payload as JToken).ToObject<VoiceServerUpdateEvent>(_serializer); | |||||
| var data = _serializer.FromJson<VoiceServerUpdateEvent>(payload as JToken); | |||||
| var guild = State.GetGuild(data.GuildId); | var guild = State.GetGuild(data.GuildId); | ||||
| if (guild != null) | if (guild != null) | ||||
| { | { | ||||
| @@ -1,9 +1,9 @@ | |||||
| #pragma warning disable CS1591 | #pragma warning disable CS1591 | ||||
| using Discord.API; | using Discord.API; | ||||
| using Discord.API.Voice; | using Discord.API.Voice; | ||||
| using Discord.Net.Converters; | |||||
| using Discord.Net.Udp; | using Discord.Net.Udp; | ||||
| using Discord.Net.WebSockets; | using Discord.Net.WebSockets; | ||||
| using Discord.Serialization; | |||||
| using Newtonsoft.Json; | using Newtonsoft.Json; | ||||
| using System; | using System; | ||||
| using System.Diagnostics; | using System.Diagnostics; | ||||
| @@ -37,7 +37,7 @@ namespace Discord.Audio | |||||
| public event Func<Exception, Task> Disconnected { add { _disconnectedEvent.Add(value); } remove { _disconnectedEvent.Remove(value); } } | public event Func<Exception, Task> Disconnected { add { _disconnectedEvent.Add(value); } remove { _disconnectedEvent.Remove(value); } } | ||||
| private readonly AsyncEvent<Func<Exception, Task>> _disconnectedEvent = new AsyncEvent<Func<Exception, Task>>(); | private readonly AsyncEvent<Func<Exception, Task>> _disconnectedEvent = new AsyncEvent<Func<Exception, Task>>(); | ||||
| private readonly JsonSerializer _serializer; | |||||
| private readonly ScopedSerializer _serializer; | |||||
| private readonly SemaphoreSlim _connectionLock; | private readonly SemaphoreSlim _connectionLock; | ||||
| private readonly MemoryStream _decompressionStream; | private readonly MemoryStream _decompressionStream; | ||||
| private readonly StreamReader _decompressionReader; | private readonly StreamReader _decompressionReader; | ||||
| @@ -52,12 +52,14 @@ namespace Discord.Audio | |||||
| public ushort UdpPort => _udp.Port; | public ushort UdpPort => _udp.Port; | ||||
| internal DiscordVoiceAPIClient(ulong guildId, WebSocketProvider webSocketProvider, UdpSocketProvider udpSocketProvider, JsonSerializer serializer = null) | |||||
| internal DiscordVoiceAPIClient(ulong guildId, WebSocketProvider webSocketProvider, UdpSocketProvider udpSocketProvider, ScopedSerializer serializer) | |||||
| { | { | ||||
| GuildId = guildId; | GuildId = guildId; | ||||
| _connectionLock = new SemaphoreSlim(1, 1); | _connectionLock = new SemaphoreSlim(1, 1); | ||||
| _udp = udpSocketProvider(); | _udp = udpSocketProvider(); | ||||
| _udp.ReceivedDatagram += (data, index, count) => _receivedPacketEvent.InvokeAsync(data, index, count); | _udp.ReceivedDatagram += (data, index, count) => _receivedPacketEvent.InvokeAsync(data, index, count); | ||||
| _serializer = serializer; | |||||
| _decompressionStream = new MemoryStream(10 * 1024); //10 KB | _decompressionStream = new MemoryStream(10 * 1024); //10 KB | ||||
| _decompressionReader = new StreamReader(_decompressionStream); | _decompressionReader = new StreamReader(_decompressionStream); | ||||
| @@ -70,23 +72,19 @@ namespace Discord.Audio | |||||
| _decompressionStream.Position = 0; | _decompressionStream.Position = 0; | ||||
| using (var zlib = new DeflateStream(compressed, CompressionMode.Decompress)) | using (var zlib = new DeflateStream(compressed, CompressionMode.Decompress)) | ||||
| zlib.CopyTo(_decompressionStream); | zlib.CopyTo(_decompressionStream); | ||||
| _decompressionStream.SetLength(_decompressionStream.Position); | |||||
| _decompressionStream.Position = 0; | _decompressionStream.Position = 0; | ||||
| using (var jsonReader = new JsonTextReader(_decompressionReader) { CloseInput = false }) | |||||
| { | |||||
| var msg = _serializer.Deserialize<SocketFrame>(jsonReader); | |||||
| if (msg != null) | |||||
| await _receivedEvent.InvokeAsync((VoiceOpCode)msg.Operation, msg.Payload).ConfigureAwait(false); | |||||
| } | |||||
| _decompressionStream.SetLength(0); | |||||
| var msg = _serializer.FromJson<SocketFrame>(_decompressionReader); | |||||
| if (msg != null) | |||||
| await _receivedEvent.InvokeAsync((VoiceOpCode)msg.Operation, msg.Payload).ConfigureAwait(false); | |||||
| } | } | ||||
| }; | }; | ||||
| WebSocketClient.TextMessage += async text => | WebSocketClient.TextMessage += async text => | ||||
| { | { | ||||
| using (var reader = new StringReader(text)) | using (var reader = new StringReader(text)) | ||||
| using (var jsonReader = new JsonTextReader(reader)) | |||||
| { | { | ||||
| var msg = _serializer.Deserialize<SocketFrame>(jsonReader); | |||||
| var msg = _serializer.FromJson<SocketFrame>(reader); | |||||
| if (msg != null) | if (msg != null) | ||||
| await _receivedEvent.InvokeAsync((VoiceOpCode)msg.Operation, msg.Payload).ConfigureAwait(false); | await _receivedEvent.InvokeAsync((VoiceOpCode)msg.Operation, msg.Payload).ConfigureAwait(false); | ||||
| } | } | ||||
| @@ -96,8 +94,6 @@ namespace Discord.Audio | |||||
| await DisconnectAsync().ConfigureAwait(false); | await DisconnectAsync().ConfigureAwait(false); | ||||
| await _disconnectedEvent.InvokeAsync(ex).ConfigureAwait(false); | await _disconnectedEvent.InvokeAsync(ex).ConfigureAwait(false); | ||||
| }; | }; | ||||
| _serializer = serializer ?? new JsonSerializer { ContractResolver = new DiscordContractResolver() }; | |||||
| } | } | ||||
| private void Dispose(bool disposing) | private void Dispose(bool disposing) | ||||
| { | { | ||||
| @@ -259,16 +255,14 @@ namespace Discord.Audio | |||||
| private string SerializeJson(object value) | private string SerializeJson(object value) | ||||
| { | { | ||||
| var sb = new StringBuilder(256); | var sb = new StringBuilder(256); | ||||
| using (TextWriter text = new StringWriter(sb, CultureInfo.InvariantCulture)) | |||||
| using (JsonWriter writer = new JsonTextWriter(text)) | |||||
| _serializer.Serialize(writer, value); | |||||
| using (TextWriter writer = new StringWriter(sb, CultureInfo.InvariantCulture)) | |||||
| _serializer.ToJson(writer, value); | |||||
| return sb.ToString(); | return sb.ToString(); | ||||
| } | } | ||||
| private T DeserializeJson<T>(Stream jsonStream) | private T DeserializeJson<T>(Stream jsonStream) | ||||
| { | { | ||||
| using (TextReader text = new StreamReader(jsonStream)) | |||||
| using (JsonReader reader = new JsonTextReader(text)) | |||||
| return _serializer.Deserialize<T>(reader); | |||||
| using (TextReader reader = new StreamReader(jsonStream)) | |||||
| return _serializer.FromJson<T>(reader); | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||