@@ -3,6 +3,6 @@ | |||
"sdk": { | |||
"version": "1.0.0-beta6", | |||
"architecture": "x64", | |||
"runtime": "coreclr" | |||
"runtime": "clr" | |||
} | |||
} |
@@ -11,6 +11,8 @@ | |||
<AssemblyName>Discord.Net</AssemblyName> | |||
<FileAlignment>512</FileAlignment> | |||
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion> | |||
<NuGetPackageImportStamp> | |||
</NuGetPackageImportStamp> | |||
</PropertyGroup> | |||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> | |||
<DebugSymbols>true</DebugSymbols> | |||
@@ -36,6 +38,10 @@ | |||
<HintPath>..\..\packages\Newtonsoft.Json.7.0.1\lib\net45\Newtonsoft.Json.dll</HintPath> | |||
<Private>True</Private> | |||
</Reference> | |||
<Reference Include="Sodium, Version=0.8.0.0, Culture=neutral, processorArchitecture=MSIL"> | |||
<HintPath>..\..\packages\libsodium-net.0.8.0\lib\Net40\Sodium.dll</HintPath> | |||
<Private>True</Private> | |||
</Reference> | |||
<Reference Include="System" /> | |||
<Reference Include="System.Net.Http" /> | |||
</ItemGroup> | |||
@@ -91,8 +97,8 @@ | |||
<Compile Include="..\Discord.Net\DiscordTextWebSocket.Events.cs"> | |||
<Link>DiscordTextWebSocket.Events.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\DiscordVoiceWebSocket.cs"> | |||
<Link>DiscordVoiceWebSocket.cs</Link> | |||
<Compile Include="..\Discord.Net\DiscordVoiceSocket.cs"> | |||
<Link>DiscordVoiceSocket.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\DiscordWebSocket.cs"> | |||
<Link>DiscordWebSocket.cs</Link> | |||
@@ -136,6 +142,13 @@ | |||
<Compile Include="Properties\AssemblyInfo.cs" /> | |||
</ItemGroup> | |||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> | |||
<Import Project="..\..\packages\Baseclass.Contrib.Nuget.Output.1.0.0\build\net40\Baseclass.Contrib.Nuget.Output.targets" Condition="Exists('..\..\packages\Baseclass.Contrib.Nuget.Output.1.0.0\build\net40\Baseclass.Contrib.Nuget.Output.targets')" /> | |||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild"> | |||
<PropertyGroup> | |||
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText> | |||
</PropertyGroup> | |||
<Error Condition="!Exists('..\..\packages\Baseclass.Contrib.Nuget.Output.1.0.0\build\net40\Baseclass.Contrib.Nuget.Output.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Baseclass.Contrib.Nuget.Output.1.0.0\build\net40\Baseclass.Contrib.Nuget.Output.targets'))" /> | |||
</Target> | |||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it. | |||
Other similar extension points exist, see Microsoft.Common.targets. | |||
<Target Name="BeforeBuild"> | |||
@@ -1,4 +1,6 @@ | |||
<?xml version="1.0" encoding="utf-8"?> | |||
<packages> | |||
<package id="Baseclass.Contrib.Nuget.Output" version="1.0.0" targetFramework="net45" /> | |||
<package id="libsodium-net" version="0.8.0" targetFramework="net45" /> | |||
<package id="Newtonsoft.Json" version="7.0.1" targetFramework="net45" /> | |||
</packages> |
@@ -50,8 +50,7 @@ | |||
public static readonly string VoiceIce = $"{Voice}/ice"; | |||
//Web Sockets | |||
public static readonly string BaseWss = "wss://" + BaseUrl; | |||
public static readonly string WebSocket_Hub = $"{BaseWss}/hub"; | |||
public static readonly string WebSocket_Hub = $"{BaseUrl}/hub"; | |||
//Website | |||
public static string InviteUrl(string code) => $"{BaseShortHttps}/{code}"; | |||
@@ -11,9 +11,9 @@ namespace Discord.API.Models | |||
public sealed class Ready | |||
{ | |||
[JsonProperty(PropertyName = "ssrc")] | |||
public int SSRC; | |||
public uint SSRC; | |||
[JsonProperty(PropertyName = "port")] | |||
public int Port; | |||
public ushort Port; | |||
[JsonProperty(PropertyName = "modes")] | |||
public string[] Modes; | |||
[JsonProperty(PropertyName = "heartbeat_interval")] | |||
@@ -19,7 +19,7 @@ namespace Discord | |||
{ | |||
private readonly DiscordClientConfig _config; | |||
private readonly DiscordTextWebSocket _webSocket; | |||
private readonly DiscordVoiceWebSocket _voiceWebSocket; | |||
private readonly DiscordVoiceSocket _voiceWebSocket; | |||
private readonly ManualResetEventSlim _blockEvent; | |||
private readonly Regex _userRegex, _channelRegex; | |||
private readonly MatchEvaluator _userRegexEvaluator, _channelRegexEvaluator; | |||
@@ -287,7 +287,7 @@ namespace Discord | |||
user => { } | |||
); | |||
_webSocket = new DiscordTextWebSocket(_config.WebSocketInterval); | |||
_webSocket = new DiscordTextWebSocket(_config.ConnectionTimeout, _config.WebSocketInterval); | |||
_webSocket.Connected += (s, e) => RaiseConnected(); | |||
_webSocket.Disconnected += async (s, e) => | |||
{ | |||
@@ -312,7 +312,7 @@ namespace Discord | |||
}; | |||
_webSocket.OnDebugMessage += (s, e) => RaiseOnDebugMessage(e.Message); | |||
_voiceWebSocket = new DiscordVoiceWebSocket(_config.WebSocketInterval); | |||
_voiceWebSocket = new DiscordVoiceSocket(_config.VoiceConnectionTimeout, _config.WebSocketInterval); | |||
_voiceWebSocket.Connected += (s, e) => RaiseVoiceConnected(); | |||
_voiceWebSocket.Disconnected += (s, e) => | |||
{ | |||
@@ -578,7 +578,7 @@ namespace Discord | |||
{ | |||
_currentVoiceEndpoint = data.Endpoint.Split(':')[0]; | |||
_currentVoiceToken = data.Token; | |||
await _voiceWebSocket.ConnectAsync("wss://" + _currentVoiceEndpoint); | |||
await _voiceWebSocket.ConnectAsync(_currentVoiceEndpoint); | |||
await _voiceWebSocket.Login(_currentVoiceServerId, UserId, SessionId, _currentVoiceToken); | |||
} | |||
} | |||
@@ -2,6 +2,10 @@ | |||
{ | |||
public class DiscordClientConfig | |||
{ | |||
/// <summary> Max time in milliseconds to wait for the web socket to connect. </summary> | |||
public int ConnectionTimeout { get; set; } = 5000; | |||
/// <summary> Max time in milliseconds to wait for the voice web socket to connect. </summary> | |||
public int VoiceConnectionTimeout { get; set; } = 10000; | |||
/// <summary> Gets or sets the time (in milliseconds) to wait after an unexpected disconnect before reconnecting. </summary> | |||
public int ReconnectDelay { get; set; } = 1000; | |||
/// <summary> Gets or sets the time (in milliseconds) to wait after an reconnect fails before retrying. </summary> | |||
@@ -11,12 +11,10 @@ namespace Discord | |||
{ | |||
internal sealed partial class DiscordTextWebSocket : DiscordWebSocket | |||
{ | |||
private const int ReadyTimeout = 2500; //Max time in milliseconds between connecting to Discord and receiving a READY event | |||
private ManualResetEventSlim _connectWaitOnLogin, _connectWaitOnLogin2; | |||
public DiscordTextWebSocket(int interval) | |||
: base(interval) | |||
public DiscordTextWebSocket(int timeout, int interval) | |||
: base(timeout, interval) | |||
{ | |||
_connectWaitOnLogin = new ManualResetEventSlim(false); | |||
_connectWaitOnLogin2 = new ManualResetEventSlim(false); | |||
@@ -40,7 +38,7 @@ namespace Discord | |||
try | |||
{ | |||
if (!_connectWaitOnLogin.Wait(ReadyTimeout, cancelToken)) //Waiting on READY message | |||
if (!_connectWaitOnLogin.Wait(_timeout, cancelToken)) //Waiting on READY message | |||
throw new Exception("No reply from Discord server"); | |||
} | |||
catch (OperationCanceledException) | |||
@@ -53,7 +51,7 @@ namespace Discord | |||
SetConnected(); | |||
} | |||
protected override void ProcessMessage(string json) | |||
protected override Task ProcessMessage(string json) | |||
{ | |||
var msg = JsonConvert.DeserializeObject<WebSocketMessage>(json); | |||
switch (msg.Operation) | |||
@@ -65,7 +63,7 @@ namespace Discord | |||
var payload = (msg.Payload as JToken).ToObject<TextWebSocketEvents.Ready>(); | |||
_heartbeatInterval = payload.HeartbeatInterval; | |||
QueueMessage(new TextWebSocketCommands.UpdateStatus()); | |||
QueueMessage(GetKeepAlive()); | |||
//QueueMessage(GetKeepAlive()); | |||
_connectWaitOnLogin.Set(); //Pre-Event | |||
} | |||
RaiseGotEvent(msg.Type, msg.Payload as JToken); | |||
@@ -77,6 +75,11 @@ namespace Discord | |||
RaiseOnDebugMessage("Unknown WebSocket operation ID: " + msg.Operation); | |||
break; | |||
} | |||
#if DNXCORE | |||
return Task.CompletedTask | |||
#else | |||
return Task.Delay(0); | |||
#endif | |||
} | |||
protected override object GetKeepAlive() | |||
@@ -0,0 +1,240 @@ | |||
using Discord.API.Models; | |||
using Discord.Helpers; | |||
using Newtonsoft.Json; | |||
using Newtonsoft.Json.Linq; | |||
using System; | |||
using System.Collections.Concurrent; | |||
using System.Linq; | |||
using System.Net; | |||
using System.Net.Sockets; | |||
using System.Threading; | |||
using System.Threading.Tasks; | |||
using WebSocketMessage = Discord.API.Models.VoiceWebSocketCommands.WebSocketMessage; | |||
namespace Discord | |||
{ | |||
internal sealed partial class DiscordVoiceSocket : DiscordWebSocket | |||
{ | |||
private ManualResetEventSlim _connectWaitOnLogin; | |||
private UdpClient _udp; | |||
private ConcurrentQueue<byte[]> _sendQueue; | |||
private string _myIp; | |||
private IPEndPoint _endpoint; | |||
private byte[] _secretKey; | |||
private string _mode; | |||
private bool _isFirst; | |||
public DiscordVoiceSocket(int timeout, int interval) | |||
: base(timeout, interval) | |||
{ | |||
_connectWaitOnLogin = new ManualResetEventSlim(false); | |||
_sendQueue = new ConcurrentQueue<byte[]>(); | |||
} | |||
protected override void OnConnect() | |||
{ | |||
_udp = new UdpClient(new IPEndPoint(IPAddress.Any, 0)); | |||
_udp.AllowNatTraversal(true); | |||
_isFirst = true; | |||
} | |||
protected override void OnDisconnect() | |||
{ | |||
_udp = null; | |||
} | |||
protected override Task[] CreateTasks(CancellationToken cancelToken) | |||
{ | |||
return new Task[] | |||
{ | |||
Task.Factory.StartNew(ReceiveAsync, cancelToken, TaskCreationOptions.LongRunning, TaskScheduler.Default).Result, | |||
Task.Factory.StartNew(SendAsync, cancelToken, TaskCreationOptions.LongRunning, TaskScheduler.Default).Result, | |||
Task.Factory.StartNew(WatcherAsync, cancelToken, TaskCreationOptions.LongRunning, TaskScheduler.Default).Result | |||
}.Concat(base.CreateTasks(cancelToken)).ToArray(); | |||
} | |||
public async Task Login(string serverId, string userId, string sessionId, string token) | |||
{ | |||
var cancelToken = _disconnectToken.Token; | |||
_connectWaitOnLogin.Reset(); | |||
_myIp = (await Http.Get("http://ipinfo.io/ip")).Trim(); | |||
VoiceWebSocketCommands.Login msg = new VoiceWebSocketCommands.Login(); | |||
msg.Payload.ServerId = serverId; | |||
msg.Payload.SessionId = sessionId; | |||
msg.Payload.Token = token; | |||
msg.Payload.UserId = userId; | |||
await SendMessage(msg, cancelToken); | |||
try | |||
{ | |||
if (!_connectWaitOnLogin.Wait(_timeout, cancelToken)) //Waiting on JoinServer message | |||
throw new Exception("No reply from Discord server"); | |||
} | |||
catch (OperationCanceledException) | |||
{ | |||
throw new InvalidOperationException("Bad Token"); | |||
} | |||
SetConnected(); | |||
} | |||
private async Task ReceiveAsync() | |||
{ | |||
var cancelToken = _disconnectToken.Token; | |||
try | |||
{ | |||
while (!cancelToken.IsCancellationRequested) | |||
{ | |||
var result = await _udp.ReceiveAsync(); | |||
ProcessUdpMessage(result); | |||
} | |||
} | |||
catch { } | |||
finally { _disconnectToken.Cancel(); } | |||
} | |||
private async Task SendAsync() | |||
{ | |||
var cancelToken = _disconnectToken.Token; | |||
try | |||
{ | |||
byte[] bytes; | |||
while (!cancelToken.IsCancellationRequested) | |||
{ | |||
while (_sendQueue.TryDequeue(out bytes)) | |||
await _udp.SendAsync(bytes, bytes.Length); | |||
await Task.Delay(_sendInterval); | |||
} | |||
} | |||
catch { } | |||
finally { _disconnectToken.Cancel(); } | |||
} | |||
private async Task WatcherAsync() | |||
{ | |||
try | |||
{ | |||
await Task.Delay(-1, _disconnectToken.Token); | |||
} | |||
catch (TaskCanceledException) { } | |||
#if DNXCORE50 | |||
finally { _udp.Dispose(); } | |||
#else | |||
finally { _udp.Close(); } | |||
#endif | |||
} | |||
protected override async Task ProcessMessage(string json) | |||
{ | |||
var msg = JsonConvert.DeserializeObject<WebSocketMessage>(json); | |||
switch (msg.Operation) | |||
{ | |||
case 2: //READY | |||
{ | |||
var payload = (msg.Payload as JToken).ToObject<VoiceWebSocketEvents.Ready>(); | |||
_heartbeatInterval = payload.HeartbeatInterval; | |||
_endpoint = new IPEndPoint((await Dns.GetHostAddressesAsync(_host)).FirstOrDefault(), payload.Port); | |||
//_mode = payload.Modes.LastOrDefault(); | |||
_mode = "plain"; | |||
_udp.Connect(_endpoint); | |||
var ssrc = payload.SSRC; | |||
_sendQueue.Enqueue(new byte[70] { | |||
(byte)((ssrc >> 24) & 0xFF), | |||
(byte)((ssrc >> 16) & 0xFF), | |||
(byte)((ssrc >> 8) & 0xFF), | |||
(byte)((ssrc >> 0) & 0xFF), | |||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, | |||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, | |||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, | |||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, | |||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0 | |||
}); | |||
} | |||
break; | |||
case 4: //SESSION_DESCRIPTION | |||
{ | |||
var payload = (msg.Payload as JToken).ToObject<VoiceWebSocketEvents.JoinServer>(); | |||
_secretKey = payload.SecretKey; | |||
_connectWaitOnLogin.Set(); | |||
} | |||
break; | |||
default: | |||
RaiseOnDebugMessage("Unknown WebSocket operation ID: " + msg.Operation); | |||
break; | |||
} | |||
} | |||
private void ProcessUdpMessage(UdpReceiveResult msg) | |||
{ | |||
if (msg.Buffer.Length > 0 && msg.RemoteEndPoint.Equals(_endpoint)) | |||
{ | |||
byte[] buffer = msg.Buffer; | |||
int length = msg.Buffer.Length; | |||
if (_isFirst) | |||
{ | |||
_isFirst = false; | |||
if (length != 70) | |||
throw new Exception($"Unexpected message length. Expected 70, got {length}."); | |||
int port = buffer[68] | buffer[69] << 8; | |||
var login2 = new VoiceWebSocketCommands.Login2(); | |||
login2.Payload.Protocol = "udp"; | |||
login2.Payload.SocketData.Address = _myIp; | |||
login2.Payload.SocketData.Mode = _mode; | |||
login2.Payload.SocketData.Port = port; | |||
QueueMessage(login2); | |||
} | |||
else | |||
{ | |||
//Parse RTP Data | |||
if (length < 12) | |||
throw new Exception($"Unexpected message length. Expected >= 12, got {length}."); | |||
byte flags = buffer[0]; | |||
if (flags != 0x80) | |||
throw new Exception("Unexpected Flags"); | |||
byte payloadType = buffer[1]; | |||
if (payloadType != 0x78) | |||
throw new Exception("Unexpected Payload Type"); | |||
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)); | |||
//Decrypt | |||
if (_mode == "xsalsa20_poly1305") | |||
{ | |||
if (length < 36) //12 + 24 | |||
throw new Exception($"Unexpected message length. Expected >= 36, got {length}."); | |||
#if !DNXCORE50 | |||
byte[] nonce = new byte[24]; //16 bytes static, 8 bytes incrementing? | |||
Buffer.BlockCopy(buffer, 12, nonce, 0, 24); | |||
byte[] cipherText = new byte[buffer.Length - 36]; | |||
Buffer.BlockCopy(buffer, 36, cipherText, 0, cipherText.Length); | |||
Sodium.SecretBox.Open(cipherText, nonce, _secretKey); | |||
#endif | |||
} | |||
else //Plain | |||
{ | |||
byte[] newBuffer = new byte[buffer.Length - 12]; | |||
Buffer.BlockCopy(buffer, 12, newBuffer, 0, newBuffer.Length); | |||
buffer = newBuffer; | |||
} | |||
} | |||
} | |||
} | |||
protected override object GetKeepAlive() | |||
{ | |||
return new VoiceWebSocketCommands.KeepAlive(); | |||
} | |||
} | |||
} |
@@ -1,165 +0,0 @@ | |||
using Discord.API.Models; | |||
using Discord.Helpers; | |||
using Newtonsoft.Json; | |||
using Newtonsoft.Json.Linq; | |||
using System; | |||
using System.Collections.Concurrent; | |||
using System.Linq; | |||
using System.Net; | |||
using System.Net.Sockets; | |||
using System.Threading; | |||
using System.Threading.Tasks; | |||
using WebSocketMessage = Discord.API.Models.VoiceWebSocketCommands.WebSocketMessage; | |||
namespace Discord | |||
{ | |||
internal sealed partial class DiscordVoiceWebSocket : DiscordWebSocket | |||
{ | |||
private const int ReadyTimeout = 2500; //Max time in milliseconds between connecting to Discord and receiving a READY event | |||
private ManualResetEventSlim _connectWaitOnLogin; | |||
private UdpClient _udp; | |||
private ConcurrentQueue<byte[]> _sendQueue; | |||
private string _ip; | |||
public DiscordVoiceWebSocket(int interval) | |||
: base(interval) | |||
{ | |||
_connectWaitOnLogin = new ManualResetEventSlim(false); | |||
_sendQueue = new ConcurrentQueue<byte[]>(); | |||
} | |||
protected override void OnConnect() | |||
{ | |||
_udp = new UdpClient(0); | |||
} | |||
protected override void OnDisconnect() | |||
{ | |||
_udp = null; | |||
} | |||
protected override Task[] CreateTasks(CancellationToken cancelToken) | |||
{ | |||
return new Task[] | |||
{ | |||
Task.Factory.StartNew(ReceiveAsync, cancelToken, TaskCreationOptions.LongRunning, TaskScheduler.Default).Result, | |||
Task.Factory.StartNew(SendAsync, cancelToken, TaskCreationOptions.LongRunning, TaskScheduler.Default).Result, | |||
Task.Factory.StartNew(WatcherAsync, cancelToken, TaskCreationOptions.LongRunning, TaskScheduler.Default).Result | |||
}.Concat(base.CreateTasks(cancelToken)).ToArray(); | |||
} | |||
public async Task Login(string serverId, string userId, string sessionId, string token) | |||
{ | |||
var cancelToken = _disconnectToken.Token; | |||
_connectWaitOnLogin.Reset(); | |||
_ip = (await Http.Get("http://ipinfo.io/ip")).Trim(); | |||
VoiceWebSocketCommands.Login msg = new VoiceWebSocketCommands.Login(); | |||
msg.Payload.ServerId = serverId; | |||
msg.Payload.SessionId = sessionId; | |||
msg.Payload.Token = token; | |||
msg.Payload.UserId = userId; | |||
await SendMessage(msg, cancelToken); | |||
try | |||
{ | |||
if (!_connectWaitOnLogin.Wait(ReadyTimeout, cancelToken)) //Waiting on JoinServer message | |||
throw new Exception("No reply from Discord server"); | |||
} | |||
catch (OperationCanceledException) | |||
{ | |||
throw new InvalidOperationException("Bad Token"); | |||
} | |||
SetConnected(); | |||
} | |||
private async Task ReceiveAsync() | |||
{ | |||
var cancelToken = _disconnectToken.Token; | |||
try | |||
{ | |||
while (!cancelToken.IsCancellationRequested) | |||
{ | |||
var result = await _udp.ReceiveAsync(); | |||
ProcessUdpMessage(result); | |||
} | |||
} | |||
catch { } | |||
finally { _disconnectToken.Cancel(); } | |||
} | |||
private async Task SendAsync() | |||
{ | |||
var cancelToken = _disconnectToken.Token; | |||
try | |||
{ | |||
byte[] bytes; | |||
while (!cancelToken.IsCancellationRequested) | |||
{ | |||
while (_sendQueue.TryDequeue(out bytes)) | |||
await SendMessage(bytes, cancelToken); | |||
await Task.Delay(_sendInterval); | |||
} | |||
} | |||
catch { } | |||
finally { _disconnectToken.Cancel(); } | |||
} | |||
private async Task WatcherAsync() | |||
{ | |||
try | |||
{ | |||
await Task.Delay(-1, _disconnectToken.Token); | |||
} | |||
catch (TaskCanceledException) { } | |||
#if DNXCORE50 | |||
finally { _udp.Dispose(); } | |||
#else | |||
finally { _udp.Close(); } | |||
#endif | |||
} | |||
protected override void ProcessMessage(string json) | |||
{ | |||
var msg = JsonConvert.DeserializeObject<WebSocketMessage>(json); | |||
switch (msg.Operation) | |||
{ | |||
case 2: | |||
{ | |||
var payload = (msg.Payload as JToken).ToObject<VoiceWebSocketEvents.Ready>(); | |||
_heartbeatInterval = payload.HeartbeatInterval; | |||
var login2 = new VoiceWebSocketCommands.Login2(); | |||
login2.Payload.Protocol = "udp"; | |||
login2.Payload.SocketData.Address = _ip; | |||
login2.Payload.SocketData.Mode = payload.Modes.Last(); | |||
login2.Payload.SocketData.Port = (_udp.Client.LocalEndPoint as IPEndPoint).Port; | |||
QueueMessage(login2); | |||
} | |||
break; | |||
case 4: | |||
{ | |||
var payload = (msg.Payload as JToken).ToObject<VoiceWebSocketEvents.JoinServer>(); | |||
QueueMessage(GetKeepAlive()); | |||
_connectWaitOnLogin.Set(); | |||
} | |||
break; | |||
default: | |||
RaiseOnDebugMessage("Unknown WebSocket operation ID: " + msg.Operation); | |||
break; | |||
} | |||
} | |||
private void ProcessUdpMessage(UdpReceiveResult msg) | |||
{ | |||
System.Diagnostics.Debug.WriteLine($"Got {msg.Buffer.Length} bytes from {msg.RemoteEndPoint}."); | |||
} | |||
protected override object GetKeepAlive() | |||
{ | |||
return new VoiceWebSocketCommands.KeepAlive(); | |||
} | |||
} | |||
} |
@@ -7,6 +7,8 @@ using System.Net.WebSockets; | |||
using System.Text; | |||
using System.Threading; | |||
using System.Threading.Tasks; | |||
using System.Net; | |||
using System.Linq; | |||
namespace Discord | |||
{ | |||
@@ -16,8 +18,9 @@ namespace Discord | |||
private const int SendChunkSize = 4096; | |||
protected volatile CancellationTokenSource _disconnectToken; | |||
protected int _heartbeatInterval; | |||
protected int _timeout, _heartbeatInterval; | |||
protected readonly int _sendInterval; | |||
protected string _host; | |||
private volatile ClientWebSocket _webSocket; | |||
private volatile Task _tasks; | |||
@@ -25,8 +28,9 @@ namespace Discord | |||
private DateTime _lastHeartbeat; | |||
private bool _isConnected; | |||
public DiscordWebSocket(int interval) | |||
public DiscordWebSocket(int timeout, int interval) | |||
{ | |||
_timeout = timeout; | |||
_sendInterval = interval; | |||
_sendQueue = new ConcurrentQueue<byte[]>(); | |||
@@ -41,10 +45,12 @@ namespace Discord | |||
_webSocket = new ClientWebSocket(); | |||
_webSocket.Options.KeepAliveInterval = TimeSpan.Zero; | |||
await _webSocket.ConnectAsync(new Uri(url), cancelToken); | |||
await _webSocket.ConnectAsync(new Uri("wss://" + url), cancelToken); | |||
_host = url; | |||
OnConnect(); | |||
_lastHeartbeat = DateTime.UtcNow; | |||
_tasks = Task.WhenAll(CreateTasks(cancelToken)) | |||
.ContinueWith(x => | |||
{ | |||
@@ -123,7 +129,10 @@ namespace Discord | |||
} | |||
while (!result.EndOfMessage); | |||
ProcessMessage(builder.ToString()); | |||
//TODO: Remove this | |||
if (this is DiscordVoiceSocket) | |||
System.Diagnostics.Debug.WriteLine(">>> " + builder.ToString()); | |||
await ProcessMessage(builder.ToString()); | |||
builder.Clear(); | |||
} | |||
@@ -157,16 +166,24 @@ namespace Discord | |||
finally { _disconnectToken.Cancel(); } | |||
} | |||
protected abstract void ProcessMessage(string json); | |||
protected abstract Task ProcessMessage(string json); | |||
protected abstract object GetKeepAlive(); | |||
protected void QueueMessage(object message) | |||
{ | |||
var bytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(message)); | |||
//TODO: Remove this | |||
if (this is DiscordVoiceSocket) | |||
System.Diagnostics.Debug.WriteLine("<<< " + JsonConvert.SerializeObject(message)); | |||
var bytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(message)); | |||
_sendQueue.Enqueue(bytes); | |||
} | |||
protected Task SendMessage(object message, CancellationToken cancelToken) | |||
=> SendMessage(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(message)), cancelToken); | |||
{ | |||
//TODO: Remove this | |||
if (this is DiscordVoiceSocket) | |||
System.Diagnostics.Debug.WriteLine("<<< " + JsonConvert.SerializeObject(message)); | |||
return SendMessage(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(message)), cancelToken); | |||
} | |||
protected async Task SendMessage(byte[] message, CancellationToken cancelToken) | |||
{ | |||
var frameCount = (int)Math.Ceiling((double)message.Length / SendChunkSize); | |||
@@ -1,49 +1,54 @@ | |||
{ | |||
"version": "0.5.0-*", | |||
"description": "An unofficial .Net API wrapper for the Discord client.", | |||
"authors": [ "RogueException" ], | |||
"tags": [ "discord", "discordapp" ], | |||
"projectUrl": "https://github.com/RogueException/Discord.Net", | |||
"licenseUrl": "http://opensource.org/licenses/MIT", | |||
"repository": { | |||
"type": "git", | |||
"url": "git://github.com/RogueException/Discord.Net" | |||
}, | |||
"configurations": { | |||
"FullDebug": { | |||
"compilationOptions": { | |||
"define": ["DEBUG","TRACE","TEST_RESPONSES"] | |||
} | |||
} | |||
}, | |||
{ | |||
"version": "0.5.0-*", | |||
"description": "An unofficial .Net API wrapper for the Discord client.", | |||
"authors": [ "RogueException" ], | |||
"tags": [ "discord", "discordapp" ], | |||
"projectUrl": "https://github.com/RogueException/Discord.Net", | |||
"licenseUrl": "http://opensource.org/licenses/MIT", | |||
"repository": { | |||
"type": "git", | |||
"url": "git://github.com/RogueException/Discord.Net" | |||
}, | |||
"configurations": { | |||
"FullDebug": { | |||
"compilationOptions": { | |||
"define": [ "DEBUG", "TRACE", "TEST_RESPONSES" ] | |||
} | |||
} | |||
}, | |||
"dependencies": { | |||
"Newtonsoft.Json": "7.0.1" | |||
}, | |||
"dependencies": { | |||
"Newtonsoft.Json": "7.0.1" | |||
}, | |||
"frameworks": { | |||
"net45": { | |||
"dependencies": { | |||
"Microsoft.Net.Http": "2.2.22" | |||
} | |||
}, | |||
"dnx451": { | |||
"dependencies": { | |||
"Microsoft.Net.Http": "2.2.22" | |||
} | |||
}, | |||
"dnxcore50": { | |||
"dependencies": { | |||
"System.Collections.Concurrent": "4.0.10", | |||
"System.Diagnostics.Debug": "4.0.10", | |||
"System.IO.Compression": "4.0.0", | |||
"System.Linq": "4.0.0", | |||
"System.Net.Requests": "4.0.10", | |||
"System.Net.Sockets": "4.0.10-beta-23019", | |||
"System.Net.WebSockets.Client": "4.0.0-beta-23123", | |||
"System.Runtime": "4.0.20", | |||
"System.Text.RegularExpressions": "4.0.10" | |||
} | |||
} | |||
} | |||
"frameworks": { | |||
"net45": { | |||
"dependencies": { | |||
"Microsoft.Net.Http": "2.2.22", | |||
"libsodium-net": "0.8.0", | |||
"Baseclass.Contrib.Nuget.Output": "2.1.0" | |||
} | |||
}, | |||
"dnx451": { | |||
"dependencies": { | |||
"Microsoft.Net.Http": "2.2.22", | |||
"libsodium-net": "0.8.0", | |||
"Baseclass.Contrib.Nuget.Output": "2.1.0" | |||
} | |||
}, | |||
"dnxcore50": { | |||
"dependencies": { | |||
"System.Collections.Concurrent": "4.0.10", | |||
"System.Diagnostics.Debug": "4.0.10", | |||
"System.IO.Compression": "4.0.0", | |||
"System.Linq": "4.0.0", | |||
"System.Net.Requests": "4.0.10", | |||
"System.Net.Sockets": "4.0.10-beta-23019", | |||
"System.Net.WebSockets.Client": "4.0.0-beta-23123", | |||
"System.Runtime": "4.0.20", | |||
"System.Text.RegularExpressions": "4.0.10", | |||
"System.Net.NameResolution": "4.0.0-beta-23019" | |||
} | |||
} | |||
} | |||
} |