fix: Different ratelimits for the same route (implement discord buckets) (#1546)
* Don't disable when there's no resetTick Sometimes Discord won't send any ratelimit headers, disabling the semaphore for endpoints that should have them. * Undo changes and change comment * Add HttpMethod to BucketIds * Add X-RateLimit-Bucket * BucketId changes - BucketId is it's own class now - Add WebhookId as a major parameter - Add shared buckets using the hash and major parameters * Add webhookId to BucketIds * Update BucketId and redirect requests * General bugfixes * Assign semaphore and follow the same standard as Reset for ResetAfter
This commit is contained in:
118
src/Discord.Net.Core/Net/BucketId.cs
Normal file
118
src/Discord.Net.Core/Net/BucketId.cs
Normal file
@@ -0,0 +1,118 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
|
||||
namespace Discord.Net
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a ratelimit bucket.
|
||||
/// </summary>
|
||||
public class BucketId : IEquatable<BucketId>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the http method used to make the request if available.
|
||||
/// </summary>
|
||||
public string HttpMethod { get; }
|
||||
/// <summary>
|
||||
/// Gets the endpoint that is going to be requested if available.
|
||||
/// </summary>
|
||||
public string Endpoint { get; }
|
||||
/// <summary>
|
||||
/// Gets the major parameters of the route.
|
||||
/// </summary>
|
||||
public IOrderedEnumerable<KeyValuePair<string, string>> MajorParameters { get; }
|
||||
/// <summary>
|
||||
/// Gets the hash of this bucket.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The hash is provided by Discord to group ratelimits.
|
||||
/// </remarks>
|
||||
public string BucketHash { get; }
|
||||
/// <summary>
|
||||
/// Gets if this bucket is a hash type.
|
||||
/// </summary>
|
||||
public bool IsHashBucket { get => BucketHash != null; }
|
||||
|
||||
private BucketId(string httpMethod, string endpoint, IEnumerable<KeyValuePair<string, string>> majorParameters, string bucketHash)
|
||||
{
|
||||
HttpMethod = httpMethod;
|
||||
Endpoint = endpoint;
|
||||
MajorParameters = majorParameters.OrderBy(x => x.Key);
|
||||
BucketHash = bucketHash;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="BucketId"/> based on the
|
||||
/// <see cref="HttpMethod"/> and <see cref="Endpoint"/>.
|
||||
/// </summary>
|
||||
/// <param name="httpMethod">Http method used to make the request.</param>
|
||||
/// <param name="endpoint">Endpoint that is going to receive requests.</param>
|
||||
/// <param name="majorParams">Major parameters of the route of this endpoint.</param>
|
||||
/// <returns>
|
||||
/// A <see cref="BucketId"/> based on the <see cref="HttpMethod"/>
|
||||
/// and the <see cref="Endpoint"/> with the provided data.
|
||||
/// </returns>
|
||||
public static BucketId Create(string httpMethod, string endpoint, Dictionary<string, string> majorParams)
|
||||
{
|
||||
Preconditions.NotNullOrWhitespace(endpoint, nameof(endpoint));
|
||||
majorParams ??= new Dictionary<string, string>();
|
||||
return new BucketId(httpMethod, endpoint, majorParams, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="BucketId"/> based on a
|
||||
/// <see cref="BucketHash"/> and a previous <see cref="BucketId"/>.
|
||||
/// </summary>
|
||||
/// <param name="hash">Bucket hash provided by Discord.</param>
|
||||
/// <param name="oldBucket"><see cref="BucketId"/> that is going to be upgraded to a hash type.</param>
|
||||
/// <returns>
|
||||
/// A <see cref="BucketId"/> based on the <see cref="BucketHash"/>
|
||||
/// and <see cref="MajorParameters"/>.
|
||||
/// </returns>
|
||||
public static BucketId Create(string hash, BucketId oldBucket)
|
||||
{
|
||||
Preconditions.NotNullOrWhitespace(hash, nameof(hash));
|
||||
Preconditions.NotNull(oldBucket, nameof(oldBucket));
|
||||
return new BucketId(null, null, oldBucket.MajorParameters, hash);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the string that will define this bucket as a hash based one.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A <see cref="string"/> that defines this bucket as a hash based one.
|
||||
/// </returns>
|
||||
public string GetBucketHash()
|
||||
=> IsHashBucket ? $"{BucketHash}:{string.Join("/", MajorParameters.Select(x => x.Value))}" : null;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the string that will define this bucket as an endpoint based one.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A <see cref="string"/> that defines this bucket as an endpoint based one.
|
||||
/// </returns>
|
||||
public string GetUniqueEndpoint()
|
||||
=> HttpMethod != null ? $"{HttpMethod} {Endpoint}" : Endpoint;
|
||||
|
||||
public override bool Equals(object obj)
|
||||
=> Equals(obj as BucketId);
|
||||
|
||||
public override int GetHashCode()
|
||||
=> IsHashBucket ? (BucketHash, string.Join("/", MajorParameters.Select(x => x.Value))).GetHashCode() : (HttpMethod, Endpoint).GetHashCode();
|
||||
|
||||
public override string ToString()
|
||||
=> GetBucketHash() ?? GetUniqueEndpoint();
|
||||
|
||||
public bool Equals(BucketId other)
|
||||
{
|
||||
if (other is null)
|
||||
return false;
|
||||
if (ReferenceEquals(this, other))
|
||||
return true;
|
||||
if (GetType() != other.GetType())
|
||||
return false;
|
||||
return ToString() == other.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
using Discord.Net;
|
||||
using System.Threading;
|
||||
|
||||
namespace Discord
|
||||
@@ -57,7 +58,7 @@ namespace Discord
|
||||
public bool? UseSystemClock { get; set; }
|
||||
|
||||
internal bool IgnoreState { get; set; }
|
||||
internal string BucketId { get; set; }
|
||||
internal BucketId BucketId { get; set; }
|
||||
internal bool IsClientBucket { get; set; }
|
||||
internal bool IsReactionBucket { get; set; }
|
||||
|
||||
|
||||
Reference in New Issue
Block a user