Skip to content

Interactions

itsmefox edited this page Jan 9, 2023 · 12 revisions

Commands

Create a new command

All slash commands are handled with the abstract class DiscordCommand. To create a new command, you have to extend this class and set a unique name as well as a description. The main method for a command is the execute method. This method is called as soon as the user uses the command.

Important: Discord gives every application 3 seconds to acknowledge the request. If this does not happen, the user will see an error message.

@Interaction
class PingCommand : DiscordCommand("ping", "Send a ping command") {

    override fun execute(event: SlashCommandInteractionEvent) {
        //Reply to the user
        val startTime = System.currentTimeMillis()
        event.reply("Ping ...").setEphemeral(true).queue {
            it.editOriginal("Pong: ${System.currentTimeMillis() - startTime}ms").queue()
        }
    }
}

Registration / Update / Deletion of commands

This process is completely handled by the InteractionInitializer, which jumps into action as soon as the first shard is connected to Discord. All commands which are extending DiscordCommand are loaded during this time. This handler will create a temporary instance of all commands and checks them against the currently on Discord registered commands. Any difference will be updated as needed. This includes deletion of no longer in the code present commands.

Options

Discord commands can have options. These options have to be defined in the method initCommandOptions(). In this method you can define them like this: this.addOption(myOption). For the option themselves we advise you to use the typed options of Aluna, found in the file Options.kt.

Here is an example for a simple string option:

private val animalOption = StringOption("animal", "Preferred animal", isRequired = true)

override fun initCommandOptions() {
    this.addOption(animalOption)
}

All currently available options are listed in the table below:

Option type Aluna typed option
STRING StringOption()
INTEGER IntegerOption()
INTEGER LongOption()
NUMBER DoubleOption()
NUMBER NumberOption()
BOOLEAN BooleanOption()
USER UserOption()
ROLE RoleOption()
CHANNEL ChannelOption()
MENTIONABLE MentionableOption()
ATTACHMENT AttachmentOption()
ATTACHMENT FileOption()

By using these types, you can access the users input with event.getTypedOption() in a type-safe manner.

Sub-Commands & Sub-Command-Groups

To structure your commands, Discord allows you to create sub-commands and sub-command groups. How they can be combined can be found in the official Discord Documentation: Subcommands and Subcommand Groups

Here is an example for a command with a sub-command and a sub-command group:

@Interaction
class AnimalsCommand(
    @SubCommandElement
    private val forestSubCommandGroup: ForestSubCommandGroup,
    @SubCommandElement
    private val bunnyCommand: BunnyCommand
) : DiscordCommand("animal", "Show images of animals", handleSubCommands = true) {

    override fun execute(event: SlashCommandInteractionEvent) {

    }

}

@Interaction
class BunnyCommand(
    private val objectMapper: ObjectMapper
) : DiscordSubCommand("bunny", "Show image of a bunny") {

    override fun execute(event: SlashCommandInteractionEvent) {

    }
}

@Interaction
class ForestSubCommandGroup(
    @SubCommandElement
    private val foxCommand: FoxCommand
) : DiscordSubCommandGroup("forest", "Forest animals")

@Interaction
class FoxCommand(
    private val objectMapper: ObjectMapper
) : DiscordSubCommand("fox", "Show image of a fox") {

    override fun execute(event: SlashCommandInteractionEvent) {

    }
}

As you can see sub-commands are always loaded over autowired dependencies and the @SubCommandElement. Also sub-commands and sub-command-groups have their own abstract classes they extend.

One important note here is that the execute method of the parent classes is also called.
For the command /animal forst fox, the following execute methods are called in this order:
AnimalsCommand.execute() -> FoxCommand.execute()
The ForestSubCommandGroup has no execute method as it is a group and can not be used without a sub command.

Auto-Complete for Options

Discord commands also support auto-complete for their options. These auto-complete request are also handled by Aluna if you set the command property observeAutoComplete = true. In that case Aluna will automatically call the method onAutoCompleteEvent() which you can and should override in that case. Same as for the command itself, your bot has to acknowledge the request in under 3 seconds. All options which have auto-complete enabled, have to be handled by this method.

Example implementation:

@Interaction
class SetPreferredAnimalCommand :
    DiscordCommand("set-preferred-animal", "Define your preferred Animal", observeAutoComplete = true) {

    private val animalOption = StringOption("animal", "Preferred animal", isRequired = true, isAutoComplete = true)

    override fun initCommandOptions() {
        this.addOption(animalOption)
    }

    override fun execute(event: SlashCommandInteractionEvent) {
        event.reply(event.getTypedOption(animalOption, "")!!).complete()
    }

    override fun onAutoCompleteEvent(option: String, event: CommandAutoCompleteInteractionEvent) {
        val options = hashMapOf<String, String>()

        options["fox"] = "Fox"
        options["bunny"] = "Bunny"
        options["deer"] = "Deer"

        event.replyStringChoices(options).queue()
    }
}

Spring Scope handling

Aluna comes with its own scope (@InteractionScoped) for all interactions. If you use the annotation @Interaction, this will automatically be used.

This scope will ensure that a user always gets the same bean for a command if there are for examples buttons which can be clicked. The same is the case if a command has auto-complete for some options. In that case a bean of the command will be created on the first auto-complete request, and it will be reused if the user than uses the command itself.

By default, Aluna will keep a bean for 14 minutes. This is to ensure that on destruction of the bean, the method onDestroy() is still able to use the same interaction hook (the stays active for 15 min). This timeout gets reset if the bean is reused because of component interaction (like buttons, select-menu or modals) as well as if the bean cot created from a auto-complete request.

If you want to change the default behavior, you can set these properties in the constructor:

  • beanTimoutDelay
  • beanUseAutoCompleteBean
  • beanRemoveObserverOnDestroy
  • beanResetObserverTimeoutOnBeanExtend
  • beanCallOnDestroy

Components handling

There are two ways to handle components with Aluna: Integrated Way or Manually over the EventHandler.

Integrated Way

To use the integrated way, the message has to be sent with the queueAndRegisterInteraction() method or manually ( example for buttons) with discordBot.registerMessageForButtonEvents(it, this). Aluna will than call the corresponding method onButtonInteraction() and onButtonInteractionTimeout() after the defined timeout. *By default the timeout is set to 14 minutes. Methods to override:

Type Methods
Button onButtonInteraction()
String Select onStringSelectInteraction()
Entity Select onEntitySelectInteraction()
Modal onModalInteraction()

Manually over the EventHandler

todo

Translation

todo

Context-Menus

Create a new context menu

All context-menus are handled with either the abstract class DiscordUserContextMenu or DiscordMessageContextMenu. To create a new context-menu, you have to extend one of these classes and set a unique name. The main method for a command is the execute method. This method is called as soon as the user uses the context-menu.

Important: Discord gives every application 3 seconds to acknowledge the request. If this does not happen, the user will see an error message.

@Interaction
class GetMessageAsWebhookMessageMenu(
    private val objectMapper: ObjectMapper
) : DiscordMessageContextMenu("Extract as Webhook") {

    override fun execute(event: MessageContextInteractionEvent) {

    }
}

Registration / Update / Deletion of context menus

This process is completely handled by the InteractionInitializer, which jumps into action as soon as the first shard is connected to Discord. All context-menus which are extending DiscordUserContextMenu or DiscordMessageContextMenu are loaded during this time. This handler will create a temporary instance of all context-menus and checks them against the currently on Discord registered context-menus. Any difference will be updated as needed. This includes deletion of no longer in the code present context-menus.

Spring Scope handling

Aluna comes with its own scope (@InteractionScoped) for all interactions. If you use the annotation @Interaction, this will automatically be used.

This scope will ensure that a user always gets the same bean for a context-menu if there are for examples buttons which can be clicked.

By default, Aluna will keep a bean for 14 minutes. This is to ensure that on destruction of the bean, the method onDestroy() is still able to use the same interaction hook (the stays active for 15 min). This timeout gets reset if the bean is reused because of component interaction (like buttons, select-menu or modals) as well as if the bean cot created from a auto-complete request.

If you want to change the default behavior, you can set these properties in the constructor:

  • beanTimoutDelay
  • beanUseAutoCompleteBean
  • beanRemoveObserverOnDestroy
  • beanCallOnDestroy

Components handling

todo

Translation

todo

Buttons

todo

Utils

todo

Select-Menus

todo

Utils

todo

Modals

todo

Utils

todo

Generic Auto-Complete Handler

todo

Additional interaction handlers

todo