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