Skip to content

Commit

Permalink
Simplify and refactor filters (#228)
Browse files Browse the repository at this point in the history
* Remove rejection handlers and generics from filters
* Made `global` flag from Filter abstract
  • Loading branch information
freya022 authored Jan 22, 2025
1 parent 9f7745e commit e279c57
Show file tree
Hide file tree
Showing 39 changed files with 281 additions and 529 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,19 @@ import io.github.freya022.botcommands.api.core.utils.simpleNestedName
import net.dv8tion.jda.api.events.interaction.command.GenericCommandInteractionEvent

/**
* Prevents application command execution by returning an error object to the command executor.
* Prevents application command execution by returning an error object to the command executor,
* before which you must acknowledge the interaction.
*
* Filters run when an application command is about to be executed,
* i.e., after the permissions/rate limits... were checked.
*
* When the final filter returns an error object of type [T],
* it will then be passed to the [ApplicationCommandRejectionHandler].
*
* ### Combining filters
*
* Filters can be combined with [`and`][and]/[`or`][or] (static methods for Java users).
*
* ### Requirements
* - Register your instance as a service with [@BService][BService].
* This is not required if you pass the instance directly to the command builder.
* - Have exactly one instance of [ApplicationCommandRejectionHandler].
* - Implement either [check] (Java) or [checkSuspend] (Kotlin).
* - (Optional) Set your filter as a command-specific filter by disabling [global].
*
Expand All @@ -31,12 +28,23 @@ import net.dv8tion.jda.api.events.interaction.command.GenericCommandInteractionE
* while command-specific filters use the insertion order.
*
* ### Example - Accepting commands only in a single channel
* **Note:** For the example's sake, I will reply directly on each failed condition,
* however, I recommend having a separate function/class to handle rejections,
* as to not duplicate code on each rejection case.
*
* ```kt
* @BService
* class MyApplicationCommandFilter : ApplicationCommandFilter<String> {
* override suspend fun checkSuspend(event: GenericCommandInteractionEvent, commandInfo: ApplicationCommandInfo): String? {
* if (event.guildChannel.idLong != 722891685755093076) {
* return "Can only run commands in <#722891685755093076>"
* class MyApplicationCommandFilter : ApplicationCommandFilter {
*
* override val global: Boolean get() = true
*
* override suspend fun checkSuspend(
* event: GenericCommandInteractionEvent,
* commandInfo: ApplicationCommandInfo
* ): String? {
* if (event.channel!!.idLong != 722891685755093076) {
* event.reply_("Can only run commands in <#722891685755093076>", ephemeral = true).await()
* return "Wrong channel"
* }
* return null
* }
Expand All @@ -47,69 +55,68 @@ import net.dv8tion.jda.api.events.interaction.command.GenericCommandInteractionE
*
* ```java
* @BService
* public class MyApplicationCommandFilter implements ApplicationCommandFilter<String> {
* public class MyApplicationCommandFilter implements ApplicationCommandFilter {
*
* @Override
* public boolean getGlobal() {
* return true;
* }
*
* @Nullable
* @Override
* public String check(@NotNull GenericCommandInteractionEvent event, @NotNull ApplicationCommandInfo commandInfo) {
* if (channel.getIdLong() != 722891685755093076L) {
* return "Can only run commands in <#722891685755093076>";
* if (event.getChannel().getIdLong() != 722891685755093076L) {
* event.reply("Can only run commands in <#722891685755093076>").setEphemeral(true).queue();
* return "Not the right channel";
* }
* return null;
* }
* }
* ```
*
* @param T Type of the error object handled by [ApplicationCommandRejectionHandler]
*
* @see ApplicationCommandRejectionHandler
* @see InterfacedService @InterfacedService
*/
@InterfacedService(acceptMultiple = true)
interface ApplicationCommandFilter<T : Any> : Filter {
interface ApplicationCommandFilter : Filter {
/**
* Returns `null` if this filter should allow the command to run, or returns your own object if it can't.
*
* The object will be passed to your [ApplicationCommandRejectionHandler] if the command is rejected.
* Checks if this interaction should run, returns `null` if this filter passes,
* or a reason for the rejection, used for logging purposes.
*/
@JvmSynthetic
suspend fun checkSuspend(event: GenericCommandInteractionEvent, commandInfo: ApplicationCommandInfo): T? =
suspend fun checkSuspend(event: GenericCommandInteractionEvent, commandInfo: ApplicationCommandInfo): String? =
check(event, commandInfo)

/**
* Returns `null` if this filter should allow the command to run, or returns your own object if it can't.
*
* The object will be passed to your [ApplicationCommandRejectionHandler] if the command is rejected.
* Checks if this interaction should run, returns `null` if this filter passes,
* or a reason for the rejection, used for logging purposes.
*/
fun check(event: GenericCommandInteractionEvent, commandInfo: ApplicationCommandInfo): T? =
fun check(event: GenericCommandInteractionEvent, commandInfo: ApplicationCommandInfo): String? =
throw NotImplementedError("${this.javaClass.simpleNestedName} must implement the 'check' or 'checkSuspend' method")
}

infix fun <T : Any> ApplicationCommandFilter<T>.or(other: ApplicationCommandFilter<T>): ApplicationCommandFilter<T> {
return object : ApplicationCommandFilter<T> {
infix fun ApplicationCommandFilter.or(other: ApplicationCommandFilter): ApplicationCommandFilter {
return object : ApplicationCommandFilter {
override val global: Boolean = false

override val description: String
get() = "(${this@or.description} || ${other.description})"

override suspend fun checkSuspend(
event: GenericCommandInteractionEvent,
commandInfo: ApplicationCommandInfo
): T? {
override suspend fun checkSuspend(event: GenericCommandInteractionEvent, commandInfo: ApplicationCommandInfo): String? {
// Elvis operator short circuits if left condition had no error
this@or.checkSuspend(event, commandInfo) ?: return null
return other.checkSuspend(event, commandInfo)
}
}
}

infix fun <T : Any> ApplicationCommandFilter<T>.and(other: ApplicationCommandFilter<T>): ApplicationCommandFilter<T> {
return object : ApplicationCommandFilter<T> {
infix fun ApplicationCommandFilter.and(other: ApplicationCommandFilter): ApplicationCommandFilter {
return object : ApplicationCommandFilter {
override val global: Boolean = false

override val description: String
get() = "(${this@and.description} && ${other.description})"

override suspend fun checkSuspend(event: GenericCommandInteractionEvent, commandInfo: ApplicationCommandInfo): T? {
override suspend fun checkSuspend(event: GenericCommandInteractionEvent, commandInfo: ApplicationCommandInfo): String? {
val errorObject = this@and.checkSuspend(event, commandInfo)
if (errorObject != null)
return errorObject
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package io.github.freya022.botcommands.api.commands.application.builder

import io.github.freya022.botcommands.api.commands.application.ApplicationCommandFilter
import io.github.freya022.botcommands.api.commands.application.ApplicationCommandRejectionHandler
import io.github.freya022.botcommands.api.commands.application.options.builder.ApplicationCommandOptionAggregateBuilder
import io.github.freya022.botcommands.api.commands.application.options.builder.ApplicationOptionRegistry
import io.github.freya022.botcommands.api.commands.builder.ExecutableCommandBuilder
Expand All @@ -17,16 +16,15 @@ interface ApplicationCommandBuilder<T> : ExecutableCommandBuilder<T>,
* Set of filters preventing this command from executing.
*
* @see ApplicationCommandFilter
* @see ApplicationCommandRejectionHandler
*/
val filters: MutableList<ApplicationCommandFilter<*>>
val filters: MutableList<ApplicationCommandFilter>
}

/**
* Convenience extension to load an [ApplicationCommandFilter] service.
*
* Typically used as `filters += filter<MyApplicationCommandFilter>()`
*/
inline fun <reified T : ApplicationCommandFilter<*>> ApplicationCommandBuilder<*>.filter(): T {
inline fun <reified T : ApplicationCommandFilter> ApplicationCommandBuilder<*>.filter(): T {
return context.getService<T>()
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,13 @@ import net.dv8tion.jda.api.events.message.MessageReceivedEvent
* Filters run when a [command variation][TextCommandBuilder.variation] is about to be executed,
* i.e., after the permissions/rate limits... were checked.
*
* When the final filter returns an error object of type [T],
* it will then be passed to the [TextCommandRejectionHandler].
*
* ### Combining filters
*
* Filters can be combined with [`and`][and]/[`or`][or] (static methods for Java users).
*
* ### Requirements
* - Register your instance as a service with [@BService][BService].
* This is not required if you pass the instance directly to the command builder.
* - Have exactly one instance of [TextCommandRejectionHandler].
* - Implement either [check] (Java) or [checkSuspend] (Kotlin).
* - (Optional) Set your filter as a command-specific filter by disabling [global].
*
Expand All @@ -32,12 +28,24 @@ import net.dv8tion.jda.api.events.message.MessageReceivedEvent
* while command-specific filters use the insertion order.
*
* ### Example - Accepting commands only in a single channel
* **Note:** For the example's sake, I will reply directly on each failed condition,
* however, I recommend having a separate function/class to handle rejections,
* as to not duplicate code on each rejection case.
*
* ```kt
* @BService
* class MyTextCommandFilter : TextCommandFilter<String> {
* override suspend fun checkSuspend(event: MessageReceivedEvent, commandVariation: TextCommandVariation, args: String): String? {
* if (event.guildChannel.idLong != 722891685755093076) {
* return "Can only run commands in <#722891685755093076>"
* class MyTextCommandFilter : TextCommandFilter {
*
* override val global: Boolean get() = true
*
* override suspend fun checkSuspend(
* event: MessageReceivedEvent,
* commandVariation: TextCommandVariation,
* args: String
* ): String? {
* if (event.channel.idLong != 722891685755093076) {
* event.message.reply("Can only run commands in <#722891685755093076>").await()
* return "Wrong channel"
* }
* return null
* }
Expand All @@ -48,70 +56,68 @@ import net.dv8tion.jda.api.events.message.MessageReceivedEvent
*
* ```java
* @BService
* public class MyTextCommandFilter implements TextCommandFilter<String> {
* public class MyTextCommandFilter implements TextCommandFilter {
*
* @Override
* public boolean getGlobal() {
* return true;
* }
*
* @Nullable
* @Override
* public String check(@NotNull MessageReceivedEvent event, @NotNull TextCommandVariation commandVariation, @NotNull String args) {
* if (channel.getIdLong() != 722891685755093076L) {
* return "Can only run commands in <#722891685755093076>";
* if (event.getChannel().getIdLong() != 722891685755093076L) {
* event.getMessage().reply("Can only run commands in <#722891685755093076>").queue();
* return "Wrong channel";
* }
* return null;
* }
* }
* ```
*
* @param T Type of the error object handled by [TextCommandRejectionHandler]
*
* @see TextCommandRejectionHandler
* @see InterfacedService @InterfacedService
*/
@InterfacedService(acceptMultiple = true)
interface TextCommandFilter<T : Any> : Filter {
interface TextCommandFilter : Filter {
/**
* Returns `null` if this filter should allow the command to run, or returns your own object if it can't.
*
* The object will be passed to your [TextCommandRejectionHandler] if the command is rejected.
* Checks if this text command should run, returns `null` if this filter passes,
* or a reason for the rejection, used for logging purposes.
*/
@JvmSynthetic
suspend fun checkSuspend(event: MessageReceivedEvent, commandVariation: TextCommandVariation, args: String): T? =
suspend fun checkSuspend(event: MessageReceivedEvent, commandVariation: TextCommandVariation, args: String): String? =
check(event, commandVariation, args)

/**
* Returns `null` if this filter should allow the command to run, or returns your own object if it can't.
*
* The object will be passed to your [TextCommandRejectionHandler] if the command is rejected.
* Checks if this text command should run, returns `null` if this filter passes,
* or a reason for the rejection, used for logging purposes.
*/
fun check(event: MessageReceivedEvent, commandVariation: TextCommandVariation, args: String): T? =
fun check(event: MessageReceivedEvent, commandVariation: TextCommandVariation, args: String): String? =
throw NotImplementedError("${this.javaClass.simpleNestedName} must implement the 'check' or 'checkSuspend' method")
}

infix fun <T : Any> TextCommandFilter<T>.or(other: TextCommandFilter<T>): TextCommandFilter<T> {
return object : TextCommandFilter<T> {
infix fun TextCommandFilter.or(other: TextCommandFilter): TextCommandFilter {
return object : TextCommandFilter {
override val global: Boolean = false

override val description: String
get() = "(${this@or.description} || ${other.description})"

override suspend fun checkSuspend(
event: MessageReceivedEvent,
commandVariation: TextCommandVariation,
args: String
): T? {
override suspend fun checkSuspend(event: MessageReceivedEvent, commandVariation: TextCommandVariation, args: String): String? {
// Elvis operator short circuits if left condition had no error
this@or.checkSuspend(event, commandVariation, args) ?: return null
return other.checkSuspend(event, commandVariation, args)
}
}
}

infix fun <T : Any> TextCommandFilter<T>.and(other: TextCommandFilter<T>): TextCommandFilter<T> {
return object : TextCommandFilter<T> {
infix fun TextCommandFilter.and(other: TextCommandFilter): TextCommandFilter {
return object : TextCommandFilter {
override val global: Boolean = false

override val description: String
get() = "(${this@and.description} && ${other.description})"

override suspend fun checkSuspend(event: MessageReceivedEvent, commandVariation: TextCommandVariation, args: String): T? {
override suspend fun checkSuspend(event: MessageReceivedEvent, commandVariation: TextCommandVariation, args: String): String? {
val errorObject = this@and.checkSuspend(event, commandVariation, args)
if (errorObject != null)
return errorObject
Expand Down
Loading

0 comments on commit e279c57

Please sign in to comment.