Added request retry modes
This commit is contained in:
@@ -32,26 +32,29 @@ namespace Discord.API
|
|||||||
protected readonly JsonSerializer _serializer;
|
protected readonly JsonSerializer _serializer;
|
||||||
protected readonly SemaphoreSlim _stateLock;
|
protected readonly SemaphoreSlim _stateLock;
|
||||||
private readonly RestClientProvider _restClientProvider;
|
private readonly RestClientProvider _restClientProvider;
|
||||||
private readonly string _userAgent;
|
|
||||||
|
|
||||||
protected string _authToken;
|
protected string _authToken;
|
||||||
protected bool _isDisposed;
|
protected bool _isDisposed;
|
||||||
private CancellationTokenSource _loginCancelToken;
|
private CancellationTokenSource _loginCancelToken;
|
||||||
private IRestClient _restClient;
|
private IRestClient _restClient;
|
||||||
|
private bool _fetchCurrentUser;
|
||||||
|
|
||||||
|
public RetryMode DefaultRetryMode { get; }
|
||||||
|
public string UserAgent { get; }
|
||||||
|
|
||||||
public LoginState LoginState { get; private set; }
|
public LoginState LoginState { get; private set; }
|
||||||
public TokenType AuthTokenType { get; private set; }
|
public TokenType AuthTokenType { get; private set; }
|
||||||
public User CurrentUser { get; private set; }
|
public User CurrentUser { get; private set; }
|
||||||
public RequestQueue RequestQueue { get; private set; }
|
public RequestQueue RequestQueue { get; private set; }
|
||||||
internal bool FetchCurrentUser { get; set; }
|
|
||||||
|
|
||||||
public DiscordRestApiClient(RestClientProvider restClientProvider, string userAgent, JsonSerializer serializer = null, RequestQueue requestQueue = null)
|
public DiscordRestApiClient(RestClientProvider restClientProvider, string userAgent, RetryMode defaultRetryMode = RetryMode.AlwaysRetry,
|
||||||
|
JsonSerializer serializer = null, RequestQueue requestQueue = null, bool fetchCurrentUser = true)
|
||||||
{
|
{
|
||||||
_restClientProvider = restClientProvider;
|
_restClientProvider = restClientProvider;
|
||||||
_userAgent = userAgent;
|
UserAgent = userAgent;
|
||||||
_serializer = serializer ?? new JsonSerializer { DateFormatString = "yyyy-MM-ddTHH:mm:ssZ", ContractResolver = new DiscordContractResolver() };
|
_serializer = serializer ?? new JsonSerializer { DateFormatString = "yyyy-MM-ddTHH:mm:ssZ", ContractResolver = new DiscordContractResolver() };
|
||||||
RequestQueue = requestQueue;
|
RequestQueue = requestQueue;
|
||||||
FetchCurrentUser = true;
|
_fetchCurrentUser = fetchCurrentUser;
|
||||||
|
|
||||||
_stateLock = new SemaphoreSlim(1, 1);
|
_stateLock = new SemaphoreSlim(1, 1);
|
||||||
|
|
||||||
@@ -61,7 +64,7 @@ namespace Discord.API
|
|||||||
{
|
{
|
||||||
_restClient = _restClientProvider(baseUrl);
|
_restClient = _restClientProvider(baseUrl);
|
||||||
_restClient.SetHeader("accept", "*/*");
|
_restClient.SetHeader("accept", "*/*");
|
||||||
_restClient.SetHeader("user-agent", _userAgent);
|
_restClient.SetHeader("user-agent", UserAgent);
|
||||||
_restClient.SetHeader("authorization", GetPrefixedToken(AuthTokenType, _authToken));
|
_restClient.SetHeader("authorization", GetPrefixedToken(AuthTokenType, _authToken));
|
||||||
}
|
}
|
||||||
internal static string GetPrefixedToken(TokenType tokenType, string token)
|
internal static string GetPrefixedToken(TokenType tokenType, string token)
|
||||||
@@ -120,8 +123,8 @@ namespace Discord.API
|
|||||||
_authToken = token;
|
_authToken = token;
|
||||||
_restClient.SetHeader("authorization", GetPrefixedToken(AuthTokenType, _authToken));
|
_restClient.SetHeader("authorization", GetPrefixedToken(AuthTokenType, _authToken));
|
||||||
|
|
||||||
if (FetchCurrentUser)
|
if (_fetchCurrentUser)
|
||||||
CurrentUser = await GetMyUserAsync(new RequestOptions { IgnoreState = true }).ConfigureAwait(false);
|
CurrentUser = await GetMyUserAsync(new RequestOptions { IgnoreState = true, RetryMode = RetryMode.AlwaysRetry }).ConfigureAwait(false);
|
||||||
|
|
||||||
LoginState = LoginState.LoggedIn;
|
LoginState = LoginState.LoggedIn;
|
||||||
}
|
}
|
||||||
@@ -257,6 +260,8 @@ namespace Discord.API
|
|||||||
{
|
{
|
||||||
if (!request.Options.IgnoreState)
|
if (!request.Options.IgnoreState)
|
||||||
CheckState();
|
CheckState();
|
||||||
|
if (request.Options.RetryMode == null)
|
||||||
|
request.Options.RetryMode = DefaultRetryMode;
|
||||||
|
|
||||||
var stopwatch = Stopwatch.StartNew();
|
var stopwatch = Stopwatch.StartNew();
|
||||||
var responseStream = await RequestQueue.SendAsync(request).ConfigureAwait(false);
|
var responseStream = await RequestQueue.SendAsync(request).ConfigureAwait(false);
|
||||||
|
|||||||
@@ -19,6 +19,9 @@ namespace Discord
|
|||||||
public const int MaxMessagesPerBatch = 100;
|
public const int MaxMessagesPerBatch = 100;
|
||||||
public const int MaxUsersPerBatch = 1000;
|
public const int MaxUsersPerBatch = 1000;
|
||||||
|
|
||||||
|
/// <summary> Gets or sets how a request should act in the case of an error, by default. </summary>
|
||||||
|
public RetryMode DefaultRetryMode { get; set; } = RetryMode.AlwaysRetry;
|
||||||
|
|
||||||
/// <summary> Gets or sets the minimum log level severity that will be sent to the LogMessage event. </summary>
|
/// <summary> Gets or sets the minimum log level severity that will be sent to the LogMessage event. </summary>
|
||||||
public LogSeverity LogLevel { get; set; } = LogSeverity.Info;
|
public LogSeverity LogLevel { get; set; } = LogSeverity.Info;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ namespace Discord
|
|||||||
DateTimeOffset? JoinedAt { get; }
|
DateTimeOffset? JoinedAt { get; }
|
||||||
/// <summary> Gets the nickname for this user. </summary>
|
/// <summary> Gets the nickname for this user. </summary>
|
||||||
string Nickname { get; }
|
string Nickname { get; }
|
||||||
|
/// <summary> Gets the guild-level permissions for this user. </summary>
|
||||||
GuildPermissions GuildPermissions { get; }
|
GuildPermissions GuildPermissions { get; }
|
||||||
|
|
||||||
/// <summary> Gets the guild for this user. </summary>
|
/// <summary> Gets the guild for this user. </summary>
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
|
#if DEBUG_LIMITS
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
#endif
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
@@ -63,7 +65,11 @@ namespace Discord.Net.Queue
|
|||||||
|
|
||||||
public async Task<Stream> SendAsync(RestRequest request)
|
public async Task<Stream> SendAsync(RestRequest request)
|
||||||
{
|
{
|
||||||
request.CancelToken = _requestCancelToken;
|
if (request.Options.CancelToken.CanBeCanceled)
|
||||||
|
request.Options.CancelToken = CancellationTokenSource.CreateLinkedTokenSource(_requestCancelToken, request.Options.CancelToken).Token;
|
||||||
|
else
|
||||||
|
request.Options.CancelToken = _requestCancelToken;
|
||||||
|
|
||||||
var bucket = GetOrCreateBucket(request.Options.BucketId, request);
|
var bucket = GetOrCreateBucket(request.Options.BucketId, request);
|
||||||
return await bucket.SendAsync(request).ConfigureAwait(false);
|
return await bucket.SendAsync(request).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using Discord.Net.Rest;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using System;
|
using System;
|
||||||
#if DEBUG_LIMITS
|
#if DEBUG_LIMITS
|
||||||
@@ -88,7 +87,10 @@ namespace Discord.Net.Queue
|
|||||||
#if DEBUG_LIMITS
|
#if DEBUG_LIMITS
|
||||||
Debug.WriteLine($"[{id}] (!) 502");
|
Debug.WriteLine($"[{id}] (!) 502");
|
||||||
#endif
|
#endif
|
||||||
continue; //Continue
|
if ((request.Options.RetryMode & RetryMode.Retry502) == 0)
|
||||||
|
throw new HttpException(HttpStatusCode.BadGateway, null);
|
||||||
|
|
||||||
|
continue; //Retry
|
||||||
default:
|
default:
|
||||||
string reason = null;
|
string reason = null;
|
||||||
if (response.Stream != null)
|
if (response.Stream != null)
|
||||||
@@ -115,13 +117,28 @@ namespace Discord.Net.Queue
|
|||||||
return response.Stream;
|
return response.Stream;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#if DEBUG_LIMITS
|
catch (TimeoutException)
|
||||||
catch
|
|
||||||
{
|
{
|
||||||
Debug.WriteLine($"[{id}] Error");
|
#if DEBUG_LIMITS
|
||||||
throw;
|
Debug.WriteLine($"[{id}] Timeout");
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
if ((request.Options.RetryMode & RetryMode.RetryTimeouts) == 0)
|
||||||
|
throw;
|
||||||
|
|
||||||
|
await Task.Delay(500);
|
||||||
|
continue; //Retry
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
#if DEBUG_LIMITS
|
||||||
|
Debug.WriteLine($"[{id}] Error");
|
||||||
|
#endif
|
||||||
|
if ((request.Options.RetryMode & RetryMode.RetryErrors) == 0)
|
||||||
|
throw;
|
||||||
|
|
||||||
|
await Task.Delay(500);
|
||||||
|
continue; //Retry
|
||||||
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
UpdateRateLimit(id, request, info, lag, false);
|
UpdateRateLimit(id, request, info, lag, false);
|
||||||
@@ -140,7 +157,7 @@ namespace Discord.Net.Queue
|
|||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
if (DateTimeOffset.UtcNow > request.TimeoutAt || request.CancelToken.IsCancellationRequested)
|
if (DateTimeOffset.UtcNow > request.TimeoutAt || request.Options.CancelToken.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
if (!isRateLimited)
|
if (!isRateLimited)
|
||||||
throw new TimeoutException();
|
throw new TimeoutException();
|
||||||
@@ -162,6 +179,10 @@ namespace Discord.Net.Queue
|
|||||||
isRateLimited = true;
|
isRateLimited = true;
|
||||||
await _queue.RaiseRateLimitTriggered(Id, null).ConfigureAwait(false);
|
await _queue.RaiseRateLimitTriggered(Id, null).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ((request.Options.RetryMode & RetryMode.RetryRatelimit) == 0)
|
||||||
|
throw new RateLimitedException();
|
||||||
|
|
||||||
if (resetAt.HasValue)
|
if (resetAt.HasValue)
|
||||||
{
|
{
|
||||||
if (resetAt > timeoutAt)
|
if (resetAt > timeoutAt)
|
||||||
@@ -171,7 +192,7 @@ namespace Discord.Net.Queue
|
|||||||
Debug.WriteLine($"[{id}] Sleeping {millis} ms (Pre-emptive)");
|
Debug.WriteLine($"[{id}] Sleeping {millis} ms (Pre-emptive)");
|
||||||
#endif
|
#endif
|
||||||
if (millis > 0)
|
if (millis > 0)
|
||||||
await Task.Delay(millis, request.CancelToken).ConfigureAwait(false);
|
await Task.Delay(millis, request.Options.CancelToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -180,7 +201,7 @@ namespace Discord.Net.Queue
|
|||||||
#if DEBUG_LIMITS
|
#if DEBUG_LIMITS
|
||||||
Debug.WriteLine($"[{id}] Sleeping 500* ms (Pre-emptive)");
|
Debug.WriteLine($"[{id}] Sleeping 500* ms (Pre-emptive)");
|
||||||
#endif
|
#endif
|
||||||
await Task.Delay(500, request.CancelToken).ConfigureAwait(false);
|
await Task.Delay(500, request.Options.CancelToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ namespace Discord.Net.Queue
|
|||||||
|
|
||||||
public override async Task<RestResponse> SendAsync()
|
public override async Task<RestResponse> SendAsync()
|
||||||
{
|
{
|
||||||
return await Client.SendAsync(Method, Endpoint, Json, CancelToken, Options.HeaderOnly).ConfigureAwait(false);
|
return await Client.SendAsync(Method, Endpoint, Json, Options.CancelToken, Options.HeaderOnly).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ namespace Discord.Net.Queue
|
|||||||
|
|
||||||
public override async Task<RestResponse> SendAsync()
|
public override async Task<RestResponse> SendAsync()
|
||||||
{
|
{
|
||||||
return await Client.SendAsync(Method, Endpoint, MultipartParams, CancelToken, Options.HeaderOnly).ConfigureAwait(false);
|
return await Client.SendAsync(Method, Endpoint, MultipartParams, Options.CancelToken, Options.HeaderOnly).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
using Discord.Net.Rest;
|
using Discord.Net.Rest;
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Discord.Net.Queue
|
namespace Discord.Net.Queue
|
||||||
@@ -14,7 +13,6 @@ namespace Discord.Net.Queue
|
|||||||
public DateTimeOffset? TimeoutAt { get; }
|
public DateTimeOffset? TimeoutAt { get; }
|
||||||
public TaskCompletionSource<Stream> Promise { get; }
|
public TaskCompletionSource<Stream> Promise { get; }
|
||||||
public RequestOptions Options { get; }
|
public RequestOptions Options { get; }
|
||||||
public CancellationToken CancelToken { get; internal set; }
|
|
||||||
|
|
||||||
public RestRequest(IRestClient client, string method, string endpoint, RequestOptions options)
|
public RestRequest(IRestClient client, string method, string endpoint, RequestOptions options)
|
||||||
{
|
{
|
||||||
@@ -24,14 +22,13 @@ namespace Discord.Net.Queue
|
|||||||
Method = method;
|
Method = method;
|
||||||
Endpoint = endpoint;
|
Endpoint = endpoint;
|
||||||
Options = options;
|
Options = options;
|
||||||
CancelToken = CancellationToken.None;
|
|
||||||
TimeoutAt = options.Timeout.HasValue ? DateTimeOffset.UtcNow.AddMilliseconds(options.Timeout.Value) : (DateTimeOffset?)null;
|
TimeoutAt = options.Timeout.HasValue ? DateTimeOffset.UtcNow.AddMilliseconds(options.Timeout.Value) : (DateTimeOffset?)null;
|
||||||
Promise = new TaskCompletionSource<Stream>();
|
Promise = new TaskCompletionSource<Stream>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual async Task<RestResponse> SendAsync()
|
public virtual async Task<RestResponse> SendAsync()
|
||||||
{
|
{
|
||||||
return await Client.SendAsync(Method, Endpoint, CancelToken, Options.HeaderOnly).ConfigureAwait(false);
|
return await Client.SendAsync(Method, Endpoint, Options.CancelToken, Options.HeaderOnly).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,18 @@
|
|||||||
namespace Discord
|
using System.Threading;
|
||||||
|
|
||||||
|
namespace Discord
|
||||||
{
|
{
|
||||||
public class RequestOptions
|
public class RequestOptions
|
||||||
{
|
{
|
||||||
public static RequestOptions Default => new RequestOptions();
|
public static RequestOptions Default => new RequestOptions();
|
||||||
|
|
||||||
/// <summary> The max time, in milliseconds, to wait for this request to complete. If null, a request will not time out. If a rate limit has been triggered for this request's bucket and will not be unpaused in time, this request will fail immediately. </summary>
|
/// <summary>
|
||||||
|
/// The max time, in milliseconds, to wait for this request to complete. If null, a request will not time out.
|
||||||
|
/// If a rate limit has been triggered for this request's bucket and will not be unpaused in time, this request will fail immediately.
|
||||||
|
/// </summary>
|
||||||
public int? Timeout { get; set; }
|
public int? Timeout { get; set; }
|
||||||
|
public CancellationToken CancelToken { get; set; } = CancellationToken.None;
|
||||||
|
public RetryMode? RetryMode { get; set; }
|
||||||
public bool HeaderOnly { get; internal set; }
|
public bool HeaderOnly { get; internal set; }
|
||||||
|
|
||||||
internal bool IgnoreState { get; set; }
|
internal bool IgnoreState { get; set; }
|
||||||
|
|||||||
22
src/Discord.Net.Core/RetryMode.cs
Normal file
22
src/Discord.Net.Core/RetryMode.cs
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Discord
|
||||||
|
{
|
||||||
|
/// <summary> Specifies how a request should act in the case of an error. </summary>
|
||||||
|
[Flags]
|
||||||
|
public enum RetryMode
|
||||||
|
{
|
||||||
|
/// <summary> If a request fails, an exception is thrown immediately. </summary>
|
||||||
|
AlwaysFail = 0x0,
|
||||||
|
/// <summary> Retry if a request timed out. </summary>
|
||||||
|
RetryTimeouts = 0x1,
|
||||||
|
/// <summary> Retry if a request failed due to a network error. </summary>
|
||||||
|
RetryErrors = 0x2,
|
||||||
|
/// <summary> Retry if a request failed due to a ratelimit. </summary>
|
||||||
|
RetryRatelimit = 0x4,
|
||||||
|
/// <summary> Retry if a request failed due to an HTTP error 502. </summary>
|
||||||
|
Retry502 = 0x8,
|
||||||
|
/// <summary> Continuously retry a request until it times out, its cancel token is triggered, or the server responds with a non-502 error. </summary>
|
||||||
|
AlwaysRetry = RetryTimeouts | RetryErrors | RetryRatelimit | Retry502,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -69,15 +69,13 @@ namespace Discord.API
|
|||||||
public ConnectionState ConnectionState { get; private set; }
|
public ConnectionState ConnectionState { get; private set; }
|
||||||
|
|
||||||
public DiscordRpcApiClient(string clientId, string userAgent, string origin, RestClientProvider restClientProvider, WebSocketProvider webSocketProvider,
|
public DiscordRpcApiClient(string clientId, string userAgent, string origin, RestClientProvider restClientProvider, WebSocketProvider webSocketProvider,
|
||||||
JsonSerializer serializer = null, RequestQueue requestQueue = null)
|
RetryMode defaultRetryMode = RetryMode.AlwaysRetry, JsonSerializer serializer = null, RequestQueue requestQueue = null)
|
||||||
: base(restClientProvider, userAgent, serializer, requestQueue)
|
: base(restClientProvider, userAgent, defaultRetryMode, serializer, requestQueue, false)
|
||||||
{
|
{
|
||||||
_connectionLock = new SemaphoreSlim(1, 1);
|
_connectionLock = new SemaphoreSlim(1, 1);
|
||||||
_clientId = clientId;
|
_clientId = clientId;
|
||||||
_origin = origin;
|
_origin = origin;
|
||||||
|
|
||||||
FetchCurrentUser = false;
|
|
||||||
|
|
||||||
_requestQueue = requestQueue ?? new RequestQueue();
|
_requestQueue = requestQueue ?? new RequestQueue();
|
||||||
_requests = new ConcurrentDictionary<Guid, RpcRequest>();
|
_requests = new ConcurrentDictionary<Guid, RpcRequest>();
|
||||||
|
|
||||||
|
|||||||
@@ -32,11 +32,12 @@ namespace Discord.API
|
|||||||
|
|
||||||
public ConnectionState ConnectionState { get; private set; }
|
public ConnectionState ConnectionState { get; private set; }
|
||||||
|
|
||||||
public DiscordSocketApiClient(RestClientProvider restClientProvider, string userAgent, WebSocketProvider webSocketProvider, JsonSerializer serializer = null, RequestQueue requestQueue = null)
|
public DiscordSocketApiClient(RestClientProvider restClientProvider, string userAgent, WebSocketProvider webSocketProvider,
|
||||||
: base(restClientProvider, userAgent, serializer, requestQueue)
|
RetryMode defaultRetryMode = RetryMode.AlwaysRetry, JsonSerializer serializer = null, RequestQueue requestQueue = null)
|
||||||
|
: base(restClientProvider, userAgent, defaultRetryMode, serializer, requestQueue, true)
|
||||||
{
|
{
|
||||||
_gatewayClient = webSocketProvider();
|
_gatewayClient = webSocketProvider();
|
||||||
//_gatewayClient.SetHeader("user-agent", DiscordConfig.UserAgent); (Causes issues in .Net 4.6+)
|
//_gatewayClient.SetHeader("user-agent", DiscordConfig.UserAgent); (Causes issues in .NET Framework 4.6+)
|
||||||
_gatewayClient.BinaryMessage += async (data, index, count) =>
|
_gatewayClient.BinaryMessage += async (data, index, count) =>
|
||||||
{
|
{
|
||||||
using (var compressed = new MemoryStream(data, index + 2, count - 2))
|
using (var compressed = new MemoryStream(data, index + 2, count - 2))
|
||||||
|
|||||||
@@ -221,7 +221,5 @@ namespace Discord.WebSocket
|
|||||||
remove { _recipientRemovedEvent.Remove(value); }
|
remove { _recipientRemovedEvent.Remove(value); }
|
||||||
}
|
}
|
||||||
private readonly AsyncEvent<Func<SocketGroupUser, Task>> _recipientRemovedEvent = new AsyncEvent<Func<SocketGroupUser, Task>>();
|
private readonly AsyncEvent<Func<SocketGroupUser, Task>> _recipientRemovedEvent = new AsyncEvent<Func<SocketGroupUser, Task>>();
|
||||||
|
|
||||||
//TODO: Add PresenceUpdated? VoiceStateUpdated?, VoiceConnected, VoiceDisconnected;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ namespace Discord.WebSocket
|
|||||||
|
|
||||||
protected override async Task OnLoginAsync(TokenType tokenType, string token)
|
protected override async Task OnLoginAsync(TokenType tokenType, string token)
|
||||||
{
|
{
|
||||||
var voiceRegions = await ApiClient.GetVoiceRegionsAsync(new RequestOptions { IgnoreState = true}).ConfigureAwait(false);
|
var voiceRegions = await ApiClient.GetVoiceRegionsAsync(new RequestOptions { IgnoreState = true, RetryMode = RetryMode.AlwaysRetry }).ConfigureAwait(false);
|
||||||
_voiceRegions = voiceRegions.Select(x => RestVoiceRegion.Create(this, x)).ToImmutableDictionary(x => x.Id);
|
_voiceRegions = voiceRegions.Select(x => RestVoiceRegion.Create(this, x)).ToImmutableDictionary(x => x.Id);
|
||||||
}
|
}
|
||||||
protected override async Task OnLogoutAsync()
|
protected override async Task OnLogoutAsync()
|
||||||
|
|||||||
Reference in New Issue
Block a user