All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.github.freya022.botcommands.api.commands.ratelimit.RateLimiter.kt Maven / Gradle / Ivy

Go to download

A Kotlin-first (and Java) framework that makes creating Discord bots a piece of cake, using the JDA library.

There is a newer version: 3.0.0-alpha.18
Show newest version
package io.github.freya022.botcommands.api.commands.ratelimit

import com.zaxxer.hikari.HikariDataSource
import io.github.bucket4j.BucketConfiguration
import io.github.bucket4j.distributed.proxy.ProxyManager
import io.github.freya022.botcommands.api.commands.ratelimit.RateLimiter.Companion.createDefault
import io.github.freya022.botcommands.api.commands.ratelimit.RateLimiter.Companion.createDefaultProxied
import io.github.freya022.botcommands.api.commands.ratelimit.bucket.*
import io.github.freya022.botcommands.api.commands.ratelimit.handler.DefaultRateLimitHandler
import io.github.freya022.botcommands.api.commands.ratelimit.handler.RateLimitHandler
import io.github.freya022.botcommands.internal.commands.ratelimit.DefaultProxyRateLimiter
import io.github.freya022.botcommands.internal.commands.ratelimit.DefaultRateLimiter

/**
 * Retrieves rate limit buckets and handles rate limits by combining [BucketAccessor] and [RateLimitHandler].
 *
 * You can also make your own implementation by either implementing this interface directly
 * or by delegating both interfaces.
 *
 * ### Persistent bucket storage
 * Since [createDefault] stores buckets in-memory, the rate limits applied will be lost upon restart,
 * however you can use [createDefaultProxied], and then pass a [ProxyManager] which stores your buckets in persistent storage.
 *
 * @see createDefault
 * @see createDefaultProxied
 */
interface RateLimiter : BucketAccessor, RateLimitHandler {
    companion object {
        /**
         * Creates a default [RateLimiter] implementation,
         * see [DefaultRateLimitHandler] and [InMemoryBucketAccessor] for details.
         *
         * ### Example
         *
         * ```kt
         * @Command
         * class SlashInMemoryRateLimit : ApplicationCommand(), GlobalApplicationCommandProvider {
         *     fun onSlashInMemoryRateLimit(event: GuildSlashEvent) {
         *         event.reply_("Hi", ephemeral = true).queue()
         *     }
         *
         *     override fun declareGlobalApplicationCommands(manager: GlobalApplicationCommandManager) {
         *         manager.slashCommand("in_memory_rate_limit", function = ::onSlashInMemoryRateLimit) {
         *             // Allow using the command once every 10 seconds
         *             // NOTE: this won't take effect if you are the bot owner
         *             val cooldown = Buckets.ofCooldown(10.seconds)
         *             // Apply limit on each user, regardless or guild/channel
         *             val rateLimiter = RateLimiter.createDefault(RateLimitScope.USER, cooldown.toSupplier())
         *             // Register anonymous rate limit, only on this command
         *             rateLimit(rateLimiter)
         *         }
         *     }
         * }
         * ```
         *
         * @param scope                 Scope of the rate limit, see [RateLimitScope] values.
         * @param configurationSupplier A supplier of [BucketConfiguration], describing the rate limits
         * @param deleteOnRefill        Whether the rate limit message should be deleted after expiring
         *
         * @see DefaultRateLimitHandler
         * @see InMemoryBucketAccessor
         *
         * @see RateLimitScope
         * @see Buckets
         */
        @JvmStatic
        fun createDefault(scope: RateLimitScope, configurationSupplier: BucketConfigurationSupplier, deleteOnRefill: Boolean = true): RateLimiter =
            DefaultRateLimiter(scope, configurationSupplier, deleteOnRefill)

        /**
         * Creates a [RateLimiter] implementation which retrieves its buckets using [proxyManager],
         * see [DefaultRateLimitHandler] and [ProxyBucketAccessor] for details.
         *
         * ### Requirements
         * The proxy requires a [String] to store the bucket key.
         *
         * #### Using a database
         * First, install the [PostgreSQL module for Bucket4J](https://bucket4j.com/8.14.0/toc.html#bucket4j-postgresql) (recommended, Bucket4J supports other RDBMs),
         * then, use the following DDL to create the tables storing the buckets,
         * I recommend you put this in a migration script, using Flyway:
         * ```sql
         * CREATE TABLE bucket(id TEXT PRIMARY KEY, state BYTEA, expires_at BIGINT);
         * ```
         *
         * **Note:**
         * - You can use any RDBMs supported by Bucket4J, as you are passing the DataSource directly.
         * - You may need to set the [default schema on your DataSource][HikariDataSource.schema] to `public`,
         * or the schema you store the table in.
         *
         * #### Anything else
         * You can also use anything that accepts a String as your bucket key, such as JCache and Redis,
         * see the [Bucket4J docs](https://bucket4j.com/8.14.0/toc.html#distributed-facilities) for more details.
         *
         * ### Example
         *
         * ```kt
         * @BService
         * class ProxyManagerProvider {
         *     @BService
         *     open fun proxyManager(hikariSourceSupplier: HikariSourceSupplier): ProxyManager {
         *         // Create a proxy to manager buckets, persisting with PostgreSQL,
         *         // see https://bucket4j.com/8.14.0/toc.html#postgresqlselectforupdatebasedproxymanager
         *         return Bucket4jPostgreSQL.selectForUpdateBasedBuilder(hikariSourceSupplier.source)
         *             // Bucket expiration, needs to be triggered manually,
         *             // see https://bucket4j.com/8.14.0/toc.html#expiration-policy
         *             .expirationAfterWrite(ExpirationAfterWriteStrategy.basedOnTimeForRefillingBucketUpToMax(1.minutes.toJavaDuration()))
         *             // RateLimiter#createDefaultProxied uses a String key
         *             .primaryKeyMapper(PreparedStatement::setString)
         *             .build()
         *     }
         * }
         * ```
         *
         * ```kt
         * @Command
         * class SlashPersistentRateLimit(private val proxyManager: ProxyManager) : ApplicationCommand(), GlobalApplicationCommandProvider {
         *     fun onSlashPersistentRateLimit(event: GuildSlashEvent) {
         *         event.reply_("Hi", ephemeral = true).queue()
         *     }
         *
         *     override fun declareGlobalApplicationCommands(manager: GlobalApplicationCommandManager) {
         *         manager.slashCommand("persistent_rate_limit", function = ::onSlashPersistentRateLimit) {
         *             // Allow using the command once every hour
         *             // NOTE: this won't take effect if you are the bot owner
         *             val cooldown = Buckets.ofCooldown(1.hours)
         *             // Apply limit on each user, regardless or guild/channel
         *             val rateLimiter = RateLimiter.createDefaultProxied(RateLimitScope.USER, proxyManager, cooldown.toSupplier())
         *             // Register anonymous rate limit, only on this command
         *             rateLimit(rateLimiter)
         *         }
         *     }
         * }
         * ```
         *
         * @param scope                 Scope of the rate limit, see [RateLimitScope] values.
         * @param proxyManager          The proxy supplying buckets from a key, based on the [scope]
         * @param configurationSupplier A supplier of [BucketConfiguration], describing the rate limits
         * @param deleteOnRefill        Whether the rate limit message should be deleted after expiring
         *
         * @see DefaultRateLimitHandler
         * @see ProxyBucketAccessor
         *
         * @see RateLimitScope
         * @see Buckets
         */
        @JvmStatic
        fun createDefaultProxied(
            scope: RateLimitScope,
            proxyManager: ProxyManager,
            configurationSupplier: BucketConfigurationSupplier,
            deleteOnRefill: Boolean = true,
        ): RateLimiter =
            DefaultProxyRateLimiter(scope, proxyManager, configurationSupplier, deleteOnRefill)
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy