Declaring rate limiters globally¶
Rate limiters can be declared in any service class, they can be referenced anywhere and can be customized.
They can be defined by implementing RateLimitProvider,
the overridden function will run before registering commands, so you can use them anywhere.
Defining bucket configurations¶
Bucket configurations define limits, there are some predefined functions from Buckets which will help us,
but you can also use a BucketConfiguration builder.
Allow 5 uses per hour, but also 2 uses in 2 minutes (to prevent bursts)
// Lets the user use the command 5 times in an hour, but also 2 times in 2 minutes to prevent spam
val bucketConfiguration = Buckets.spikeProtected(
capacity = 5, // 5 uses
duration = 1.hours, // Gives 5 tokens gradually, during an hour (1 token every 12 minutes)
spikeCapacity = 2, // 2 uses
spikeDuration = 2.minutes // Give 2 tokens every 2 minutes
)
// Lets the user use the command 5 times in an hour, but also 2 times in 2 minutes to prevent spam
final var bucketConfiguration = Buckets.createSpikeProtected(
5, // 5 uses
Duration.ofHours(1), // Gives 5 tokens gradually, during an hour (1 token every 12 minutes)
2, // 2 uses
Duration.ofMinutes(2) // Give 2 tokens every 2 minutes
);
Info
A cooldown is a bucket with a single limit, which has a single token and is regenerated after the cooldown time.
Supplying bucket configurations¶
The framework requests a BucketConfigurationSupplier, allowing you to create buckets on a per-request basis.
Note
By default, a bucket is cached depending on which scope the rate limit applies to,
so the supplier won't run on all requests, you can use change that with a custom BucketAccessor.
However, for most cases, you'll use the same configuration,
use BucketConfigurationSupplier.constant (or the BucketConfiguration.toSupplier() extension for Kotlin users)
to make a supplier that returns the same configuration.
Creating a rate limiter¶
Default rate limiters¶
The default rate limiters should provide you ready-made implementations for both in-memory and proxied buckets, refer to the example attached to them.
Per-user rate limiter using the previous configuration
val rateLimiter = RateLimiter.createDefault(
// Apply to each user, regardless of channel/guild
RateLimitScope.USER,
// Delete the message telling the user about the remaining rate limit after it expires
deleteOnRefill = true,
// Give our constant bucket configuration
configurationSupplier = bucketConfiguration.toSupplier()
)
final var rateLimiter = RateLimiter.createDefault(
// Apply to each user, regardless of channel/guild
RateLimitScope.USER,
// Give our constant bucket configuration
BucketConfigurationSupplier.constant(bucketConfiguration),
// Delete the message telling the user about the remaining rate limit after it expires
true
);
Info
You can also create a persistent bucket with RateLimiter.createDefaultProxied.
Custom rate limiter¶
A custom RateLimiter can also be created, which is the combination of:
BucketAccessor: Retrieves aBucketbased on the contextRateLimitHandler: Handles when an interaction has been rate limited (often to tell the user about it)
Tip
When making a custom rate limiter, you can delegate one of the default implementations to avoid reimplementing existing behavior.
You can also use BucketKeySupplier to help you define functions
returning a bucket key (an identifier basically), based on the execution context.
Registering the rate limiter¶
You can now register using RateLimitManager#rateLimit(String, RateLimiter),
the group (name of the rate limiter) must be unique.
Full example
@BService
class WikiRateLimitProvider : RateLimitProvider {
override fun declareRateLimit(manager: RateLimitManager) {
// Lets the user use the command 5 times in an hour, but also 2 times in 2 minutes to prevent spam
val bucketConfiguration = Buckets.spikeProtected(
capacity = 5, // 5 uses
duration = 1.hours, // Gives 5 tokens gradually, during an hour (1 token every 12 minutes)
spikeCapacity = 2, // 2 uses
spikeDuration = 2.minutes // Give 2 tokens every 2 minutes
)
val rateLimiter = RateLimiter.createDefault(
// Apply to each user, regardless of channel/guild
RateLimitScope.USER,
// Delete the message telling the user about the remaining rate limit after it expires
deleteOnRefill = true,
// Give our constant bucket configuration
configurationSupplier = bucketConfiguration.toSupplier()
)
// Register, any command can use it
manager.rateLimit(RATE_LIMIT_GROUP, rateLimiter)
}
companion object {
// The name of the rate limit, so you can reference it in your commands/components
const val RATE_LIMIT_GROUP = "Wiki"
}
}
@BService
@NullMarked // Everything is non-null unless @Nullable
public class WikiRateLimitProvider implements RateLimitProvider {
// The name of the rate limit, so you can reference it in your commands/components
public static final String RATE_LIMIT_GROUP = "Wiki";
@Override
public void declareRateLimit(RateLimitManager rateLimitManager) {
// Lets the user use the command 5 times in an hour, but also 2 times in 2 minutes to prevent spam
final var bucketConfiguration = Buckets.createSpikeProtected(
5, // 5 uses
Duration.ofHours(1), // Gives 5 tokens gradually, during an hour (1 token every 12 minutes)
2, // 2 uses
Duration.ofMinutes(2) // Give 2 tokens every 2 minutes
);
final var rateLimiter = RateLimiter.createDefault(
// Apply to each user, regardless of channel/guild
RateLimitScope.USER,
// Give our constant bucket configuration
BucketConfigurationSupplier.constant(bucketConfiguration),
// Delete the message telling the user about the remaining rate limit after it expires
true
);
// Register
rateLimitManager.rateLimit(RATE_LIMIT_GROUP, rateLimiter);
}
}