Skip to content

v3.0.0-alpha.19 | Configurable command cache and QoL improvements

Compare
Choose a tag to compare
@freya022 freya022 released this 30 Sep 18:22
· 99 commits to 3.X since this release
61de333

JDA version

Overview

Added a configurable command cache, components which reset their timeout on use, and a bunch of QoL changes.

Common interfaces for command builders (#194)

Option aggregate builders now share the same methods as command builder to declare options.

Breaking changes

  • (In aggregate) nestedOptionVararg -> optionVararg
  • (In aggregate) nestedAggregate -> aggregate
  • inlineClassOption and inlineClassOptionVararg were moved to extensions
  • Moved some classes related to option builders
    • This shouldn't affect most users as the types should not be used explicitly outside of extensions, you can re-import at worst

Changes

  • Command, option and aggregate builders are now interfaces

Additions

  • Added support for value classes in annotated options of user/message context commands
  • Added inlineClassAggregate, lets you add any option inside an inline class aggregator
    • Basically, no longer limited to an option/vararg

Priority parameter on ParameterResolverFactory (#197)

This enables overriding existing parameters resolvers.

To override a built-in resolver with your own, set your factory's (or the @Resolver annotation) priority to higher than 0.

Note: Spring users may see a warning related to annotation property aliasing, you can ignore it.
This is due to @BService and @ResolverFactory both having a priority property.

Additions

  • Added ParameterResolverFactory#priority
  • Added a priority property for @Resolver

Changes

  • Resolvers are now chosen based off their compatibility and priority
    • Errors if both have same priority

New way of getting custom text command prefixes (#198)

You can now get custom text command prefixes based on a given GuildMessageChannel,
methods to get the supported/preferred prefixes now accurately return the prefixes used in other places.

Possible breaking changes - If you had prefixes set with `SettingsProvider`

Retrieving allowed prefixes of a command in a specific channel has slightly changed:

Previous behavior
  • If ping-as-prefix is enabled, and the command starts with the bot's mention, then the command is accepted
  • Else, the prefixes are gathered, and if it starts with any, the command is accepted:
    • If a SettingsProvider is available, from SettingProvider#getPrefixes(Guild)
    • Else, from BTextConfig#prefixes
New behavior

The command is accepted if any prefix given by TextCommandsContext#getPrefixes(GuildMessageChannel) matches:

  • If a TextPrefixSupplier is available, from TextPrefixSupplier#getPrefixes(GuildMessageChannel)
  • If a SettingsProvider is available, from SettingProvider#getPrefixes(Guild) (now deprecated)
  • Else, from BTextConfig#prefixes + the bot's mention if BTextConfig#usePingAsPrefix is enabled

Deprecations

  • SettingsProvider
  • SettingsProvider#getPrefixes(Guild)
  • TextCommandsContext#prefixes
  • TextCommandsContext#isPingAsPrefix
  • TextCommandsContext#getPreferredPrefix(JDA)

Changes

  • The prefix used in the content of the built-in help command is now determined by TextCommandsContext#getPreferredPrefix(GuildMessageChannel)

Additions

  • Added TextPrefixSupplier
    • Returns the allowed prefixes for a given GuildMessageChannel
    • Returns the preferred prefix for a given GuildMessageChannel

Configurable application commands cache (#201)

This allows you to configure the path in which files are written, or to switch to a database storage,
but also disable caching.

Deprecations

  • Deprecated BApplicationConfig#onlineAppCommandCheckEnabled
    • Moved to ApplicationCommandsCacheConfig#checkOnline
  • Deprecated BApplicationConfig#logApplicationData
    • Moved to ApplicationCommandsCacheConfig#logDataIf
  • Deprecated BApplicationConfig#diffEngine
    • Moved to ApplicationCommandsCacheConfig#diffEngine

Changes

  • If the application commands cache folder is not writable, an in-memory cache is used.

Additions

  • Added BApplicationConfig#fileCache, databaseCache and disableCache
    • All of these have the properties that were deprecated
    • fileCache lets you configure where the cache is, may be useful for Docker containers
    • (Recommended) databaseCache reuses the existing ConnectionSupplier, more resilient to write errors, no extra files in containers
    • disableCache always updates commands on startup, not recommended

Added more component delete methods and resetTimeoutOnUse (#202)

Breaking changes

  • deleteComponentsById now takes integers instead of strings
    • These are the integers returned by the internalId property
    • Useful if you need cheap storage of used component IDs, used in paginators for example
    • If you need to delete components by their String ID, use deleteComponents with IdentifiableComponent.fromId

Additions

  • Added resetTimeoutOnUse
    • Resets the timeout everytime the component is used
    • Can be set on a component group, using any component inside that group resets the timeout
  • Added a BContext property in component factories
  • Added static methods to make fake IdentifiableComponents
  • Added deleteComponents taking IdentifiableComponents
  • Added deleteRows taking Collection<LayoutComponent>
    • Useful when deleting components from a JDA Message

Misc

Prioritized rate limiter (#205)

Added a RestRateLimiter implementation which enforces the 50 requests/s,
preventing cloudflare bans (as best as it can, depends on your host).

This rate limiter is used by default when using the new factories in JDAService,
which I highly recommend using.

Changes

  • If the default RestRateLimiter is used, application commands are pushed at a rate of 20 requests/s

Additions

  • Added PriorityGlobalRestRateLimiter
    • Enforces 50 requests/s
    • There may be a lot of command updates, so their requests have lower priority
    • Prevents CF bans on large bots as all requests are fired at once due to each request being on its own bucket

Single-shard and sharded factories in JDAService (#206)

Static factories similar to JDA's were added,
they configure the event manager and rest config,
but also the intents and cache flags.

Example

Before
public class Bot extends JDAService {
    @Override
    public void createJDA(BReadyEvent event, IEventManager eventManager) {
        JDABuilder.createLight(token, getIntents())
                .setEventManager(eventManager)
                .enableCache(getCacheFlags())
                .setRestConfig(getDefaultRestConfig()) // new
                // Other configuration, such as activity, member cache policy...
                .build();
    }
}
After
public class Bot extends JDAService {
    @Override
    public void createJDA(BReadyEvent event, IEventManager eventManager) {
        light(token)
                // Other configuration, such as activity, member cache policy...
                .build();
    }
}

Ability to read meta-annotations recursively (#207)

This allows combining one or more annotations on a single annotation.

Breaking changes

  • @Filter can no longer be used on classes

Changes

  • Custom condition annotations and their checkers no longer need to be in the search path
  • Most, if not all, annotations can be used as meta-annotations
  • The following annotations can be read multiple times, when they are used indirectly (as meta-annotations):
    • @DeclarationFilter
    • @Filter
    • @ChannelTypes
    • @MentionsString
      Be aware that such annotations are overridden if the same annotation is used directly

Refactor rate limiters to allow easier use of proxied buckets (#208)

These changes improve the customization of rate limiters and how the bucket keys and configurations are retrieved.

Breaking changes

  • Renamed Cooldown#rateLimitScope => scope
  • Removed BucketFactory
    • Utility methods were moved to Buckets
    • Replaced with BucketConfigurationSupplier
  • Removed RateLimiterFactory
    • Registering a rate limiter only requires the "group" (name) and a RateLimiter
  • Renamed DefaultBucketAccessor -> InMemoryBucketAccessor
  • Moved DefaultRateLimiter to internal package
    • Use RateLimiter#createDefault instead
  • The rateLimitReference method while building a component, now accepts a ComponentRateLimitReference
    • You can create one with the createRateLimitReference method of your component factory

Additions

  • Added RateLimiter#createDefaultProxied
    • Allows you to give a Bucket4J ProxyManager to use store buckets remotely, such as in a database
  • Added BucketConfigurationSupplier
    • Lets you return different buckets based on the execution context
    • You can use Buckets + BucketConfigurationSupplier#constant (BucketConfiguration#toSupplier in Kotlin) to always use the same "bucket configuration"
  • Added ProxyBucketAccessor
    • Uses a BucketKeySupplier for the... bucket key, yea
  • Added AnnotatedRateLimiterFactory
    • Lets you configure which RateLimiter to use on annotated rate limits / cooldown

Examples

Command rate limiting, with code-declaration:

Before
private const val commandRateLimitGroup = "MyCommand: my_command"

@Command
class MyCommand(val buttons: Buttons) : RateLimitProvider {
    @JDASlashCommand("my_command")
    @RateLimitReference(commandRateLimitGroup)
    suspend fun onSlashMyCommand(event: GuildSlashEvent) {
        // ...
    }
    
    override fun declareRateLimit(manager: RateLimitManager) {
        val commandBucketFactory = BucketFactory.ofCooldown(1.hour)
        val commandLimiterFactory = RateLimiter.defaultFactory(RateLimitScope.USER, deleteOnRefill = true)
        
        manager.rateLimit(commandRateLimitGroup, commandBucketFactory, commandLimiterFactory)
    }
}
After
private const val commandRateLimitGroup = "MyCommand: my_command"

@Command
class MyCommand : RateLimitProvider {
    @JDASlashCommand("my_command")
    @RateLimitReference(commandRateLimitGroup)
    suspend fun onSlashMyCommand(event: GuildSlashEvent) {
        // ...
    }
    
    override fun declareRateLimit(manager: RateLimitManager) {
        val commandBucketConfiguration = Buckets.ofCooldown(1.hour)
        val commandRateLimiter = RateLimiter.createDefault(
            RateLimitScope.USER,
            configurationSupplier = commandBucketConfiguration.toSupplier(), // Constant supplier
            deleteOnRefill = true
        )
        
        manager.rateLimit(commandRateLimitGroup, commandRateLimiter)
    }
}

Component rate limiting:

Before
@Command
class MyCommand(private val buttons: Buttons) : RateLimitProvider {
    private val retryRateLimitGroup = "MyCommand: retry"
    
    @JDASlashCommand("my_command")
    suspend fun onSlashMyCommand(event: GuildSlashEvent) {
        val button = buttons.primary("Retry").ephemeral {
            rateLimitReference(retryRateLimitGroup)
        }
        
        event.replyComponents(row(button)).queue()
    }
    
    override fun declareRateLimit(manager: RateLimitManager) {
        val retryBucketFactory = BucketFactory.ofCooldown(1.hour)
        val retryLimiterFactory = RateLimiter.defaultFactory(RateLimitScope.USER, deleteOnRefill = true)
        
        manager.rateLimit(retryRateLimitGroup, retryBucketFactory, retryLimiterFactory)
    }
}
After
@Command
class MyCommand(private val buttons: Buttons) : RateLimitProvider {
    // I recommend the "group" to be the class name, and the "discriminator" to be the component's purpose/label
    // Throws if the group + discriminator is already taken
    private val retryRateLimitRef = buttons.createRateLimitReference("MyCommand", "retry")
    
    @JDASlashCommand("my_command")
    suspend fun onSlashMyCommand(event: GuildSlashEvent) {
        val button = buttons.primary("Retry").ephemeral {
            rateLimitReference(retryRateLimitRef)
        }
        
        event.replyComponents(row(button)).queue()
    }
    
    override fun declareRateLimit(manager: RateLimitManager) {
        val retryBucketConfiguration = Buckets.ofCooldown(1.hour)
        val retryRateLimiter = RateLimiter.createDefault(
            RateLimitScope.USER,
            configurationSupplier = commandBucketConfiguration.toSupplier(), // Constant supplier
            deleteOnRefill = true
        )
        
        manager.rateLimit(retryRateLimitRef.group, retryRateLimiter)
    }
}

Also check out the examples in the RateLimiter and AnnotatedRateLimiterFactory docs

Improved instance suppliers (#209)

Changes

  • Added missing Any bound on T of InstanceSupplier
  • Made InstanceSupplier a functional interface
  • The class being supplied no longer requires being annotated with a service annotation
    • You can still put annotations, they will be taken into account for the resulting service

Additions

  • Added a reified overload of BServiceConfigBuilder.registerInstanceSupplier

Breaking changes

Components

  • Moved most non-writable properties in builders, away from the API
  • Removed deprecated methods from Components

Misc

  • Moved DeclarationSite and IDeclarationSiteHolder to core package
  • Renamed parameter of Member#ban and Guild#ban: duration -> deletionTimeframe to match JDA
  • Bot owners bypassing user permissions and rate limits is now opt-in
    • Can be configured in BConfig#enableOwnerBypass

Deprecations

Database

  • Deprecated SQLCodes.kt
    • You can copy the source code and remove the deprecation annotations if you are using PostgreSQL

Changes

Dependencies

  • JDA: 5.0.0 -> 5.1.1
  • kotlinx.coroutines: 1.8.1 -> 1.9.0
  • Bucket4J: 8.12.0 -> 8.14.0
  • ClassGraph: 4.8.172 -> 4.8.715

Emojis

  • Lazily retrieve default emojis
    • Avoids loading emojis on startup, causing ~400ms of delay

DI

  • Added methods to get service names by annotation
  • Added methods to get annotations from a named service

Spring

  • Check versions before starting anything

Additions

Localization

  • Added a customizable folder name for DefaultJsonLocalizationMapReader
    • Meaning you can make a service factory with a different folder name, and it will pick up localization files in it.
    • See the example

JDA Init

  • Added JDAService.defaultIntents(intents...) to give the default intents + provided intents
    • Useful for JDAService#defaultIntents

Modals

  • Added BModalsConfig#enable to switch modals on/off
  • Added @RequiresModals as a service condition
  • Added Modal#awaitOrNull

Database

  • Made HikariSourceSupplier an interfaced service
    • Meaning you can get it with the default DI, without having to know the exact type

Default DI

  • Added BServiceConfig#debug
    • Logs all service checks/creations
    • Includes nests, duration and error message

Misc

  • Added extensions using ranges
    • SelectMenu.Builder#setRequiredRange
    • TextInputBuilder#setRequiredRange

Fixes

Application commands

  • Fixed options of subcommands not being differed
  • Always update application commands if one isn't found
  • Fixed an issue occurring when multiple commands with the same name, but different types, had no metadata assigned

Commands

  • Fixed small display issue of the command path in exceptions

Spring

  • Fixed Spring metadata not being included in JAR

Extensions

  • Handle UNKNOWN_USER in Guild#retrieveMemberOrNull

Misc

  • Fixed exceptions during classpath scanning due to missing dependencies

Don't hesitate to check out the examples and the wiki.

Full Changelog: v3.0.0-alpha.18...v3.0.0-alpha.19