The library offers two ways to handle interactions with message components (buttons and select menus).
The first way is to treat component interactions as regular commands, which consists of declaring a listener that is going to be called every time a component with a specific
customId is interacted with. The structure is similar to creating commands:
The class implements
ComponentInteractionListener<Void> and overrides
run(ButtonInteractionContext) (it has several
run() overloads, one for each type of component, here we want a button. For select menus you're supposed to override
@ComponentCommand annotation specifies the customId to listen for. Let's make a chat input command to create the message containing the button:
As usual, unless you are using the Botrino framework, you need to register them manually:
@ComponentCommand annotation is in fact not required if you aren't using the Botrino framework. You may as well override the
customId() method from
ComponentInteractionListener. The annotation is still required when using the Botrino framework, as it will only auto-register listeners containing that annotation, but if you are already overriding
customId() you can use
@ComponentCommand alone without the value. An example might be more clear:
In many cases, you want to use components as a way to make your commands more interactive, for example if you need confirmation from the user to perform an action. You would need some way to "pause" the execution of your command and resume when the user has given a response by clicking a button or a select menu. This is made easy with the
awaitSelectMenuItems(customId) methods. Here's an example of a simple command waiting for the user to select an item and display the value clicked:
- Since you want to listen for one specific select menu (and not all select menus with some customId), you generate a customId that is unique for each invocation of the
/selectcommand. You can easily generate a random string via
- A first followup is sent with the message containing the select menu.
- Once the message has been sent, call
awaitSelectMenuItems(customId)with the same customId generated previously. It will wait for the user to interact with the menu and will emit the value clicked.
- The value received is then displayed via a new followup message.
If you don't make the customId unique on each run, there will be conflicts when the
/select command is run several times consecutively by the same user in the same channel.
Here is another example with
awaitButtonClick(customId) that asks the user to confirm when resetting a user's nickname:
The code is quite self-explanatory: we display two buttons, one for "yes" and one for "no". We use
Mono.firstWithValue to only wait for the first click on either of the two buttons, and depending on which button was clicked, we execute one or the other action.
There exists a more generic method
awaitComponentInteraction that lets you manipulate the underlying interaction context before returning a value. It accepts a
ComponentInteractionListener<R> that you can construct via its static methods
button(String, Function) and
selectMenu(String, Function), each accepting the customId and a function receiving a
SelectMenuInteractionContext and producing a value of any type.
Making a pagination system is one of the most obvious use cases for message components. The library provides a static method
MessagePaginator::paginate to build paginated messages easily. See the example below:
paginatemethod takes 3 arguments. The first one is the interaction context, the second one is the total number of pages, and the last one is a function that receives a state and produces the message to display. An overload exists allowing you to specify the initial page number (by default it starts at the first page).
stateholds information on the current state of the paginator, such as the current page number and whether it is active
- To render the buttons, the state exposes three methods to build previous, next and close buttons respectively. The state object controls whether the buttons are enabled or disabled according to whether we are at first page (in which case previous button should be disabled), at last page (in which case next button should be disabled), or if the paginator has already closed, in which case all buttons should be disabled.
The paginator automatically closes as per the
await_component_timeout_seconds value defined in the configuration.