Added libsodium, set up UDP socket for voice.
This commit is contained in:
@@ -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()
|
||||
|
||||
240
src/Discord.Net/DiscordVoiceSocket.cs
Normal file
240
src/Discord.Net/DiscordVoiceSocket.cs
Normal file
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user