* support X-RateLimit-Reset sending integer or float values This changes the way that the X-RateLimit-Request header is parsed, so that it will work with both integer seconds and float values with seconds and milliseconds * Add RateLimitPrecision enum, set X-RateLimit-Precision Adds the RateLimitPrecision enum, with Second and Millisecond values. (Do we want to use an extension method to convert it into a string, or is ToString().ToLower() fine?) Adds RateLimitPrecision as a parameter to DiscordRestApiClient, and to DiscordConfig so that it can set the X-RateLimit-Precision header.pull/1376/head
@@ -36,7 +36,7 @@ namespace Discord | |||||
typeof(DiscordConfig).GetTypeInfo().Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion ?? | typeof(DiscordConfig).GetTypeInfo().Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion ?? | ||||
typeof(DiscordConfig).GetTypeInfo().Assembly.GetName().Version.ToString(3) ?? | typeof(DiscordConfig).GetTypeInfo().Assembly.GetName().Version.ToString(3) ?? | ||||
"Unknown"; | "Unknown"; | ||||
/// <summary> | /// <summary> | ||||
/// Gets the user agent that Discord.Net uses in its clients. | /// Gets the user agent that Discord.Net uses in its clients. | ||||
/// </summary> | /// </summary> | ||||
@@ -123,7 +123,7 @@ namespace Discord | |||||
/// The currently set <see cref="RetryMode"/>. | /// The currently set <see cref="RetryMode"/>. | ||||
/// </returns> | /// </returns> | ||||
public RetryMode DefaultRetryMode { get; set; } = RetryMode.AlwaysRetry; | public RetryMode DefaultRetryMode { get; set; } = RetryMode.AlwaysRetry; | ||||
/// <summary> | /// <summary> | ||||
/// Gets or sets the minimum log level severity that will be sent to the Log event. | /// Gets or sets the minimum log level severity that will be sent to the Log event. | ||||
/// </summary> | /// </summary> | ||||
@@ -140,5 +140,17 @@ namespace Discord | |||||
/// the API version it uses on startup. | /// the API version it uses on startup. | ||||
/// </remarks> | /// </remarks> | ||||
internal bool DisplayInitialLog { get; set; } = true; | internal bool DisplayInitialLog { get; set; } = true; | ||||
/// <summary> | |||||
/// Gets or sets the level of precision of the rate limit reset response. | |||||
/// </summary> | |||||
/// <remarks> | |||||
/// If set to <see cref="RateLimitPrecision.Second"/>, this value will be rounded up to the | |||||
/// nearest second. | |||||
/// </remarks> | |||||
/// <returns> | |||||
/// The currently set <see cref="RateLimitPrecision"/>. | |||||
/// </returns> | |||||
public RateLimitPrecision RateLimitPrecision { get; set; } = RateLimitPrecision.Second; | |||||
} | } | ||||
} | } |
@@ -0,0 +1,18 @@ | |||||
namespace Discord | |||||
{ | |||||
/// <summary> | |||||
/// Specifies the level of precision to request in the rate limit | |||||
/// response header. | |||||
/// </summary> | |||||
public enum RateLimitPrecision | |||||
{ | |||||
/// <summary> | |||||
/// Specifies precision rounded up to the nearest whole second | |||||
/// </summary> | |||||
Second, | |||||
/// <summary> | |||||
/// Specifies precision rounded to the nearest millisecond. | |||||
/// </summary> | |||||
Millisecond | |||||
} | |||||
} |
@@ -45,17 +45,19 @@ namespace Discord.API | |||||
internal string AuthToken { get; private set; } | internal string AuthToken { get; private set; } | ||||
internal IRestClient RestClient { get; private set; } | internal IRestClient RestClient { get; private set; } | ||||
internal ulong? CurrentUserId { get; set; } | internal ulong? CurrentUserId { get; set; } | ||||
public RateLimitPrecision RateLimitPrecision { get; private set; } | |||||
internal JsonSerializer Serializer => _serializer; | internal JsonSerializer Serializer => _serializer; | ||||
/// <exception cref="ArgumentException">Unknown OAuth token type.</exception> | /// <exception cref="ArgumentException">Unknown OAuth token type.</exception> | ||||
public DiscordRestApiClient(RestClientProvider restClientProvider, string userAgent, RetryMode defaultRetryMode = RetryMode.AlwaysRetry, | public DiscordRestApiClient(RestClientProvider restClientProvider, string userAgent, RetryMode defaultRetryMode = RetryMode.AlwaysRetry, | ||||
JsonSerializer serializer = null) | |||||
JsonSerializer serializer = null, RateLimitPrecision rateLimitPrecision = RateLimitPrecision.Second) | |||||
{ | { | ||||
_restClientProvider = restClientProvider; | _restClientProvider = restClientProvider; | ||||
UserAgent = userAgent; | UserAgent = userAgent; | ||||
DefaultRetryMode = defaultRetryMode; | DefaultRetryMode = defaultRetryMode; | ||||
_serializer = serializer ?? new JsonSerializer { ContractResolver = new DiscordContractResolver() }; | _serializer = serializer ?? new JsonSerializer { ContractResolver = new DiscordContractResolver() }; | ||||
RateLimitPrecision = rateLimitPrecision; | |||||
RequestQueue = new RequestQueue(); | RequestQueue = new RequestQueue(); | ||||
_stateLock = new SemaphoreSlim(1, 1); | _stateLock = new SemaphoreSlim(1, 1); | ||||
@@ -71,6 +73,7 @@ namespace Discord.API | |||||
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)); | ||||
RestClient.SetHeader("X-RateLimit-Precision", RateLimitPrecision.ToString().ToLower()); | |||||
} | } | ||||
/// <exception cref="ArgumentException">Unknown OAuth token type.</exception> | /// <exception cref="ArgumentException">Unknown OAuth token type.</exception> | ||||
internal static string GetPrefixedToken(TokenType tokenType, string token) | internal static string GetPrefixedToken(TokenType tokenType, string token) | ||||
@@ -21,7 +21,7 @@ namespace Discord.Net | |||||
Remaining = headers.TryGetValue("X-RateLimit-Remaining", out temp) && | Remaining = headers.TryGetValue("X-RateLimit-Remaining", out temp) && | ||||
int.TryParse(temp, out var remaining) ? remaining : (int?)null; | int.TryParse(temp, out var remaining) ? remaining : (int?)null; | ||||
Reset = headers.TryGetValue("X-RateLimit-Reset", out temp) && | Reset = headers.TryGetValue("X-RateLimit-Reset", out temp) && | ||||
int.TryParse(temp, out var reset) ? DateTimeOffset.FromUnixTimeSeconds(reset) : (DateTimeOffset?)null; | |||||
float.TryParse(temp, out var reset) ? DateTimeOffset.FromUnixTimeMilliseconds((long)(reset * 1000)) : (DateTimeOffset?)null; | |||||
RetryAfter = headers.TryGetValue("Retry-After", out temp) && | RetryAfter = headers.TryGetValue("Retry-After", out temp) && | ||||
int.TryParse(temp, out var retryAfter) ? retryAfter : (int?)null; | int.TryParse(temp, out var retryAfter) ? retryAfter : (int?)null; | ||||
Lag = headers.TryGetValue("Date", out temp) && | Lag = headers.TryGetValue("Date", out temp) && | ||||
@@ -80,7 +80,8 @@ namespace Discord.WebSocket | |||||
internal BaseSocketClient(DiscordSocketConfig config, DiscordRestApiClient client) | internal BaseSocketClient(DiscordSocketConfig config, DiscordRestApiClient client) | ||||
: base(config, client) => BaseConfig = config; | : base(config, client) => BaseConfig = config; | ||||
private static DiscordSocketApiClient CreateApiClient(DiscordSocketConfig config) | private static DiscordSocketApiClient CreateApiClient(DiscordSocketConfig config) | ||||
=> new DiscordSocketApiClient(config.RestClientProvider, config.WebSocketProvider, DiscordRestConfig.UserAgent); | |||||
=> new DiscordSocketApiClient(config.RestClientProvider, config.WebSocketProvider, DiscordRestConfig.UserAgent, | |||||
rateLimitPrecision: config.RateLimitPrecision); | |||||
/// <summary> | /// <summary> | ||||
/// Gets a Discord application information for the logged-in user. | /// Gets a Discord application information for the logged-in user. | ||||
@@ -85,7 +85,8 @@ namespace Discord.WebSocket | |||||
} | } | ||||
} | } | ||||
private static API.DiscordSocketApiClient CreateApiClient(DiscordSocketConfig config) | private static API.DiscordSocketApiClient CreateApiClient(DiscordSocketConfig config) | ||||
=> new API.DiscordSocketApiClient(config.RestClientProvider, config.WebSocketProvider, DiscordRestConfig.UserAgent); | |||||
=> new API.DiscordSocketApiClient(config.RestClientProvider, config.WebSocketProvider, DiscordRestConfig.UserAgent, | |||||
rateLimitPrecision: config.RateLimitPrecision); | |||||
internal override async Task OnLoginAsync(TokenType tokenType, string token) | internal override async Task OnLoginAsync(TokenType tokenType, string token) | ||||
{ | { | ||||
@@ -38,8 +38,9 @@ namespace Discord.API | |||||
public ConnectionState ConnectionState { get; private set; } | public ConnectionState ConnectionState { get; private set; } | ||||
public DiscordSocketApiClient(RestClientProvider restClientProvider, WebSocketProvider webSocketProvider, string userAgent, | public DiscordSocketApiClient(RestClientProvider restClientProvider, WebSocketProvider webSocketProvider, string userAgent, | ||||
string url = null, RetryMode defaultRetryMode = RetryMode.AlwaysRetry, JsonSerializer serializer = null) | |||||
: base(restClientProvider, userAgent, defaultRetryMode, serializer) | |||||
string url = null, RetryMode defaultRetryMode = RetryMode.AlwaysRetry, JsonSerializer serializer = null, | |||||
RateLimitPrecision rateLimitPrecision = RateLimitPrecision.Second) | |||||
: base(restClientProvider, userAgent, defaultRetryMode, serializer, rateLimitPrecision) | |||||
{ | { | ||||
_gatewayUrl = url; | _gatewayUrl = url; | ||||
if (url != null) | if (url != null) | ||||
@@ -176,7 +176,8 @@ namespace Discord.WebSocket | |||||
_largeGuilds = new ConcurrentQueue<ulong>(); | _largeGuilds = new ConcurrentQueue<ulong>(); | ||||
} | } | ||||
private static API.DiscordSocketApiClient CreateApiClient(DiscordSocketConfig config) | private static API.DiscordSocketApiClient CreateApiClient(DiscordSocketConfig config) | ||||
=> new API.DiscordSocketApiClient(config.RestClientProvider, config.WebSocketProvider, DiscordRestConfig.UserAgent, config.GatewayHost); | |||||
=> new API.DiscordSocketApiClient(config.RestClientProvider, config.WebSocketProvider, DiscordRestConfig.UserAgent, config.GatewayHost, | |||||
rateLimitPrecision: config.RateLimitPrecision); | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
internal override void Dispose(bool disposing) | internal override void Dispose(bool disposing) | ||||
{ | { | ||||