Added libsodium, set up UDP socket for voice.
This commit is contained in:
@@ -3,6 +3,6 @@
|
|||||||
"sdk": {
|
"sdk": {
|
||||||
"version": "1.0.0-beta6",
|
"version": "1.0.0-beta6",
|
||||||
"architecture": "x64",
|
"architecture": "x64",
|
||||||
"runtime": "coreclr"
|
"runtime": "clr"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -11,6 +11,8 @@
|
|||||||
<AssemblyName>Discord.Net</AssemblyName>
|
<AssemblyName>Discord.Net</AssemblyName>
|
||||||
<FileAlignment>512</FileAlignment>
|
<FileAlignment>512</FileAlignment>
|
||||||
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
||||||
|
<NuGetPackageImportStamp>
|
||||||
|
</NuGetPackageImportStamp>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||||
<DebugSymbols>true</DebugSymbols>
|
<DebugSymbols>true</DebugSymbols>
|
||||||
@@ -36,6 +38,10 @@
|
|||||||
<HintPath>..\..\packages\Newtonsoft.Json.7.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
|
<HintPath>..\..\packages\Newtonsoft.Json.7.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||||
<Private>True</Private>
|
<Private>True</Private>
|
||||||
</Reference>
|
</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" />
|
||||||
<Reference Include="System.Net.Http" />
|
<Reference Include="System.Net.Http" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
@@ -91,8 +97,8 @@
|
|||||||
<Compile Include="..\Discord.Net\DiscordTextWebSocket.Events.cs">
|
<Compile Include="..\Discord.Net\DiscordTextWebSocket.Events.cs">
|
||||||
<Link>DiscordTextWebSocket.Events.cs</Link>
|
<Link>DiscordTextWebSocket.Events.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="..\Discord.Net\DiscordVoiceWebSocket.cs">
|
<Compile Include="..\Discord.Net\DiscordVoiceSocket.cs">
|
||||||
<Link>DiscordVoiceWebSocket.cs</Link>
|
<Link>DiscordVoiceSocket.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="..\Discord.Net\DiscordWebSocket.cs">
|
<Compile Include="..\Discord.Net\DiscordWebSocket.cs">
|
||||||
<Link>DiscordWebSocket.cs</Link>
|
<Link>DiscordWebSocket.cs</Link>
|
||||||
@@ -136,6 +142,13 @@
|
|||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
<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.
|
<!-- 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.
|
Other similar extension points exist, see Microsoft.Common.targets.
|
||||||
<Target Name="BeforeBuild">
|
<Target Name="BeforeBuild">
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<packages>
|
<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" />
|
<package id="Newtonsoft.Json" version="7.0.1" targetFramework="net45" />
|
||||||
</packages>
|
</packages>
|
||||||
@@ -50,8 +50,7 @@
|
|||||||
public static readonly string VoiceIce = $"{Voice}/ice";
|
public static readonly string VoiceIce = $"{Voice}/ice";
|
||||||
|
|
||||||
//Web Sockets
|
//Web Sockets
|
||||||
public static readonly string BaseWss = "wss://" + BaseUrl;
|
public static readonly string WebSocket_Hub = $"{BaseUrl}/hub";
|
||||||
public static readonly string WebSocket_Hub = $"{BaseWss}/hub";
|
|
||||||
|
|
||||||
//Website
|
//Website
|
||||||
public static string InviteUrl(string code) => $"{BaseShortHttps}/{code}";
|
public static string InviteUrl(string code) => $"{BaseShortHttps}/{code}";
|
||||||
|
|||||||
@@ -11,9 +11,9 @@ namespace Discord.API.Models
|
|||||||
public sealed class Ready
|
public sealed class Ready
|
||||||
{
|
{
|
||||||
[JsonProperty(PropertyName = "ssrc")]
|
[JsonProperty(PropertyName = "ssrc")]
|
||||||
public int SSRC;
|
public uint SSRC;
|
||||||
[JsonProperty(PropertyName = "port")]
|
[JsonProperty(PropertyName = "port")]
|
||||||
public int Port;
|
public ushort Port;
|
||||||
[JsonProperty(PropertyName = "modes")]
|
[JsonProperty(PropertyName = "modes")]
|
||||||
public string[] Modes;
|
public string[] Modes;
|
||||||
[JsonProperty(PropertyName = "heartbeat_interval")]
|
[JsonProperty(PropertyName = "heartbeat_interval")]
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ namespace Discord
|
|||||||
{
|
{
|
||||||
private readonly DiscordClientConfig _config;
|
private readonly DiscordClientConfig _config;
|
||||||
private readonly DiscordTextWebSocket _webSocket;
|
private readonly DiscordTextWebSocket _webSocket;
|
||||||
private readonly DiscordVoiceWebSocket _voiceWebSocket;
|
private readonly DiscordVoiceSocket _voiceWebSocket;
|
||||||
private readonly ManualResetEventSlim _blockEvent;
|
private readonly ManualResetEventSlim _blockEvent;
|
||||||
private readonly Regex _userRegex, _channelRegex;
|
private readonly Regex _userRegex, _channelRegex;
|
||||||
private readonly MatchEvaluator _userRegexEvaluator, _channelRegexEvaluator;
|
private readonly MatchEvaluator _userRegexEvaluator, _channelRegexEvaluator;
|
||||||
@@ -287,7 +287,7 @@ namespace Discord
|
|||||||
user => { }
|
user => { }
|
||||||
);
|
);
|
||||||
|
|
||||||
_webSocket = new DiscordTextWebSocket(_config.WebSocketInterval);
|
_webSocket = new DiscordTextWebSocket(_config.ConnectionTimeout, _config.WebSocketInterval);
|
||||||
_webSocket.Connected += (s, e) => RaiseConnected();
|
_webSocket.Connected += (s, e) => RaiseConnected();
|
||||||
_webSocket.Disconnected += async (s, e) =>
|
_webSocket.Disconnected += async (s, e) =>
|
||||||
{
|
{
|
||||||
@@ -312,7 +312,7 @@ namespace Discord
|
|||||||
};
|
};
|
||||||
_webSocket.OnDebugMessage += (s, e) => RaiseOnDebugMessage(e.Message);
|
_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.Connected += (s, e) => RaiseVoiceConnected();
|
||||||
_voiceWebSocket.Disconnected += (s, e) =>
|
_voiceWebSocket.Disconnected += (s, e) =>
|
||||||
{
|
{
|
||||||
@@ -578,7 +578,7 @@ namespace Discord
|
|||||||
{
|
{
|
||||||
_currentVoiceEndpoint = data.Endpoint.Split(':')[0];
|
_currentVoiceEndpoint = data.Endpoint.Split(':')[0];
|
||||||
_currentVoiceToken = data.Token;
|
_currentVoiceToken = data.Token;
|
||||||
await _voiceWebSocket.ConnectAsync("wss://" + _currentVoiceEndpoint);
|
await _voiceWebSocket.ConnectAsync(_currentVoiceEndpoint);
|
||||||
await _voiceWebSocket.Login(_currentVoiceServerId, UserId, SessionId, _currentVoiceToken);
|
await _voiceWebSocket.Login(_currentVoiceServerId, UserId, SessionId, _currentVoiceToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,10 @@
|
|||||||
{
|
{
|
||||||
public class DiscordClientConfig
|
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>
|
/// <summary> Gets or sets the time (in milliseconds) to wait after an unexpected disconnect before reconnecting. </summary>
|
||||||
public int ReconnectDelay { get; set; } = 1000;
|
public int ReconnectDelay { get; set; } = 1000;
|
||||||
/// <summary> Gets or sets the time (in milliseconds) to wait after an reconnect fails before retrying. </summary>
|
/// <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
|
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;
|
private ManualResetEventSlim _connectWaitOnLogin, _connectWaitOnLogin2;
|
||||||
|
|
||||||
public DiscordTextWebSocket(int interval)
|
public DiscordTextWebSocket(int timeout, int interval)
|
||||||
: base(interval)
|
: base(timeout, interval)
|
||||||
{
|
{
|
||||||
_connectWaitOnLogin = new ManualResetEventSlim(false);
|
_connectWaitOnLogin = new ManualResetEventSlim(false);
|
||||||
_connectWaitOnLogin2 = new ManualResetEventSlim(false);
|
_connectWaitOnLogin2 = new ManualResetEventSlim(false);
|
||||||
@@ -40,7 +38,7 @@ namespace Discord
|
|||||||
|
|
||||||
try
|
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");
|
throw new Exception("No reply from Discord server");
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
@@ -53,7 +51,7 @@ namespace Discord
|
|||||||
SetConnected();
|
SetConnected();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void ProcessMessage(string json)
|
protected override Task ProcessMessage(string json)
|
||||||
{
|
{
|
||||||
var msg = JsonConvert.DeserializeObject<WebSocketMessage>(json);
|
var msg = JsonConvert.DeserializeObject<WebSocketMessage>(json);
|
||||||
switch (msg.Operation)
|
switch (msg.Operation)
|
||||||
@@ -65,7 +63,7 @@ namespace Discord
|
|||||||
var payload = (msg.Payload as JToken).ToObject<TextWebSocketEvents.Ready>();
|
var payload = (msg.Payload as JToken).ToObject<TextWebSocketEvents.Ready>();
|
||||||
_heartbeatInterval = payload.HeartbeatInterval;
|
_heartbeatInterval = payload.HeartbeatInterval;
|
||||||
QueueMessage(new TextWebSocketCommands.UpdateStatus());
|
QueueMessage(new TextWebSocketCommands.UpdateStatus());
|
||||||
QueueMessage(GetKeepAlive());
|
//QueueMessage(GetKeepAlive());
|
||||||
_connectWaitOnLogin.Set(); //Pre-Event
|
_connectWaitOnLogin.Set(); //Pre-Event
|
||||||
}
|
}
|
||||||
RaiseGotEvent(msg.Type, msg.Payload as JToken);
|
RaiseGotEvent(msg.Type, msg.Payload as JToken);
|
||||||
@@ -77,6 +75,11 @@ namespace Discord
|
|||||||
RaiseOnDebugMessage("Unknown WebSocket operation ID: " + msg.Operation);
|
RaiseOnDebugMessage("Unknown WebSocket operation ID: " + msg.Operation);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
#if DNXCORE
|
||||||
|
return Task.CompletedTask
|
||||||
|
#else
|
||||||
|
return Task.Delay(0);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override object GetKeepAlive()
|
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.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using System.Net;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace Discord
|
namespace Discord
|
||||||
{
|
{
|
||||||
@@ -16,8 +18,9 @@ namespace Discord
|
|||||||
private const int SendChunkSize = 4096;
|
private const int SendChunkSize = 4096;
|
||||||
|
|
||||||
protected volatile CancellationTokenSource _disconnectToken;
|
protected volatile CancellationTokenSource _disconnectToken;
|
||||||
protected int _heartbeatInterval;
|
protected int _timeout, _heartbeatInterval;
|
||||||
protected readonly int _sendInterval;
|
protected readonly int _sendInterval;
|
||||||
|
protected string _host;
|
||||||
|
|
||||||
private volatile ClientWebSocket _webSocket;
|
private volatile ClientWebSocket _webSocket;
|
||||||
private volatile Task _tasks;
|
private volatile Task _tasks;
|
||||||
@@ -25,8 +28,9 @@ namespace Discord
|
|||||||
private DateTime _lastHeartbeat;
|
private DateTime _lastHeartbeat;
|
||||||
private bool _isConnected;
|
private bool _isConnected;
|
||||||
|
|
||||||
public DiscordWebSocket(int interval)
|
public DiscordWebSocket(int timeout, int interval)
|
||||||
{
|
{
|
||||||
|
_timeout = timeout;
|
||||||
_sendInterval = interval;
|
_sendInterval = interval;
|
||||||
|
|
||||||
_sendQueue = new ConcurrentQueue<byte[]>();
|
_sendQueue = new ConcurrentQueue<byte[]>();
|
||||||
@@ -41,10 +45,12 @@ namespace Discord
|
|||||||
|
|
||||||
_webSocket = new ClientWebSocket();
|
_webSocket = new ClientWebSocket();
|
||||||
_webSocket.Options.KeepAliveInterval = TimeSpan.Zero;
|
_webSocket.Options.KeepAliveInterval = TimeSpan.Zero;
|
||||||
await _webSocket.ConnectAsync(new Uri(url), cancelToken);
|
await _webSocket.ConnectAsync(new Uri("wss://" + url), cancelToken);
|
||||||
|
_host = url;
|
||||||
|
|
||||||
OnConnect();
|
OnConnect();
|
||||||
|
|
||||||
|
_lastHeartbeat = DateTime.UtcNow;
|
||||||
_tasks = Task.WhenAll(CreateTasks(cancelToken))
|
_tasks = Task.WhenAll(CreateTasks(cancelToken))
|
||||||
.ContinueWith(x =>
|
.ContinueWith(x =>
|
||||||
{
|
{
|
||||||
@@ -123,7 +129,10 @@ namespace Discord
|
|||||||
}
|
}
|
||||||
while (!result.EndOfMessage);
|
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();
|
builder.Clear();
|
||||||
}
|
}
|
||||||
@@ -157,16 +166,24 @@ namespace Discord
|
|||||||
finally { _disconnectToken.Cancel(); }
|
finally { _disconnectToken.Cancel(); }
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract void ProcessMessage(string json);
|
protected abstract Task ProcessMessage(string json);
|
||||||
protected abstract object GetKeepAlive();
|
protected abstract object GetKeepAlive();
|
||||||
|
|
||||||
protected void QueueMessage(object message)
|
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);
|
_sendQueue.Enqueue(bytes);
|
||||||
}
|
}
|
||||||
protected Task SendMessage(object message, CancellationToken cancelToken)
|
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)
|
protected async Task SendMessage(byte[] message, CancellationToken cancelToken)
|
||||||
{
|
{
|
||||||
var frameCount = (int)Math.Ceiling((double)message.Length / SendChunkSize);
|
var frameCount = (int)Math.Ceiling((double)message.Length / SendChunkSize);
|
||||||
|
|||||||
@@ -1,49 +1,54 @@
|
|||||||
{
|
{
|
||||||
"version": "0.5.0-*",
|
"version": "0.5.0-*",
|
||||||
"description": "An unofficial .Net API wrapper for the Discord client.",
|
"description": "An unofficial .Net API wrapper for the Discord client.",
|
||||||
"authors": [ "RogueException" ],
|
"authors": [ "RogueException" ],
|
||||||
"tags": [ "discord", "discordapp" ],
|
"tags": [ "discord", "discordapp" ],
|
||||||
"projectUrl": "https://github.com/RogueException/Discord.Net",
|
"projectUrl": "https://github.com/RogueException/Discord.Net",
|
||||||
"licenseUrl": "http://opensource.org/licenses/MIT",
|
"licenseUrl": "http://opensource.org/licenses/MIT",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git://github.com/RogueException/Discord.Net"
|
"url": "git://github.com/RogueException/Discord.Net"
|
||||||
},
|
},
|
||||||
"configurations": {
|
"configurations": {
|
||||||
"FullDebug": {
|
"FullDebug": {
|
||||||
"compilationOptions": {
|
"compilationOptions": {
|
||||||
"define": ["DEBUG","TRACE","TEST_RESPONSES"]
|
"define": [ "DEBUG", "TRACE", "TEST_RESPONSES" ]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Newtonsoft.Json": "7.0.1"
|
"Newtonsoft.Json": "7.0.1"
|
||||||
},
|
},
|
||||||
|
|
||||||
"frameworks": {
|
"frameworks": {
|
||||||
"net45": {
|
"net45": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.Net.Http": "2.2.22"
|
"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"
|
"dnx451": {
|
||||||
}
|
"dependencies": {
|
||||||
},
|
"Microsoft.Net.Http": "2.2.22",
|
||||||
"dnxcore50": {
|
"libsodium-net": "0.8.0",
|
||||||
"dependencies": {
|
"Baseclass.Contrib.Nuget.Output": "2.1.0"
|
||||||
"System.Collections.Concurrent": "4.0.10",
|
}
|
||||||
"System.Diagnostics.Debug": "4.0.10",
|
},
|
||||||
"System.IO.Compression": "4.0.0",
|
"dnxcore50": {
|
||||||
"System.Linq": "4.0.0",
|
"dependencies": {
|
||||||
"System.Net.Requests": "4.0.10",
|
"System.Collections.Concurrent": "4.0.10",
|
||||||
"System.Net.Sockets": "4.0.10-beta-23019",
|
"System.Diagnostics.Debug": "4.0.10",
|
||||||
"System.Net.WebSockets.Client": "4.0.0-beta-23123",
|
"System.IO.Compression": "4.0.0",
|
||||||
"System.Runtime": "4.0.20",
|
"System.Linq": "4.0.0",
|
||||||
"System.Text.RegularExpressions": "4.0.10"
|
"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