Merge branch 'dev' into feature/rich-embeds
This commit is contained in:
@@ -165,30 +165,30 @@ namespace Discord.API
|
|||||||
|
|
||||||
//Core
|
//Core
|
||||||
internal Task SendAsync(string method, Expression<Func<string>> endpointExpr, BucketIds ids,
|
internal Task SendAsync(string method, Expression<Func<string>> endpointExpr, BucketIds ids,
|
||||||
string clientBucketId = null, RequestOptions options = null, [CallerMemberName] string funcName = null)
|
ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null, [CallerMemberName] string funcName = null)
|
||||||
=> SendAsync(method, GetEndpoint(endpointExpr), GetBucketId(ids, endpointExpr, AuthTokenType, funcName), clientBucketId, options);
|
=> SendAsync(method, GetEndpoint(endpointExpr), GetBucketId(ids, endpointExpr, AuthTokenType, funcName), clientBucket, options);
|
||||||
public async Task SendAsync(string method, string endpoint,
|
public async Task SendAsync(string method, string endpoint,
|
||||||
string bucketId = null, string clientBucketId = null, RequestOptions options = null)
|
string bucketId = null, ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null)
|
||||||
{
|
{
|
||||||
options = options ?? new RequestOptions();
|
options = options ?? new RequestOptions();
|
||||||
options.HeaderOnly = true;
|
options.HeaderOnly = true;
|
||||||
options.BucketId = bucketId;
|
options.BucketId = AuthTokenType == TokenType.User ? ClientBucket.Get(clientBucket).Id : bucketId;
|
||||||
options.ClientBucketId = AuthTokenType == TokenType.User ? clientBucketId : null;
|
options.IsClientBucket = AuthTokenType == TokenType.User;
|
||||||
|
|
||||||
var request = new RestRequest(_restClient, method, endpoint, options);
|
var request = new RestRequest(_restClient, method, endpoint, options);
|
||||||
await SendInternalAsync(method, endpoint, request).ConfigureAwait(false);
|
await SendInternalAsync(method, endpoint, request).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal Task SendJsonAsync(string method, Expression<Func<string>> endpointExpr, object payload, BucketIds ids,
|
internal Task SendJsonAsync(string method, Expression<Func<string>> endpointExpr, object payload, BucketIds ids,
|
||||||
string clientBucketId = null, RequestOptions options = null, [CallerMemberName] string funcName = null)
|
ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null, [CallerMemberName] string funcName = null)
|
||||||
=> SendJsonAsync(method, GetEndpoint(endpointExpr), payload, GetBucketId(ids, endpointExpr, AuthTokenType, funcName), clientBucketId, options);
|
=> SendJsonAsync(method, GetEndpoint(endpointExpr), payload, GetBucketId(ids, endpointExpr, AuthTokenType, funcName), clientBucket, options);
|
||||||
public async Task SendJsonAsync(string method, string endpoint, object payload,
|
public async Task SendJsonAsync(string method, string endpoint, object payload,
|
||||||
string bucketId = null, string clientBucketId = null, RequestOptions options = null)
|
string bucketId = null, ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null)
|
||||||
{
|
{
|
||||||
options = options ?? new RequestOptions();
|
options = options ?? new RequestOptions();
|
||||||
options.HeaderOnly = true;
|
options.HeaderOnly = true;
|
||||||
options.BucketId = bucketId;
|
options.BucketId = AuthTokenType == TokenType.User ? ClientBucket.Get(clientBucket).Id : bucketId;
|
||||||
options.ClientBucketId = AuthTokenType == TokenType.User ? clientBucketId : null;
|
options.IsClientBucket = AuthTokenType == TokenType.User;
|
||||||
|
|
||||||
var json = payload != null ? SerializeJson(payload) : null;
|
var json = payload != null ? SerializeJson(payload) : null;
|
||||||
var request = new JsonRestRequest(_restClient, method, endpoint, json, options);
|
var request = new JsonRestRequest(_restClient, method, endpoint, json, options);
|
||||||
@@ -196,43 +196,43 @@ namespace Discord.API
|
|||||||
}
|
}
|
||||||
|
|
||||||
internal Task SendMultipartAsync(string method, Expression<Func<string>> endpointExpr, IReadOnlyDictionary<string, object> multipartArgs, BucketIds ids,
|
internal Task SendMultipartAsync(string method, Expression<Func<string>> endpointExpr, IReadOnlyDictionary<string, object> multipartArgs, BucketIds ids,
|
||||||
string clientBucketId = null, RequestOptions options = null, [CallerMemberName] string funcName = null)
|
ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null, [CallerMemberName] string funcName = null)
|
||||||
=> SendMultipartAsync(method, GetEndpoint(endpointExpr), multipartArgs, GetBucketId(ids, endpointExpr, AuthTokenType, funcName), clientBucketId, options);
|
=> SendMultipartAsync(method, GetEndpoint(endpointExpr), multipartArgs, GetBucketId(ids, endpointExpr, AuthTokenType, funcName), clientBucket, options);
|
||||||
public async Task SendMultipartAsync(string method, string endpoint, IReadOnlyDictionary<string, object> multipartArgs,
|
public async Task SendMultipartAsync(string method, string endpoint, IReadOnlyDictionary<string, object> multipartArgs,
|
||||||
string bucketId = null, string clientBucketId = null, RequestOptions options = null)
|
string bucketId = null, ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null)
|
||||||
{
|
{
|
||||||
options = options ?? new RequestOptions();
|
options = options ?? new RequestOptions();
|
||||||
options.HeaderOnly = true;
|
options.HeaderOnly = true;
|
||||||
options.BucketId = bucketId;
|
options.BucketId = AuthTokenType == TokenType.User ? ClientBucket.Get(clientBucket).Id : bucketId;
|
||||||
options.ClientBucketId = AuthTokenType == TokenType.User ? clientBucketId : null;
|
options.IsClientBucket = AuthTokenType == TokenType.User;
|
||||||
|
|
||||||
var request = new MultipartRestRequest(_restClient, method, endpoint, multipartArgs, options);
|
var request = new MultipartRestRequest(_restClient, method, endpoint, multipartArgs, options);
|
||||||
await SendInternalAsync(method, endpoint, request).ConfigureAwait(false);
|
await SendInternalAsync(method, endpoint, request).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal Task<TResponse> SendAsync<TResponse>(string method, Expression<Func<string>> endpointExpr, BucketIds ids,
|
internal Task<TResponse> SendAsync<TResponse>(string method, Expression<Func<string>> endpointExpr, BucketIds ids,
|
||||||
string clientBucketId = null, RequestOptions options = null, [CallerMemberName] string funcName = null) where TResponse : class
|
ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null, [CallerMemberName] string funcName = null) where TResponse : class
|
||||||
=> SendAsync<TResponse>(method, GetEndpoint(endpointExpr), GetBucketId(ids, endpointExpr, AuthTokenType, funcName), clientBucketId, options);
|
=> SendAsync<TResponse>(method, GetEndpoint(endpointExpr), GetBucketId(ids, endpointExpr, AuthTokenType, funcName), clientBucket, options);
|
||||||
public async Task<TResponse> SendAsync<TResponse>(string method, string endpoint,
|
public async Task<TResponse> SendAsync<TResponse>(string method, string endpoint,
|
||||||
string bucketId = null, string clientBucketId = null, RequestOptions options = null) where TResponse : class
|
string bucketId = null, ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null) where TResponse : class
|
||||||
{
|
{
|
||||||
options = options ?? new RequestOptions();
|
options = options ?? new RequestOptions();
|
||||||
options.BucketId = bucketId;
|
options.BucketId = AuthTokenType == TokenType.User ? ClientBucket.Get(clientBucket).Id : bucketId;
|
||||||
options.ClientBucketId = AuthTokenType == TokenType.User ? clientBucketId : null;
|
options.IsClientBucket = AuthTokenType == TokenType.User;
|
||||||
|
|
||||||
var request = new RestRequest(_restClient, method, endpoint, options);
|
var request = new RestRequest(_restClient, method, endpoint, options);
|
||||||
return DeserializeJson<TResponse>(await SendInternalAsync(method, endpoint, request).ConfigureAwait(false));
|
return DeserializeJson<TResponse>(await SendInternalAsync(method, endpoint, request).ConfigureAwait(false));
|
||||||
}
|
}
|
||||||
|
|
||||||
internal Task<TResponse> SendJsonAsync<TResponse>(string method, Expression<Func<string>> endpointExpr, object payload, BucketIds ids,
|
internal Task<TResponse> SendJsonAsync<TResponse>(string method, Expression<Func<string>> endpointExpr, object payload, BucketIds ids,
|
||||||
string clientBucketId = null, RequestOptions options = null, [CallerMemberName] string funcName = null) where TResponse : class
|
ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null, [CallerMemberName] string funcName = null) where TResponse : class
|
||||||
=> SendJsonAsync<TResponse>(method, GetEndpoint(endpointExpr), payload, GetBucketId(ids, endpointExpr, AuthTokenType, funcName), clientBucketId, options);
|
=> SendJsonAsync<TResponse>(method, GetEndpoint(endpointExpr), payload, GetBucketId(ids, endpointExpr, AuthTokenType, funcName), clientBucket, options);
|
||||||
public async Task<TResponse> SendJsonAsync<TResponse>(string method, string endpoint, object payload,
|
public async Task<TResponse> SendJsonAsync<TResponse>(string method, string endpoint, object payload,
|
||||||
string bucketId = null, string clientBucketId = null, RequestOptions options = null) where TResponse : class
|
string bucketId = null, ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null) where TResponse : class
|
||||||
{
|
{
|
||||||
options = options ?? new RequestOptions();
|
options = options ?? new RequestOptions();
|
||||||
options.BucketId = bucketId;
|
options.BucketId = AuthTokenType == TokenType.User ? ClientBucket.Get(clientBucket).Id : bucketId;
|
||||||
options.ClientBucketId = AuthTokenType == TokenType.User ? clientBucketId : null;
|
options.IsClientBucket = AuthTokenType == TokenType.User;
|
||||||
|
|
||||||
var json = payload != null ? SerializeJson(payload) : null;
|
var json = payload != null ? SerializeJson(payload) : null;
|
||||||
var request = new JsonRestRequest(_restClient, method, endpoint, json, options);
|
var request = new JsonRestRequest(_restClient, method, endpoint, json, options);
|
||||||
@@ -240,14 +240,14 @@ namespace Discord.API
|
|||||||
}
|
}
|
||||||
|
|
||||||
internal Task<TResponse> SendMultipartAsync<TResponse>(string method, Expression<Func<string>> endpointExpr, IReadOnlyDictionary<string, object> multipartArgs, BucketIds ids,
|
internal Task<TResponse> SendMultipartAsync<TResponse>(string method, Expression<Func<string>> endpointExpr, IReadOnlyDictionary<string, object> multipartArgs, BucketIds ids,
|
||||||
string clientBucketId = null, RequestOptions options = null, [CallerMemberName] string funcName = null)
|
ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null, [CallerMemberName] string funcName = null)
|
||||||
=> SendMultipartAsync<TResponse>(method, GetEndpoint(endpointExpr), multipartArgs, GetBucketId(ids, endpointExpr, AuthTokenType, funcName), clientBucketId, options);
|
=> SendMultipartAsync<TResponse>(method, GetEndpoint(endpointExpr), multipartArgs, GetBucketId(ids, endpointExpr, AuthTokenType, funcName), clientBucket, options);
|
||||||
public async Task<TResponse> SendMultipartAsync<TResponse>(string method, string endpoint, IReadOnlyDictionary<string, object> multipartArgs,
|
public async Task<TResponse> SendMultipartAsync<TResponse>(string method, string endpoint, IReadOnlyDictionary<string, object> multipartArgs,
|
||||||
string bucketId = null, string clientBucketId = null, RequestOptions options = null)
|
string bucketId = null, ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null)
|
||||||
{
|
{
|
||||||
options = options ?? new RequestOptions();
|
options = options ?? new RequestOptions();
|
||||||
options.BucketId = bucketId;
|
options.BucketId = AuthTokenType == TokenType.User ? ClientBucket.Get(clientBucket).Id : bucketId;
|
||||||
options.ClientBucketId = AuthTokenType == TokenType.User ? clientBucketId : null;
|
options.IsClientBucket = AuthTokenType == TokenType.User;
|
||||||
|
|
||||||
var request = new MultipartRestRequest(_restClient, method, endpoint, multipartArgs, options);
|
var request = new MultipartRestRequest(_restClient, method, endpoint, multipartArgs, options);
|
||||||
return DeserializeJson<TResponse>(await SendInternalAsync(method, endpoint, request).ConfigureAwait(false));
|
return DeserializeJson<TResponse>(await SendInternalAsync(method, endpoint, request).ConfigureAwait(false));
|
||||||
@@ -447,7 +447,7 @@ namespace Discord.API
|
|||||||
options = RequestOptions.CreateOrClone(options);
|
options = RequestOptions.CreateOrClone(options);
|
||||||
|
|
||||||
var ids = new BucketIds(channelId: channelId);
|
var ids = new BucketIds(channelId: channelId);
|
||||||
return await SendJsonAsync<Message>("POST", () => $"channels/{channelId}/messages", args, ids, clientBucketId: ClientBucket.SendEditId, options: options).ConfigureAwait(false);
|
return await SendJsonAsync<Message>("POST", () => $"channels/{channelId}/messages", args, ids, clientBucket: ClientBucketType.SendEdit, options: options).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
public async Task<Message> UploadFileAsync(ulong channelId, UploadFileParams args, RequestOptions options = null)
|
public async Task<Message> UploadFileAsync(ulong channelId, UploadFileParams args, RequestOptions options = null)
|
||||||
{
|
{
|
||||||
@@ -466,7 +466,7 @@ namespace Discord.API
|
|||||||
}
|
}
|
||||||
|
|
||||||
var ids = new BucketIds(channelId: channelId);
|
var ids = new BucketIds(channelId: channelId);
|
||||||
return await SendMultipartAsync<Message>("POST", () => $"channels/{channelId}/messages", args.ToDictionary(), ids, clientBucketId: ClientBucket.SendEditId, options: options).ConfigureAwait(false);
|
return await SendMultipartAsync<Message>("POST", () => $"channels/{channelId}/messages", args.ToDictionary(), ids, clientBucket: ClientBucketType.SendEdit, options: options).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
public async Task DeleteMessageAsync(ulong channelId, ulong messageId, RequestOptions options = null)
|
public async Task DeleteMessageAsync(ulong channelId, ulong messageId, RequestOptions options = null)
|
||||||
{
|
{
|
||||||
@@ -512,7 +512,7 @@ namespace Discord.API
|
|||||||
options = RequestOptions.CreateOrClone(options);
|
options = RequestOptions.CreateOrClone(options);
|
||||||
|
|
||||||
var ids = new BucketIds(channelId: channelId);
|
var ids = new BucketIds(channelId: channelId);
|
||||||
return await SendJsonAsync<Message>("PATCH", () => $"channels/{channelId}/messages/{messageId}", args, ids, clientBucketId: ClientBucket.SendEditId, options: options).ConfigureAwait(false);
|
return await SendJsonAsync<Message>("PATCH", () => $"channels/{channelId}/messages/{messageId}", args, ids, clientBucket: ClientBucketType.SendEdit, options: options).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
public async Task AckMessageAsync(ulong channelId, ulong messageId, RequestOptions options = null)
|
public async Task AckMessageAsync(ulong channelId, ulong messageId, RequestOptions options = null)
|
||||||
{
|
{
|
||||||
@@ -1061,19 +1061,6 @@ namespace Discord.API
|
|||||||
using (JsonReader reader = new JsonTextReader(text))
|
using (JsonReader reader = new JsonTextReader(text))
|
||||||
return _serializer.Deserialize<T>(reader);
|
return _serializer.Deserialize<T>(reader);
|
||||||
}
|
}
|
||||||
internal string GetBucketId(ulong guildId = 0, ulong channelId = 0, [CallerMemberName] string methodName = "")
|
|
||||||
{
|
|
||||||
if (guildId != 0)
|
|
||||||
{
|
|
||||||
if (channelId != 0)
|
|
||||||
return $"{methodName}({guildId}/{channelId})";
|
|
||||||
else
|
|
||||||
return $"{methodName}({guildId})";
|
|
||||||
}
|
|
||||||
else if (channelId != 0)
|
|
||||||
return $"{methodName}({channelId})";
|
|
||||||
return $"{methodName}()";
|
|
||||||
}
|
|
||||||
|
|
||||||
internal class BucketIds
|
internal class BucketIds
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ namespace Discord
|
|||||||
public const string CDNUrl = "https://discordcdn.com/";
|
public const string CDNUrl = "https://discordcdn.com/";
|
||||||
public const string InviteUrl = "https://discord.gg/";
|
public const string InviteUrl = "https://discord.gg/";
|
||||||
|
|
||||||
|
public const int DefaultRequestTimeout = 15000;
|
||||||
public const int MaxMessageSize = 2000;
|
public const int MaxMessageSize = 2000;
|
||||||
public const int MaxMessagesPerBatch = 100;
|
public const int MaxMessagesPerBatch = 100;
|
||||||
public const int MaxUsersPerBatch = 1000;
|
public const int MaxUsersPerBatch = 1000;
|
||||||
|
|||||||
@@ -2,25 +2,47 @@
|
|||||||
|
|
||||||
namespace Discord.Net.Queue
|
namespace Discord.Net.Queue
|
||||||
{
|
{
|
||||||
public struct ClientBucket
|
public enum ClientBucketType
|
||||||
{
|
{
|
||||||
public const string SendEditId = "<send_edit>";
|
Unbucketed = 0,
|
||||||
|
SendEdit = 1
|
||||||
|
}
|
||||||
|
internal struct ClientBucket
|
||||||
|
{
|
||||||
|
private static readonly ImmutableDictionary<ClientBucketType, ClientBucket> _defsByType;
|
||||||
|
private static readonly ImmutableDictionary<string, ClientBucket> _defsById;
|
||||||
|
|
||||||
private static readonly ImmutableDictionary<string, ClientBucket> _defs;
|
|
||||||
static ClientBucket()
|
static ClientBucket()
|
||||||
{
|
{
|
||||||
var builder = ImmutableDictionary.CreateBuilder<string, ClientBucket>();
|
var buckets = new[]
|
||||||
builder.Add(SendEditId, new ClientBucket(10, 10));
|
{
|
||||||
_defs = builder.ToImmutable();
|
new ClientBucket(ClientBucketType.Unbucketed, "<unbucketed>", 10, 10),
|
||||||
|
new ClientBucket(ClientBucketType.SendEdit, "<send_edit>", 10, 10)
|
||||||
|
};
|
||||||
|
|
||||||
|
var builder = ImmutableDictionary.CreateBuilder<ClientBucketType, ClientBucket>();
|
||||||
|
foreach (var bucket in buckets)
|
||||||
|
builder.Add(bucket.Type, bucket);
|
||||||
|
_defsByType = builder.ToImmutable();
|
||||||
|
|
||||||
|
var builder2 = ImmutableDictionary.CreateBuilder<string, ClientBucket>();
|
||||||
|
foreach (var bucket in buckets)
|
||||||
|
builder2.Add(bucket.Id, bucket);
|
||||||
|
_defsById = builder2.ToImmutable();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ClientBucket Get(string id) =>_defs[id];
|
public static ClientBucket Get(ClientBucketType type) => _defsByType[type];
|
||||||
|
public static ClientBucket Get(string id) => _defsById[id];
|
||||||
|
|
||||||
|
public ClientBucketType Type { get; }
|
||||||
|
public string Id { get; }
|
||||||
public int WindowCount { get; }
|
public int WindowCount { get; }
|
||||||
public int WindowSeconds { get; }
|
public int WindowSeconds { get; }
|
||||||
|
|
||||||
public ClientBucket(int count, int seconds)
|
public ClientBucket(ClientBucketType type, string id, int count, int seconds)
|
||||||
{
|
{
|
||||||
|
Type = type;
|
||||||
|
Id = id;
|
||||||
WindowCount = count;
|
WindowCount = count;
|
||||||
WindowSeconds = seconds;
|
WindowSeconds = seconds;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,7 +79,9 @@ namespace Discord.Net.Queue
|
|||||||
int millis = (int)Math.Ceiling((_waitUntil - DateTimeOffset.UtcNow).TotalMilliseconds);
|
int millis = (int)Math.Ceiling((_waitUntil - DateTimeOffset.UtcNow).TotalMilliseconds);
|
||||||
if (millis > 0)
|
if (millis > 0)
|
||||||
{
|
{
|
||||||
|
#if DEBUG_LIMITS
|
||||||
Debug.WriteLine($"[{id}] Sleeping {millis} ms (Pre-emptive) [Global]");
|
Debug.WriteLine($"[{id}] Sleeping {millis} ms (Pre-emptive) [Global]");
|
||||||
|
#endif
|
||||||
await Task.Delay(millis).ConfigureAwait(false);
|
await Task.Delay(millis).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using System;
|
using System;
|
||||||
|
#if DEBUG_LIMITS
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
#endif
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
@@ -27,8 +29,8 @@ namespace Discord.Net.Queue
|
|||||||
|
|
||||||
_lock = new object();
|
_lock = new object();
|
||||||
|
|
||||||
if (request.Options.ClientBucketId != null)
|
if (request.Options.IsClientBucket)
|
||||||
WindowCount = ClientBucket.Get(request.Options.ClientBucketId).WindowCount;
|
WindowCount = ClientBucket.Get(request.Options.BucketId).WindowCount;
|
||||||
else
|
else
|
||||||
WindowCount = 1; //Only allow one request until we get a header back
|
WindowCount = 1; //Only allow one request until we get a header back
|
||||||
_semaphore = WindowCount;
|
_semaphore = WindowCount;
|
||||||
@@ -40,14 +42,18 @@ namespace Discord.Net.Queue
|
|||||||
public async Task<Stream> SendAsync(RestRequest request)
|
public async Task<Stream> SendAsync(RestRequest request)
|
||||||
{
|
{
|
||||||
int id = Interlocked.Increment(ref nextId);
|
int id = Interlocked.Increment(ref nextId);
|
||||||
|
#if DEBUG_LIMITS
|
||||||
Debug.WriteLine($"[{id}] Start");
|
Debug.WriteLine($"[{id}] Start");
|
||||||
|
#endif
|
||||||
LastAttemptAt = DateTimeOffset.UtcNow;
|
LastAttemptAt = DateTimeOffset.UtcNow;
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
await _queue.EnterGlobalAsync(id, request).ConfigureAwait(false);
|
await _queue.EnterGlobalAsync(id, request).ConfigureAwait(false);
|
||||||
await EnterAsync(id, request).ConfigureAwait(false);
|
await EnterAsync(id, request).ConfigureAwait(false);
|
||||||
|
|
||||||
|
#if DEBUG_LIMITS
|
||||||
Debug.WriteLine($"[{id}] Sending...");
|
Debug.WriteLine($"[{id}] Sending...");
|
||||||
|
#endif
|
||||||
var response = await request.SendAsync().ConfigureAwait(false);
|
var response = await request.SendAsync().ConfigureAwait(false);
|
||||||
TimeSpan lag = DateTimeOffset.UtcNow - DateTimeOffset.Parse(response.Headers["Date"]);
|
TimeSpan lag = DateTimeOffset.UtcNow - DateTimeOffset.Parse(response.Headers["Date"]);
|
||||||
var info = new RateLimitInfo(response.Headers);
|
var info = new RateLimitInfo(response.Headers);
|
||||||
@@ -59,18 +65,24 @@ namespace Discord.Net.Queue
|
|||||||
case (HttpStatusCode)429:
|
case (HttpStatusCode)429:
|
||||||
if (info.IsGlobal)
|
if (info.IsGlobal)
|
||||||
{
|
{
|
||||||
|
#if DEBUG_LIMITS
|
||||||
Debug.WriteLine($"[{id}] (!) 429 [Global]");
|
Debug.WriteLine($"[{id}] (!) 429 [Global]");
|
||||||
|
#endif
|
||||||
_queue.PauseGlobal(info, lag);
|
_queue.PauseGlobal(info, lag);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
#if DEBUG_LIMITS
|
||||||
Debug.WriteLine($"[{id}] (!) 429");
|
Debug.WriteLine($"[{id}] (!) 429");
|
||||||
|
#endif
|
||||||
UpdateRateLimit(id, request, info, lag, true);
|
UpdateRateLimit(id, request, info, lag, true);
|
||||||
}
|
}
|
||||||
await _queue.RaiseRateLimitTriggered(Id, info).ConfigureAwait(false);
|
await _queue.RaiseRateLimitTriggered(Id, info).ConfigureAwait(false);
|
||||||
continue; //Retry
|
continue; //Retry
|
||||||
case HttpStatusCode.BadGateway: //502
|
case HttpStatusCode.BadGateway: //502
|
||||||
|
#if DEBUG_LIMITS
|
||||||
Debug.WriteLine($"[{id}] (!) 502");
|
Debug.WriteLine($"[{id}] (!) 502");
|
||||||
|
#endif
|
||||||
continue; //Continue
|
continue; //Continue
|
||||||
default:
|
default:
|
||||||
string reason = null;
|
string reason = null;
|
||||||
@@ -92,9 +104,13 @@ namespace Discord.Net.Queue
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
#if DEBUG_LIMITS
|
||||||
Debug.WriteLine($"[{id}] Success");
|
Debug.WriteLine($"[{id}] Success");
|
||||||
|
#endif
|
||||||
UpdateRateLimit(id, request, info, lag, false);
|
UpdateRateLimit(id, request, info, lag, false);
|
||||||
|
#if DEBUG_LIMITS
|
||||||
Debug.WriteLine($"[{id}] Stop");
|
Debug.WriteLine($"[{id}] Stop");
|
||||||
|
#endif
|
||||||
return response.Stream;
|
return response.Stream;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -135,7 +151,9 @@ namespace Discord.Net.Queue
|
|||||||
if (resetAt > timeoutAt)
|
if (resetAt > timeoutAt)
|
||||||
throw new RateLimitedException();
|
throw new RateLimitedException();
|
||||||
int millis = (int)Math.Ceiling((resetAt.Value - DateTimeOffset.UtcNow).TotalMilliseconds);
|
int millis = (int)Math.Ceiling((resetAt.Value - DateTimeOffset.UtcNow).TotalMilliseconds);
|
||||||
|
#if DEBUG_LIMITS
|
||||||
Debug.WriteLine($"[{id}] Sleeping {millis} ms (Pre-emptive)");
|
Debug.WriteLine($"[{id}] Sleeping {millis} ms (Pre-emptive)");
|
||||||
|
#endif
|
||||||
if (millis > 0)
|
if (millis > 0)
|
||||||
await Task.Delay(millis, request.CancelToken).ConfigureAwait(false);
|
await Task.Delay(millis, request.CancelToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
@@ -143,13 +161,17 @@ namespace Discord.Net.Queue
|
|||||||
{
|
{
|
||||||
if ((timeoutAt.Value - DateTimeOffset.UtcNow).TotalMilliseconds < 500.0)
|
if ((timeoutAt.Value - DateTimeOffset.UtcNow).TotalMilliseconds < 500.0)
|
||||||
throw new RateLimitedException();
|
throw new RateLimitedException();
|
||||||
|
#if DEBUG_LIMITS
|
||||||
Debug.WriteLine($"[{id}] Sleeping 500* ms (Pre-emptive)");
|
Debug.WriteLine($"[{id}] Sleeping 500* ms (Pre-emptive)");
|
||||||
|
#endif
|
||||||
await Task.Delay(500, request.CancelToken).ConfigureAwait(false);
|
await Task.Delay(500, request.CancelToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
#if DEBUG_LIMITS
|
||||||
else
|
else
|
||||||
Debug.WriteLine($"[{id}] Entered Semaphore ({_semaphore}/{WindowCount} remaining)");
|
Debug.WriteLine($"[{id}] Entered Semaphore ({_semaphore}/{WindowCount} remaining)");
|
||||||
|
#endif
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -166,7 +188,9 @@ namespace Discord.Net.Queue
|
|||||||
{
|
{
|
||||||
WindowCount = info.Limit.Value;
|
WindowCount = info.Limit.Value;
|
||||||
_semaphore = info.Remaining.Value;
|
_semaphore = info.Remaining.Value;
|
||||||
|
#if DEBUG_LIMITS
|
||||||
Debug.WriteLine($"[{id}] Upgraded Semaphore to {info.Remaining.Value}/{WindowCount}");
|
Debug.WriteLine($"[{id}] Upgraded Semaphore to {info.Remaining.Value}/{WindowCount}");
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
var now = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
|
var now = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
|
||||||
@@ -182,24 +206,32 @@ namespace Discord.Net.Queue
|
|||||||
{
|
{
|
||||||
//RetryAfter is more accurate than Reset, where available
|
//RetryAfter is more accurate than Reset, where available
|
||||||
resetTick = DateTimeOffset.UtcNow.AddMilliseconds(info.RetryAfter.Value);
|
resetTick = DateTimeOffset.UtcNow.AddMilliseconds(info.RetryAfter.Value);
|
||||||
|
#if DEBUG_LIMITS
|
||||||
Debug.WriteLine($"[{id}] Retry-After: {info.RetryAfter.Value} ({info.RetryAfter.Value} ms)");
|
Debug.WriteLine($"[{id}] Retry-After: {info.RetryAfter.Value} ({info.RetryAfter.Value} ms)");
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
else if (info.Reset.HasValue)
|
else if (info.Reset.HasValue)
|
||||||
{
|
{
|
||||||
resetTick = info.Reset.Value.AddSeconds(/*1.0 +*/ lag.TotalSeconds);
|
resetTick = info.Reset.Value.AddSeconds(/*1.0 +*/ lag.TotalSeconds);
|
||||||
int diff = (int)(resetTick.Value - DateTimeOffset.UtcNow).TotalMilliseconds;
|
int diff = (int)(resetTick.Value - DateTimeOffset.UtcNow).TotalMilliseconds;
|
||||||
|
#if DEBUG_LIMITS
|
||||||
Debug.WriteLine($"[{id}] X-RateLimit-Reset: {info.Reset.Value.ToUnixTimeSeconds()} ({diff} ms, {lag.TotalMilliseconds} ms lag)");
|
Debug.WriteLine($"[{id}] X-RateLimit-Reset: {info.Reset.Value.ToUnixTimeSeconds()} ({diff} ms, {lag.TotalMilliseconds} ms lag)");
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
else if (request.Options.ClientBucketId != null)
|
else if (request.Options.IsClientBucket && request.Options.BucketId != null)
|
||||||
{
|
{
|
||||||
resetTick = DateTimeOffset.UtcNow.AddSeconds(ClientBucket.Get(request.Options.ClientBucketId).WindowSeconds);
|
resetTick = DateTimeOffset.UtcNow.AddSeconds(ClientBucket.Get(request.Options.BucketId).WindowSeconds);
|
||||||
Debug.WriteLine($"[{id}] Client Bucket ({ClientBucket.Get(request.Options.ClientBucketId).WindowSeconds * 1000} ms)");
|
#if DEBUG_LIMITS
|
||||||
|
Debug.WriteLine($"[{id}] Client Bucket ({ClientBucket.Get(request.Options.BucketId).WindowSeconds * 1000} ms)");
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
if (resetTick == null)
|
if (resetTick == null)
|
||||||
{
|
{
|
||||||
WindowCount = 0; //No rate limit info, disable limits on this bucket (should only ever happen with a user token)
|
WindowCount = 0; //No rate limit info, disable limits on this bucket (should only ever happen with a user token)
|
||||||
|
#if DEBUG_LIMITS
|
||||||
Debug.WriteLine($"[{id}] Disabled Semaphore");
|
Debug.WriteLine($"[{id}] Disabled Semaphore");
|
||||||
|
#endif
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -207,7 +239,9 @@ namespace Discord.Net.Queue
|
|||||||
{
|
{
|
||||||
_resetTick = resetTick;
|
_resetTick = resetTick;
|
||||||
LastAttemptAt = resetTick.Value; //Make sure we dont destroy this until after its been reset
|
LastAttemptAt = resetTick.Value; //Make sure we dont destroy this until after its been reset
|
||||||
|
#if DEBUG_LIMITS
|
||||||
Debug.WriteLine($"[{id}] Reset in {(int)Math.Ceiling((resetTick - DateTimeOffset.UtcNow).Value.TotalMilliseconds)} ms");
|
Debug.WriteLine($"[{id}] Reset in {(int)Math.Ceiling((resetTick - DateTimeOffset.UtcNow).Value.TotalMilliseconds)} ms");
|
||||||
|
#endif
|
||||||
|
|
||||||
if (!hasQueuedReset)
|
if (!hasQueuedReset)
|
||||||
{
|
{
|
||||||
@@ -227,7 +261,9 @@ namespace Discord.Net.Queue
|
|||||||
millis = (int)Math.Ceiling((_resetTick.Value - DateTimeOffset.UtcNow).TotalMilliseconds);
|
millis = (int)Math.Ceiling((_resetTick.Value - DateTimeOffset.UtcNow).TotalMilliseconds);
|
||||||
if (millis <= 0) //Make sure we havent gotten a more accurate reset time
|
if (millis <= 0) //Make sure we havent gotten a more accurate reset time
|
||||||
{
|
{
|
||||||
|
#if DEBUG_LIMITS
|
||||||
Debug.WriteLine($"[{id}] * Reset *");
|
Debug.WriteLine($"[{id}] * Reset *");
|
||||||
|
#endif
|
||||||
_semaphore = WindowCount;
|
_semaphore = WindowCount;
|
||||||
_resetTick = null;
|
_resetTick = null;
|
||||||
return;
|
return;
|
||||||
@@ -236,4 +272,4 @@ namespace Discord.Net.Queue
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ namespace Discord.Net.Rest
|
|||||||
cancelToken = CancellationTokenSource.CreateLinkedTokenSource(_cancelToken, cancelToken).Token;
|
cancelToken = CancellationTokenSource.CreateLinkedTokenSource(_cancelToken, cancelToken).Token;
|
||||||
HttpResponseMessage response = await _client.SendAsync(request, cancelToken).ConfigureAwait(false);
|
HttpResponseMessage response = await _client.SendAsync(request, cancelToken).ConfigureAwait(false);
|
||||||
|
|
||||||
var headers = response.Headers.ToDictionary(x => x.Key, x => x.Value.FirstOrDefault());
|
var headers = response.Headers.ToDictionary(x => x.Key, x => x.Value.FirstOrDefault(), StringComparer.OrdinalIgnoreCase);
|
||||||
var stream = !headerOnly ? await response.Content.ReadAsStreamAsync().ConfigureAwait(false) : null;
|
var stream = !headerOnly ? await response.Content.ReadAsStreamAsync().ConfigureAwait(false) : null;
|
||||||
|
|
||||||
return new RestResponse(response.StatusCode, headers, stream);
|
return new RestResponse(response.StatusCode, headers, stream);
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
internal bool IgnoreState { get; set; }
|
internal bool IgnoreState { get; set; }
|
||||||
internal string BucketId { get; set; }
|
internal string BucketId { get; set; }
|
||||||
internal string ClientBucketId { get; set; }
|
internal bool IsClientBucket { get; set; }
|
||||||
|
|
||||||
internal static RequestOptions CreateOrClone(RequestOptions options)
|
internal static RequestOptions CreateOrClone(RequestOptions options)
|
||||||
{
|
{
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
|
|
||||||
public RequestOptions()
|
public RequestOptions()
|
||||||
{
|
{
|
||||||
Timeout = 30000;
|
Timeout = DiscordConfig.DefaultRequestTimeout;
|
||||||
}
|
}
|
||||||
|
|
||||||
public RequestOptions Clone() => MemberwiseClone() as RequestOptions;
|
public RequestOptions Clone() => MemberwiseClone() as RequestOptions;
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ namespace Discord.Rest
|
|||||||
{
|
{
|
||||||
public static string UserAgent { get; } = $"DiscordBot (https://github.com/RogueException/Discord.Net, v{Version})";
|
public static string UserAgent { get; } = $"DiscordBot (https://github.com/RogueException/Discord.Net, v{Version})";
|
||||||
|
|
||||||
internal const int RestTimeout = 10000;
|
|
||||||
internal const int MessageQueueInterval = 100;
|
internal const int MessageQueueInterval = 100;
|
||||||
internal const int WebSocketQueueInterval = 100;
|
internal const int WebSocketQueueInterval = 100;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user