Added connecting to voice chat.
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": "clr"
|
"runtime": "coreclr"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -8,30 +8,6 @@ using System;
|
|||||||
|
|
||||||
namespace Discord.API.Models
|
namespace Discord.API.Models
|
||||||
{
|
{
|
||||||
internal class WebSocketMessage
|
|
||||||
{
|
|
||||||
[JsonProperty(PropertyName = "op")]
|
|
||||||
public int Operation;
|
|
||||||
[JsonProperty(PropertyName = "t")]
|
|
||||||
public string Type;
|
|
||||||
[JsonProperty(PropertyName = "d")]
|
|
||||||
public object Payload;
|
|
||||||
}
|
|
||||||
internal abstract class WebSocketMessage<T> : WebSocketMessage
|
|
||||||
where T : new()
|
|
||||||
{
|
|
||||||
public WebSocketMessage() { Payload = new T(); }
|
|
||||||
public WebSocketMessage(int op) { Operation = op; Payload = new T(); }
|
|
||||||
public WebSocketMessage(int op, T payload) { Operation = op; Payload = payload; }
|
|
||||||
|
|
||||||
[JsonIgnore]
|
|
||||||
public new T Payload
|
|
||||||
{
|
|
||||||
get { if (base.Payload is JToken) { base.Payload = (base.Payload as JToken).ToObject<T>(); } return (T)base.Payload; }
|
|
||||||
set { base.Payload = value; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Users
|
//Users
|
||||||
internal class UserReference
|
internal class UserReference
|
||||||
{
|
{
|
||||||
@@ -83,9 +59,9 @@ namespace Discord.API.Models
|
|||||||
[JsonProperty(PropertyName = "session_id")]
|
[JsonProperty(PropertyName = "session_id")]
|
||||||
public string SessionId;
|
public string SessionId;
|
||||||
[JsonProperty(PropertyName = "self_mute")]
|
[JsonProperty(PropertyName = "self_mute")]
|
||||||
public bool IsSelfMuted;
|
public bool? IsSelfMuted;
|
||||||
[JsonProperty(PropertyName = "self_deaf")]
|
[JsonProperty(PropertyName = "self_deaf")]
|
||||||
public bool IsSelfDeafened;
|
public bool? IsSelfDeafened;
|
||||||
[JsonProperty(PropertyName = "mute")]
|
[JsonProperty(PropertyName = "mute")]
|
||||||
public bool IsMuted;
|
public bool IsMuted;
|
||||||
[JsonProperty(PropertyName = "deaf")]
|
[JsonProperty(PropertyName = "deaf")]
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#pragma warning disable CS0169
|
#pragma warning disable CS0169
|
||||||
|
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
@@ -10,6 +11,29 @@ namespace Discord.API.Models
|
|||||||
{
|
{
|
||||||
internal static class TextWebSocketCommands
|
internal static class TextWebSocketCommands
|
||||||
{
|
{
|
||||||
|
public class WebSocketMessage
|
||||||
|
{
|
||||||
|
[JsonProperty(PropertyName = "op")]
|
||||||
|
public int Operation;
|
||||||
|
[JsonProperty(PropertyName = "t")]
|
||||||
|
public string Type;
|
||||||
|
[JsonProperty(PropertyName = "d")]
|
||||||
|
public object Payload;
|
||||||
|
}
|
||||||
|
internal abstract class WebSocketMessage<T> : WebSocketMessage
|
||||||
|
where T : new()
|
||||||
|
{
|
||||||
|
public WebSocketMessage() { Payload = new T(); }
|
||||||
|
public WebSocketMessage(int op) { Operation = op; Payload = new T(); }
|
||||||
|
public WebSocketMessage(int op, T payload) { Operation = op; Payload = payload; }
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public new T Payload
|
||||||
|
{
|
||||||
|
get { if (base.Payload is JToken) { base.Payload = (base.Payload as JToken).ToObject<T>(); } return (T)base.Payload; }
|
||||||
|
set { base.Payload = value; }
|
||||||
|
}
|
||||||
|
}
|
||||||
public sealed class KeepAlive : WebSocketMessage<int>
|
public sealed class KeepAlive : WebSocketMessage<int>
|
||||||
{
|
{
|
||||||
private static DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
|
private static DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
|
||||||
|
|||||||
@@ -3,13 +3,34 @@
|
|||||||
#pragma warning disable CS0169
|
#pragma warning disable CS0169
|
||||||
|
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using System;
|
using Newtonsoft.Json.Linq;
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace Discord.API.Models
|
namespace Discord.API.Models
|
||||||
{
|
{
|
||||||
internal static class VoiceWebSocketCommands
|
internal static class VoiceWebSocketCommands
|
||||||
{
|
{
|
||||||
|
public class WebSocketMessage
|
||||||
|
{
|
||||||
|
[JsonProperty(PropertyName = "op")]
|
||||||
|
public int Operation;
|
||||||
|
[JsonProperty(PropertyName = "d")]
|
||||||
|
public object Payload;
|
||||||
|
}
|
||||||
|
internal abstract class WebSocketMessage<T> : WebSocketMessage
|
||||||
|
where T : new()
|
||||||
|
{
|
||||||
|
public WebSocketMessage() { Payload = new T(); }
|
||||||
|
public WebSocketMessage(int op) { Operation = op; Payload = new T(); }
|
||||||
|
public WebSocketMessage(int op, T payload) { Operation = op; Payload = payload; }
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public new T Payload
|
||||||
|
{
|
||||||
|
get { if (base.Payload is JToken) { base.Payload = (base.Payload as JToken).ToObject<T>(); } return (T)base.Payload; }
|
||||||
|
set { base.Payload = value; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public sealed class KeepAlive : WebSocketMessage<object>
|
public sealed class KeepAlive : WebSocketMessage<object>
|
||||||
{
|
{
|
||||||
public KeepAlive() : base(3, null) { }
|
public KeepAlive() : base(3, null) { }
|
||||||
@@ -29,12 +50,12 @@ namespace Discord.API.Models
|
|||||||
public string Token;
|
public string Token;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public sealed class Login2 : WebSocketMessage<Login.Data>
|
public sealed class Login2 : WebSocketMessage<Login2.Data>
|
||||||
{
|
{
|
||||||
public Login2() : base(1) { }
|
public Login2() : base(1) { }
|
||||||
public class Data
|
public class Data
|
||||||
{
|
{
|
||||||
public class PCData
|
public class SocketInfo
|
||||||
{
|
{
|
||||||
[JsonProperty(PropertyName = "address")]
|
[JsonProperty(PropertyName = "address")]
|
||||||
public string Address;
|
public string Address;
|
||||||
@@ -45,8 +66,8 @@ namespace Discord.API.Models
|
|||||||
}
|
}
|
||||||
[JsonProperty(PropertyName = "protocol")]
|
[JsonProperty(PropertyName = "protocol")]
|
||||||
public string Protocol = "udp";
|
public string Protocol = "udp";
|
||||||
[JsonProperty(PropertyName = "token")]
|
[JsonProperty(PropertyName = "data")]
|
||||||
public string Token;
|
public SocketInfo SocketData = new SocketInfo();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,5 +8,24 @@ namespace Discord.API.Models
|
|||||||
{
|
{
|
||||||
internal static class VoiceWebSocketEvents
|
internal static class VoiceWebSocketEvents
|
||||||
{
|
{
|
||||||
|
public sealed class Ready
|
||||||
|
{
|
||||||
|
[JsonProperty(PropertyName = "ssrc")]
|
||||||
|
public int SSRC;
|
||||||
|
[JsonProperty(PropertyName = "port")]
|
||||||
|
public int Port;
|
||||||
|
[JsonProperty(PropertyName = "modes")]
|
||||||
|
public string[] Modes;
|
||||||
|
[JsonProperty(PropertyName = "heartbeat_interval")]
|
||||||
|
public int HeartbeatInterval;
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class JoinServer
|
||||||
|
{
|
||||||
|
[JsonProperty(PropertyName = "secret_key")]
|
||||||
|
public byte[] SecretKey;
|
||||||
|
[JsonProperty(PropertyName = "mode")]
|
||||||
|
public string Mode;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -249,6 +249,18 @@ namespace Discord
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Voice
|
//Voice
|
||||||
|
public event EventHandler VoiceConnected;
|
||||||
|
private void RaiseVoiceConnected()
|
||||||
|
{
|
||||||
|
if (VoiceConnected != null)
|
||||||
|
VoiceConnected(this, EventArgs.Empty);
|
||||||
|
}
|
||||||
|
public event EventHandler VoiceDisconnected;
|
||||||
|
private void RaiseVoiceDisconnected()
|
||||||
|
{
|
||||||
|
if (VoiceDisconnected != null)
|
||||||
|
VoiceDisconnected(this, EventArgs.Empty);
|
||||||
|
}
|
||||||
public sealed class VoiceServerUpdatedEventArgs : EventArgs
|
public sealed class VoiceServerUpdatedEventArgs : EventArgs
|
||||||
{
|
{
|
||||||
public readonly Server Server;
|
public readonly Server Server;
|
||||||
|
|||||||
@@ -19,6 +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 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;
|
||||||
@@ -26,12 +27,8 @@ namespace Discord
|
|||||||
private readonly Random _rand;
|
private readonly Random _rand;
|
||||||
|
|
||||||
private volatile CancellationTokenSource _disconnectToken;
|
private volatile CancellationTokenSource _disconnectToken;
|
||||||
private volatile Task _tasks;
|
private volatile Task _tasks;
|
||||||
|
private string _currentVoiceServerId, _currentVoiceEndpoint, _currentVoiceToken;
|
||||||
#if !DNXCORE50
|
|
||||||
private readonly DiscordVoiceWebSocket _voiceWebSocket;
|
|
||||||
private string _currentVoiceServer, _currentVoiceToken;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/// <summary> Returns the User object for the current logged in user. </summary>
|
/// <summary> Returns the User object for the current logged in user. </summary>
|
||||||
public User User { get; private set; }
|
public User User { get; private set; }
|
||||||
@@ -64,6 +61,9 @@ namespace Discord
|
|||||||
/// <remarks> This collection does not guarantee any ordering. </remarks>
|
/// <remarks> This collection does not guarantee any ordering. </remarks>
|
||||||
public IEnumerable<Role> Roles => _roles;
|
public IEnumerable<Role> Roles => _roles;
|
||||||
private readonly AsyncCache<Role, API.Models.Role> _roles;
|
private readonly AsyncCache<Role, API.Models.Role> _roles;
|
||||||
|
|
||||||
|
public string CurrentVoiceServerId { get { return _currentVoiceEndpoint != null ? _currentVoiceToken : null; } }
|
||||||
|
public Server CurrentVoiceServer => _servers[CurrentVoiceServerId];
|
||||||
|
|
||||||
/// <summary> Returns true if the user has successfully logged in and the websocket connection has been established. </summary>
|
/// <summary> Returns true if the user has successfully logged in and the websocket connection has been established. </summary>
|
||||||
public bool IsConnected => _isConnected;
|
public bool IsConnected => _isConnected;
|
||||||
@@ -306,34 +306,15 @@ namespace Discord
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
_webSocket.OnDebugMessage += (s, e) => RaiseOnDebugMessage(e.Message);
|
_webSocket.OnDebugMessage += (s, e) => RaiseOnDebugMessage(e.Message);
|
||||||
|
|
||||||
#if !DNXCORE50
|
|
||||||
_voiceWebSocket = new DiscordVoiceWebSocket(_config.WebSocketInterval);
|
_voiceWebSocket = new DiscordVoiceWebSocket(_config.WebSocketInterval);
|
||||||
_voiceWebSocket.Connected += (s, e) => RaiseConnected();
|
_voiceWebSocket.Connected += (s, e) => RaiseVoiceConnected();
|
||||||
_voiceWebSocket.Disconnected += async (s, e) =>
|
_voiceWebSocket.Disconnected += (s, e) =>
|
||||||
{
|
{
|
||||||
//Reconnect if we didn't cause the disconnect
|
//TODO: Reconnect if we didn't cause the disconnect
|
||||||
RaiseDisconnected();
|
RaiseVoiceDisconnected();
|
||||||
while (!_disconnectToken.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await Task.Delay(_config.ReconnectDelay);
|
|
||||||
await _voiceWebSocket.ConnectAsync(Endpoints.WebSocket_Hub);
|
|
||||||
if (_currentVoiceServer != null)
|
|
||||||
await _voiceWebSocket.Login(_currentVoiceServer, UserId, SessionId, _currentVoiceToken);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
RaiseOnDebugMessage($"Reconnect Failed: {ex.Message}");
|
|
||||||
//Net is down? We can keep trying to reconnect until the user runs Disconnect()
|
|
||||||
await Task.Delay(_config.FailedReconnectDelay);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
_voiceWebSocket.OnDebugMessage += (s, e) => RaiseOnVoiceDebugMessage(e.Message);
|
_voiceWebSocket.OnDebugMessage += (s, e) => RaiseOnVoiceDebugMessage(e.Message);
|
||||||
#endif
|
|
||||||
|
|
||||||
#pragma warning disable CS1998 //Disable unused async keyword warning
|
#pragma warning disable CS1998 //Disable unused async keyword warning
|
||||||
_webSocket.GotEvent += async (s, e) =>
|
_webSocket.GotEvent += async (s, e) =>
|
||||||
@@ -587,16 +568,14 @@ namespace Discord
|
|||||||
var server = _servers[data.ServerId];
|
var server = _servers[data.ServerId];
|
||||||
server.VoiceServer = data.Endpoint;
|
server.VoiceServer = data.Endpoint;
|
||||||
try { RaiseVoiceServerUpdated(server, data.Endpoint); } catch { }
|
try { RaiseVoiceServerUpdated(server, data.Endpoint); } catch { }
|
||||||
|
|
||||||
#if !DNXCORE50
|
if (data.ServerId == _currentVoiceServerId)
|
||||||
if (data.ServerId == _currentVoiceServer)
|
|
||||||
{
|
{
|
||||||
_currentVoiceServer = data.Endpoint;
|
_currentVoiceEndpoint = data.Endpoint.Split(':')[0];
|
||||||
_currentVoiceToken = data.Token;
|
_currentVoiceToken = data.Token;
|
||||||
await _voiceWebSocket.ConnectAsync(_currentVoiceServer);
|
await _voiceWebSocket.ConnectAsync("wss://" + _currentVoiceEndpoint);
|
||||||
await _voiceWebSocket.Login(_currentVoiceServer, UserId, SessionId, _currentVoiceToken);
|
await _voiceWebSocket.Login(_currentVoiceServerId, UserId, SessionId, _currentVoiceToken);
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -667,7 +646,6 @@ namespace Discord
|
|||||||
catch { }
|
catch { }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary> Returns the user with the specified id, or null if none was found. </summary>
|
/// <summary> Returns the user with the specified id, or null if none was found. </summary>
|
||||||
public User GetUser(string id) => _users[id];
|
public User GetUser(string id) => _users[id];
|
||||||
/// <summary> Returns the user with the specified name and discriminator, or null if none was found. </summary>
|
/// <summary> Returns the user with the specified name and discriminator, or null if none was found. </summary>
|
||||||
@@ -918,9 +896,7 @@ namespace Discord
|
|||||||
_tasks = _tasks.ContinueWith(async x =>
|
_tasks = _tasks.ContinueWith(async x =>
|
||||||
{
|
{
|
||||||
await _webSocket.DisconnectAsync();
|
await _webSocket.DisconnectAsync();
|
||||||
#if !DNXCORE50
|
|
||||||
await _voiceWebSocket.DisconnectAsync();
|
await _voiceWebSocket.DisconnectAsync();
|
||||||
#endif
|
|
||||||
|
|
||||||
//Clear send queue
|
//Clear send queue
|
||||||
Message ignored;
|
Message ignored;
|
||||||
@@ -1252,6 +1228,29 @@ namespace Discord
|
|||||||
|
|
||||||
|
|
||||||
//Voice
|
//Voice
|
||||||
|
public Task JoinVoice(Server server, Channel channel)
|
||||||
|
=> JoinVoice(server.Id, channel.Id);
|
||||||
|
public Task JoinVoice(Server server, string channelId)
|
||||||
|
=> JoinVoice(server.Id, channelId);
|
||||||
|
public Task JoinVoice(string serverId, Channel channel)
|
||||||
|
=> JoinVoice(serverId, channel.Id);
|
||||||
|
public async Task JoinVoice(string serverId, string channelId)
|
||||||
|
{
|
||||||
|
await LeaveVoice();
|
||||||
|
_currentVoiceServerId = serverId;
|
||||||
|
_webSocket.JoinVoice(serverId, channelId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task LeaveVoice()
|
||||||
|
{
|
||||||
|
await _voiceWebSocket.DisconnectAsync();
|
||||||
|
if (_currentVoiceEndpoint != null)
|
||||||
|
_webSocket.LeaveVoice();
|
||||||
|
_currentVoiceEndpoint = null;
|
||||||
|
_currentVoiceServerId = null;
|
||||||
|
_currentVoiceToken = null;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary> Mutes a user on the provided server. </summary>
|
/// <summary> Mutes a user on the provided server. </summary>
|
||||||
public Task Mute(Server server, User user)
|
public Task Mute(Server server, User user)
|
||||||
=> Mute(server.Id, user.Id);
|
=> Mute(server.Id, user.Id);
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
using Discord.API.Models;
|
using Discord.API.Models;
|
||||||
using Discord.Helpers;
|
using Discord.Helpers;
|
||||||
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using System;
|
using System;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using WebSocketMessage = Discord.API.Models.TextWebSocketCommands.WebSocketMessage;
|
||||||
|
|
||||||
namespace Discord
|
namespace Discord
|
||||||
{
|
{
|
||||||
@@ -51,22 +53,25 @@ namespace Discord
|
|||||||
SetConnected();
|
SetConnected();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void ProcessMessage(WebSocketMessage msg)
|
protected override void ProcessMessage(string json)
|
||||||
{
|
{
|
||||||
|
var msg = JsonConvert.DeserializeObject<WebSocketMessage>(json);
|
||||||
switch (msg.Operation)
|
switch (msg.Operation)
|
||||||
{
|
{
|
||||||
case 0:
|
case 0:
|
||||||
if (msg.Type == "READY")
|
|
||||||
{
|
{
|
||||||
var payload = (msg.Payload as JToken).ToObject<TextWebSocketEvents.Ready>();
|
if (msg.Type == "READY")
|
||||||
_heartbeatInterval = payload.HeartbeatInterval;
|
{
|
||||||
QueueMessage(new TextWebSocketCommands.UpdateStatus());
|
var payload = (msg.Payload as JToken).ToObject<TextWebSocketEvents.Ready>();
|
||||||
QueueMessage(new TextWebSocketCommands.KeepAlive());
|
_heartbeatInterval = payload.HeartbeatInterval;
|
||||||
_connectWaitOnLogin.Set(); //Pre-Event
|
QueueMessage(new TextWebSocketCommands.UpdateStatus());
|
||||||
|
QueueMessage(GetKeepAlive());
|
||||||
|
_connectWaitOnLogin.Set(); //Pre-Event
|
||||||
|
}
|
||||||
|
RaiseGotEvent(msg.Type, msg.Payload as JToken);
|
||||||
|
if (msg.Type == "READY")
|
||||||
|
_connectWaitOnLogin2.Set(); //Post-Event
|
||||||
}
|
}
|
||||||
RaiseGotEvent(msg.Type, msg.Payload as JToken);
|
|
||||||
if (msg.Type == "READY")
|
|
||||||
_connectWaitOnLogin2.Set(); //Post-Event
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
RaiseOnDebugMessage("Unknown WebSocket operation ID: " + msg.Operation);
|
RaiseOnDebugMessage("Unknown WebSocket operation ID: " + msg.Operation);
|
||||||
@@ -74,7 +79,7 @@ namespace Discord
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override WebSocketMessage GetKeepAlive()
|
protected override object GetKeepAlive()
|
||||||
{
|
{
|
||||||
return new TextWebSocketCommands.KeepAlive();
|
return new TextWebSocketCommands.KeepAlive();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
#if !DNXCORE50
|
//#if !DNXCORE50
|
||||||
using Discord.API.Models;
|
using Discord.API.Models;
|
||||||
using Discord.Helpers;
|
using Discord.Helpers;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
using System.Net.Sockets;
|
using System.Net.Sockets;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using WebSocketMessage = Discord.API.Models.VoiceWebSocketCommands.WebSocketMessage;
|
||||||
|
|
||||||
namespace Discord
|
namespace Discord
|
||||||
{
|
{
|
||||||
@@ -14,20 +18,28 @@ namespace Discord
|
|||||||
{
|
{
|
||||||
private const int ReadyTimeout = 2500; //Max time in milliseconds between connecting to Discord and receiving a READY event
|
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;
|
||||||
private UdpClient _udp;
|
private UdpClient _udp;
|
||||||
private ConcurrentQueue<byte[]> _sendQueue;
|
private ConcurrentQueue<byte[]> _sendQueue;
|
||||||
|
private string _ip;
|
||||||
|
|
||||||
public DiscordVoiceWebSocket(int interval)
|
public DiscordVoiceWebSocket(int interval)
|
||||||
: base(interval)
|
: base(interval)
|
||||||
{
|
{
|
||||||
_connectWaitOnLogin = new ManualResetEventSlim(false);
|
_connectWaitOnLogin = new ManualResetEventSlim(false);
|
||||||
_connectWaitOnLogin2 = new ManualResetEventSlim(false);
|
|
||||||
|
|
||||||
_udp = new UdpClient();
|
|
||||||
_sendQueue = new ConcurrentQueue<byte[]>();
|
_sendQueue = new ConcurrentQueue<byte[]>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void OnConnect()
|
||||||
|
{
|
||||||
|
_udp = new UdpClient(0);
|
||||||
|
}
|
||||||
|
protected override void OnDisconnect()
|
||||||
|
{
|
||||||
|
_udp = null;
|
||||||
|
}
|
||||||
|
|
||||||
protected override Task[] CreateTasks(CancellationToken cancelToken)
|
protected override Task[] CreateTasks(CancellationToken cancelToken)
|
||||||
{
|
{
|
||||||
return new Task[]
|
return new Task[]
|
||||||
@@ -37,14 +49,14 @@ namespace Discord
|
|||||||
Task.Factory.StartNew(WatcherAsync, cancelToken, TaskCreationOptions.LongRunning, TaskScheduler.Default).Result
|
Task.Factory.StartNew(WatcherAsync, cancelToken, TaskCreationOptions.LongRunning, TaskScheduler.Default).Result
|
||||||
}.Concat(base.CreateTasks(cancelToken)).ToArray();
|
}.Concat(base.CreateTasks(cancelToken)).ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Login(string serverId, string userId, string sessionId, string token)
|
public async Task Login(string serverId, string userId, string sessionId, string token)
|
||||||
{
|
{
|
||||||
var cancelToken = _disconnectToken.Token;
|
var cancelToken = _disconnectToken.Token;
|
||||||
|
|
||||||
_connectWaitOnLogin.Reset();
|
_connectWaitOnLogin.Reset();
|
||||||
_connectWaitOnLogin2.Reset();
|
|
||||||
|
|
||||||
string ip = await Http.Get("http://ipinfo.io/ip");
|
_ip = (await Http.Get("http://ipinfo.io/ip")).Trim();
|
||||||
|
|
||||||
VoiceWebSocketCommands.Login msg = new VoiceWebSocketCommands.Login();
|
VoiceWebSocketCommands.Login msg = new VoiceWebSocketCommands.Login();
|
||||||
msg.Payload.ServerId = serverId;
|
msg.Payload.ServerId = serverId;
|
||||||
@@ -52,18 +64,17 @@ namespace Discord
|
|||||||
msg.Payload.Token = token;
|
msg.Payload.Token = token;
|
||||||
msg.Payload.UserId = userId;
|
msg.Payload.UserId = userId;
|
||||||
await SendMessage(msg, cancelToken);
|
await SendMessage(msg, cancelToken);
|
||||||
|
System.Diagnostics.Debug.WriteLine("<<< " + JsonConvert.SerializeObject(msg));
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!_connectWaitOnLogin.Wait(ReadyTimeout, cancelToken)) //Waiting on READY message
|
if (!_connectWaitOnLogin.Wait(ReadyTimeout, cancelToken)) //Waiting on JoinServer message
|
||||||
throw new Exception("No reply from Discord server");
|
throw new Exception("No reply from Discord server");
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("Bad Token");
|
throw new InvalidOperationException("Bad Token");
|
||||||
}
|
}
|
||||||
try { _connectWaitOnLogin2.Wait(cancelToken); } //Waiting on READY handler
|
|
||||||
catch (OperationCanceledException) { return; }
|
|
||||||
|
|
||||||
SetConnected();
|
SetConnected();
|
||||||
}
|
}
|
||||||
@@ -105,21 +116,57 @@ namespace Discord
|
|||||||
{
|
{
|
||||||
await Task.Delay(-1, _disconnectToken.Token);
|
await Task.Delay(-1, _disconnectToken.Token);
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException) { }
|
catch (TaskCanceledException) { }
|
||||||
_udp.Close();
|
#if DNXCORE50
|
||||||
|
finally { _udp.Dispose(); }
|
||||||
|
#else
|
||||||
|
finally { _udp.Close(); }
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void ProcessMessage(WebSocketMessage msg)
|
protected override void ProcessMessage(string json)
|
||||||
{
|
{
|
||||||
|
var msg = JsonConvert.DeserializeObject<WebSocketMessage>(json);
|
||||||
|
System.Diagnostics.Debug.WriteLine(">>> " + JsonConvert.SerializeObject(msg));
|
||||||
|
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);
|
||||||
|
|
||||||
|
System.Diagnostics.Debug.WriteLine("<<< " + JsonConvert.SerializeObject(login2));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
{
|
||||||
|
var payload = (msg.Payload as JToken).ToObject<VoiceWebSocketEvents.JoinServer>();
|
||||||
|
QueueMessage(GetKeepAlive());
|
||||||
|
|
||||||
|
System.Diagnostics.Debug.WriteLine("<<< " + JsonConvert.SerializeObject(GetKeepAlive()));
|
||||||
|
_connectWaitOnLogin.Set();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
RaiseOnDebugMessage("Unknown WebSocket operation ID: " + msg.Operation);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
private void ProcessUdpMessage(UdpReceiveResult msg)
|
private void ProcessUdpMessage(UdpReceiveResult msg)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override WebSocketMessage GetKeepAlive()
|
protected override object GetKeepAlive()
|
||||||
{
|
{
|
||||||
return new VoiceWebSocketCommands.KeepAlive();
|
return new VoiceWebSocketCommands.KeepAlive();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
//#endif
|
||||||
@@ -43,6 +43,8 @@ namespace Discord
|
|||||||
_webSocket.Options.KeepAliveInterval = TimeSpan.Zero;
|
_webSocket.Options.KeepAliveInterval = TimeSpan.Zero;
|
||||||
await _webSocket.ConnectAsync(new Uri(url), cancelToken);
|
await _webSocket.ConnectAsync(new Uri(url), cancelToken);
|
||||||
|
|
||||||
|
OnConnect();
|
||||||
|
|
||||||
_tasks = Task.WhenAll(CreateTasks(cancelToken))
|
_tasks = Task.WhenAll(CreateTasks(cancelToken))
|
||||||
.ContinueWith(x =>
|
.ContinueWith(x =>
|
||||||
{
|
{
|
||||||
@@ -58,6 +60,8 @@ namespace Discord
|
|||||||
byte[] ignored;
|
byte[] ignored;
|
||||||
while (_sendQueue.TryDequeue(out ignored)) { }
|
while (_sendQueue.TryDequeue(out ignored)) { }
|
||||||
|
|
||||||
|
OnDisconnect();
|
||||||
|
|
||||||
if (_isConnected)
|
if (_isConnected)
|
||||||
{
|
{
|
||||||
_isConnected = false;
|
_isConnected = false;
|
||||||
@@ -75,6 +79,8 @@ namespace Discord
|
|||||||
try { await _tasks; } catch (NullReferenceException) { }
|
try { await _tasks; } catch (NullReferenceException) { }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
protected virtual void OnConnect() { }
|
||||||
|
protected virtual void OnDisconnect() { }
|
||||||
|
|
||||||
protected void SetConnected()
|
protected void SetConnected()
|
||||||
{
|
{
|
||||||
@@ -117,8 +123,7 @@ namespace Discord
|
|||||||
}
|
}
|
||||||
while (!result.EndOfMessage);
|
while (!result.EndOfMessage);
|
||||||
|
|
||||||
var msg = JsonConvert.DeserializeObject<WebSocketMessage>(builder.ToString());
|
ProcessMessage(builder.ToString());
|
||||||
ProcessMessage(msg);
|
|
||||||
|
|
||||||
builder.Clear();
|
builder.Clear();
|
||||||
}
|
}
|
||||||
@@ -152,8 +157,8 @@ namespace Discord
|
|||||||
finally { _disconnectToken.Cancel(); }
|
finally { _disconnectToken.Cancel(); }
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract void ProcessMessage(WebSocketMessage msg);
|
protected abstract void ProcessMessage(string json);
|
||||||
protected abstract WebSocketMessage GetKeepAlive();
|
protected abstract object GetKeepAlive();
|
||||||
|
|
||||||
protected void QueueMessage(object message)
|
protected void QueueMessage(object message)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ using System.Reflection;
|
|||||||
using System.Net;
|
using System.Net;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
using Discord.API;
|
||||||
|
|
||||||
#if TEST_RESPONSES
|
#if TEST_RESPONSES
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
@@ -23,7 +24,9 @@ namespace Discord.Helpers
|
|||||||
#endif
|
#endif
|
||||||
private static readonly HttpClient _client;
|
private static readonly HttpClient _client;
|
||||||
private static readonly HttpMethod _patch;
|
private static readonly HttpMethod _patch;
|
||||||
|
#if TEST_RESPONSES
|
||||||
private static readonly JsonSerializerSettings _settings;
|
private static readonly JsonSerializerSettings _settings;
|
||||||
|
#endif
|
||||||
|
|
||||||
static Http()
|
static Http()
|
||||||
{
|
{
|
||||||
@@ -41,12 +44,12 @@ namespace Discord.Helpers
|
|||||||
string version = typeof(Http).GetTypeInfo().Assembly.GetName().Version.ToString(2);
|
string version = typeof(Http).GetTypeInfo().Assembly.GetName().Version.ToString(2);
|
||||||
_client.DefaultRequestHeaders.Add("user-agent", $"Discord.Net/{version} (https://github.com/RogueException/Discord.Net)");
|
_client.DefaultRequestHeaders.Add("user-agent", $"Discord.Net/{version} (https://github.com/RogueException/Discord.Net)");
|
||||||
|
|
||||||
_settings = new JsonSerializerSettings();
|
|
||||||
#if TEST_RESPONSES
|
#if TEST_RESPONSES
|
||||||
|
_settings = new JsonSerializerSettings();
|
||||||
_settings.CheckAdditionalContent = true;
|
_settings.CheckAdditionalContent = true;
|
||||||
_settings.MissingMemberHandling = MissingMemberHandling.Error;
|
_settings.MissingMemberHandling = MissingMemberHandling.Error;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string _token;
|
private static string _token;
|
||||||
public static string Token
|
public static string Token
|
||||||
@@ -121,14 +124,18 @@ namespace Discord.Helpers
|
|||||||
where ResponseT : class
|
where ResponseT : class
|
||||||
{
|
{
|
||||||
string responseJson = await SendRequest(method, path, content, true);
|
string responseJson = await SendRequest(method, path, content, true);
|
||||||
var response = JsonConvert.DeserializeObject<ResponseT>(responseJson, _settings);
|
#if TEST_RESPONSES
|
||||||
return response;
|
if (path.StartsWith(Endpoints.BaseApi))
|
||||||
|
return JsonConvert.DeserializeObject<ResponseT>(responseJson, _settings);
|
||||||
|
#endif
|
||||||
|
return JsonConvert.DeserializeObject<ResponseT>(responseJson);
|
||||||
}
|
}
|
||||||
private static async Task<string> Send(HttpMethod method, string path, HttpContent content)
|
private static async Task<string> Send(HttpMethod method, string path, HttpContent content)
|
||||||
{
|
{
|
||||||
string responseJson = await SendRequest(method, path, content, _isDebug);
|
string responseJson = await SendRequest(method, path, content, _isDebug);
|
||||||
#if TEST_RESPONSES
|
#if TEST_RESPONSES
|
||||||
CheckEmptyResponse(responseJson);
|
if (path.StartsWith(Endpoints.BaseApi))
|
||||||
|
CheckEmptyResponse(responseJson);
|
||||||
#endif
|
#endif
|
||||||
return responseJson;
|
return responseJson;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,6 +54,10 @@ namespace Discord
|
|||||||
|
|
||||||
/// <summary> Returns a collection of all channels within this server. </summary>
|
/// <summary> Returns a collection of all channels within this server. </summary>
|
||||||
public IEnumerable<Channel> Channels => _client.Channels.Where(x => x.ServerId == Id);
|
public IEnumerable<Channel> Channels => _client.Channels.Where(x => x.ServerId == Id);
|
||||||
|
/// <summary> Returns a collection of all channels within this server. </summary>
|
||||||
|
public IEnumerable<Channel> TextChannels => _client.Channels.Where(x => x.ServerId == Id && x.Type == ChannelTypes.Text);
|
||||||
|
/// <summary> Returns a collection of all channels within this server. </summary>
|
||||||
|
public IEnumerable<Channel> VoiceChannels => _client.Channels.Where(x => x.ServerId == Id && x.Type == ChannelTypes.Voice);
|
||||||
/// <summary> Returns a collection of all roles within this server. </summary>
|
/// <summary> Returns a collection of all roles within this server. </summary>
|
||||||
public IEnumerable<Role> Roles => _client.Roles.Where(x => x.ServerId == Id);
|
public IEnumerable<Role> Roles => _client.Roles.Where(x => x.ServerId == Id);
|
||||||
|
|
||||||
@@ -78,8 +82,10 @@ namespace Discord
|
|||||||
member.VoiceChannelId = extendedModel.ChannelId;
|
member.VoiceChannelId = extendedModel.ChannelId;
|
||||||
member.IsDeafened = extendedModel.IsDeafened;
|
member.IsDeafened = extendedModel.IsDeafened;
|
||||||
member.IsMuted = extendedModel.IsMuted;
|
member.IsMuted = extendedModel.IsMuted;
|
||||||
member.IsSelfDeafened = extendedModel.IsSelfDeafened;
|
if (extendedModel.IsSelfDeafened.HasValue)
|
||||||
member.IsSelfMuted = extendedModel.IsSelfMuted;
|
member.IsSelfDeafened = extendedModel.IsSelfDeafened.Value;
|
||||||
|
if (extendedModel.IsSelfMuted.HasValue)
|
||||||
|
member.IsSelfMuted = extendedModel.IsSelfMuted.Value;
|
||||||
member.IsSuppressed = extendedModel.IsSuppressed;
|
member.IsSuppressed = extendedModel.IsSuppressed;
|
||||||
member.SessionId = extendedModel.SessionId;
|
member.SessionId = extendedModel.SessionId;
|
||||||
member.Token = extendedModel.Token;
|
member.Token = extendedModel.Token;
|
||||||
|
|||||||
@@ -39,6 +39,7 @@
|
|||||||
"System.IO.Compression": "4.0.0",
|
"System.IO.Compression": "4.0.0",
|
||||||
"System.Linq": "4.0.0",
|
"System.Linq": "4.0.0",
|
||||||
"System.Net.Requests": "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.Net.WebSockets.Client": "4.0.0-beta-23123",
|
||||||
"System.Runtime": "4.0.20",
|
"System.Runtime": "4.0.20",
|
||||||
"System.Text.RegularExpressions": "4.0.10"
|
"System.Text.RegularExpressions": "4.0.10"
|
||||||
|
|||||||
Reference in New Issue
Block a user