| @@ -51,14 +51,14 @@ namespace Discord.Commands | |||||
| .Description("Returns information about commands.") | .Description("Returns information about commands.") | ||||
| .Do(async e => | .Do(async e => | ||||
| { | { | ||||
| Channel replyChannel = _config.HelpMode == HelpMode.Public ? e.Channel : await e.User.CreateChannel(); | |||||
| Channel replyChannel = _config.HelpMode == HelpMode.Public ? e.Channel : await e.User.CreateChannel().ConfigureAwait(false); | |||||
| if (e.Args.Length > 0) //Show command help | if (e.Args.Length > 0) //Show command help | ||||
| { | { | ||||
| var map = _map.GetItem(string.Join(" ", e.Args)); | var map = _map.GetItem(string.Join(" ", e.Args)); | ||||
| if (map != null) | if (map != null) | ||||
| await ShowCommandHelp(map, e.User, e.Channel, replyChannel); | |||||
| await ShowCommandHelp(map, e.User, e.Channel, replyChannel).ConfigureAwait(false); | |||||
| else | else | ||||
| await replyChannel.SendMessage("Unable to display help: Unknown command."); | |||||
| await replyChannel.SendMessage("Unable to display help: Unknown command.").ConfigureAwait(false); | |||||
| } | } | ||||
| else //Show general help | else //Show general help | ||||
| await ShowGeneralHelp(e.User, e.Channel, replyChannel); | await ShowGeneralHelp(e.User, e.Channel, replyChannel); | ||||
| @@ -509,6 +509,9 @@ | |||||
| <Compile Include="..\Discord.Net\Net\TimeoutException.cs"> | <Compile Include="..\Discord.Net\Net\TimeoutException.cs"> | ||||
| <Link>Net\TimeoutException.cs</Link> | <Link>Net\TimeoutException.cs</Link> | ||||
| </Compile> | </Compile> | ||||
| <Compile Include="..\Discord.Net\Net\WebSocketException.cs"> | |||||
| <Link>Net\WebSockets\WebSocketException.cs</Link> | |||||
| </Compile> | |||||
| <Compile Include="..\Discord.Net\Net\WebSockets\GatewaySocket.cs"> | <Compile Include="..\Discord.Net\Net\WebSockets\GatewaySocket.cs"> | ||||
| <Link>Net\WebSockets\GatewaySocket.cs</Link> | <Link>Net\WebSockets\GatewaySocket.cs</Link> | ||||
| </Compile> | </Compile> | ||||
| @@ -19,6 +19,6 @@ | |||||
| /// <summary> C←S - Used to notify a client that they must reconnect to another gateway. </summary> | /// <summary> C←S - Used to notify a client that they must reconnect to another gateway. </summary> | ||||
| Redirect = 7, | Redirect = 7, | ||||
| /// <summary> C→S - Used to request all members that were withheld by large_threshold </summary> | /// <summary> C→S - Used to request all members that were withheld by large_threshold </summary> | ||||
| RequestGuildMembers = 8 | |||||
| RequestGuildMembers = 99 | |||||
| } | } | ||||
| } | } | ||||
| @@ -159,7 +159,7 @@ namespace Discord.Legacy | |||||
| if (messages == null) throw new ArgumentNullException(nameof(messages)); | if (messages == null) throw new ArgumentNullException(nameof(messages)); | ||||
| foreach (var message in messages) | foreach (var message in messages) | ||||
| await message.Delete(); | |||||
| await message.Delete().ConfigureAwait(false); | |||||
| } | } | ||||
| [Obsolete("Use Channel.DownloadMessages")] | [Obsolete("Use Channel.DownloadMessages")] | ||||
| @@ -128,7 +128,7 @@ namespace Discord | |||||
| Connected += async (s, e) => | Connected += async (s, e) => | ||||
| { | { | ||||
| ClientAPI.CancelToken = CancelToken; | ClientAPI.CancelToken = CancelToken; | ||||
| await SendStatus(); | |||||
| await SendStatus().ConfigureAwait(false); | |||||
| }; | }; | ||||
| //Extensibility | //Extensibility | ||||
| @@ -250,7 +250,7 @@ namespace Discord | |||||
| } | } | ||||
| //Cache other stuff | //Cache other stuff | ||||
| var regionsResponse = (await ClientAPI.Send(new GetVoiceRegionsRequest())); | |||||
| var regionsResponse = (await ClientAPI.Send(new GetVoiceRegionsRequest()).ConfigureAwait(false)); | |||||
| _regions = regionsResponse.Select(x => new Region(x.Id, x.Name, x.Hostname, x.Port)) | _regions = regionsResponse.Select(x => new Region(x.Id, x.Name, x.Hostname, x.Port)) | ||||
| .ToDictionary(x => x.Id); | .ToDictionary(x => x.Id); | ||||
| break; | break; | ||||
| @@ -213,7 +213,7 @@ namespace Discord | |||||
| #region Invites | #region Invites | ||||
| /// <summary> Gets all active (non-expired) invites to this server. </summary> | /// <summary> Gets all active (non-expired) invites to this server. </summary> | ||||
| public async Task<IEnumerable<Invite>> GetInvites() | public async Task<IEnumerable<Invite>> GetInvites() | ||||
| => (await Server.GetInvites()).Where(x => x.Channel.Id == Id); | |||||
| => (await Server.GetInvites().ConfigureAwait(false)).Where(x => x.Channel.Id == Id); | |||||
| /// <summary> Creates a new invite to this channel. </summary> | /// <summary> Creates a new invite to this channel. </summary> | ||||
| /// <param name="maxAge"> Time (in seconds) until the invite expires. Set to null to never expire. </param> | /// <param name="maxAge"> Time (in seconds) until the invite expires. Set to null to never expire. </param> | ||||
| @@ -0,0 +1,25 @@ | |||||
| using System; | |||||
| namespace Discord.Net | |||||
| { | |||||
| public class WebSocketException : Exception | |||||
| { | |||||
| public int Code { get; } | |||||
| public string Reason { get; } | |||||
| public WebSocketException(int code, string reason) | |||||
| : base(GenerateMessage(code, reason)) | |||||
| { | |||||
| Code = code; | |||||
| Reason = reason; | |||||
| } | |||||
| private static string GenerateMessage(int? code, string reason) | |||||
| { | |||||
| if (!String.IsNullOrEmpty(reason)) | |||||
| return $"Received close code {code}: {reason}"; | |||||
| else | |||||
| return $"Received close code {code}"; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -13,8 +13,10 @@ namespace Discord.Net.WebSockets | |||||
| { | { | ||||
| private int _lastSequence; | private int _lastSequence; | ||||
| private string _sessionId; | private string _sessionId; | ||||
| private string _token; | |||||
| private int _reconnects; | |||||
| public string Token { get; internal set; } | |||||
| public string Token { get { return _token; } internal set { _token = value; _sessionId = null; } } | |||||
| public GatewaySocket(DiscordClient client, JsonSerializer serializer, Logger logger) | public GatewaySocket(DiscordClient client, JsonSerializer serializer, Logger logger) | ||||
| : base(client, serializer, logger) | : base(client, serializer, logger) | ||||
| @@ -29,20 +31,21 @@ namespace Discord.Net.WebSockets | |||||
| public async Task Connect() | public async Task Connect() | ||||
| { | { | ||||
| await BeginConnect().ConfigureAwait(false); | await BeginConnect().ConfigureAwait(false); | ||||
| SendIdentify(Token); | |||||
| if (_sessionId == null) | |||||
| SendIdentify(Token); | |||||
| else | |||||
| SendResume(); | |||||
| } | } | ||||
| private async Task Redirect() | |||||
| { | |||||
| await BeginConnect().ConfigureAwait(false); | |||||
| SendResume(); | |||||
| } | |||||
| private async Task Reconnect() | private async Task Reconnect() | ||||
| { | { | ||||
| try | try | ||||
| { | { | ||||
| var cancelToken = ParentCancelToken.Value; | var cancelToken = ParentCancelToken.Value; | ||||
| await Task.Delay(_client.Config.ReconnectDelay, cancelToken).ConfigureAwait(false); | |||||
| while (!cancelToken.IsCancellationRequested) | |||||
| if (_reconnects++ == 0) | |||||
| await Task.Delay(_client.Config.ReconnectDelay, cancelToken).ConfigureAwait(false); | |||||
| else | |||||
| await Task.Delay(_client.Config.FailedReconnectDelay, cancelToken).ConfigureAwait(false); | |||||
| while (!cancelToken.IsCancellationRequested) | |||||
| { | { | ||||
| try | try | ||||
| { | { | ||||
| @@ -69,8 +72,15 @@ namespace Discord.Net.WebSockets | |||||
| tasks.Add(HeartbeatAsync(CancelToken)); | tasks.Add(HeartbeatAsync(CancelToken)); | ||||
| await _taskManager.Start(tasks, _cancelTokenSource).ConfigureAwait(false); | await _taskManager.Start(tasks, _cancelTokenSource).ConfigureAwait(false); | ||||
| } | } | ||||
| protected override Task Cleanup() | |||||
| { | |||||
| var ex = _taskManager.Exception; | |||||
| if (ex != null && (ex as WebSocketException)?.Code != 1012) | |||||
| _sessionId = null; //Reset session unless close code 1012 | |||||
| return base.Cleanup(); | |||||
| } | |||||
| protected override async Task ProcessMessage(string json) | |||||
| protected override async Task ProcessMessage(string json) | |||||
| { | { | ||||
| await base.ProcessMessage(json).ConfigureAwait(false); | await base.ProcessMessage(json).ConfigureAwait(false); | ||||
| var msg = JsonConvert.DeserializeObject<WebSocketMessage>(json); | var msg = JsonConvert.DeserializeObject<WebSocketMessage>(json); | ||||
| @@ -85,9 +95,10 @@ namespace Discord.Net.WebSockets | |||||
| JToken token = msg.Payload as JToken; | JToken token = msg.Payload as JToken; | ||||
| if (msg.Type == "READY") | if (msg.Type == "READY") | ||||
| { | { | ||||
| var payload = token.ToObject<ReadyEvent>(_serializer); | |||||
| _reconnects = 0; | |||||
| var payload = token.ToObject<ReadyEvent>(_serializer); | |||||
| _sessionId = payload.SessionId; | _sessionId = payload.SessionId; | ||||
| _heartbeatInterval = payload.HeartbeatInterval; | |||||
| _heartbeatInterval = payload.HeartbeatInterval; | |||||
| } | } | ||||
| else if (msg.Type == "RESUMED") | else if (msg.Type == "RESUMED") | ||||
| { | { | ||||
| @@ -106,7 +117,7 @@ namespace Discord.Net.WebSockets | |||||
| { | { | ||||
| Host = payload.Url; | Host = payload.Url; | ||||
| Logger.Info("Redirected to " + payload.Url); | Logger.Info("Redirected to " + payload.Url); | ||||
| await Redirect().ConfigureAwait(false); | |||||
| await Reconnect().ConfigureAwait(false); | |||||
| } | } | ||||
| } | } | ||||
| break; | break; | ||||
| @@ -83,13 +83,7 @@ namespace Discord.Net.WebSockets | |||||
| { | { | ||||
| Exception ex; | Exception ex; | ||||
| if (e is ClosedEventArgs) | if (e is ClosedEventArgs) | ||||
| { | |||||
| int code = (e as ClosedEventArgs).Code; | |||||
| string reason = (e as ClosedEventArgs).Reason; | |||||
| if (String.IsNullOrEmpty(reason)) | |||||
| reason = "No reason"; | |||||
| ex = new Exception($"Received close code {code}: {reason}"); | |||||
| } | |||||
| ex = new WebSocketException((e as ClosedEventArgs).Code, (e as ClosedEventArgs).Reason); | |||||
| else | else | ||||
| ex = new Exception($"Connection lost"); | ex = new Exception($"Connection lost"); | ||||
| _taskManager.SignalError(ex, isUnexpected: true); | _taskManager.SignalError(ex, isUnexpected: true); | ||||
| @@ -125,7 +125,7 @@ namespace Discord.Net.WebSockets | |||||
| _cancelTokenSource = null; | _cancelTokenSource = null; | ||||
| _connectedEvent.Reset(); | _connectedEvent.Reset(); | ||||
| if (oldState == ConnectionState.Connected) | |||||
| if (oldState == ConnectionState.Connecting || oldState == ConnectionState.Connected) | |||||
| { | { | ||||
| var ex = _taskManager.Exception; | var ex = _taskManager.Exception; | ||||
| if (ex == null) | if (ex == null) | ||||