v3.0.0-alpha.19 | Configurable command cache and QoL improvements
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
andinlineClassOptionVararg
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, fromSettingProvider#getPrefixes(Guild)
- Else, from
BTextConfig#prefixes
- If a
New behavior
The command is accepted if any prefix given by TextCommandsContext#getPrefixes(GuildMessageChannel)
matches:
- If a
TextPrefixSupplier
is available, fromTextPrefixSupplier#getPrefixes(GuildMessageChannel)
- If a
SettingsProvider
is available, fromSettingProvider#getPrefixes(Guild)
(now deprecated) - Else, from
BTextConfig#prefixes
+ the bot's mention ifBTextConfig#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
- Returns the allowed prefixes for a given
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
- Moved to
- Deprecated
BApplicationConfig#logApplicationData
- Moved to
ApplicationCommandsCacheConfig#logDataIf
- Moved to
- Deprecated
BApplicationConfig#diffEngine
- Moved to
ApplicationCommandsCacheConfig#diffEngine
- Moved to
Changes
- If the application commands cache folder is not writable, an in-memory cache is used.
Additions
- Added
BApplicationConfig#fileCache
,databaseCache
anddisableCache
- 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 existingConnectionSupplier
, 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, usedeleteComponents
withIdentifiableComponent.fromId
- These are the integers returned by the
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
IdentifiableComponent
s - Added
deleteComponents
takingIdentifiableComponent
s - Added
deleteRows
takingCollection<LayoutComponent>
- Useful when deleting components from a JDA
Message
- Useful when deleting components from a JDA
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
- Utility methods were moved to
- Removed
RateLimiterFactory
- Registering a rate limiter only requires the "group" (name) and a
RateLimiter
- Registering a rate limiter only requires the "group" (name) and a
- Renamed
DefaultBucketAccessor
->InMemoryBucketAccessor
- Moved
DefaultRateLimiter
to internal package- Use
RateLimiter#createDefault
instead
- Use
- The
rateLimitReference
method while building a component, now accepts aComponentRateLimitReference
- You can create one with the
createRateLimitReference
method of your component factory
- You can create one with the
Additions
- Added
RateLimiter#createDefaultProxied
- Allows you to give a Bucket4J
ProxyManager
to use store buckets remotely, such as in a database
- Allows you to give a Bucket4J
- 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
- Uses a
- Added
AnnotatedRateLimiterFactory
- Lets you configure which
RateLimiter
to use on annotated rate limits / cooldown
- Lets you configure which
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 onT
ofInstanceSupplier
- 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
andIDeclarationSiteHolder
tocore
package - Renamed parameter of
Member#ban
andGuild#ban
:duration
->deletionTimeframe
to match JDA - Bot owners bypassing user permissions and rate limits is now opt-in
- Can be configured in
BConfig#enableOwnerBypass
- Can be configured in
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
- Useful for
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