@@ -110,6 +110,9 @@ | |||
<Compile Include="..\Discord.Net\Helpers\JsonHttpClient.cs"> | |||
<Link>Helpers\JsonHttpClient.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\Helpers\JsonHttpClient.Events.cs"> | |||
<Link>Helpers\JsonHttpClient.Events.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\HttpException.cs"> | |||
<Link>HttpException.cs</Link> | |||
</Compile> | |||
@@ -10,7 +10,10 @@ namespace Discord | |||
WebSocketRawInput, //TODO: Make Http instanced and add a rawoutput event | |||
WebSocketUnknownOpCode, | |||
WebSocketUnknownEvent, | |||
VoiceOutput | |||
XHRRawOutput, | |||
XHRTiming, | |||
VoiceInput, | |||
VoiceOutput, | |||
} | |||
public sealed class LogMessageEventArgs : EventArgs | |||
{ | |||
@@ -90,8 +90,6 @@ namespace Discord | |||
_blockEvent = new ManualResetEventSlim(true); | |||
_config = config ?? new DiscordClientConfig(); | |||
_rand = new Random(); | |||
_http = new JsonHttpClient(); | |||
_api = new DiscordAPI(_http); | |||
_serializer = new JsonSerializer(); | |||
#if TEST_RESPONSES | |||
@@ -367,7 +365,12 @@ namespace Discord | |||
} | |||
); | |||
_webSocket = new DiscordDataSocket(this, _config.ConnectionTimeout, _config.WebSocketInterval); | |||
_http = new JsonHttpClient(config.EnableDebug); | |||
_api = new DiscordAPI(_http); | |||
if (_config.EnableDebug) | |||
_http.OnDebugMessage += (s, e) => RaiseOnDebugMessage(e.Type, e.Message); | |||
_webSocket = new DiscordDataSocket(this, config.ConnectionTimeout, config.WebSocketInterval, config.EnableDebug); | |||
_webSocket.Connected += (s, e) => RaiseConnected(); | |||
_webSocket.Disconnected += async (s, e) => | |||
{ | |||
@@ -406,7 +409,7 @@ namespace Discord | |||
#if !DNXCORE50 | |||
if (_config.EnableVoice) | |||
{ | |||
_voiceWebSocket = new DiscordVoiceSocket(this, _config.VoiceConnectionTimeout, _config.WebSocketInterval); | |||
_voiceWebSocket = new DiscordVoiceSocket(this, _config.VoiceConnectionTimeout, _config.WebSocketInterval, config.EnableDebug); | |||
_voiceWebSocket.Connected += (s, e) => RaiseVoiceConnected(); | |||
_voiceWebSocket.Disconnected += async (s, e) => | |||
{ | |||
@@ -1,5 +1,4 @@ | |||
using Discord.API.Models; | |||
using Discord.Helpers; | |||
using Newtonsoft.Json; | |||
using Newtonsoft.Json.Linq; | |||
using System; | |||
@@ -13,8 +12,8 @@ namespace Discord | |||
{ | |||
private readonly ManualResetEventSlim _connectWaitOnLogin, _connectWaitOnLogin2; | |||
public DiscordDataSocket(DiscordClient client, int timeout, int interval) | |||
: base(client, timeout, interval) | |||
public DiscordDataSocket(DiscordClient client, int timeout, int interval, bool isDebug) | |||
: base(client, timeout, interval, isDebug) | |||
{ | |||
_connectWaitOnLogin = new ManualResetEventSlim(false); | |||
_connectWaitOnLogin2 = new ManualResetEventSlim(false); | |||
@@ -72,7 +71,8 @@ namespace Discord | |||
} | |||
break; | |||
default: | |||
RaiseOnDebugMessage(DebugMessageType.WebSocketUnknownOpCode, "Unknown DataSocket op: " + msg.Operation); | |||
if (_isDebug) | |||
RaiseOnDebugMessage(DebugMessageType.WebSocketUnknownOpCode, "Unknown DataSocket op: " + msg.Operation); | |||
break; | |||
} | |||
#if DNXCORE | |||
@@ -49,8 +49,8 @@ namespace Discord | |||
#endif | |||
#endif | |||
public DiscordVoiceSocket(DiscordClient client, int timeout, int interval) | |||
: base(client, timeout, interval) | |||
public DiscordVoiceSocket(DiscordClient client, int timeout, int interval, bool isDebug) | |||
: base(client, timeout, interval, isDebug) | |||
{ | |||
_connectWaitOnLogin = new ManualResetEventSlim(false); | |||
#if !DNXCORE50 | |||
@@ -287,7 +287,8 @@ namespace Discord | |||
break; | |||
#endif | |||
default: | |||
RaiseOnDebugMessage(DebugMessageType.WebSocketUnknownOpCode, "Unknown VoiceSocket op: " + msg.Operation); | |||
if (_isDebug) | |||
RaiseOnDebugMessage(DebugMessageType.WebSocketUnknownOpCode, "Unknown VoiceSocket op: " + msg.Operation); | |||
break; | |||
} | |||
#if DNXCORE50 | |||
@@ -322,25 +323,42 @@ namespace Discord | |||
else | |||
{ | |||
//Parse RTP Data | |||
/*if (length < 12) | |||
throw new Exception($"Unexpected message length. Expected >= 12, got {length}."); | |||
if (length < 12) | |||
{ | |||
if (_isDebug) | |||
RaiseOnDebugMessage(DebugMessageType.VoiceInput, $"Unexpected message length. Expected >= 12, got {length}."); | |||
return; | |||
} | |||
byte flags = buffer[0]; | |||
if (flags != 0x80) | |||
throw new Exception("Unexpected Flags"); | |||
{ | |||
if (_isDebug) | |||
RaiseOnDebugMessage(DebugMessageType.VoiceInput, $"Unexpected Flags: {flags}"); | |||
return; | |||
} | |||
byte payloadType = buffer[1]; | |||
if (payloadType != 0x78) | |||
throw new Exception("Unexpected Payload Type"); | |||
{ | |||
if (_isDebug) | |||
RaiseOnDebugMessage(DebugMessageType.VoiceInput, $"Unexpected Payload Type: {flags}"); | |||
return; | |||
} | |||
ushort sequenceNumber = (ushort)((buffer[2] << 8) | buffer[3]); | |||
uint timestamp = (uint)((buffer[4] << 24) | (buffer[5] << 16) | | |||
(buffer[6] << 8) | (buffer[7] << 0)); | |||
uint ssrc = (uint)((buffer[8] << 24) | (buffer[9] << 16) | | |||
(buffer[10] << 8) | (buffer[11] << 0)); | |||
ushort sequenceNumber = (ushort)((buffer[2] << 8) | | |||
buffer[3] << 0); | |||
uint timestamp = (uint)((buffer[4] << 24) | | |||
(buffer[5] << 16) | | |||
(buffer[6] << 8) | | |||
(buffer[7] << 0)); | |||
uint ssrc = (uint)((buffer[8] << 24) | | |||
(buffer[9] << 16) | | |||
(buffer[10] << 8) | | |||
(buffer[11] << 0)); | |||
//Decrypt | |||
if (_mode == "xsalsa20_poly1305") | |||
/*if (_mode == "xsalsa20_poly1305") | |||
{ | |||
if (length < 36) //12 + 24 | |||
throw new Exception($"Unexpected message length. Expected >= 36, got {length}."); | |||
@@ -362,6 +380,8 @@ namespace Discord | |||
buffer = newBuffer; | |||
}*/ | |||
if (_isDebug) | |||
RaiseOnDebugMessage(DebugMessageType.VoiceInput, $"Received {buffer.Length - 12} bytes."); | |||
//TODO: Use Voice Data | |||
} | |||
} | |||
@@ -14,22 +14,24 @@ namespace Discord | |||
private const int SendChunkSize = 4096; | |||
protected readonly DiscordClient _client; | |||
protected volatile CancellationTokenSource _disconnectToken; | |||
protected int _timeout, _heartbeatInterval; | |||
protected readonly int _sendInterval; | |||
protected string _host; | |||
protected readonly bool _isDebug; | |||
private readonly ConcurrentQueue<byte[]> _sendQueue; | |||
protected volatile CancellationTokenSource _disconnectToken; | |||
private volatile ClientWebSocket _webSocket; | |||
private volatile Task _tasks; | |||
private ConcurrentQueue<byte[]> _sendQueue; | |||
protected string _host; | |||
protected int _timeout, _heartbeatInterval; | |||
private DateTime _lastHeartbeat; | |||
private bool _isConnected; | |||
public DiscordWebSocket(DiscordClient client, int timeout, int interval) | |||
public DiscordWebSocket(DiscordClient client, int timeout, int interval, bool isDebug) | |||
{ | |||
_client = client; | |||
_timeout = timeout; | |||
_sendInterval = interval; | |||
_isDebug = isDebug; | |||
_sendQueue = new ConcurrentQueue<byte[]>(); | |||
} | |||
@@ -0,0 +1,14 @@ | |||
using System; | |||
namespace Discord.Helpers | |||
{ | |||
internal partial class JsonHttpClient | |||
{ | |||
public event EventHandler<LogMessageEventArgs> OnDebugMessage; | |||
protected void RaiseOnDebugMessage(DebugMessageType type, string message) | |||
{ | |||
if (OnDebugMessage != null) | |||
OnDebugMessage(this, new LogMessageEventArgs(type, message)); | |||
} | |||
} | |||
} |
@@ -1,7 +1,6 @@ | |||
using Discord.API; | |||
using Newtonsoft.Json; | |||
using System; | |||
using System.Diagnostics; | |||
using System.IO; | |||
using System.Globalization; | |||
using System.Net.Http; | |||
@@ -9,24 +8,22 @@ using System.Net; | |||
using System.Reflection; | |||
using System.Text; | |||
using System.Threading.Tasks; | |||
using System.Diagnostics; | |||
namespace Discord.Helpers | |||
{ | |||
internal class JsonHttpClient | |||
internal partial class JsonHttpClient | |||
{ | |||
#if TEST_RESPONSES | |||
private const bool _isDebug = true; | |||
#else | |||
private const bool _isDebug = false; | |||
#endif | |||
private bool _isDebug; | |||
private readonly HttpClient _client; | |||
private readonly HttpMethod _patch; | |||
#if TEST_RESPONSES | |||
private readonly JsonSerializerSettings _settings; | |||
#endif | |||
public JsonHttpClient() | |||
public JsonHttpClient(bool isDebug) | |||
{ | |||
_isDebug = isDebug; | |||
_patch = new HttpMethod("PATCH"); //Not sure why this isn't a default... | |||
_client = new HttpClient(new HttpClientHandler | |||
@@ -131,8 +128,8 @@ namespace Discord.Helpers | |||
private async Task<string> Send(HttpMethod method, string path, HttpContent content) | |||
{ | |||
string responseJson = await SendRequest(method, path, content, true); | |||
if (path.StartsWith(Endpoints.BaseApi)) | |||
CheckEmptyResponse(responseJson); | |||
if (path.StartsWith(Endpoints.BaseApi) && !string.IsNullOrEmpty(responseJson)) | |||
throw new Exception("API check failed: Response is not empty."); | |||
return responseJson; | |||
} | |||
#else | |||
@@ -142,9 +139,25 @@ namespace Discord.Helpers | |||
private async Task<string> SendRequest(HttpMethod method, string path, HttpContent content, bool hasResponse) | |||
{ | |||
#if TEST_RESPONSES | |||
Stopwatch stopwatch = Stopwatch.StartNew(); | |||
#endif | |||
Stopwatch stopwatch = null; | |||
if (_isDebug) | |||
{ | |||
if (content != null) | |||
{ | |||
if (content is StringContent) | |||
{ | |||
string json = await (content as StringContent).ReadAsStringAsync(); | |||
RaiseOnDebugMessage(DebugMessageType.XHRRawOutput, $"{method} {path}: {json}"); | |||
} | |||
else | |||
{ | |||
byte[] bytes = await content.ReadAsByteArrayAsync(); | |||
RaiseOnDebugMessage(DebugMessageType.XHRRawOutput, $"{method} {path}: {bytes.Length} bytes"); | |||
} | |||
} | |||
stopwatch = Stopwatch.StartNew(); | |||
} | |||
string result; | |||
using (HttpRequestMessage msg = new HttpRequestMessage(method, path)) | |||
{ | |||
@@ -168,21 +181,14 @@ namespace Discord.Helpers | |||
} | |||
} | |||
#if TEST_RESPONSES | |||
stopwatch.Stop(); | |||
Debug.WriteLine($"{method} {path}: {Math.Round(stopwatch.ElapsedTicks / (double)TimeSpan.TicksPerMillisecond, 2)}ms"); | |||
#endif | |||
if (_isDebug) | |||
{ | |||
stopwatch.Stop(); | |||
RaiseOnDebugMessage(DebugMessageType.XHRTiming, $"{method} {path}: {Math.Round(stopwatch.ElapsedTicks / (double)TimeSpan.TicksPerMillisecond, 2)}ms"); | |||
} | |||
return result; | |||
} | |||
#if TEST_RESPONSES | |||
private void CheckEmptyResponse(string json) | |||
{ | |||
if (!string.IsNullOrEmpty(json)) | |||
throw new Exception("API check failed: Response is not empty."); | |||
} | |||
#endif | |||
private StringContent AsJson(object obj) | |||
{ | |||
return new StringContent(JsonConvert.SerializeObject(obj), Encoding.UTF8, "application/json"); | |||