| @@ -7,7 +7,7 @@ | |||||
| <ProjectGuid>{1B5603B4-6F8F-4289-B945-7BAAE523D740}</ProjectGuid> | <ProjectGuid>{1B5603B4-6F8F-4289-B945-7BAAE523D740}</ProjectGuid> | ||||
| <OutputType>Library</OutputType> | <OutputType>Library</OutputType> | ||||
| <AppDesignerFolder>Properties</AppDesignerFolder> | <AppDesignerFolder>Properties</AppDesignerFolder> | ||||
| <RootNamespace>Discord.Net</RootNamespace> | |||||
| <RootNamespace>Discord</RootNamespace> | |||||
| <AssemblyName>Discord.Net.Commands</AssemblyName> | <AssemblyName>Discord.Net.Commands</AssemblyName> | ||||
| <FileAlignment>512</FileAlignment> | <FileAlignment>512</FileAlignment> | ||||
| <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> | <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> | ||||
| @@ -7,7 +7,7 @@ | |||||
| <ProjectGuid>{8D71A857-879A-4A10-859E-5FF824ED6688}</ProjectGuid> | <ProjectGuid>{8D71A857-879A-4A10-859E-5FF824ED6688}</ProjectGuid> | ||||
| <OutputType>Library</OutputType> | <OutputType>Library</OutputType> | ||||
| <AppDesignerFolder>Properties</AppDesignerFolder> | <AppDesignerFolder>Properties</AppDesignerFolder> | ||||
| <RootNamespace>Discord.Net</RootNamespace> | |||||
| <RootNamespace>Discord</RootNamespace> | |||||
| <AssemblyName>Discord.Net</AssemblyName> | <AssemblyName>Discord.Net</AssemblyName> | ||||
| <FileAlignment>512</FileAlignment> | <FileAlignment>512</FileAlignment> | ||||
| <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> | <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> | ||||
| @@ -159,6 +159,9 @@ | |||||
| <Compile Include="..\Discord.Net\Net\API\Endpoints.cs"> | <Compile Include="..\Discord.Net\Net\API\Endpoints.cs"> | ||||
| <Link>Net\API\Endpoints.cs</Link> | <Link>Net\API\Endpoints.cs</Link> | ||||
| </Compile> | </Compile> | ||||
| <Compile Include="..\Discord.Net\Net\API\HttpException.cs"> | |||||
| <Link>Net\API\HttpException.cs</Link> | |||||
| </Compile> | |||||
| <Compile Include="..\Discord.Net\Net\API\Requests.cs"> | <Compile Include="..\Discord.Net\Net\API\Requests.cs"> | ||||
| <Link>Net\API\Requests.cs</Link> | <Link>Net\API\Requests.cs</Link> | ||||
| </Compile> | </Compile> | ||||
| @@ -177,9 +180,6 @@ | |||||
| <Compile Include="..\Discord.Net\Net\API\RestClient.SharpRest.cs"> | <Compile Include="..\Discord.Net\Net\API\RestClient.SharpRest.cs"> | ||||
| <Link>Net\API\RestClient.SharpRest.cs</Link> | <Link>Net\API\RestClient.SharpRest.cs</Link> | ||||
| </Compile> | </Compile> | ||||
| <Compile Include="..\Discord.Net\Net\HttpException.cs"> | |||||
| <Link>Net\HttpException.cs</Link> | |||||
| </Compile> | |||||
| <Compile Include="..\Discord.Net\Net\WebSockets\Commands.cs"> | <Compile Include="..\Discord.Net\Net\WebSockets\Commands.cs"> | ||||
| <Link>Net\WebSockets\Commands.cs</Link> | <Link>Net\WebSockets\Commands.cs</Link> | ||||
| </Compile> | </Compile> | ||||
| @@ -187,7 +187,7 @@ | |||||
| <Link>Net\WebSockets\DataWebSocket.cs</Link> | <Link>Net\WebSockets\DataWebSocket.cs</Link> | ||||
| </Compile> | </Compile> | ||||
| <Compile Include="..\Discord.Net\Net\WebSockets\DataWebSockets.Events.cs"> | <Compile Include="..\Discord.Net\Net\WebSockets\DataWebSockets.Events.cs"> | ||||
| <Link>Net\DataWebSockets.Events.cs</Link> | |||||
| <Link>Net\WebSockets\DataWebSockets.Events.cs</Link> | |||||
| </Compile> | </Compile> | ||||
| <Compile Include="..\Discord.Net\Net\WebSockets\Events.cs"> | <Compile Include="..\Discord.Net\Net\WebSockets\Events.cs"> | ||||
| <Link>Net\WebSockets\Events.cs</Link> | <Link>Net\WebSockets\Events.cs</Link> | ||||
| @@ -216,6 +216,9 @@ | |||||
| <Compile Include="..\Discord.Net\Net\WebSockets\WebSocketMessage.cs"> | <Compile Include="..\Discord.Net\Net\WebSockets\WebSocketMessage.cs"> | ||||
| <Link>Net\WebSockets\WebSocketMessage.cs</Link> | <Link>Net\WebSockets\WebSocketMessage.cs</Link> | ||||
| </Compile> | </Compile> | ||||
| <Compile Include="..\Discord.Net\TimeoutException.cs"> | |||||
| <Link>TimeoutException.cs</Link> | |||||
| </Compile> | |||||
| <Compile Include="Properties\AssemblyInfo.cs" /> | <Compile Include="Properties\AssemblyInfo.cs" /> | ||||
| </ItemGroup> | </ItemGroup> | ||||
| <ItemGroup /> | <ItemGroup /> | ||||
| @@ -95,7 +95,7 @@ namespace Discord | |||||
| _api = new DiscordAPIClient(_config.LogLevel); | _api = new DiscordAPIClient(_config.LogLevel); | ||||
| _dataSocket = new DataWebSocket(this); | _dataSocket = new DataWebSocket(this); | ||||
| _dataSocket.Connected += (s, e) => { if (_state == (int)DiscordClientState.Connecting) CompleteConnect(); }; | _dataSocket.Connected += (s, e) => { if (_state == (int)DiscordClientState.Connecting) CompleteConnect(); }; | ||||
| _dataSocket.Disconnected += async (s, e) => { RaiseDisconnected(e); if (e.WasUnexpected) await Reconnect(_token); }; | |||||
| _dataSocket.Disconnected += async (s, e) => { RaiseDisconnected(e); if (e.WasUnexpected) await _dataSocket.Login(_token); }; | |||||
| if (_config.EnableVoice) | if (_config.EnableVoice) | ||||
| { | { | ||||
| _voiceSocket = new VoiceWebSocket(this); | _voiceSocket = new VoiceWebSocket(this); | ||||
| @@ -112,7 +112,7 @@ namespace Discord | |||||
| } | } | ||||
| RaiseVoiceDisconnected(e); | RaiseVoiceDisconnected(e); | ||||
| if (e.WasUnexpected) | if (e.WasUnexpected) | ||||
| await _voiceSocket.Reconnect(_cancelToken); | |||||
| await _voiceSocket.Reconnect(); | |||||
| }; | }; | ||||
| _voiceSocket.IsSpeaking += (s, e) => | _voiceSocket.IsSpeaking += (s, e) => | ||||
| { | { | ||||
| @@ -292,6 +292,8 @@ namespace Discord | |||||
| } | } | ||||
| } | } | ||||
| break; | break; | ||||
| case "RESUMED": | |||||
| break; | |||||
| //Servers | //Servers | ||||
| case "GUILD_CREATE": | case "GUILD_CREATE": | ||||
| @@ -358,7 +360,7 @@ namespace Discord | |||||
| var user = _users.GetOrAdd(data.User.Id); | var user = _users.GetOrAdd(data.User.Id); | ||||
| user.Update(data.User); | user.Update(data.User); | ||||
| if (_config.TrackActivity) | if (_config.TrackActivity) | ||||
| user.UpdateActivity(DateTime.UtcNow); | |||||
| user.UpdateActivity(); | |||||
| var member = _members.GetOrAdd(data.User.Id, data.GuildId); | var member = _members.GetOrAdd(data.User.Id, data.GuildId); | ||||
| member.Update(data); | member.Update(data); | ||||
| RaiseUserAdded(member); | RaiseUserAdded(member); | ||||
| @@ -536,7 +538,7 @@ namespace Discord | |||||
| if (user != null) | if (user != null) | ||||
| { | { | ||||
| if (_config.TrackActivity) | if (_config.TrackActivity) | ||||
| user.UpdateActivity(DateTime.UtcNow); | |||||
| user.UpdateActivity(); | |||||
| if (channel != null) | if (channel != null) | ||||
| RaiseUserIsTyping(user, channel); | RaiseUserIsTyping(user, channel); | ||||
| } | } | ||||
| @@ -550,8 +552,8 @@ namespace Discord | |||||
| var server = _servers[data.GuildId]; | var server = _servers[data.GuildId]; | ||||
| if (_config.EnableVoice) | if (_config.EnableVoice) | ||||
| { | { | ||||
| string host = "wss://" + data.Endpoint.Split(':')[0]; | |||||
| await _voiceSocket.Login(host, data.GuildId, _currentUserId, _dataSocket.SessionId, data.Token, _cancelToken).ConfigureAwait(false); | |||||
| _voiceSocket.Host = "wss://" + data.Endpoint.Split(':')[0]; | |||||
| await _voiceSocket.Login(data.GuildId, _currentUserId, _dataSocket.SessionId, data.Token, _cancelToken).ConfigureAwait(false); | |||||
| } | } | ||||
| } | } | ||||
| break; | break; | ||||
| @@ -582,11 +584,6 @@ namespace Discord | |||||
| }; | }; | ||||
| } | } | ||||
| private void _dataSocket_Connected(object sender, EventArgs e) | |||||
| { | |||||
| throw new NotImplementedException(); | |||||
| } | |||||
| //Connection | //Connection | ||||
| /// <summary> Connects to the Discord server with the provided token. </summary> | /// <summary> Connects to the Discord server with the provided token. </summary> | ||||
| public async Task Connect(string token) | public async Task Connect(string token) | ||||
| @@ -594,46 +591,54 @@ namespace Discord | |||||
| if (_state != (int)DiscordClientState.Disconnected) | if (_state != (int)DiscordClientState.Disconnected) | ||||
| await Disconnect().ConfigureAwait(false); | await Disconnect().ConfigureAwait(false); | ||||
| if (_config.LogLevel >= LogMessageSeverity.Verbose) | |||||
| RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Authentication, $"Using cached token."); | |||||
| await ConnectInternal(token).ConfigureAwait(false); | |||||
| } | |||||
| await ConnectInternal(token) | |||||
| .Timeout(_config.ConnectionTimeout) | |||||
| .ConfigureAwait(false); | |||||
| } | |||||
| /// <summary> Connects to the Discord server with the provided email and password. </summary> | /// <summary> Connects to the Discord server with the provided email and password. </summary> | ||||
| /// <returns> Returns a token for future connections. </returns> | /// <returns> Returns a token for future connections. </returns> | ||||
| public async Task<string> Connect(string email, string password) | public async Task<string> Connect(string email, string password) | ||||
| { | { | ||||
| if (_state != (int)DiscordClientState.Disconnected) | if (_state != (int)DiscordClientState.Disconnected) | ||||
| await Disconnect().ConfigureAwait(false); | await Disconnect().ConfigureAwait(false); | ||||
| var response = await _api.Login(email, password).ConfigureAwait(false); | |||||
| if (_config.LogLevel >= LogMessageSeverity.Verbose) | |||||
| RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Authentication, "Login successful, got token."); | |||||
| return await ConnectInternal(response.Token).ConfigureAwait(false); | |||||
| } | |||||
| private Task Reconnect(string token) | |||||
| { | |||||
| if (_config.LogLevel >= LogMessageSeverity.Verbose) | |||||
| RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Authentication, $"Using cached token."); | |||||
| return ConnectInternal(token); | |||||
| string token; | |||||
| try | |||||
| { | |||||
| var cancelToken = new CancellationTokenSource(); | |||||
| cancelToken.CancelAfter(5000); | |||||
| _api.CancelToken = cancelToken.Token; | |||||
| var response = await _api.Login(email, password).ConfigureAwait(false); | |||||
| token = response.Token; | |||||
| if (_config.LogLevel >= LogMessageSeverity.Verbose) | |||||
| RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Authentication, "Login successful, got token."); | |||||
| } | |||||
| catch (TaskCanceledException) { throw new TimeoutException(); } | |||||
| return await ConnectInternal(token) | |||||
| .Timeout(_config.ConnectionTimeout) | |||||
| .ConfigureAwait(false); | |||||
| } | } | ||||
| private async Task<string> ConnectInternal(string token) | private async Task<string> ConnectInternal(string token) | ||||
| { | { | ||||
| try | |||||
| try | |||||
| { | { | ||||
| _disconnectedEvent.Reset(); | _disconnectedEvent.Reset(); | ||||
| _cancelTokenSource = new CancellationTokenSource(); | _cancelTokenSource = new CancellationTokenSource(); | ||||
| _cancelToken = _cancelTokenSource.Token; | _cancelToken = _cancelTokenSource.Token; | ||||
| _state = (int)DiscordClientState.Connecting; | |||||
| _api.Token = token; | _api.Token = token; | ||||
| _api.CancelToken = _cancelToken; | |||||
| _token = token; | |||||
| _state = (int)DiscordClientState.Connecting; | |||||
| string url = (await _api.GetWebSocketEndpoint().ConfigureAwait(false)).Url; | string url = (await _api.GetWebSocketEndpoint().ConfigureAwait(false)).Url; | ||||
| url = "wss://gateway-besaid.discord.gg/"; | |||||
| if (_config.LogLevel >= LogMessageSeverity.Verbose) | if (_config.LogLevel >= LogMessageSeverity.Verbose) | ||||
| RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Authentication, $"Websocket endpoint: {url}"); | RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Authentication, $"Websocket endpoint: {url}"); | ||||
| await _dataSocket.Login(url, token, _cancelToken).ConfigureAwait(false); | |||||
| _dataSocket.Host = url; | |||||
| _dataSocket.ParentCancelToken = _cancelToken; | |||||
| await _dataSocket.Login(token).ConfigureAwait(false); | |||||
| _runTask = RunTasks(); | _runTask = RunTasks(); | ||||
| @@ -641,8 +646,7 @@ namespace Discord | |||||
| { | { | ||||
| //Cancel if either Disconnect is called, data socket errors or timeout is reached | //Cancel if either Disconnect is called, data socket errors or timeout is reached | ||||
| var cancelToken = CancellationTokenSource.CreateLinkedTokenSource(_cancelToken, _dataSocket.CancelToken).Token; | var cancelToken = CancellationTokenSource.CreateLinkedTokenSource(_cancelToken, _dataSocket.CancelToken).Token; | ||||
| if (!_connectedEvent.Wait(_config.ConnectionTimeout, cancelToken)) | |||||
| throw new Exception("Operation timed out."); | |||||
| _connectedEvent.Wait(cancelToken); | |||||
| } | } | ||||
| catch (OperationCanceledException) | catch (OperationCanceledException) | ||||
| { | { | ||||
| @@ -656,6 +660,7 @@ namespace Discord | |||||
| } | } | ||||
| catch | catch | ||||
| { | { | ||||
| await Disconnect().ConfigureAwait(false); | await Disconnect().ConfigureAwait(false); | ||||
| throw; | throw; | ||||
| } | } | ||||
| @@ -13,5 +13,26 @@ namespace Discord.Helpers | |||||
| CompletedTask = Task.Delay(0); | CompletedTask = Task.Delay(0); | ||||
| #endif | #endif | ||||
| } | } | ||||
| public static async Task Timeout(this Task self, int milliseconds) | |||||
| { | |||||
| Task timeoutTask = Task.Delay(milliseconds); | |||||
| Task finishedTask = await Task.WhenAny(self, timeoutTask); | |||||
| if (finishedTask == timeoutTask) | |||||
| { | |||||
| throw new TimeoutException(); | |||||
| } | |||||
| else | |||||
| await self; | |||||
| } | |||||
| public static async Task<T> Timeout<T>(this Task<T> self, int milliseconds) | |||||
| { | |||||
| Task timeoutTask = Task.Delay(milliseconds); | |||||
| Task finishedTask = await Task.WhenAny(self, timeoutTask).ConfigureAwait(false); | |||||
| if (finishedTask == timeoutTask) | |||||
| throw new TimeoutException(); | |||||
| else | |||||
| return await self.ConfigureAwait(false); | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| @@ -1,4 +1,5 @@ | |||||
| using System; | using System; | ||||
| using System.Threading; | |||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
| namespace Discord.Net.API | namespace Discord.Net.API | ||||
| @@ -21,6 +22,12 @@ namespace Discord.Net.API | |||||
| get { return _token; } | get { return _token; } | ||||
| set { _token = value; _rest.SetToken(value); } | set { _token = value; _rest.SetToken(value); } | ||||
| } | } | ||||
| private CancellationToken _cancelToken; | |||||
| public CancellationToken CancelToken | |||||
| { | |||||
| get { return _cancelToken; } | |||||
| set { _cancelToken = value; _rest.SetCancelToken(value); } | |||||
| } | |||||
| //Auth | //Auth | ||||
| public Task<Responses.Gateway> GetWebSocketEndpoint() | public Task<Responses.Gateway> GetWebSocketEndpoint() | ||||
| @@ -191,5 +198,15 @@ namespace Discord.Net.API | |||||
| var request = new Requests.ChangeAvatar { Avatar = $"data:{type},/9j/{base64}", CurrentEmail = currentEmail, CurrentPassword = currentPassword }; | var request = new Requests.ChangeAvatar { Avatar = $"data:{type},/9j/{base64}", CurrentEmail = currentEmail, CurrentPassword = currentPassword }; | ||||
| return _rest.Patch<Responses.ChangeProfile>(Endpoints.UserMe, request); | return _rest.Patch<Responses.ChangeProfile>(Endpoints.UserMe, request); | ||||
| } | } | ||||
| //Other | |||||
| /*public Task<Responses.Status> GetUnresolvedIncidents() | |||||
| { | |||||
| return _rest.Get<Responses.Status>(Endpoints.StatusUnresolvedMaintenance); | |||||
| } | |||||
| public Task<Responses.Status> GetActiveIncidents() | |||||
| { | |||||
| return _rest.Get<Responses.Status>(Endpoints.StatusActiveMaintenance); | |||||
| }*/ | |||||
| } | } | ||||
| } | } | ||||
| @@ -1,7 +1,8 @@ | |||||
| namespace Discord.Net.API | namespace Discord.Net.API | ||||
| { | { | ||||
| internal static class Endpoints | internal static class Endpoints | ||||
| { | |||||
| { | |||||
| public const string BaseStatusApi = "https://status.discordapp.com/api/v2/"; | |||||
| public const string BaseApi = "https://discordapp.com/api/"; | public const string BaseApi = "https://discordapp.com/api/"; | ||||
| //public const string Track = "track"; | //public const string Track = "track"; | ||||
| public const string Gateway = "gateway"; | public const string Gateway = "gateway"; | ||||
| @@ -41,5 +42,8 @@ | |||||
| public const string Voice = "voice"; | public const string Voice = "voice"; | ||||
| public const string VoiceRegions = "voice/regions"; | public const string VoiceRegions = "voice/regions"; | ||||
| public const string VoiceIce = "voice/ice"; | public const string VoiceIce = "voice/ice"; | ||||
| } | |||||
| public const string StatusActiveMaintenance = "scheduled-maintenances/active.json"; | |||||
| public const string StatusUnresolvedMaintenance = "scheduled-maintenances/unresolved.json"; | |||||
| } | |||||
| } | } | ||||
| @@ -1,7 +1,7 @@ | |||||
| using System; | using System; | ||||
| using System.Net; | using System.Net; | ||||
| namespace Discord.Net | |||||
| namespace Discord.Net.API | |||||
| { | { | ||||
| public class HttpException : Exception | public class HttpException : Exception | ||||
| { | { | ||||
| @@ -7,8 +7,7 @@ using System.Threading.Tasks; | |||||
| namespace Discord.Net.WebSockets | namespace Discord.Net.WebSockets | ||||
| { | { | ||||
| internal partial class DataWebSocket : WebSocket | internal partial class DataWebSocket : WebSocket | ||||
| { | |||||
| private string _redirectServer; | |||||
| { | |||||
| private int _lastSeq; | private int _lastSeq; | ||||
| public string SessionId => _sessionId; | public string SessionId => _sessionId; | ||||
| @@ -18,29 +17,25 @@ namespace Discord.Net.WebSockets | |||||
| : base(client) | : base(client) | ||||
| { | { | ||||
| } | } | ||||
| public async Task Login(string host, string token, CancellationToken cancelToken) | |||||
| public async Task Login(string token) | |||||
| { | { | ||||
| await base.Connect(host, cancelToken); | |||||
| await Connect(); | |||||
| Commands.Login msg = new Commands.Login(); | Commands.Login msg = new Commands.Login(); | ||||
| msg.Payload.Token = token; | msg.Payload.Token = token; | ||||
| msg.Payload.Properties["$device"] = "Discord.Net"; | msg.Payload.Properties["$device"] = "Discord.Net"; | ||||
| QueueMessage(msg); | QueueMessage(msg); | ||||
| } | } | ||||
| protected override Task[] Run() | |||||
| private async Task Redirect(string server) | |||||
| { | { | ||||
| //Send resume session if we were transferred | |||||
| if (_redirectServer != null) | |||||
| { | |||||
| var resumeMsg = new Commands.Resume(); | |||||
| resumeMsg.Payload.SessionId = _sessionId; | |||||
| resumeMsg.Payload.Sequence = _lastSeq; | |||||
| QueueMessage(resumeMsg); | |||||
| _redirectServer = null; | |||||
| } | |||||
| return base.Run(); | |||||
| await DisconnectInternal(isUnexpected: false); | |||||
| await Connect(); | |||||
| var resumeMsg = new Commands.Resume(); | |||||
| resumeMsg.Payload.SessionId = _sessionId; | |||||
| resumeMsg.Payload.Sequence = _lastSeq; | |||||
| QueueMessage(resumeMsg); | |||||
| } | } | ||||
| protected override async Task ProcessMessage(string json) | protected override async Task ProcessMessage(string json) | ||||
| @@ -54,27 +49,31 @@ namespace Discord.Net.WebSockets | |||||
| case 0: | case 0: | ||||
| { | { | ||||
| JToken token = msg.Payload as JToken; | JToken token = msg.Payload as JToken; | ||||
| if (msg.Type == "READY") | |||||
| if (msg.Type == "READY") | |||||
| { | { | ||||
| var payload = token.ToObject<Events.Ready>(); | var payload = token.ToObject<Events.Ready>(); | ||||
| _sessionId = payload.SessionId; | _sessionId = payload.SessionId; | ||||
| _heartbeatInterval = payload.HeartbeatInterval; | _heartbeatInterval = payload.HeartbeatInterval; | ||||
| QueueMessage(new Commands.UpdateStatus()); | QueueMessage(new Commands.UpdateStatus()); | ||||
| } | } | ||||
| else if (msg.Type == "RESUMED") | |||||
| { | |||||
| var payload = token.ToObject<Events.Resumed>(); | |||||
| _heartbeatInterval = payload.HeartbeatInterval; | |||||
| QueueMessage(new Commands.UpdateStatus()); | |||||
| } | |||||
| RaiseReceivedEvent(msg.Type, token); | RaiseReceivedEvent(msg.Type, token); | ||||
| if (msg.Type == "READY") | |||||
| if (msg.Type == "READY" || msg.Type == "RESUMED") | |||||
| CompleteConnect(); | CompleteConnect(); | ||||
| /*if (_logLevel >= LogMessageSeverity.Info) | |||||
| RaiseOnLog(LogMessageSeverity.Info, "Got Event: " + msg.Type);*/ | |||||
| } | } | ||||
| break; | break; | ||||
| case 7: //Redirect | case 7: //Redirect | ||||
| { | { | ||||
| var payload = (msg.Payload as JToken).ToObject<Events.Redirect>(); | var payload = (msg.Payload as JToken).ToObject<Events.Redirect>(); | ||||
| _host = payload.Url; | |||||
| Host = payload.Url; | |||||
| if (_logLevel >= LogMessageSeverity.Info) | if (_logLevel >= LogMessageSeverity.Info) | ||||
| RaiseOnLog(LogMessageSeverity.Info, "Redirected to " + payload.Url); | RaiseOnLog(LogMessageSeverity.Info, "Redirected to " + payload.Url); | ||||
| await DisconnectInternal(new Exception("Server is redirecting."), true); | |||||
| await Redirect(payload.Url); | |||||
| } | } | ||||
| break; | break; | ||||
| default: | default: | ||||
| @@ -36,6 +36,11 @@ namespace Discord.Net.WebSockets | |||||
| [JsonProperty(PropertyName = "heartbeat_interval")] | [JsonProperty(PropertyName = "heartbeat_interval")] | ||||
| public int HeartbeatInterval; | public int HeartbeatInterval; | ||||
| } | } | ||||
| public sealed class Resumed | |||||
| { | |||||
| [JsonProperty(PropertyName = "heartbeat_interval")] | |||||
| public int HeartbeatInterval; | |||||
| } | |||||
| public sealed class Redirect | public sealed class Redirect | ||||
| { | { | ||||
| @@ -53,13 +53,12 @@ namespace Discord.Net.WebSockets | |||||
| _targetAudioBufferLength = client.Config.VoiceBufferLength / 20; //20 ms frames | _targetAudioBufferLength = client.Config.VoiceBufferLength / 20; //20 ms frames | ||||
| } | } | ||||
| public async Task Login(string host, string serverId, string userId, string sessionId, string token, CancellationToken cancelToken) | |||||
| public async Task Login(string serverId, string userId, string sessionId, string token, CancellationToken cancelToken) | |||||
| { | { | ||||
| if (_serverId == serverId && _userId == userId && _sessionId == sessionId && _token == token) | if (_serverId == serverId && _userId == userId && _sessionId == sessionId && _token == token) | ||||
| { | { | ||||
| //Adjust the host and tell the system to reconnect | //Adjust the host and tell the system to reconnect | ||||
| _host = host; | |||||
| await DisconnectInternal(new Exception("Server transfer occurred.")); | |||||
| await DisconnectInternal(new Exception("Server transfer occurred."), isUnexpected: false); | |||||
| return; | return; | ||||
| } | } | ||||
| @@ -68,18 +67,19 @@ namespace Discord.Net.WebSockets | |||||
| _sessionId = sessionId; | _sessionId = sessionId; | ||||
| _token = token; | _token = token; | ||||
| await Connect(host, cancelToken); | |||||
| await Connect(); | |||||
| } | } | ||||
| public async Task Reconnect(CancellationToken cancelToken) | |||||
| public async Task Reconnect() | |||||
| { | { | ||||
| try | try | ||||
| { | { | ||||
| await Task.Delay(_client.Config.ReconnectDelay, cancelToken).ConfigureAwait(false); | |||||
| var cancelToken = ParentCancelToken; | |||||
| await Task.Delay(_client.Config.ReconnectDelay, cancelToken).ConfigureAwait(false); | |||||
| while (!cancelToken.IsCancellationRequested) | while (!cancelToken.IsCancellationRequested) | ||||
| { | { | ||||
| try | try | ||||
| { | { | ||||
| await Connect(_host, cancelToken).ConfigureAwait(false); | |||||
| await Connect().ConfigureAwait(false); | |||||
| break; | break; | ||||
| } | } | ||||
| catch (OperationCanceledException) { throw; } | catch (OperationCanceledException) { throw; } | ||||
| @@ -295,7 +295,7 @@ namespace Discord.Net.WebSockets | |||||
| var payload = (msg.Payload as JToken).ToObject<VoiceEvents.Ready>(); | var payload = (msg.Payload as JToken).ToObject<VoiceEvents.Ready>(); | ||||
| _heartbeatInterval = payload.HeartbeatInterval; | _heartbeatInterval = payload.HeartbeatInterval; | ||||
| _ssrc = payload.SSRC; | _ssrc = payload.SSRC; | ||||
| _endpoint = new IPEndPoint((await Dns.GetHostAddressesAsync(_host.Replace("wss://", "")).ConfigureAwait(false)).FirstOrDefault(), payload.Port); | |||||
| _endpoint = new IPEndPoint((await Dns.GetHostAddressesAsync(Host.Replace("wss://", "")).ConfigureAwait(false)).FirstOrDefault(), payload.Port); | |||||
| //_mode = payload.Modes.LastOrDefault(); | //_mode = payload.Modes.LastOrDefault(); | ||||
| _isEncrypted = !payload.Modes.Contains("plain"); | _isEncrypted = !payload.Modes.Contains("plain"); | ||||
| _udp.Connect(_endpoint); | _udp.Connect(_endpoint); | ||||
| @@ -38,7 +38,8 @@ namespace Discord.Net.WebSockets | |||||
| protected readonly DiscordClient _client; | protected readonly DiscordClient _client; | ||||
| protected readonly LogMessageSeverity _logLevel; | protected readonly LogMessageSeverity _logLevel; | ||||
| protected string _host; | |||||
| public string Host { get; set; } | |||||
| protected int _loginTimeout, _heartbeatInterval; | protected int _loginTimeout, _heartbeatInterval; | ||||
| private DateTime _lastHeartbeat; | private DateTime _lastHeartbeat; | ||||
| private Task _runTask; | private Task _runTask; | ||||
| @@ -49,6 +50,7 @@ namespace Discord.Net.WebSockets | |||||
| protected ExceptionDispatchInfo _disconnectReason; | protected ExceptionDispatchInfo _disconnectReason; | ||||
| private bool _wasDisconnectUnexpected; | private bool _wasDisconnectUnexpected; | ||||
| public CancellationToken ParentCancelToken { get; set; } | |||||
| public CancellationToken CancelToken => _cancelToken; | public CancellationToken CancelToken => _cancelToken; | ||||
| private CancellationTokenSource _cancelTokenSource; | private CancellationTokenSource _cancelTokenSource; | ||||
| protected CancellationToken _cancelToken; | protected CancellationToken _cancelToken; | ||||
| @@ -69,22 +71,24 @@ namespace Discord.Net.WebSockets | |||||
| }; | }; | ||||
| } | } | ||||
| protected virtual async Task Connect(string host, CancellationToken cancelToken) | |||||
| protected virtual async Task Connect() | |||||
| { | { | ||||
| if (_state != (int)WebSocketState.Disconnected) | if (_state != (int)WebSocketState.Disconnected) | ||||
| throw new InvalidOperationException("Client is already connected or connecting to the server."); | throw new InvalidOperationException("Client is already connected or connecting to the server."); | ||||
| try | |||||
| try | |||||
| { | { | ||||
| await Disconnect().ConfigureAwait(false); | await Disconnect().ConfigureAwait(false); | ||||
| _state = (int)WebSocketState.Connecting; | _state = (int)WebSocketState.Connecting; | ||||
| _cancelTokenSource = new CancellationTokenSource(); | _cancelTokenSource = new CancellationTokenSource(); | ||||
| _cancelToken = CancellationTokenSource.CreateLinkedTokenSource(_cancelTokenSource.Token, cancelToken).Token; | |||||
| if (ParentCancelToken != null) | |||||
| _cancelToken = CancellationTokenSource.CreateLinkedTokenSource(_cancelTokenSource.Token, ParentCancelToken).Token; | |||||
| else | |||||
| _cancelToken = _cancelTokenSource.Token; | |||||
| await _engine.Connect(host, _cancelToken).ConfigureAwait(false); | |||||
| _host = host; | |||||
| await _engine.Connect(Host, _cancelToken).ConfigureAwait(false); | |||||
| _lastHeartbeat = DateTime.UtcNow; | _lastHeartbeat = DateTime.UtcNow; | ||||
| _runTask = RunTasks(); | _runTask = RunTasks(); | ||||
| @@ -0,0 +1,16 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| using System.Linq; | |||||
| using System.Text; | |||||
| using System.Threading.Tasks; | |||||
| namespace Discord | |||||
| { | |||||
| public sealed class TimeoutException : Exception | |||||
| { | |||||
| internal TimeoutException() | |||||
| : base("An operation has timed out.") | |||||
| { | |||||
| } | |||||
| } | |||||
| } | |||||