Skip to content

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 a Bucket based on the context
  • RateLimitHandler: 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);
    }
}