Browse Source

Abstracted serialization

voice-allocs
RogueException 8 years ago
parent
commit
c83b1bd321
24 changed files with 290 additions and 202 deletions
  1. +2
    -2
      src/Discord.Net.Core/Format.cs
  2. +6
    -4
      src/Discord.Net.Rest/BaseDiscordClient.cs
  3. +9
    -13
      src/Discord.Net.Rest/DiscordRestApiClient.cs
  4. +13
    -4
      src/Discord.Net.Rest/DiscordRestClient.cs
  5. +1
    -4
      src/Discord.Net.Rest/Net/DefaultRestClient.cs
  6. +1
    -1
      src/Discord.Net.Rest/Serialization/JsonConverters/ArrayConverter.cs
  7. +1
    -1
      src/Discord.Net.Rest/Serialization/JsonConverters/DiscordContractResolver.cs
  8. +1
    -1
      src/Discord.Net.Rest/Serialization/JsonConverters/ImageConverter.cs
  9. +1
    -1
      src/Discord.Net.Rest/Serialization/JsonConverters/NullableConverter.cs
  10. +1
    -1
      src/Discord.Net.Rest/Serialization/JsonConverters/OptionalConverter.cs
  11. +1
    -1
      src/Discord.Net.Rest/Serialization/JsonConverters/PermissionTargetConverter.cs
  12. +1
    -1
      src/Discord.Net.Rest/Serialization/JsonConverters/StringEntityConverter.cs
  13. +1
    -1
      src/Discord.Net.Rest/Serialization/JsonConverters/UInt64Converter.cs
  14. +1
    -1
      src/Discord.Net.Rest/Serialization/JsonConverters/UInt64EntityConverter.cs
  15. +1
    -1
      src/Discord.Net.Rest/Serialization/JsonConverters/UInt64EntityOrIdConverter.cs
  16. +1
    -1
      src/Discord.Net.Rest/Serialization/JsonConverters/UserStatusConverter.cs
  17. +80
    -0
      src/Discord.Net.Rest/Serialization/Serializer.cs
  18. +40
    -30
      src/Discord.Net.Rpc/DiscordRpcApiClient.cs
  19. +30
    -29
      src/Discord.Net.Rpc/DiscordRpcClient.cs
  20. +12
    -14
      src/Discord.Net.WebSocket/Audio/AudioClient.cs
  21. +15
    -7
      src/Discord.Net.WebSocket/DiscordShardedClient.cs
  22. +9
    -12
      src/Discord.Net.WebSocket/DiscordSocketApiClient.cs
  23. +48
    -52
      src/Discord.Net.WebSocket/DiscordSocketClient.cs
  24. +14
    -20
      src/Discord.Net.WebSocket/DiscordVoiceApiClient.cs

+ 2
- 2
src/Discord.Net.Core/Format.cs View File

@@ -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;
} }


+ 6
- 4
src/Discord.Net.Rest/BaseDiscordClient.cs View File

@@ -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)


+ 9
- 13
src/Discord.Net.Rest/DiscordRestApiClient.cs View File

@@ -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


+ 13
- 4
src/Discord.Net.Rest/DiscordRestClient.cs View File

@@ -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
- 4
src/Discord.Net.Rest/Net/DefaultRestClient.cs View File

@@ -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)
{ {


src/Discord.Net.Rest/Net/Converters/ArrayConverter.cs → src/Discord.Net.Rest/Serialization/JsonConverters/ArrayConverter.cs View File

@@ -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
{ {

src/Discord.Net.Rest/Net/Converters/DiscordContractResolver.cs → src/Discord.Net.Rest/Serialization/JsonConverters/DiscordContractResolver.cs View File

@@ -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
{ {

src/Discord.Net.Rest/Net/Converters/ImageConverter.cs → src/Discord.Net.Rest/Serialization/JsonConverters/ImageConverter.cs View File

@@ -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
{ {

src/Discord.Net.Rest/Net/Converters/NullableConverter.cs → src/Discord.Net.Rest/Serialization/JsonConverters/NullableConverter.cs View File

@@ -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

src/Discord.Net.Rest/Net/Converters/OptionalConverter.cs → src/Discord.Net.Rest/Serialization/JsonConverters/OptionalConverter.cs View File

@@ -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
{ {

src/Discord.Net.Rest/Net/Converters/PermissionTargetConverter.cs → src/Discord.Net.Rest/Serialization/JsonConverters/PermissionTargetConverter.cs View File

@@ -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
{ {

src/Discord.Net.Rest/Net/Converters/StringEntityConverter.cs → src/Discord.Net.Rest/Serialization/JsonConverters/StringEntityConverter.cs View File

@@ -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
{ {

src/Discord.Net.Rest/Net/Converters/UInt64Converter.cs → src/Discord.Net.Rest/Serialization/JsonConverters/UInt64Converter.cs View File

@@ -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
{ {

src/Discord.Net.Rest/Net/Converters/UInt64EntityConverter.cs → src/Discord.Net.Rest/Serialization/JsonConverters/UInt64EntityConverter.cs View File

@@ -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
{ {

src/Discord.Net.Rest/Net/Converters/UInt64EntityOrIdConverter.cs → src/Discord.Net.Rest/Serialization/JsonConverters/UInt64EntityOrIdConverter.cs View File

@@ -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
{ {

src/Discord.Net.Rest/Net/Converters/UserStatusConverter.cs → src/Discord.Net.Rest/Serialization/JsonConverters/UserStatusConverter.cs View File

@@ -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
{ {

+ 80
- 0
src/Discord.Net.Rest/Serialization/Serializer.cs View File

@@ -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);
}
}

+ 40
- 30
src/Discord.Net.Rpc/DiscordRpcApiClient.cs View File

@@ -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;
} }


+ 30
- 29
src/Discord.Net.Rpc/DiscordRpcClient.cs View File

@@ -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);
} }


+ 12
- 14
src/Discord.Net.WebSocket/Audio/AudioClient.cs View File

@@ -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);


+ 15
- 7
src/Discord.Net.WebSocket/DiscordShardedClient.cs View File

@@ -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)
{ {


+ 9
- 12
src/Discord.Net.WebSocket/DiscordSocketApiClient.cs View File

@@ -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);
} }


+ 48
- 52
src/Discord.Net.WebSocket/DiscordSocketClient.cs View File

@@ -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)
{ {


+ 14
- 20
src/Discord.Net.WebSocket/DiscordVoiceApiClient.cs View File

@@ -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);
} }
} }
} }

Loading…
Cancel
Save