Skip to content

Command Buttons

Interaction Acknowledging

If your button does something other than editing the message, you'll need to ack the request via ButtonClickContext#ack(). If all of your buttons do nothing with the message it's a pretty safe bet to put the entire button handler in the ack queue callback.

Command buttons are a default part of JDA and Discord's featureset, and this library lets you use them. However, the implementation is a bit weird, so let's dive in.

Buttons are defined in the constructor, even though they're realistically only needed when the command is actually called. To get around this, the library stores a lazy button function that's called passing the SlashCommandContext as a parameter and returning a HashSet<Button>. But you don't need to worry about that, because making buttons is rather simple:

Example Button Usage
    init {
        buttons { ctx ->
            val id = newComponentId()
                .user(ctx.user().id)
                .action("delete")
            danger(id, "Delete")
        }        
    }

Note the above usage of newComponentId(). Normally, in JDA, Component IDs are stored in Strings. However, in this library, to provide a somewhat consistent button component ID format, there's a class named ButtonComponentId.

Component IDs should follow the format commandName:user:action:value.

commandName

The name of the command that created the button. This must match the command name otherwise the command library will not call the button click handler.

user

The user who pressed the button, to prevent spoofing.

action

The action to perform, to discern between buttons programmatically.

value

A possible value needed when the button is pressed, such as a numerical value for messages to delete in a purge command.

To handle the button, you can assume every call of your handler to be of a button for that specific command. You'll need to handle ID checking, and action/value reading.

Example Button Handler Implementation
    override fun handleButtonClick(ctx: ButtonClickContext) {
        val id = ctx.parsedComponent() // ButtonComponentId
        if (id.user() != ctx.user().id) return
        when (id.action()) {
            "delete" -> //delete the message
            /etc
        }
    }

In order to utilize the buttons that are generated by the function set in your primary constructor, you'll want to call actionRowsFrom(SlashCommandContext) on any ReplyAction, which will call the button generator function and add all of the buttons to the message.

A Functional Example
class DestructOnDemand : SlashCommand("destruct", "Dummy message with a button allowing you to delete it at will.") {
    init {
        buttons { ctx ->
            val id = newComponentId()
                .user(ctx.user().id)
                .action("delete")
            danger(id, "Delete")
        }        
    }

    override fun handleSlashCommand(ctx: SlashCommandContext) {
        ctx.reply("lmao")
            .actionRowsFrom(ctx)
            .queue()
    }

    override fun handleButtonClick(ctx: ButtonClickContext) {
        val id = ctx.parsedComponent()
        if (id.user() != ctx.user().id) return
        when (id.action()) {
            "delete" -> ctx.ack().queue { ctx.message?.delete()?.queue() }
        }
    }
}