| @@ -3,6 +3,6 @@ | |||||
| "sdk": { | "sdk": { | ||||
| "version": "1.0.0-beta6", | "version": "1.0.0-beta6", | ||||
| "architecture": "x64", | "architecture": "x64", | ||||
| "runtime": "coreclr" | |||||
| "runtime": "clr" | |||||
| } | } | ||||
| } | } | ||||
| @@ -58,11 +58,17 @@ | |||||
| <Compile Include="..\Discord.Net\API\Models\Common.cs"> | <Compile Include="..\Discord.Net\API\Models\Common.cs"> | ||||
| <Link>API\Models\Common.cs</Link> | <Link>API\Models\Common.cs</Link> | ||||
| </Compile> | </Compile> | ||||
| <Compile Include="..\Discord.Net\API\Models\WebSocketCommands.cs"> | |||||
| <Link>API\Models\WebSocketCommands.cs</Link> | |||||
| <Compile Include="..\Discord.Net\API\Models\TextWebSocketCommands.cs"> | |||||
| <Link>API\Models\TextWebSocketCommands.cs</Link> | |||||
| </Compile> | </Compile> | ||||
| <Compile Include="..\Discord.Net\API\Models\WebSocketEvents.cs"> | |||||
| <Link>API\Models\WebSocketEvents.cs</Link> | |||||
| <Compile Include="..\Discord.Net\API\Models\TextWebSocketEvents.cs"> | |||||
| <Link>API\Models\TextWebSocketEvents.cs</Link> | |||||
| </Compile> | |||||
| <Compile Include="..\Discord.Net\API\Models\VoiceWebSocketCommands.cs"> | |||||
| <Link>API\Models\VoiceWebSocketCommands.cs</Link> | |||||
| </Compile> | |||||
| <Compile Include="..\Discord.Net\API\Models\VoiceWebSocketEvents.cs"> | |||||
| <Link>API\Models\VoiceWebSocketEvents.cs</Link> | |||||
| </Compile> | </Compile> | ||||
| <Compile Include="..\Discord.Net\Channel.cs"> | <Compile Include="..\Discord.Net\Channel.cs"> | ||||
| <Link>Channel.cs</Link> | <Link>Channel.cs</Link> | ||||
| @@ -79,6 +85,15 @@ | |||||
| <Compile Include="..\Discord.Net\DiscordClientConfig.cs"> | <Compile Include="..\Discord.Net\DiscordClientConfig.cs"> | ||||
| <Link>DiscordClientConfig.cs</Link> | <Link>DiscordClientConfig.cs</Link> | ||||
| </Compile> | </Compile> | ||||
| <Compile Include="..\Discord.Net\DiscordTextWebSocket.cs"> | |||||
| <Link>DiscordTextWebSocket.cs</Link> | |||||
| </Compile> | |||||
| <Compile Include="..\Discord.Net\DiscordTextWebSocket.Events.cs"> | |||||
| <Link>DiscordTextWebSocket.Events.cs</Link> | |||||
| </Compile> | |||||
| <Compile Include="..\Discord.Net\DiscordVoiceWebSocket.cs"> | |||||
| <Link>DiscordVoiceWebSocket.cs</Link> | |||||
| </Compile> | |||||
| <Compile Include="..\Discord.Net\DiscordWebSocket.cs"> | <Compile Include="..\Discord.Net\DiscordWebSocket.cs"> | ||||
| <Link>DiscordWebSocket.cs</Link> | <Link>DiscordWebSocket.cs</Link> | ||||
| </Compile> | </Compile> | ||||
| @@ -91,6 +106,9 @@ | |||||
| <Compile Include="..\Discord.Net\Helpers\Http.cs"> | <Compile Include="..\Discord.Net\Helpers\Http.cs"> | ||||
| <Link>Helpers\Http.cs</Link> | <Link>Helpers\Http.cs</Link> | ||||
| </Compile> | </Compile> | ||||
| <Compile Include="..\Discord.Net\HttpException.cs"> | |||||
| <Link>HttpException.cs</Link> | |||||
| </Compile> | |||||
| <Compile Include="..\Discord.Net\Invite.cs"> | <Compile Include="..\Discord.Net\Invite.cs"> | ||||
| <Link>Invite.cs</Link> | <Link>Invite.cs</Link> | ||||
| </Compile> | </Compile> | ||||
| @@ -115,7 +133,6 @@ | |||||
| <Compile Include="..\Discord.Net\User.cs"> | <Compile Include="..\Discord.Net\User.cs"> | ||||
| <Link>User.cs</Link> | <Link>User.cs</Link> | ||||
| </Compile> | </Compile> | ||||
| <Compile Include="HttpException.cs" /> | |||||
| <Compile Include="Properties\AssemblyInfo.cs" /> | <Compile Include="Properties\AssemblyInfo.cs" /> | ||||
| </ItemGroup> | </ItemGroup> | ||||
| <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> | <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> | ||||
| @@ -1,16 +0,0 @@ | |||||
| using System; | |||||
| using System.Net; | |||||
| namespace Discord | |||||
| { | |||||
| public class HttpException : Exception | |||||
| { | |||||
| public HttpStatusCode StatusCode { get; } | |||||
| public HttpException(HttpStatusCode statusCode) | |||||
| : base($"The server responded with error {statusCode}") | |||||
| { | |||||
| StatusCode = statusCode; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -8,7 +8,7 @@ using System.Collections.Generic; | |||||
| namespace Discord.API.Models | namespace Discord.API.Models | ||||
| { | { | ||||
| internal static class WebSocketCommands | |||||
| internal static class TextWebSocketCommands | |||||
| { | { | ||||
| public sealed class KeepAlive : WebSocketMessage<int> | public sealed class KeepAlive : WebSocketMessage<int> | ||||
| { | { | ||||
| @@ -3,11 +3,10 @@ | |||||
| #pragma warning disable CS0169 | #pragma warning disable CS0169 | ||||
| using Newtonsoft.Json; | using Newtonsoft.Json; | ||||
| using System; | |||||
| namespace Discord.API.Models | namespace Discord.API.Models | ||||
| { | { | ||||
| internal static class WebSocketEvents | |||||
| internal static class TextWebSocketEvents | |||||
| { | { | ||||
| public sealed class Ready | public sealed class Ready | ||||
| { | { | ||||
| @@ -0,0 +1,53 @@ | |||||
| //Ignore unused/unassigned variable warnings | |||||
| #pragma warning disable CS0649 | |||||
| #pragma warning disable CS0169 | |||||
| using Newtonsoft.Json; | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| namespace Discord.API.Models | |||||
| { | |||||
| internal static class VoiceWebSocketCommands | |||||
| { | |||||
| public sealed class KeepAlive : WebSocketMessage<object> | |||||
| { | |||||
| public KeepAlive() : base(3, null) { } | |||||
| } | |||||
| public sealed class Login : WebSocketMessage<Login.Data> | |||||
| { | |||||
| public Login() : base(0) { } | |||||
| public class Data | |||||
| { | |||||
| [JsonProperty(PropertyName = "server_id")] | |||||
| public string ServerId; | |||||
| [JsonProperty(PropertyName = "user_id")] | |||||
| public string UserId; | |||||
| [JsonProperty(PropertyName = "session_id")] | |||||
| public string SessionId; | |||||
| [JsonProperty(PropertyName = "token")] | |||||
| public string Token; | |||||
| } | |||||
| } | |||||
| public sealed class Login2 : WebSocketMessage<Login.Data> | |||||
| { | |||||
| public Login2() : base(1) { } | |||||
| public class Data | |||||
| { | |||||
| public class PCData | |||||
| { | |||||
| [JsonProperty(PropertyName = "address")] | |||||
| public string Address; | |||||
| [JsonProperty(PropertyName = "port")] | |||||
| public int Port; | |||||
| [JsonProperty(PropertyName = "mode")] | |||||
| public string Mode = "xsalsa20_poly1305"; | |||||
| } | |||||
| [JsonProperty(PropertyName = "protocol")] | |||||
| public string Protocol = "udp"; | |||||
| [JsonProperty(PropertyName = "token")] | |||||
| public string Token; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,12 @@ | |||||
| //Ignore unused/unassigned variable warnings | |||||
| #pragma warning disable CS0649 | |||||
| #pragma warning disable CS0169 | |||||
| using Newtonsoft.Json; | |||||
| namespace Discord.API.Models | |||||
| { | |||||
| internal static class VoiceWebSocketEvents | |||||
| { | |||||
| } | |||||
| } | |||||
| @@ -16,6 +16,12 @@ namespace Discord | |||||
| if (DebugMessage != null) | if (DebugMessage != null) | ||||
| DebugMessage(this, new LogMessageEventArgs(message)); | DebugMessage(this, new LogMessageEventArgs(message)); | ||||
| } | } | ||||
| public event EventHandler<LogMessageEventArgs> VoiceDebugMessage; | |||||
| private void RaiseOnVoiceDebugMessage(string message) | |||||
| { | |||||
| if (VoiceDebugMessage != null) | |||||
| VoiceDebugMessage(this, new LogMessageEventArgs(message)); | |||||
| } | |||||
| //General | //General | ||||
| public event EventHandler Connected; | public event EventHandler Connected; | ||||
| @@ -17,16 +17,22 @@ namespace Discord | |||||
| /// <summary> Provides a connection to the DiscordApp service. </summary> | /// <summary> Provides a connection to the DiscordApp service. </summary> | ||||
| public partial class DiscordClient | public partial class DiscordClient | ||||
| { | { | ||||
| private DiscordClientConfig _config; | |||||
| private DiscordWebSocket _webSocket; | |||||
| private ManualResetEventSlim _blockEvent; | |||||
| private volatile CancellationTokenSource _disconnectToken; | |||||
| private volatile Task _tasks; | |||||
| private readonly DiscordClientConfig _config; | |||||
| private readonly DiscordTextWebSocket _webSocket; | |||||
| private readonly ManualResetEventSlim _blockEvent; | |||||
| private readonly Regex _userRegex, _channelRegex; | private readonly Regex _userRegex, _channelRegex; | ||||
| private readonly MatchEvaluator _userRegexEvaluator, _channelRegexEvaluator; | private readonly MatchEvaluator _userRegexEvaluator, _channelRegexEvaluator; | ||||
| private readonly JsonSerializer _serializer; | private readonly JsonSerializer _serializer; | ||||
| private readonly Random _rand; | private readonly Random _rand; | ||||
| private volatile CancellationTokenSource _disconnectToken; | |||||
| private volatile Task _tasks; | |||||
| #if !DNXCORE50 | |||||
| private readonly DiscordVoiceWebSocket _voiceWebSocket; | |||||
| private string _currentVoiceServer; | |||||
| #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; } | ||||
| /// <summary> Returns the id of the current logged in user. </summary> | /// <summary> Returns the id of the current logged in user. </summary> | ||||
| @@ -276,7 +282,7 @@ namespace Discord | |||||
| user => { } | user => { } | ||||
| ); | ); | ||||
| _webSocket = new DiscordWebSocket(_config.WebSocketInterval); | |||||
| _webSocket = new DiscordTextWebSocket(_config.WebSocketInterval); | |||||
| _webSocket.Connected += (s, e) => RaiseConnected(); | _webSocket.Connected += (s, e) => RaiseConnected(); | ||||
| _webSocket.Disconnected += async (s, e) => | _webSocket.Disconnected += async (s, e) => | ||||
| { | { | ||||
| @@ -287,7 +293,8 @@ namespace Discord | |||||
| try | try | ||||
| { | { | ||||
| await Task.Delay(_config.ReconnectDelay); | await Task.Delay(_config.ReconnectDelay); | ||||
| await _webSocket.ConnectAsync(Endpoints.WebSocket_Hub, true); | |||||
| await _webSocket.ConnectAsync(Endpoints.WebSocket_Hub); | |||||
| await _webSocket.Login(); | |||||
| break; | break; | ||||
| } | } | ||||
| catch (Exception ex) | catch (Exception ex) | ||||
| @@ -298,6 +305,35 @@ 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) => | |||||
| { | |||||
| //Reconnect if we didn't cause the disconnect | |||||
| RaiseDisconnected(); | |||||
| while (!_disconnectToken.IsCancellationRequested) | |||||
| { | |||||
| try | |||||
| { | |||||
| await Task.Delay(_config.ReconnectDelay); | |||||
| await _voiceWebSocket.ConnectAsync(Endpoints.WebSocket_Hub); | |||||
| await _voiceWebSocket.Login(_currentVoiceServer, UserId, SessionId); | |||||
| 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); | |||||
| #endif | |||||
| _webSocket.GotEvent += (s, e) => | _webSocket.GotEvent += (s, e) => | ||||
| { | { | ||||
| switch (e.Type) | switch (e.Type) | ||||
| @@ -305,7 +341,7 @@ namespace Discord | |||||
| //Global | //Global | ||||
| case "READY": //Resync | case "READY": //Resync | ||||
| { | { | ||||
| var data = e.Event.ToObject<WebSocketEvents.Ready>(_serializer); | |||||
| var data = e.Event.ToObject<TextWebSocketEvents.Ready>(_serializer); | |||||
| _servers.Clear(); | _servers.Clear(); | ||||
| _channels.Clear(); | _channels.Clear(); | ||||
| @@ -324,21 +360,21 @@ namespace Discord | |||||
| //Servers | //Servers | ||||
| case "GUILD_CREATE": | case "GUILD_CREATE": | ||||
| { | { | ||||
| var data = e.Event.ToObject<WebSocketEvents.GuildCreate>(_serializer); | |||||
| var data = e.Event.ToObject<TextWebSocketEvents.GuildCreate>(_serializer); | |||||
| var server = _servers.Update(data.Id, data); | var server = _servers.Update(data.Id, data); | ||||
| try { RaiseServerCreated(server); } catch { } | try { RaiseServerCreated(server); } catch { } | ||||
| } | } | ||||
| break; | break; | ||||
| case "GUILD_UPDATE": | case "GUILD_UPDATE": | ||||
| { | { | ||||
| var data = e.Event.ToObject<WebSocketEvents.GuildUpdate>(_serializer); | |||||
| var data = e.Event.ToObject<TextWebSocketEvents.GuildUpdate>(_serializer); | |||||
| var server = _servers.Update(data.Id, data); | var server = _servers.Update(data.Id, data); | ||||
| try { RaiseServerUpdated(server); } catch { } | try { RaiseServerUpdated(server); } catch { } | ||||
| } | } | ||||
| break; | break; | ||||
| case "GUILD_DELETE": | case "GUILD_DELETE": | ||||
| { | { | ||||
| var data = e.Event.ToObject<WebSocketEvents.GuildDelete>(_serializer); | |||||
| var data = e.Event.ToObject<TextWebSocketEvents.GuildDelete>(_serializer); | |||||
| var server = _servers.Remove(data.Id); | var server = _servers.Remove(data.Id); | ||||
| if (server != null) | if (server != null) | ||||
| try { RaiseServerDestroyed(server); } catch { } | try { RaiseServerDestroyed(server); } catch { } | ||||
| @@ -348,21 +384,21 @@ namespace Discord | |||||
| //Channels | //Channels | ||||
| case "CHANNEL_CREATE": | case "CHANNEL_CREATE": | ||||
| { | { | ||||
| var data = e.Event.ToObject<WebSocketEvents.ChannelCreate>(_serializer); | |||||
| var data = e.Event.ToObject<TextWebSocketEvents.ChannelCreate>(_serializer); | |||||
| var channel = _channels.Update(data.Id, data.GuildId, data); | var channel = _channels.Update(data.Id, data.GuildId, data); | ||||
| try { RaiseChannelCreated(channel); } catch { } | try { RaiseChannelCreated(channel); } catch { } | ||||
| } | } | ||||
| break; | break; | ||||
| case "CHANNEL_UPDATE": | case "CHANNEL_UPDATE": | ||||
| { | { | ||||
| var data = e.Event.ToObject<WebSocketEvents.ChannelUpdate>(_serializer); | |||||
| var data = e.Event.ToObject<TextWebSocketEvents.ChannelUpdate>(_serializer); | |||||
| var channel = _channels.Update(data.Id, data.GuildId, data); | var channel = _channels.Update(data.Id, data.GuildId, data); | ||||
| try { RaiseChannelUpdated(channel); } catch { } | try { RaiseChannelUpdated(channel); } catch { } | ||||
| } | } | ||||
| break; | break; | ||||
| case "CHANNEL_DELETE": | case "CHANNEL_DELETE": | ||||
| { | { | ||||
| var data = e.Event.ToObject<WebSocketEvents.ChannelDelete>(_serializer); | |||||
| var data = e.Event.ToObject<TextWebSocketEvents.ChannelDelete>(_serializer); | |||||
| var channel = _channels.Remove(data.Id); | var channel = _channels.Remove(data.Id); | ||||
| if (channel != null) | if (channel != null) | ||||
| try { RaiseChannelDestroyed(channel); } catch { } | try { RaiseChannelDestroyed(channel); } catch { } | ||||
| @@ -372,7 +408,7 @@ namespace Discord | |||||
| //Members | //Members | ||||
| case "GUILD_MEMBER_ADD": | case "GUILD_MEMBER_ADD": | ||||
| { | { | ||||
| var data = e.Event.ToObject<WebSocketEvents.GuildMemberAdd>(_serializer); | |||||
| var data = e.Event.ToObject<TextWebSocketEvents.GuildMemberAdd>(_serializer); | |||||
| var user = _users.Update(data.User.Id, data.User); | var user = _users.Update(data.User.Id, data.User); | ||||
| var server = _servers[data.ServerId]; | var server = _servers[data.ServerId]; | ||||
| if (server != null) | if (server != null) | ||||
| @@ -384,7 +420,7 @@ namespace Discord | |||||
| break; | break; | ||||
| case "GUILD_MEMBER_UPDATE": | case "GUILD_MEMBER_UPDATE": | ||||
| { | { | ||||
| var data = e.Event.ToObject<WebSocketEvents.GuildMemberUpdate>(_serializer); | |||||
| var data = e.Event.ToObject<TextWebSocketEvents.GuildMemberUpdate>(_serializer); | |||||
| var user = _users.Update(data.User.Id, data.User); | var user = _users.Update(data.User.Id, data.User); | ||||
| var server = _servers[data.ServerId]; | var server = _servers[data.ServerId]; | ||||
| if (server != null) | if (server != null) | ||||
| @@ -396,7 +432,7 @@ namespace Discord | |||||
| break; | break; | ||||
| case "GUILD_MEMBER_REMOVE": | case "GUILD_MEMBER_REMOVE": | ||||
| { | { | ||||
| var data = e.Event.ToObject<WebSocketEvents.GuildMemberRemove>(_serializer); | |||||
| var data = e.Event.ToObject<TextWebSocketEvents.GuildMemberRemove>(_serializer); | |||||
| var server = _servers[data.ServerId]; | var server = _servers[data.ServerId]; | ||||
| if (server != null) | if (server != null) | ||||
| { | { | ||||
| @@ -410,21 +446,21 @@ namespace Discord | |||||
| //Roles | //Roles | ||||
| case "GUILD_ROLE_CREATE": | case "GUILD_ROLE_CREATE": | ||||
| { | { | ||||
| var data = e.Event.ToObject<WebSocketEvents.GuildRoleCreateUpdate>(_serializer); | |||||
| var data = e.Event.ToObject<TextWebSocketEvents.GuildRoleCreateUpdate>(_serializer); | |||||
| var role = _roles.Update(data.Role.Id, data.ServerId, data.Role); | var role = _roles.Update(data.Role.Id, data.ServerId, data.Role); | ||||
| try { RaiseRoleCreated(role); } catch { } | try { RaiseRoleCreated(role); } catch { } | ||||
| } | } | ||||
| break; | break; | ||||
| case "GUILD_ROLE_UPDATE": | case "GUILD_ROLE_UPDATE": | ||||
| { | { | ||||
| var data = e.Event.ToObject<WebSocketEvents.GuildRoleCreateUpdate>(_serializer); | |||||
| var data = e.Event.ToObject<TextWebSocketEvents.GuildRoleCreateUpdate>(_serializer); | |||||
| var role = _roles.Update(data.Role.Id, data.ServerId, data.Role); | var role = _roles.Update(data.Role.Id, data.ServerId, data.Role); | ||||
| try { RaiseRoleUpdated(role); } catch { } | try { RaiseRoleUpdated(role); } catch { } | ||||
| } | } | ||||
| break; | break; | ||||
| case "GUILD_ROLE_DELETE": | case "GUILD_ROLE_DELETE": | ||||
| { | { | ||||
| var data = e.Event.ToObject<WebSocketEvents.GuildRoleDelete>(_serializer); | |||||
| var data = e.Event.ToObject<TextWebSocketEvents.GuildRoleDelete>(_serializer); | |||||
| var role = _roles.Remove(data.RoleId); | var role = _roles.Remove(data.RoleId); | ||||
| if (role != null) | if (role != null) | ||||
| try { RaiseRoleDeleted(role); } catch { } | try { RaiseRoleDeleted(role); } catch { } | ||||
| @@ -434,7 +470,7 @@ namespace Discord | |||||
| //Bans | //Bans | ||||
| case "GUILD_BAN_ADD": | case "GUILD_BAN_ADD": | ||||
| { | { | ||||
| var data = e.Event.ToObject<WebSocketEvents.GuildBanAddRemove>(_serializer); | |||||
| var data = e.Event.ToObject<TextWebSocketEvents.GuildBanAddRemove>(_serializer); | |||||
| var user = _users.Update(data.User.Id, data.User); | var user = _users.Update(data.User.Id, data.User); | ||||
| var server = _servers[data.ServerId]; | var server = _servers[data.ServerId]; | ||||
| try { RaiseBanAdded(user, server); } catch { } | try { RaiseBanAdded(user, server); } catch { } | ||||
| @@ -442,7 +478,7 @@ namespace Discord | |||||
| break; | break; | ||||
| case "GUILD_BAN_REMOVE": | case "GUILD_BAN_REMOVE": | ||||
| { | { | ||||
| var data = e.Event.ToObject<WebSocketEvents.GuildBanAddRemove>(_serializer); | |||||
| var data = e.Event.ToObject<TextWebSocketEvents.GuildBanAddRemove>(_serializer); | |||||
| var user = _users.Update(data.User.Id, data.User); | var user = _users.Update(data.User.Id, data.User); | ||||
| var server = _servers[data.ServerId]; | var server = _servers[data.ServerId]; | ||||
| if (server != null && server.RemoveBan(user.Id)) | if (server != null && server.RemoveBan(user.Id)) | ||||
| @@ -455,7 +491,7 @@ namespace Discord | |||||
| //Messages | //Messages | ||||
| case "MESSAGE_CREATE": | case "MESSAGE_CREATE": | ||||
| { | { | ||||
| var data = e.Event.ToObject<WebSocketEvents.MessageCreate>(_serializer); | |||||
| var data = e.Event.ToObject<TextWebSocketEvents.MessageCreate>(_serializer); | |||||
| Message msg = null; | Message msg = null; | ||||
| bool wasLocal = _config.UseMessageQueue && data.Author.Id == UserId && data.Nonce != null; | bool wasLocal = _config.UseMessageQueue && data.Author.Id == UserId && data.Nonce != null; | ||||
| if (wasLocal) | if (wasLocal) | ||||
| @@ -478,14 +514,14 @@ namespace Discord | |||||
| break; | break; | ||||
| case "MESSAGE_UPDATE": | case "MESSAGE_UPDATE": | ||||
| { | { | ||||
| var data = e.Event.ToObject<WebSocketEvents.MessageUpdate>(_serializer); | |||||
| var data = e.Event.ToObject<TextWebSocketEvents.MessageUpdate>(_serializer); | |||||
| var msg = _messages.Update(data.Id, data.ChannelId, data); | var msg = _messages.Update(data.Id, data.ChannelId, data); | ||||
| try { RaiseMessageUpdated(msg); } catch { } | try { RaiseMessageUpdated(msg); } catch { } | ||||
| } | } | ||||
| break; | break; | ||||
| case "MESSAGE_DELETE": | case "MESSAGE_DELETE": | ||||
| { | { | ||||
| var data = e.Event.ToObject<WebSocketEvents.MessageDelete>(_serializer); | |||||
| var data = e.Event.ToObject<TextWebSocketEvents.MessageDelete>(_serializer); | |||||
| var msg = GetMessage(data.MessageId); | var msg = GetMessage(data.MessageId); | ||||
| if (msg != null) | if (msg != null) | ||||
| { | { | ||||
| @@ -496,7 +532,7 @@ namespace Discord | |||||
| break; | break; | ||||
| case "MESSAGE_ACK": | case "MESSAGE_ACK": | ||||
| { | { | ||||
| var data = e.Event.ToObject<WebSocketEvents.MessageAck>(_serializer); | |||||
| var data = e.Event.ToObject<TextWebSocketEvents.MessageAck>(_serializer); | |||||
| var msg = GetMessage(data.MessageId); | var msg = GetMessage(data.MessageId); | ||||
| if (msg != null) | if (msg != null) | ||||
| try { RaiseMessageRead(msg); } catch { } | try { RaiseMessageRead(msg); } catch { } | ||||
| @@ -506,7 +542,7 @@ namespace Discord | |||||
| //Statuses | //Statuses | ||||
| case "PRESENCE_UPDATE": | case "PRESENCE_UPDATE": | ||||
| { | { | ||||
| var data = e.Event.ToObject<WebSocketEvents.PresenceUpdate>(_serializer); | |||||
| var data = e.Event.ToObject<TextWebSocketEvents.PresenceUpdate>(_serializer); | |||||
| var user = _users.Update(data.User.Id, data.User); | var user = _users.Update(data.User.Id, data.User); | ||||
| var server = _servers[data.ServerId]; | var server = _servers[data.ServerId]; | ||||
| if (server != null) | if (server != null) | ||||
| @@ -518,7 +554,7 @@ namespace Discord | |||||
| break; | break; | ||||
| case "VOICE_STATE_UPDATE": | case "VOICE_STATE_UPDATE": | ||||
| { | { | ||||
| var data = e.Event.ToObject<WebSocketEvents.VoiceStateUpdate>(_serializer); | |||||
| var data = e.Event.ToObject<TextWebSocketEvents.VoiceStateUpdate>(_serializer); | |||||
| var server = _servers[data.ServerId]; | var server = _servers[data.ServerId]; | ||||
| if (server != null) | if (server != null) | ||||
| { | { | ||||
| @@ -530,7 +566,7 @@ namespace Discord | |||||
| break; | break; | ||||
| case "TYPING_START": | case "TYPING_START": | ||||
| { | { | ||||
| var data = e.Event.ToObject<WebSocketEvents.TypingStart>(_serializer); | |||||
| var data = e.Event.ToObject<TextWebSocketEvents.TypingStart>(_serializer); | |||||
| var channel = _channels[data.ChannelId]; | var channel = _channels[data.ChannelId]; | ||||
| var user = _users[data.UserId]; | var user = _users[data.UserId]; | ||||
| if (user != null) | if (user != null) | ||||
| @@ -545,7 +581,7 @@ namespace Discord | |||||
| //Voice | //Voice | ||||
| case "VOICE_SERVER_UPDATE": | case "VOICE_SERVER_UPDATE": | ||||
| { | { | ||||
| var data = e.Event.ToObject<WebSocketEvents.VoiceServerUpdate>(_serializer); | |||||
| var data = e.Event.ToObject<TextWebSocketEvents.VoiceServerUpdate>(_serializer); | |||||
| var server = _servers[data.ServerId]; | var server = _servers[data.ServerId]; | ||||
| try { RaiseVoiceServerUpdated(server, data.Endpoint); } catch { } | try { RaiseVoiceServerUpdated(server, data.Endpoint); } catch { } | ||||
| } | } | ||||
| @@ -554,7 +590,7 @@ namespace Discord | |||||
| //Settings | //Settings | ||||
| case "USER_UPDATE": | case "USER_UPDATE": | ||||
| { | { | ||||
| var data = e.Event.ToObject<WebSocketEvents.UserUpdate>(_serializer); | |||||
| var data = e.Event.ToObject<TextWebSocketEvents.UserUpdate>(_serializer); | |||||
| var user = _users.Update(data.Id, data); | var user = _users.Update(data.Id, data); | ||||
| try { RaiseUserUpdated(user); } catch { } | try { RaiseUserUpdated(user); } catch { } | ||||
| } | } | ||||
| @@ -571,7 +607,6 @@ namespace Discord | |||||
| break; | break; | ||||
| } | } | ||||
| }; | }; | ||||
| _webSocket.OnDebugMessage += (s, e) => RaiseOnDebugMessage(e.Message); | |||||
| } | } | ||||
| private async Task SendAsync() | private async Task SendAsync() | ||||
| @@ -822,8 +857,9 @@ namespace Discord | |||||
| { | { | ||||
| try | try | ||||
| { | { | ||||
| await _webSocket.ConnectAsync(Endpoints.WebSocket_Hub); | |||||
| Http.Token = token; | Http.Token = token; | ||||
| await _webSocket.ConnectAsync(Endpoints.WebSocket_Hub, true); | |||||
| await _webSocket.Login(); | |||||
| success = true; | success = true; | ||||
| } | } | ||||
| catch (InvalidOperationException) //Bad Token | catch (InvalidOperationException) //Bad Token | ||||
| @@ -835,27 +871,27 @@ namespace Discord | |||||
| if (!success && password != null) //Email/Password login | if (!success && password != null) //Email/Password login | ||||
| { | { | ||||
| //Open websocket while we wait for login response | //Open websocket while we wait for login response | ||||
| Task socketTask = _webSocket.ConnectAsync(Endpoints.WebSocket_Hub, false); | |||||
| Task socketTask = _webSocket.ConnectAsync(Endpoints.WebSocket_Hub); | |||||
| var response = await DiscordAPI.Login(emailOrUsername, password); | var response = await DiscordAPI.Login(emailOrUsername, password); | ||||
| await socketTask; | await socketTask; | ||||
| //Wait for websocket to finish connecting, then send token | //Wait for websocket to finish connecting, then send token | ||||
| token = response.Token; | token = response.Token; | ||||
| Http.Token = token; | Http.Token = token; | ||||
| _webSocket.Login(); | |||||
| await _webSocket.Login(); | |||||
| success = true; | success = true; | ||||
| } | } | ||||
| if (!success && password == null) //Anonymous login | if (!success && password == null) //Anonymous login | ||||
| { | { | ||||
| //Open websocket while we wait for login response | //Open websocket while we wait for login response | ||||
| Task socketTask = _webSocket.ConnectAsync(Endpoints.WebSocket_Hub, false); | |||||
| Task socketTask = _webSocket.ConnectAsync(Endpoints.WebSocket_Hub); | |||||
| var response = await DiscordAPI.LoginAnonymous(emailOrUsername); | var response = await DiscordAPI.LoginAnonymous(emailOrUsername); | ||||
| await socketTask; | await socketTask; | ||||
| //Wait for websocket to finish connecting, then send token | //Wait for websocket to finish connecting, then send token | ||||
| token = response.Token; | token = response.Token; | ||||
| Http.Token = token; | Http.Token = token; | ||||
| _webSocket.Login(); | |||||
| await _webSocket.Login(); | |||||
| success = true; | success = true; | ||||
| } | } | ||||
| if (success) | if (success) | ||||
| @@ -868,6 +904,9 @@ namespace Discord | |||||
| _tasks = _tasks.ContinueWith(async x => | _tasks = _tasks.ContinueWith(async x => | ||||
| { | { | ||||
| await _webSocket.DisconnectAsync(); | await _webSocket.DisconnectAsync(); | ||||
| #if !DNXCORE50 | |||||
| await _voiceWebSocket.DisconnectAsync(); | |||||
| #endif | |||||
| //Clear send queue | //Clear send queue | ||||
| Message ignored; | Message ignored; | ||||
| @@ -0,0 +1,25 @@ | |||||
| using Newtonsoft.Json.Linq; | |||||
| using System; | |||||
| namespace Discord | |||||
| { | |||||
| internal partial class DiscordTextWebSocket | |||||
| { | |||||
| public event EventHandler<MessageEventArgs> GotEvent; | |||||
| public sealed class MessageEventArgs : EventArgs | |||||
| { | |||||
| public readonly string Type; | |||||
| public readonly JToken Event; | |||||
| internal MessageEventArgs(string type, JToken data) | |||||
| { | |||||
| Type = type; | |||||
| Event = data; | |||||
| } | |||||
| } | |||||
| private void RaiseGotEvent(string type, JToken payload) | |||||
| { | |||||
| if (GotEvent != null) | |||||
| GotEvent(this, new MessageEventArgs(type, payload)); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,82 @@ | |||||
| using Discord.API.Models; | |||||
| using Discord.Helpers; | |||||
| using Newtonsoft.Json.Linq; | |||||
| using System; | |||||
| using System.Threading; | |||||
| using System.Threading.Tasks; | |||||
| namespace Discord | |||||
| { | |||||
| internal sealed partial class DiscordTextWebSocket : DiscordWebSocket | |||||
| { | |||||
| private const int ReadyTimeout = 2500; //Max time in milliseconds between connecting to Discord and receiving a READY event | |||||
| private ManualResetEventSlim _connectWaitOnLogin, _connectWaitOnLogin2; | |||||
| public DiscordTextWebSocket(int interval) | |||||
| : base(interval) | |||||
| { | |||||
| _connectWaitOnLogin = new ManualResetEventSlim(false); | |||||
| _connectWaitOnLogin2 = new ManualResetEventSlim(false); | |||||
| } | |||||
| public async Task Login() | |||||
| { | |||||
| var cancelToken = _disconnectToken.Token; | |||||
| _connectWaitOnLogin.Reset(); | |||||
| _connectWaitOnLogin2.Reset(); | |||||
| TextWebSocketCommands.Login msg = new TextWebSocketCommands.Login(); | |||||
| msg.Payload.Token = Http.Token; | |||||
| msg.Payload.Properties["$os"] = ""; | |||||
| msg.Payload.Properties["$browser"] = ""; | |||||
| msg.Payload.Properties["$device"] = "Discord.Net"; | |||||
| msg.Payload.Properties["$referrer"] = ""; | |||||
| msg.Payload.Properties["$referring_domain"] = ""; | |||||
| await SendMessage(msg, cancelToken); | |||||
| try | |||||
| { | |||||
| if (!_connectWaitOnLogin.Wait(ReadyTimeout, cancelToken)) //Waiting on READY 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(); | |||||
| } | |||||
| protected override void ProcessMessage(WebSocketMessage msg) | |||||
| { | |||||
| switch (msg.Operation) | |||||
| { | |||||
| case 0: | |||||
| if (msg.Type == "READY") | |||||
| { | |||||
| var payload = (msg.Payload as JToken).ToObject<TextWebSocketEvents.Ready>(); | |||||
| _heartbeatInterval = payload.HeartbeatInterval; | |||||
| QueueMessage(new TextWebSocketCommands.UpdateStatus()); | |||||
| QueueMessage(new TextWebSocketCommands.KeepAlive()); | |||||
| _connectWaitOnLogin.Set(); //Pre-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); | |||||
| break; | |||||
| } | |||||
| } | |||||
| protected override WebSocketMessage GetKeepAlive() | |||||
| { | |||||
| return new TextWebSocketCommands.KeepAlive(); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,125 @@ | |||||
| #if !DNXCORE50 | |||||
| using Discord.API.Models; | |||||
| using Discord.Helpers; | |||||
| using System; | |||||
| using System.Collections.Concurrent; | |||||
| using System.Linq; | |||||
| using System.Net.Sockets; | |||||
| using System.Threading; | |||||
| using System.Threading.Tasks; | |||||
| 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, _connectWaitOnLogin2; | |||||
| private UdpClient _udp; | |||||
| private ConcurrentQueue<byte[]> _sendQueue; | |||||
| public DiscordVoiceWebSocket(int interval) | |||||
| : base(interval) | |||||
| { | |||||
| _connectWaitOnLogin = new ManualResetEventSlim(false); | |||||
| _connectWaitOnLogin2 = new ManualResetEventSlim(false); | |||||
| _udp = new UdpClient(); | |||||
| _sendQueue = new ConcurrentQueue<byte[]>(); | |||||
| } | |||||
| 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) | |||||
| { | |||||
| var cancelToken = _disconnectToken.Token; | |||||
| _connectWaitOnLogin.Reset(); | |||||
| _connectWaitOnLogin2.Reset(); | |||||
| string ip = await Http.Get("http://ipinfo.io/ip"); | |||||
| VoiceWebSocketCommands.Login msg = new VoiceWebSocketCommands.Login(); | |||||
| msg.Payload.Token = Http.Token; | |||||
| msg.Payload.ServerId = serverId; | |||||
| msg.Payload.UserId = userId; | |||||
| msg.Payload.SessionId = sessionId; | |||||
| await SendMessage(msg, cancelToken); | |||||
| try | |||||
| { | |||||
| if (!_connectWaitOnLogin.Wait(ReadyTimeout, cancelToken)) //Waiting on READY 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(); | |||||
| } | |||||
| 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 (OperationCanceledException) { } | |||||
| _udp.Close(); | |||||
| } | |||||
| protected override void ProcessMessage(WebSocketMessage msg) | |||||
| { | |||||
| } | |||||
| private void ProcessUdpMessage(UdpReceiveResult msg) | |||||
| { | |||||
| } | |||||
| protected override WebSocketMessage GetKeepAlive() | |||||
| { | |||||
| return new VoiceWebSocketCommands.KeepAlive(); | |||||
| } | |||||
| } | |||||
| } | |||||
| #endif | |||||
| @@ -1,9 +1,8 @@ | |||||
| using Newtonsoft.Json.Linq; | |||||
| using System; | |||||
| using System; | |||||
| namespace Discord | namespace Discord | ||||
| { | { | ||||
| internal partial class DiscordWebSocket | |||||
| internal abstract partial class DiscordWebSocket | |||||
| { | { | ||||
| public event EventHandler Connected; | public event EventHandler Connected; | ||||
| private void RaiseConnected() | private void RaiseConnected() | ||||
| @@ -19,25 +18,8 @@ namespace Discord | |||||
| Disconnected(this, EventArgs.Empty); | Disconnected(this, EventArgs.Empty); | ||||
| } | } | ||||
| public event EventHandler<MessageEventArgs> GotEvent; | |||||
| public sealed class MessageEventArgs : EventArgs | |||||
| { | |||||
| public readonly string Type; | |||||
| public readonly JToken Event; | |||||
| internal MessageEventArgs(string type, JToken data) | |||||
| { | |||||
| Type = type; | |||||
| Event = data; | |||||
| } | |||||
| } | |||||
| private void RaiseGotEvent(string type, JToken payload) | |||||
| { | |||||
| if (GotEvent != null) | |||||
| GotEvent(this, new MessageEventArgs(type, payload)); | |||||
| } | |||||
| public event EventHandler<DiscordClient.LogMessageEventArgs> OnDebugMessage; | public event EventHandler<DiscordClient.LogMessageEventArgs> OnDebugMessage; | ||||
| private void RaiseOnDebugMessage(string message) | |||||
| protected void RaiseOnDebugMessage(string message) | |||||
| { | { | ||||
| if (OnDebugMessage != null) | if (OnDebugMessage != null) | ||||
| OnDebugMessage(this, new DiscordClient.LogMessageEventArgs(message)); | OnDebugMessage(this, new DiscordClient.LogMessageEventArgs(message)); | ||||
| @@ -1,5 +1,4 @@ | |||||
| using Discord.API.Models; | using Discord.API.Models; | ||||
| using Discord.Helpers; | |||||
| using Newtonsoft.Json; | using Newtonsoft.Json; | ||||
| using Newtonsoft.Json.Linq; | using Newtonsoft.Json.Linq; | ||||
| using System; | using System; | ||||
| @@ -11,31 +10,29 @@ using System.Threading.Tasks; | |||||
| namespace Discord | namespace Discord | ||||
| { | { | ||||
| internal sealed partial class DiscordWebSocket : IDisposable | |||||
| internal abstract partial class DiscordWebSocket : IDisposable | |||||
| { | { | ||||
| private const int ReceiveChunkSize = 4096; | private const int ReceiveChunkSize = 4096; | ||||
| private const int SendChunkSize = 4096; | private const int SendChunkSize = 4096; | ||||
| private const int ReadyTimeout = 2500; //Max time in milliseconds between connecting to Discord and receiving a READY event | |||||
| protected volatile CancellationTokenSource _disconnectToken; | |||||
| protected int _heartbeatInterval; | |||||
| protected readonly int _sendInterval; | |||||
| private volatile ClientWebSocket _webSocket; | private volatile ClientWebSocket _webSocket; | ||||
| private volatile CancellationTokenSource _disconnectToken; | |||||
| private volatile Task _tasks; | private volatile Task _tasks; | ||||
| private ConcurrentQueue<byte[]> _sendQueue; | private ConcurrentQueue<byte[]> _sendQueue; | ||||
| private int _heartbeatInterval, _sendInterval; | |||||
| private DateTime _lastHeartbeat; | private DateTime _lastHeartbeat; | ||||
| private ManualResetEventSlim _connectWaitOnLogin, _connectWaitOnLogin2; | |||||
| private bool _isConnected; | private bool _isConnected; | ||||
| public DiscordWebSocket(int interval) | public DiscordWebSocket(int interval) | ||||
| { | { | ||||
| _sendInterval = interval; | _sendInterval = interval; | ||||
| _connectWaitOnLogin = new ManualResetEventSlim(false); | |||||
| _connectWaitOnLogin2 = new ManualResetEventSlim(false); | |||||
| _sendQueue = new ConcurrentQueue<byte[]>(); | _sendQueue = new ConcurrentQueue<byte[]>(); | ||||
| } | } | ||||
| public async Task ConnectAsync(string url, bool autoLogin) | |||||
| public async Task ConnectAsync(string url) | |||||
| { | { | ||||
| await DisconnectAsync(); | await DisconnectAsync(); | ||||
| @@ -46,9 +43,7 @@ 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); | ||||
| _tasks = Task.WhenAll( | |||||
| await Task.Factory.StartNew(ReceiveAsync, cancelToken, TaskCreationOptions.LongRunning, TaskScheduler.Default), | |||||
| await Task.Factory.StartNew(SendAsync, cancelToken, TaskCreationOptions.LongRunning, TaskScheduler.Default)) | |||||
| _tasks = Task.WhenAll(CreateTasks(cancelToken)) | |||||
| .ContinueWith(x => | .ContinueWith(x => | ||||
| { | { | ||||
| //Do not clean up until both tasks have ended | //Do not clean up until both tasks have ended | ||||
| @@ -71,48 +66,29 @@ namespace Discord | |||||
| _tasks = null; | _tasks = null; | ||||
| }); | }); | ||||
| if (autoLogin) | |||||
| Login(); | |||||
| } | |||||
| public void Login() | |||||
| } | |||||
| public async Task DisconnectAsync() | |||||
| { | { | ||||
| var cancelToken = _disconnectToken.Token; | |||||
| _connectWaitOnLogin.Reset(); | |||||
| _connectWaitOnLogin2.Reset(); | |||||
| WebSocketCommands.Login msg = new WebSocketCommands.Login(); | |||||
| msg.Payload.Token = Http.Token; | |||||
| msg.Payload.Properties["$os"] = ""; | |||||
| msg.Payload.Properties["$browser"] = ""; | |||||
| msg.Payload.Properties["$device"] = "Discord.Net"; | |||||
| msg.Payload.Properties["$referrer"] = ""; | |||||
| msg.Payload.Properties["$referring_domain"] = ""; | |||||
| SendMessage(msg, _disconnectToken.Token); | |||||
| try | |||||
| { | |||||
| if (!_connectWaitOnLogin.Wait(ReadyTimeout, cancelToken)) //Waiting on READY message | |||||
| throw new Exception("No reply from Discord server"); | |||||
| } | |||||
| catch (OperationCanceledException) | |||||
| if (_tasks != null) | |||||
| { | { | ||||
| throw new InvalidOperationException("Bad Token"); | |||||
| try { _disconnectToken.Cancel(); } catch (NullReferenceException) { } | |||||
| try { await _tasks; } catch (NullReferenceException) { } | |||||
| } | } | ||||
| try { _connectWaitOnLogin2.Wait(cancelToken); } //Waiting on READY handler | |||||
| catch (OperationCanceledException) { return; } | |||||
| } | |||||
| protected void SetConnected() | |||||
| { | |||||
| _isConnected = true; | _isConnected = true; | ||||
| RaiseConnected(); | RaiseConnected(); | ||||
| } | } | ||||
| public async Task DisconnectAsync() | |||||
| protected virtual Task[] CreateTasks(CancellationToken cancelToken) | |||||
| { | { | ||||
| if (_tasks != null) | |||||
| return new Task[] | |||||
| { | { | ||||
| try { _disconnectToken.Cancel(); } catch (NullReferenceException) { } | |||||
| try { await _tasks; } catch (NullReferenceException) { } | |||||
| } | |||||
| Task.Factory.StartNew(ReceiveAsync, cancelToken, TaskCreationOptions.LongRunning, TaskScheduler.Default).Result, | |||||
| Task.Factory.StartNew(SendAsync, cancelToken, TaskCreationOptions.LongRunning, TaskScheduler.Default).Result | |||||
| }; | |||||
| } | } | ||||
| private async Task ReceiveAsync() | private async Task ReceiveAsync() | ||||
| @@ -142,25 +118,7 @@ namespace Discord | |||||
| while (!result.EndOfMessage); | while (!result.EndOfMessage); | ||||
| var msg = JsonConvert.DeserializeObject<WebSocketMessage>(builder.ToString()); | var msg = JsonConvert.DeserializeObject<WebSocketMessage>(builder.ToString()); | ||||
| switch (msg.Operation) | |||||
| { | |||||
| case 0: | |||||
| if (msg.Type == "READY") | |||||
| { | |||||
| var payload = (msg.Payload as JToken).ToObject<WebSocketEvents.Ready>(); | |||||
| _heartbeatInterval = payload.HeartbeatInterval; | |||||
| QueueMessage(new WebSocketCommands.UpdateStatus()); | |||||
| QueueMessage(new WebSocketCommands.KeepAlive()); | |||||
| _connectWaitOnLogin.Set(); //Pre-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); | |||||
| break; | |||||
| } | |||||
| ProcessMessage(msg); | |||||
| builder.Clear(); | builder.Clear(); | ||||
| } | } | ||||
| @@ -168,11 +126,10 @@ namespace Discord | |||||
| catch { } | catch { } | ||||
| finally { _disconnectToken.Cancel(); } | finally { _disconnectToken.Cancel(); } | ||||
| } | } | ||||
| private async Task SendAsync() | private async Task SendAsync() | ||||
| { | { | ||||
| var cancelToken = _disconnectToken.Token; | var cancelToken = _disconnectToken.Token; | ||||
| try | |||||
| try | |||||
| { | { | ||||
| byte[] bytes; | byte[] bytes; | ||||
| while (_webSocket.State == WebSocketState.Open && !cancelToken.IsCancellationRequested) | while (_webSocket.State == WebSocketState.Open && !cancelToken.IsCancellationRequested) | ||||
| @@ -182,7 +139,7 @@ namespace Discord | |||||
| DateTime now = DateTime.UtcNow; | DateTime now = DateTime.UtcNow; | ||||
| if ((now - _lastHeartbeat).TotalMilliseconds > _heartbeatInterval) | if ((now - _lastHeartbeat).TotalMilliseconds > _heartbeatInterval) | ||||
| { | { | ||||
| await SendMessage(new WebSocketCommands.KeepAlive(), cancelToken); | |||||
| await SendMessage(GetKeepAlive(), cancelToken); | |||||
| _lastHeartbeat = now; | _lastHeartbeat = now; | ||||
| } | } | ||||
| } | } | ||||
| @@ -195,15 +152,17 @@ namespace Discord | |||||
| finally { _disconnectToken.Cancel(); } | finally { _disconnectToken.Cancel(); } | ||||
| } | } | ||||
| private void QueueMessage(object message) | |||||
| protected abstract void ProcessMessage(WebSocketMessage msg); | |||||
| protected abstract WebSocketMessage GetKeepAlive(); | |||||
| protected void QueueMessage(object message) | |||||
| { | { | ||||
| var bytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(message)); | var bytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(message)); | ||||
| _sendQueue.Enqueue(bytes); | _sendQueue.Enqueue(bytes); | ||||
| } | } | ||||
| private Task SendMessage(object message, CancellationToken cancelToken) | |||||
| protected Task SendMessage(object message, CancellationToken cancelToken) | |||||
| => SendMessage(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(message)), cancelToken); | => SendMessage(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(message)), cancelToken); | ||||
| private 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); | ||||