From c6af57974b14b059b174d39905e6804a27744965 Mon Sep 17 00:00:00 2001 From: Brandon Smith Date: Sun, 23 Aug 2015 16:59:49 -0300 Subject: [PATCH] Added connecting to voice chat. --- global.json | 2 +- src/Discord.Net/API/Models/Common.cs | 28 +------ .../API/Models/TextWebSocketCommands.cs | 24 ++++++ .../API/Models/VoiceWebSocketCommands.cs | 33 ++++++-- .../API/Models/VoiceWebSocketEvents.cs | 19 +++++ src/Discord.Net/DiscordClient.Events.cs | 12 +++ src/Discord.Net/DiscordClient.cs | 79 +++++++++---------- src/Discord.Net/DiscordTextWebSocket.cs | 27 ++++--- src/Discord.Net/DiscordVoiceWebSocket.cs | 75 ++++++++++++++---- src/Discord.Net/DiscordWebSocket.cs | 13 ++- src/Discord.Net/Helpers/Http.cs | 17 ++-- src/Discord.Net/Server.cs | 10 ++- src/Discord.Net/project.json | 1 + 13 files changed, 231 insertions(+), 109 deletions(-) diff --git a/global.json b/global.json index e59a3a1b5..4a056d884 100644 --- a/global.json +++ b/global.json @@ -3,6 +3,6 @@ "sdk": { "version": "1.0.0-beta6", "architecture": "x64", - "runtime": "clr" + "runtime": "coreclr" } } \ No newline at end of file diff --git a/src/Discord.Net/API/Models/Common.cs b/src/Discord.Net/API/Models/Common.cs index 79aa5a88e..1c067c54b 100644 --- a/src/Discord.Net/API/Models/Common.cs +++ b/src/Discord.Net/API/Models/Common.cs @@ -8,30 +8,6 @@ using System; 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 : 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(); } return (T)base.Payload; } - set { base.Payload = value; } - } - } - //Users internal class UserReference { @@ -83,9 +59,9 @@ namespace Discord.API.Models [JsonProperty(PropertyName = "session_id")] public string SessionId; [JsonProperty(PropertyName = "self_mute")] - public bool IsSelfMuted; + public bool? IsSelfMuted; [JsonProperty(PropertyName = "self_deaf")] - public bool IsSelfDeafened; + public bool? IsSelfDeafened; [JsonProperty(PropertyName = "mute")] public bool IsMuted; [JsonProperty(PropertyName = "deaf")] diff --git a/src/Discord.Net/API/Models/TextWebSocketCommands.cs b/src/Discord.Net/API/Models/TextWebSocketCommands.cs index c56ca394f..a1e8edcdd 100644 --- a/src/Discord.Net/API/Models/TextWebSocketCommands.cs +++ b/src/Discord.Net/API/Models/TextWebSocketCommands.cs @@ -3,6 +3,7 @@ #pragma warning disable CS0169 using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; @@ -10,6 +11,29 @@ namespace Discord.API.Models { 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 : 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(); } return (T)base.Payload; } + set { base.Payload = value; } + } + } public sealed class KeepAlive : WebSocketMessage { private static DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); diff --git a/src/Discord.Net/API/Models/VoiceWebSocketCommands.cs b/src/Discord.Net/API/Models/VoiceWebSocketCommands.cs index d508d1c6c..81da13522 100644 --- a/src/Discord.Net/API/Models/VoiceWebSocketCommands.cs +++ b/src/Discord.Net/API/Models/VoiceWebSocketCommands.cs @@ -3,13 +3,34 @@ #pragma warning disable CS0169 using Newtonsoft.Json; -using System; -using System.Collections.Generic; +using Newtonsoft.Json.Linq; namespace Discord.API.Models { internal static class VoiceWebSocketCommands { + public class WebSocketMessage + { + [JsonProperty(PropertyName = "op")] + public int Operation; + [JsonProperty(PropertyName = "d")] + public object Payload; + } + internal abstract class WebSocketMessage : 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(); } return (T)base.Payload; } + set { base.Payload = value; } + } + } + public sealed class KeepAlive : WebSocketMessage { public KeepAlive() : base(3, null) { } @@ -29,12 +50,12 @@ namespace Discord.API.Models public string Token; } } - public sealed class Login2 : WebSocketMessage + public sealed class Login2 : WebSocketMessage { public Login2() : base(1) { } public class Data { - public class PCData + public class SocketInfo { [JsonProperty(PropertyName = "address")] public string Address; @@ -45,8 +66,8 @@ namespace Discord.API.Models } [JsonProperty(PropertyName = "protocol")] public string Protocol = "udp"; - [JsonProperty(PropertyName = "token")] - public string Token; + [JsonProperty(PropertyName = "data")] + public SocketInfo SocketData = new SocketInfo(); } } } diff --git a/src/Discord.Net/API/Models/VoiceWebSocketEvents.cs b/src/Discord.Net/API/Models/VoiceWebSocketEvents.cs index 2aa15e28d..c6bd1d9fb 100644 --- a/src/Discord.Net/API/Models/VoiceWebSocketEvents.cs +++ b/src/Discord.Net/API/Models/VoiceWebSocketEvents.cs @@ -8,5 +8,24 @@ namespace Discord.API.Models { 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; + } } } diff --git a/src/Discord.Net/DiscordClient.Events.cs b/src/Discord.Net/DiscordClient.Events.cs index 48d727b0c..41a8c46d8 100644 --- a/src/Discord.Net/DiscordClient.Events.cs +++ b/src/Discord.Net/DiscordClient.Events.cs @@ -249,6 +249,18 @@ namespace Discord } //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 readonly Server Server; diff --git a/src/Discord.Net/DiscordClient.cs b/src/Discord.Net/DiscordClient.cs index f3f0fc6d2..e3e1a8a61 100644 --- a/src/Discord.Net/DiscordClient.cs +++ b/src/Discord.Net/DiscordClient.cs @@ -19,6 +19,7 @@ namespace Discord { private readonly DiscordClientConfig _config; private readonly DiscordTextWebSocket _webSocket; + private readonly DiscordVoiceWebSocket _voiceWebSocket; private readonly ManualResetEventSlim _blockEvent; private readonly Regex _userRegex, _channelRegex; private readonly MatchEvaluator _userRegexEvaluator, _channelRegexEvaluator; @@ -26,12 +27,8 @@ namespace Discord private readonly Random _rand; private volatile CancellationTokenSource _disconnectToken; - private volatile Task _tasks; - -#if !DNXCORE50 - private readonly DiscordVoiceWebSocket _voiceWebSocket; - private string _currentVoiceServer, _currentVoiceToken; -#endif + private volatile Task _tasks; + private string _currentVoiceServerId, _currentVoiceEndpoint, _currentVoiceToken; /// Returns the User object for the current logged in user. public User User { get; private set; } @@ -64,6 +61,9 @@ namespace Discord /// This collection does not guarantee any ordering. public IEnumerable Roles => _roles; private readonly AsyncCache _roles; + + public string CurrentVoiceServerId { get { return _currentVoiceEndpoint != null ? _currentVoiceToken : null; } } + public Server CurrentVoiceServer => _servers[CurrentVoiceServerId]; /// Returns true if the user has successfully logged in and the websocket connection has been established. public bool IsConnected => _isConnected; @@ -306,34 +306,15 @@ namespace Discord } }; _webSocket.OnDebugMessage += (s, e) => RaiseOnDebugMessage(e.Message); - -#if !DNXCORE50 + _voiceWebSocket = new DiscordVoiceWebSocket(_config.WebSocketInterval); - _voiceWebSocket.Connected += (s, e) => RaiseConnected(); - _voiceWebSocket.Disconnected += async (s, e) => + _voiceWebSocket.Connected += (s, e) => RaiseVoiceConnected(); + _voiceWebSocket.Disconnected += (s, e) => { - //Reconnect if we didn't cause the disconnect - RaiseDisconnected(); - 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); - } - } + //TODO: Reconnect if we didn't cause the disconnect + RaiseVoiceDisconnected(); }; _voiceWebSocket.OnDebugMessage += (s, e) => RaiseOnVoiceDebugMessage(e.Message); -#endif #pragma warning disable CS1998 //Disable unused async keyword warning _webSocket.GotEvent += async (s, e) => @@ -587,16 +568,14 @@ namespace Discord var server = _servers[data.ServerId]; server.VoiceServer = data.Endpoint; try { RaiseVoiceServerUpdated(server, data.Endpoint); } catch { } - -#if !DNXCORE50 - if (data.ServerId == _currentVoiceServer) + + if (data.ServerId == _currentVoiceServerId) { - _currentVoiceServer = data.Endpoint; + _currentVoiceEndpoint = data.Endpoint.Split(':')[0]; _currentVoiceToken = data.Token; - await _voiceWebSocket.ConnectAsync(_currentVoiceServer); - await _voiceWebSocket.Login(_currentVoiceServer, UserId, SessionId, _currentVoiceToken); + await _voiceWebSocket.ConnectAsync("wss://" + _currentVoiceEndpoint); + await _voiceWebSocket.Login(_currentVoiceServerId, UserId, SessionId, _currentVoiceToken); } -#endif } break; @@ -667,7 +646,6 @@ namespace Discord catch { } } - /// Returns the user with the specified id, or null if none was found. public User GetUser(string id) => _users[id]; /// Returns the user with the specified name and discriminator, or null if none was found. @@ -918,9 +896,7 @@ namespace Discord _tasks = _tasks.ContinueWith(async x => { await _webSocket.DisconnectAsync(); -#if !DNXCORE50 await _voiceWebSocket.DisconnectAsync(); -#endif //Clear send queue Message ignored; @@ -1252,6 +1228,29 @@ namespace Discord //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; + } + /// Mutes a user on the provided server. public Task Mute(Server server, User user) => Mute(server.Id, user.Id); diff --git a/src/Discord.Net/DiscordTextWebSocket.cs b/src/Discord.Net/DiscordTextWebSocket.cs index 90e3fa5d2..0fa35228b 100644 --- a/src/Discord.Net/DiscordTextWebSocket.cs +++ b/src/Discord.Net/DiscordTextWebSocket.cs @@ -1,9 +1,11 @@ using Discord.API.Models; using Discord.Helpers; +using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System; using System.Threading; using System.Threading.Tasks; +using WebSocketMessage = Discord.API.Models.TextWebSocketCommands.WebSocketMessage; namespace Discord { @@ -51,22 +53,25 @@ namespace Discord SetConnected(); } - protected override void ProcessMessage(WebSocketMessage msg) + protected override void ProcessMessage(string json) { + var msg = JsonConvert.DeserializeObject(json); switch (msg.Operation) { case 0: - if (msg.Type == "READY") { - var payload = (msg.Payload as JToken).ToObject(); - _heartbeatInterval = payload.HeartbeatInterval; - QueueMessage(new TextWebSocketCommands.UpdateStatus()); - QueueMessage(new TextWebSocketCommands.KeepAlive()); - _connectWaitOnLogin.Set(); //Pre-Event + if (msg.Type == "READY") + { + var payload = (msg.Payload as JToken).ToObject(); + _heartbeatInterval = payload.HeartbeatInterval; + 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; default: 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(); } diff --git a/src/Discord.Net/DiscordVoiceWebSocket.cs b/src/Discord.Net/DiscordVoiceWebSocket.cs index 4f273c8fc..ec7d06857 100644 --- a/src/Discord.Net/DiscordVoiceWebSocket.cs +++ b/src/Discord.Net/DiscordVoiceWebSocket.cs @@ -1,12 +1,16 @@ -#if !DNXCORE50 +//#if !DNXCORE50 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 { @@ -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 ManualResetEventSlim _connectWaitOnLogin, _connectWaitOnLogin2; + private ManualResetEventSlim _connectWaitOnLogin; private UdpClient _udp; private ConcurrentQueue _sendQueue; + private string _ip; public DiscordVoiceWebSocket(int interval) : base(interval) { _connectWaitOnLogin = new ManualResetEventSlim(false); - _connectWaitOnLogin2 = new ManualResetEventSlim(false); - _udp = new UdpClient(); _sendQueue = new ConcurrentQueue(); } + protected override void OnConnect() + { + _udp = new UdpClient(0); + } + protected override void OnDisconnect() + { + _udp = null; + } + protected override Task[] CreateTasks(CancellationToken cancelToken) { return new Task[] @@ -37,14 +49,14 @@ namespace Discord 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(); - _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(); msg.Payload.ServerId = serverId; @@ -52,18 +64,17 @@ namespace Discord msg.Payload.Token = token; msg.Payload.UserId = userId; await SendMessage(msg, cancelToken); + System.Diagnostics.Debug.WriteLine("<<< " + JsonConvert.SerializeObject(msg)); 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"); } catch (OperationCanceledException) { throw new InvalidOperationException("Bad Token"); } - try { _connectWaitOnLogin2.Wait(cancelToken); } //Waiting on READY handler - catch (OperationCanceledException) { return; } SetConnected(); } @@ -105,21 +116,57 @@ namespace Discord { await Task.Delay(-1, _disconnectToken.Token); } - catch (OperationCanceledException) { } - _udp.Close(); + catch (TaskCanceledException) { } +#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(json); + System.Diagnostics.Debug.WriteLine(">>> " + JsonConvert.SerializeObject(msg)); + switch (msg.Operation) + { + case 2: + { + var payload = (msg.Payload as JToken).ToObject(); + _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(); + 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) { } - protected override WebSocketMessage GetKeepAlive() + protected override object GetKeepAlive() { return new VoiceWebSocketCommands.KeepAlive(); } } } -#endif \ No newline at end of file +//#endif \ No newline at end of file diff --git a/src/Discord.Net/DiscordWebSocket.cs b/src/Discord.Net/DiscordWebSocket.cs index 0ec80e678..40fb903ab 100644 --- a/src/Discord.Net/DiscordWebSocket.cs +++ b/src/Discord.Net/DiscordWebSocket.cs @@ -43,6 +43,8 @@ namespace Discord _webSocket.Options.KeepAliveInterval = TimeSpan.Zero; await _webSocket.ConnectAsync(new Uri(url), cancelToken); + OnConnect(); + _tasks = Task.WhenAll(CreateTasks(cancelToken)) .ContinueWith(x => { @@ -58,6 +60,8 @@ namespace Discord byte[] ignored; while (_sendQueue.TryDequeue(out ignored)) { } + OnDisconnect(); + if (_isConnected) { _isConnected = false; @@ -75,6 +79,8 @@ namespace Discord try { await _tasks; } catch (NullReferenceException) { } } } + protected virtual void OnConnect() { } + protected virtual void OnDisconnect() { } protected void SetConnected() { @@ -117,8 +123,7 @@ namespace Discord } while (!result.EndOfMessage); - var msg = JsonConvert.DeserializeObject(builder.ToString()); - ProcessMessage(msg); + ProcessMessage(builder.ToString()); builder.Clear(); } @@ -152,8 +157,8 @@ namespace Discord finally { _disconnectToken.Cancel(); } } - protected abstract void ProcessMessage(WebSocketMessage msg); - protected abstract WebSocketMessage GetKeepAlive(); + protected abstract void ProcessMessage(string json); + protected abstract object GetKeepAlive(); protected void QueueMessage(object message) { diff --git a/src/Discord.Net/Helpers/Http.cs b/src/Discord.Net/Helpers/Http.cs index 943a486d6..558542fb9 100644 --- a/src/Discord.Net/Helpers/Http.cs +++ b/src/Discord.Net/Helpers/Http.cs @@ -7,6 +7,7 @@ using System.Reflection; using System.Net; using System.IO; using System.Globalization; +using Discord.API; #if TEST_RESPONSES using System.Diagnostics; @@ -23,7 +24,9 @@ namespace Discord.Helpers #endif private static readonly HttpClient _client; private static readonly HttpMethod _patch; +#if TEST_RESPONSES private static readonly JsonSerializerSettings _settings; +#endif static Http() { @@ -41,12 +44,12 @@ namespace Discord.Helpers string version = typeof(Http).GetTypeInfo().Assembly.GetName().Version.ToString(2); _client.DefaultRequestHeaders.Add("user-agent", $"Discord.Net/{version} (https://github.com/RogueException/Discord.Net)"); - _settings = new JsonSerializerSettings(); #if TEST_RESPONSES + _settings = new JsonSerializerSettings(); _settings.CheckAdditionalContent = true; _settings.MissingMemberHandling = MissingMemberHandling.Error; #endif - } + } private static string _token; public static string Token @@ -121,14 +124,18 @@ namespace Discord.Helpers where ResponseT : class { string responseJson = await SendRequest(method, path, content, true); - var response = JsonConvert.DeserializeObject(responseJson, _settings); - return response; +#if TEST_RESPONSES + if (path.StartsWith(Endpoints.BaseApi)) + return JsonConvert.DeserializeObject(responseJson, _settings); +#endif + return JsonConvert.DeserializeObject(responseJson); } private static async Task Send(HttpMethod method, string path, HttpContent content) { string responseJson = await SendRequest(method, path, content, _isDebug); #if TEST_RESPONSES - CheckEmptyResponse(responseJson); + if (path.StartsWith(Endpoints.BaseApi)) + CheckEmptyResponse(responseJson); #endif return responseJson; } diff --git a/src/Discord.Net/Server.cs b/src/Discord.Net/Server.cs index f67d916f9..bcab3629d 100644 --- a/src/Discord.Net/Server.cs +++ b/src/Discord.Net/Server.cs @@ -54,6 +54,10 @@ namespace Discord /// Returns a collection of all channels within this server. public IEnumerable Channels => _client.Channels.Where(x => x.ServerId == Id); + /// Returns a collection of all channels within this server. + public IEnumerable TextChannels => _client.Channels.Where(x => x.ServerId == Id && x.Type == ChannelTypes.Text); + /// Returns a collection of all channels within this server. + public IEnumerable VoiceChannels => _client.Channels.Where(x => x.ServerId == Id && x.Type == ChannelTypes.Voice); /// Returns a collection of all roles within this server. public IEnumerable Roles => _client.Roles.Where(x => x.ServerId == Id); @@ -78,8 +82,10 @@ namespace Discord member.VoiceChannelId = extendedModel.ChannelId; member.IsDeafened = extendedModel.IsDeafened; member.IsMuted = extendedModel.IsMuted; - member.IsSelfDeafened = extendedModel.IsSelfDeafened; - member.IsSelfMuted = extendedModel.IsSelfMuted; + if (extendedModel.IsSelfDeafened.HasValue) + member.IsSelfDeafened = extendedModel.IsSelfDeafened.Value; + if (extendedModel.IsSelfMuted.HasValue) + member.IsSelfMuted = extendedModel.IsSelfMuted.Value; member.IsSuppressed = extendedModel.IsSuppressed; member.SessionId = extendedModel.SessionId; member.Token = extendedModel.Token; diff --git a/src/Discord.Net/project.json b/src/Discord.Net/project.json index 764f5df9c..15f9a8973 100644 --- a/src/Discord.Net/project.json +++ b/src/Discord.Net/project.json @@ -39,6 +39,7 @@ "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"