@@ -3,7 +3,7 @@ | |||
public static class Format | |||
{ | |||
// Characters which need escaping | |||
private static string[] SensitiveCharacters = { "\\", "*", "_", "~", "`" }; | |||
private static string[] _sensitiveCharacters = { "\\", "*", "_", "~", "`" }; | |||
/// <summary> Returns a markdown-formatted string with bold formatting. </summary> | |||
public static string Bold(string text) => $"**{text}**"; | |||
@@ -26,7 +26,7 @@ | |||
/// <summary> Sanitizes the string, safely escaping any Markdown sequences. </summary> | |||
public static string Sanitize(string text) | |||
{ | |||
foreach (string unsafeChar in SensitiveCharacters) | |||
foreach (string unsafeChar in _sensitiveCharacters) | |||
text = text.Replace(unsafeChar, $"\\{unsafeChar}"); | |||
return text; | |||
} | |||
@@ -22,23 +22,25 @@ namespace Discord.Rest | |||
private readonly SemaphoreSlim _stateLock; | |||
private bool _isFirstLogin, _isDisposed; | |||
internal API.DiscordRestApiClient ApiClient { get; } | |||
internal LogManager LogManager { get; } | |||
internal API.DiscordRestApiClient ApiClient { get; private set; } | |||
public LoginState LoginState { get; private set; } | |||
public ISelfUser CurrentUser { get; protected set; } | |||
public TokenType TokenType => ApiClient.AuthTokenType; | |||
/// <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.Message += async msg => await _logEvent.InvokeAsync(msg).ConfigureAwait(false); | |||
_stateLock = new SemaphoreSlim(1, 1); | |||
_restLogger = LogManager.CreateLogger("Rest"); | |||
_isFirstLogin = config.DisplayInitialLog; | |||
} | |||
internal void SetApiClient(API.DiscordRestApiClient client) | |||
{ | |||
ApiClient = client; | |||
ApiClient.RequestQueue.RateLimitTriggered += async (id, info) => | |||
{ | |||
if (info == null) | |||
@@ -1,10 +1,9 @@ | |||
#pragma warning disable CS1591 | |||
using Discord.API.Rest; | |||
using Discord.Net; | |||
using Discord.Net.Converters; | |||
using Discord.Net.Queue; | |||
using Discord.Net.Rest; | |||
using Newtonsoft.Json; | |||
using Discord.Serialization; | |||
using System; | |||
using System.Collections.Concurrent; | |||
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); } } | |||
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 ScopedSerializer _serializer; | |||
private readonly RestClientProvider _restClientProvider; | |||
protected bool _isDisposed; | |||
@@ -45,13 +44,12 @@ namespace Discord.API | |||
internal IRestClient RestClient { get; private 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; | |||
UserAgent = userAgent; | |||
_serializer = serializer; | |||
DefaultRetryMode = defaultRetryMode; | |||
_serializer = serializer ?? new JsonSerializer { DateFormatString = "yyyy-MM-ddTHH:mm:ssZ", ContractResolver = new DiscordContractResolver() }; | |||
RequestQueue = new RequestQueue(); | |||
_stateLock = new SemaphoreSlim(1, 1); | |||
@@ -1159,16 +1157,14 @@ namespace Discord.API | |||
protected string SerializeJson(object value) | |||
{ | |||
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(); | |||
} | |||
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 | |||
@@ -1,4 +1,5 @@ | |||
using System.Collections.Generic; | |||
using Discord.Serialization; | |||
using System.Collections.Generic; | |||
using System.Collections.Immutable; | |||
using System.IO; | |||
using System.Threading.Tasks; | |||
@@ -7,15 +8,23 @@ namespace Discord.Rest | |||
{ | |||
public class DiscordRestClient : BaseDiscordClient, IDiscordClient | |||
{ | |||
private readonly ScopedSerializer _serializer; | |||
private RestApplication _applicationInfo; | |||
public new RestSelfUser CurrentUser => base.CurrentUser as RestSelfUser; | |||
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) | |||
{ | |||
if (disposing) | |||
@@ -1,5 +1,4 @@ | |||
using Newtonsoft.Json; | |||
using System; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Globalization; | |||
using System.IO; | |||
@@ -18,7 +17,6 @@ namespace Discord.Net.Rest | |||
private readonly HttpClient _client; | |||
private readonly string _baseUrl; | |||
private readonly JsonSerializer _errorDeserializer; | |||
private CancellationToken _cancelToken; | |||
private bool _isDisposed; | |||
@@ -35,7 +33,6 @@ namespace Discord.Net.Rest | |||
SetHeader("accept-encoding", "gzip, deflate"); | |||
_cancelToken = CancellationToken.None; | |||
_errorDeserializer = new JsonSerializer(); | |||
} | |||
private void Dispose(bool disposing) | |||
{ | |||
@@ -2,7 +2,7 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
namespace Discord.Net.Converters | |||
namespace Discord.Serialization.JsonConverters | |||
{ | |||
internal class ArrayConverter<T> : JsonConverter | |||
{ |
@@ -6,7 +6,7 @@ using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Reflection; | |||
namespace Discord.Net.Converters | |||
namespace Discord.Serialization.JsonConverters | |||
{ | |||
internal class DiscordContractResolver : DefaultContractResolver | |||
{ |
@@ -2,7 +2,7 @@ | |||
using System; | |||
using Model = Discord.API.Image; | |||
namespace Discord.Net.Converters | |||
namespace Discord.Serialization.JsonConverters | |||
{ | |||
internal class ImageConverter : JsonConverter | |||
{ |
@@ -1,7 +1,7 @@ | |||
using Newtonsoft.Json; | |||
using System; | |||
namespace Discord.Net.Converters | |||
namespace Discord.Serialization.JsonConverters | |||
{ | |||
internal class NullableConverter<T> : JsonConverter | |||
where T : struct |
@@ -1,7 +1,7 @@ | |||
using Newtonsoft.Json; | |||
using System; | |||
namespace Discord.Net.Converters | |||
namespace Discord.Serialization.JsonConverters | |||
{ | |||
internal class OptionalConverter<T> : JsonConverter | |||
{ |
@@ -1,7 +1,7 @@ | |||
using Newtonsoft.Json; | |||
using System; | |||
namespace Discord.Net.Converters | |||
namespace Discord.Serialization.JsonConverters | |||
{ | |||
internal class PermissionTargetConverter : JsonConverter | |||
{ |
@@ -1,7 +1,7 @@ | |||
using Newtonsoft.Json; | |||
using System; | |||
namespace Discord.Net.Converters | |||
namespace Discord.Serialization.JsonConverters | |||
{ | |||
internal class StringEntityConverter : JsonConverter | |||
{ |
@@ -2,7 +2,7 @@ | |||
using System; | |||
using System.Globalization; | |||
namespace Discord.Net.Converters | |||
namespace Discord.Serialization.JsonConverters | |||
{ | |||
internal class UInt64Converter : JsonConverter | |||
{ |
@@ -2,7 +2,7 @@ | |||
using System; | |||
using System.Globalization; | |||
namespace Discord.Net.Converters | |||
namespace Discord.Serialization.JsonConverters | |||
{ | |||
internal class UInt64EntityConverter : JsonConverter | |||
{ |
@@ -2,7 +2,7 @@ | |||
using Newtonsoft.Json; | |||
using System; | |||
namespace Discord.Net.Converters | |||
namespace Discord.Serialization.JsonConverters | |||
{ | |||
internal class UInt64EntityOrIdConverter<T> : JsonConverter | |||
{ |
@@ -1,7 +1,7 @@ | |||
using Newtonsoft.Json; | |||
using System; | |||
namespace Discord.Net.Converters | |||
namespace Discord.Serialization.JsonConverters | |||
{ | |||
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.WebSockets; | |||
using Discord.Rpc; | |||
using Discord.Serialization; | |||
using Newtonsoft.Json; | |||
using Newtonsoft.Json.Linq; | |||
using System; | |||
@@ -19,12 +20,13 @@ namespace Discord.API | |||
{ | |||
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; } | |||
@@ -37,13 +39,13 @@ namespace Discord.API | |||
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)); | |||
} | |||
} | |||
@@ -58,40 +60,46 @@ namespace Discord.API | |||
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 ConcurrentDictionary<Guid, RpcRequest> _requests; | |||
private readonly string _clientId; | |||
private readonly ConcurrentDictionary<Guid, IRpcRequest> _requests; | |||
private readonly IWebSocketClient _webSocketClient; | |||
private readonly SemaphoreSlim _connectionLock; | |||
private readonly string _clientId; | |||
private readonly MemoryStream _decompressionStream; | |||
private readonly StreamReader _decompressionReader; | |||
private CancellationTokenSource _stateCancelToken; | |||
private string _origin; | |||
public ConnectionState ConnectionState { get; private set; } | |||
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); | |||
_clientId = clientId; | |||
_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.SetHeader("user-agent", DiscordConfig.UserAgent); (Causes issues in .Net 4.6+) | |||
_webSocketClient.SetHeader("origin", _origin); | |||
_webSocketClient.BinaryMessage += async (data, index, count) => | |||
{ | |||
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)) | |||
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); | |||
if (msg.Nonce.IsSpecified && msg.Nonce.Value.HasValue) | |||
ProcessMessage(msg); | |||
@@ -101,12 +109,14 @@ namespace Discord.API | |||
_webSocketClient.TextMessage += async 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 => | |||
@@ -219,7 +229,7 @@ namespace Discord.API | |||
payload = new API.Rpc.RpcFrame { Cmd = cmd, Event = evt, Args = payload, Nonce = guid }; | |||
if (payload != null) | |||
{ | |||
var json = SerializeJson(payload); | |||
string json = SerializeJson(payload); | |||
bytes = Encoding.UTF8.GetBytes(json); | |||
} | |||
@@ -249,7 +259,7 @@ namespace Discord.API | |||
{ | |||
ClientId = _clientId, | |||
Scopes = scopes, | |||
RpcToken = rpcToken != null ? rpcToken : Optional.Create<string>() | |||
RpcToken = rpcToken ?? Optional.Create<string>() | |||
}; | |||
if (options.Timeout == null) | |||
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) | |||
{ | |||
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") | |||
{ | |||
var _ = requestTracker.SetExceptionAsync(msg.Data.GetValueOrDefault() as JToken, _serializer); | |||
var _ = requestTracker.SetExceptionAsync(msg.Data.GetValueOrDefault() as JToken); | |||
} | |||
else | |||
{ | |||
var _ = requestTracker.SetResultAsync(msg.Data.GetValueOrDefault() as JToken, _serializer); | |||
var _ = requestTracker.SetResultAsync(msg.Data.GetValueOrDefault() as JToken); | |||
} | |||
return true; | |||
} | |||
@@ -1,8 +1,6 @@ | |||
using Discord.API.Rpc; | |||
using Discord.Logging; | |||
using Discord.Net.Converters; | |||
using Discord.Rest; | |||
using Newtonsoft.Json; | |||
using Newtonsoft.Json.Linq; | |||
using System; | |||
using System.Collections.Generic; | |||
@@ -10,22 +8,23 @@ using System.Collections.Immutable; | |||
using System.Linq; | |||
using System.Threading.Tasks; | |||
using System.Threading; | |||
using Discord.Serialization; | |||
namespace Discord.Rpc | |||
{ | |||
public partial class DiscordRpcClient : BaseDiscordClient, IDiscordClient | |||
{ | |||
private readonly JsonSerializer _serializer; | |||
private readonly ConnectionManager _connection; | |||
private readonly Logger _rpcLogger; | |||
private readonly SemaphoreSlim _stateLock, _authorizeLock; | |||
private readonly ScopedSerializer _serializer; | |||
public ConnectionState ConnectionState { get; private set; } | |||
public IReadOnlyCollection<string> Scopes { get; private set; } | |||
public DateTimeOffset TokenExpiresAt { get; private set; } | |||
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; } | |||
/// <summary> Creates a new RPC discord client. </summary> | |||
@@ -38,18 +37,18 @@ namespace Discord.Rpc | |||
_stateLock = new SemaphoreSlim(1, 1); | |||
_authorizeLock = new SemaphoreSlim(1, 1); | |||
_rpcLogger = LogManager.CreateLogger("RPC"); | |||
_serializer = Serializer.CreateScope(); | |||
_serializer.Error += async ex => | |||
{ | |||
await _rpcLogger.WarningAsync("Serializer Error", ex); | |||
}; | |||
_connection = new ConnectionManager(_stateLock, _rpcLogger, config.ConnectionTimeout, | |||
OnConnectingAsync, OnDisconnectingAsync, x => ApiClient.Disconnected += x); | |||
_connection.Connected += () => _connectedEvent.InvokeAsync(); | |||
_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.ReceivedRpcEvent += ProcessMessageAsync; | |||
} | |||
@@ -185,10 +184,12 @@ namespace Discord.Rpc | |||
{ | |||
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); | |||
var model = new API.Rpc.VoiceSettings | |||
@@ -294,9 +295,9 @@ namespace Discord.Rpc | |||
case "READY": | |||
{ | |||
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 | |||
}; | |||
@@ -336,7 +337,7 @@ namespace Discord.Rpc | |||
case "CHANNEL_CREATE": | |||
{ | |||
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); | |||
await _channelCreatedEvent.InvokeAsync(channel).ConfigureAwait(false); | |||
@@ -347,7 +348,7 @@ namespace Discord.Rpc | |||
case "GUILD_CREATE": | |||
{ | |||
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); | |||
await _guildCreatedEvent.InvokeAsync(guild).ConfigureAwait(false); | |||
@@ -356,7 +357,7 @@ namespace Discord.Rpc | |||
case "GUILD_STATUS": | |||
{ | |||
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); | |||
await _guildStatusUpdatedEvent.InvokeAsync(guildStatus).ConfigureAwait(false); | |||
@@ -367,7 +368,7 @@ namespace Discord.Rpc | |||
case "VOICE_STATE_CREATE": | |||
{ | |||
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); | |||
await _voiceStateCreatedEvent.InvokeAsync(voiceState).ConfigureAwait(false); | |||
@@ -376,7 +377,7 @@ namespace Discord.Rpc | |||
case "VOICE_STATE_UPDATE": | |||
{ | |||
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); | |||
await _voiceStateUpdatedEvent.InvokeAsync(voiceState).ConfigureAwait(false); | |||
@@ -385,7 +386,7 @@ namespace Discord.Rpc | |||
case "VOICE_STATE_DELETE": | |||
{ | |||
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); | |||
await _voiceStateDeletedEvent.InvokeAsync(voiceState).ConfigureAwait(false); | |||
@@ -395,7 +396,7 @@ namespace Discord.Rpc | |||
case "SPEAKING_START": | |||
{ | |||
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); | |||
} | |||
@@ -403,7 +404,7 @@ namespace Discord.Rpc | |||
case "SPEAKING_STOP": | |||
{ | |||
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); | |||
} | |||
@@ -411,7 +412,7 @@ namespace Discord.Rpc | |||
case "VOICE_SETTINGS_UPDATE": | |||
{ | |||
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); | |||
await _voiceSettingsUpdated.InvokeAsync(settings).ConfigureAwait(false); | |||
@@ -422,7 +423,7 @@ namespace Discord.Rpc | |||
case "MESSAGE_CREATE": | |||
{ | |||
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); | |||
await _messageReceivedEvent.InvokeAsync(msg).ConfigureAwait(false); | |||
@@ -431,7 +432,7 @@ namespace Discord.Rpc | |||
case "MESSAGE_UPDATE": | |||
{ | |||
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); | |||
await _messageUpdatedEvent.InvokeAsync(msg).ConfigureAwait(false); | |||
@@ -440,7 +441,7 @@ namespace Discord.Rpc | |||
case "MESSAGE_DELETE": | |||
{ | |||
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); | |||
} | |||
@@ -1,9 +1,7 @@ | |||
using Discord.API.Voice; | |||
using Discord.Audio.Streams; | |||
using Discord.Logging; | |||
using Discord.Net.Converters; | |||
using Discord.WebSocket; | |||
using Newtonsoft.Json; | |||
using Newtonsoft.Json.Linq; | |||
using System; | |||
using System.Collections.Concurrent; | |||
@@ -13,6 +11,7 @@ using System.Threading; | |||
using System.Threading.Tasks; | |||
using System.Collections.Generic; | |||
using System.Runtime.InteropServices; | |||
using Discord.Serialization; | |||
namespace Discord.Audio | |||
{ | |||
@@ -32,7 +31,7 @@ namespace Discord.Audio | |||
} | |||
private readonly Logger _audioLogger; | |||
private readonly JsonSerializer _serializer; | |||
private readonly ScopedSerializer _serializer; | |||
private readonly ConnectionManager _connection; | |||
private readonly SemaphoreSlim _stateLock; | |||
private readonly ConcurrentQueue<long> _heartbeatTimes; | |||
@@ -68,7 +67,13 @@ namespace Discord.Audio | |||
ChannelId = channelId; | |||
_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.SentDiscovery += async () => await _audioLogger.DebugAsync($"Sent Discovery").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>(); | |||
_streams = new ConcurrentDictionary<ulong, StreamPair>(); | |||
_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); | |||
UdpLatencyUpdated += async (old, val) => await _audioLogger.DebugAsync($"UDP Latency = {val} ms").ConfigureAwait(false); | |||
@@ -239,7 +237,7 @@ namespace Discord.Audio | |||
case VoiceOpCode.Ready: | |||
{ | |||
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; | |||
@@ -255,7 +253,7 @@ namespace Discord.Audio | |||
case VoiceOpCode.SessionDescription: | |||
{ | |||
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) | |||
throw new InvalidOperationException($"Discord selected an unexpected mode: {data.Mode}"); | |||
@@ -291,7 +289,7 @@ namespace Discord.Audio | |||
{ | |||
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 | |||
await _speakingUpdatedEvent.InvokeAsync(data.UserId, data.Speaking); | |||
@@ -6,6 +6,7 @@ using System.IO; | |||
using System.Linq; | |||
using System.Threading.Tasks; | |||
using System.Threading; | |||
using Discord.Serialization; | |||
namespace Discord.WebSocket | |||
{ | |||
@@ -13,6 +14,8 @@ namespace Discord.WebSocket | |||
{ | |||
private readonly DiscordSocketConfig _baseConfig; | |||
private readonly SemaphoreSlim _connectionGroupLock; | |||
private readonly ScopedSerializer _serializer; | |||
private int[] _shardIds; | |||
private Dictionary<int, int> _shardIdsToIndex; | |||
private DiscordSocketClient[] _shards; | |||
@@ -25,7 +28,7 @@ namespace Discord.WebSocket | |||
public Game? Game => _shards[0].Game; | |||
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<ISocketPrivateChannel> PrivateChannels => GetPrivateChannels().ToReadOnlyCollection(() => GetPrivateChannelCount()); | |||
public IReadOnlyCollection<DiscordSocketClient> Shards => _shards; | |||
@@ -34,13 +37,12 @@ namespace Discord.WebSocket | |||
/// <summary> Creates a new REST/WebSocket discord client. </summary> | |||
public DiscordShardedClient() : this(null, new DiscordSocketConfig()) { } | |||
/// <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> | |||
public DiscordShardedClient(int[] ids) : this(ids, new DiscordSocketConfig()) { } | |||
/// <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) | |||
throw new ArgumentException($"{nameof(config.ShardId)} must not be set."); | |||
@@ -52,6 +54,14 @@ namespace Discord.WebSocket | |||
_baseConfig = config; | |||
_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) | |||
_automaticShards = true; | |||
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) | |||
{ | |||
@@ -4,8 +4,8 @@ using Discord.API.Rest; | |||
using Discord.Net.Queue; | |||
using Discord.Net.Rest; | |||
using Discord.Net.WebSockets; | |||
using Discord.Serialization; | |||
using Discord.WebSocket; | |||
using Newtonsoft.Json; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.IO; | |||
@@ -36,13 +36,14 @@ namespace Discord.API | |||
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; | |||
if (url != null) | |||
_isExplicitUrl = true; | |||
_decompressionStream = new MemoryStream(10 * 1024); //10 KB | |||
_decompressionReader = new StreamReader(_decompressionStream); | |||
@@ -58,20 +59,16 @@ namespace Discord.API | |||
_decompressionStream.SetLength(_decompressionStream.Position); | |||
_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 => | |||
{ | |||
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) | |||
await _receivedGatewayEvent.InvokeAsync((GatewayOpCode)msg.Operation, msg.Sequence, msg.Type, msg.Payload).ConfigureAwait(false); | |||
} | |||
@@ -1,11 +1,10 @@ | |||
using Discord.API; | |||
using Discord.API.Gateway; | |||
using Discord.Logging; | |||
using Discord.Net.Converters; | |||
using Discord.Net.Udp; | |||
using Discord.Net.WebSockets; | |||
using Discord.Rest; | |||
using Newtonsoft.Json; | |||
using Discord.Serialization; | |||
using Newtonsoft.Json.Linq; | |||
using System; | |||
using System.Collections.Concurrent; | |||
@@ -22,13 +21,12 @@ namespace Discord.WebSocket | |||
public partial class DiscordSocketClient : BaseDiscordClient, IDiscordClient | |||
{ | |||
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 ConcurrentQueue<long> _heartbeatTimes; | |||
private readonly ConnectionManager _connection; | |||
private readonly Logger _gatewayLogger; | |||
private readonly SemaphoreSlim _stateLock; | |||
private string _sessionId; | |||
private int _lastSeq; | |||
@@ -72,10 +70,9 @@ namespace Discord.WebSocket | |||
/// <summary> Creates a new REST/WebSocket discord client. </summary> | |||
public DiscordSocketClient() : this(new DiscordSocketConfig()) { } | |||
/// <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; | |||
TotalShards = config.TotalShards ?? 1; | |||
@@ -87,9 +84,17 @@ namespace Discord.WebSocket | |||
HandlerTimeout = config.HandlerTimeout; | |||
State = new ClientState(0, 0); | |||
_heartbeatTimes = new ConcurrentQueue<long>(); | |||
_stateLock = new SemaphoreSlim(1, 1); | |||
_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, | |||
OnConnectingAsync, OnDisconnectingAsync, x => ApiClient.Disconnected += x); | |||
_connection.Connected += () => TimedInvokeAsync(_connectedEvent, nameof(Connected)); | |||
@@ -98,13 +103,6 @@ namespace Discord.WebSocket | |||
_nextAudioId = 1; | |||
_connectionGroupLock = groupLock; | |||
_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.ReceivedGatewayEvent += ProcessMessageAsync; | |||
@@ -127,8 +125,6 @@ namespace Discord.WebSocket | |||
_voiceRegions = ImmutableDictionary.Create<string, RestVoiceRegion>(); | |||
_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) | |||
{ | |||
if (disposing) | |||
@@ -399,7 +395,7 @@ namespace Discord.WebSocket | |||
case GatewayOpCode.Hello: | |||
{ | |||
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); | |||
} | |||
@@ -455,7 +451,7 @@ namespace Discord.WebSocket | |||
{ | |||
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 currentUser = SocketSelfUser.Create(this, state, data.User); | |||
@@ -525,7 +521,7 @@ namespace Discord.WebSocket | |||
//Guilds | |||
case "GUILD_CREATE": | |||
{ | |||
var data = (payload as JToken).ToObject<ExtendedGuild>(_serializer); | |||
var data = _serializer.FromJson<ExtendedGuild>(payload as JToken); | |||
if (data.Unavailable == false) | |||
{ | |||
@@ -577,7 +573,7 @@ namespace Discord.WebSocket | |||
{ | |||
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); | |||
if (guild != null) | |||
{ | |||
@@ -596,7 +592,7 @@ namespace Discord.WebSocket | |||
{ | |||
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); | |||
if (guild != null) | |||
{ | |||
@@ -614,7 +610,7 @@ namespace Discord.WebSocket | |||
case "GUILD_SYNC": | |||
{ | |||
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); | |||
if (guild != null) | |||
{ | |||
@@ -635,7 +631,7 @@ namespace Discord.WebSocket | |||
break; | |||
case "GUILD_DELETE": | |||
{ | |||
var data = (payload as JToken).ToObject<ExtendedGuild>(_serializer); | |||
var data = _serializer.FromJson<ExtendedGuild>(payload as JToken); | |||
if (data.Unavailable == true) | |||
{ | |||
type = "GUILD_UNAVAILABLE"; | |||
@@ -677,7 +673,7 @@ namespace Discord.WebSocket | |||
{ | |||
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; | |||
if (data.GuildId.IsSpecified) | |||
{ | |||
@@ -709,7 +705,7 @@ namespace Discord.WebSocket | |||
{ | |||
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); | |||
if (channel != null) | |||
{ | |||
@@ -737,7 +733,7 @@ namespace Discord.WebSocket | |||
await _gatewayLogger.DebugAsync("Received Dispatch (CHANNEL_DELETE)").ConfigureAwait(false); | |||
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) | |||
{ | |||
var guild = State.GetGuild(data.GuildId.Value); | |||
@@ -775,7 +771,7 @@ namespace Discord.WebSocket | |||
{ | |||
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); | |||
if (guild != null) | |||
{ | |||
@@ -801,7 +797,7 @@ namespace Discord.WebSocket | |||
{ | |||
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); | |||
if (guild != null) | |||
{ | |||
@@ -839,7 +835,7 @@ namespace Discord.WebSocket | |||
{ | |||
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); | |||
if (guild != null) | |||
{ | |||
@@ -874,7 +870,7 @@ namespace Discord.WebSocket | |||
{ | |||
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); | |||
if (guild != null) | |||
{ | |||
@@ -898,7 +894,7 @@ namespace Discord.WebSocket | |||
{ | |||
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) | |||
{ | |||
var user = channel.GetOrAddUser(data.User); | |||
@@ -915,7 +911,7 @@ namespace Discord.WebSocket | |||
{ | |||
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) | |||
{ | |||
var user = channel.RemoveUser(data.User.Id); | |||
@@ -940,7 +936,7 @@ namespace Discord.WebSocket | |||
{ | |||
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); | |||
if (guild != null) | |||
{ | |||
@@ -964,7 +960,7 @@ namespace Discord.WebSocket | |||
{ | |||
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); | |||
if (guild != null) | |||
{ | |||
@@ -999,7 +995,7 @@ namespace Discord.WebSocket | |||
{ | |||
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); | |||
if (guild != null) | |||
{ | |||
@@ -1033,7 +1029,7 @@ namespace Discord.WebSocket | |||
{ | |||
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); | |||
if (guild != null) | |||
{ | |||
@@ -1059,7 +1055,7 @@ namespace Discord.WebSocket | |||
{ | |||
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); | |||
if (guild != null) | |||
{ | |||
@@ -1087,7 +1083,7 @@ namespace Discord.WebSocket | |||
{ | |||
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) | |||
{ | |||
var guild = (channel as SocketGuildChannel)?.Guild; | |||
@@ -1134,7 +1130,7 @@ namespace Discord.WebSocket | |||
{ | |||
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) | |||
{ | |||
var guild = (channel as SocketGuildChannel)?.Guild; | |||
@@ -1181,7 +1177,7 @@ namespace Discord.WebSocket | |||
{ | |||
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) | |||
{ | |||
var guild = (channel as SocketGuildChannel)?.Guild; | |||
@@ -1208,7 +1204,7 @@ namespace Discord.WebSocket | |||
{ | |||
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) | |||
{ | |||
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); | |||
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) | |||
{ | |||
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); | |||
var data = (payload as JToken).ToObject<RemoveAllReactionsEvent>(_serializer); | |||
var data = _serializer.FromJson<RemoveAllReactionsEvent>(payload as JToken); | |||
if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) | |||
{ | |||
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); | |||
var data = (payload as JToken).ToObject<MessageDeleteBulkEvent>(_serializer); | |||
var data = _serializer.FromJson<MessageDeleteBulkEvent>(payload as JToken); | |||
if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) | |||
{ | |||
var guild = (channel as SocketGuildChannel)?.Guild; | |||
@@ -1309,7 +1305,7 @@ namespace Discord.WebSocket | |||
{ | |||
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) | |||
{ | |||
@@ -1368,7 +1364,7 @@ namespace Discord.WebSocket | |||
{ | |||
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) | |||
{ | |||
var guild = (channel as SocketGuildChannel)?.Guild; | |||
@@ -1390,7 +1386,7 @@ namespace Discord.WebSocket | |||
{ | |||
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) | |||
{ | |||
var before = CurrentUser.Clone(); | |||
@@ -1410,7 +1406,7 @@ namespace Discord.WebSocket | |||
{ | |||
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; | |||
SocketVoiceState before, after; | |||
if (data.GuildId != null) | |||
@@ -1482,7 +1478,7 @@ namespace Discord.WebSocket | |||
{ | |||
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); | |||
if (guild != null) | |||
{ | |||
@@ -1,9 +1,9 @@ | |||
#pragma warning disable CS1591 | |||
using Discord.API; | |||
using Discord.API.Voice; | |||
using Discord.Net.Converters; | |||
using Discord.Net.Udp; | |||
using Discord.Net.WebSockets; | |||
using Discord.Serialization; | |||
using Newtonsoft.Json; | |||
using System; | |||
using System.Diagnostics; | |||
@@ -37,7 +37,7 @@ namespace Discord.Audio | |||
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 JsonSerializer _serializer; | |||
private readonly ScopedSerializer _serializer; | |||
private readonly SemaphoreSlim _connectionLock; | |||
private readonly MemoryStream _decompressionStream; | |||
private readonly StreamReader _decompressionReader; | |||
@@ -52,12 +52,14 @@ namespace Discord.Audio | |||
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; | |||
_connectionLock = new SemaphoreSlim(1, 1); | |||
_udp = udpSocketProvider(); | |||
_udp.ReceivedDatagram += (data, index, count) => _receivedPacketEvent.InvokeAsync(data, index, count); | |||
_serializer = serializer; | |||
_decompressionStream = new MemoryStream(10 * 1024); //10 KB | |||
_decompressionReader = new StreamReader(_decompressionStream); | |||
@@ -70,23 +72,19 @@ namespace Discord.Audio | |||
_decompressionStream.Position = 0; | |||
using (var zlib = new DeflateStream(compressed, CompressionMode.Decompress)) | |||
zlib.CopyTo(_decompressionStream); | |||
_decompressionStream.SetLength(_decompressionStream.Position); | |||
_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 => | |||
{ | |||
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) | |||
await _receivedEvent.InvokeAsync((VoiceOpCode)msg.Operation, msg.Payload).ConfigureAwait(false); | |||
} | |||
@@ -96,8 +94,6 @@ namespace Discord.Audio | |||
await DisconnectAsync().ConfigureAwait(false); | |||
await _disconnectedEvent.InvokeAsync(ex).ConfigureAwait(false); | |||
}; | |||
_serializer = serializer ?? new JsonSerializer { ContractResolver = new DiscordContractResolver() }; | |||
} | |||
private void Dispose(bool disposing) | |||
{ | |||
@@ -259,16 +255,14 @@ namespace Discord.Audio | |||
private string SerializeJson(object value) | |||
{ | |||
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(); | |||
} | |||
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); | |||
} | |||
} | |||
} |