Interaction Handler System
Relevant source files
The following files were used as context for generating this wiki page:
The Interaction Handler System is the central nervous system for Amina's responses to user interactions within Discord. It is an event-driven architecture responsible for receiving, identifying, and routing all non-command interactions, such as button clicks, select menu choices, and modal submissions, to the appropriate business logic. This system ensures a modular, scalable, and maintainable approach to handling the complex workflows initiated by interactive components.
The core of the system is the interactionCreate event listener, which acts as a primary router. It inspects the type and customId of each incoming interaction and delegates it to a specific handler module (e.g., ticket, roles, dev). This delegation is governed by a strict naming convention for customIds, which allows for efficient and predictable routing without a monolithic logic block.
Core Architecture
The system is designed around the interactionCreate event from discord.js. When a user interacts with a component, this event is fired and captured by the main listener, which then orchestrates the response.
High-Level Interaction Flow
The following diagram illustrates the journey of an interaction from the user to the final handler function.
This diagram shows the path of a user interaction through the system, from the initial Discord event to the execution of specific feature logic.
Sources: src/events/interactions/interactionCreate.ts
Interaction Routing in interactionCreate.ts
The file src/events/interactions/interactionCreate.ts is the heart of the handler system. It contains a large conditional block that triages interactions based on their type.
The primary routing mechanism relies on a customId naming convention. Most handlers are dynamically imported to improve performance and reduce initial memory load.
Interaction Types Handled
The router explicitly handles the following interaction types:
| Interaction Type | Method Check | Example customId Prefix |
|---|---|---|
| Autocomplete | isAutocomplete() | N/A (handled separately) |
| Slash Command | isChatInputCommand() | N/A (handled by commandHandler) |
| Context Menu | isContextMenuCommand() | N/A (handled by contextHandler) |
| Button | isButton() | profile:btn:, admin:btn: |
| Modal Submit | isModalSubmit() | ticket:modal:, purge:modal: |
| String Select Menu | isStringSelectMenu() | dev:menu:, minaai:menu: |
| Channel Select Menu | isChannelSelectMenu() | purge:channel:, admin:channel: |
| User Select Menu | isUserSelectMenu() | roles:user:select, ticket:user: |
| Role Select Menu | isRoleSelectMenu() | admin:role:, roles:role: |
Sources: src/events/interactions/interactionCreate.ts:15-814
Custom ID Naming Convention
A standardized customId format is crucial for the router to function correctly. The general pattern is:
feature:type:action
feature: The name of the module (e.g.,ticket,roles,dev,admin).type: The type of component (e.g.,btn,menu,modal,channel).action: The specific action to be performed (e.g.,back,confirm,category).
For more complex interactions that need to pass state, a pipe (|) is used to append key-value pairs.
feature:type:action|key1:value1|key2:value2
This state is parsed using the parseCustomIdState helper function. An example is the button to confirm topic removal in the ticket system: ticket:btn:topic_remove_confirm|topic:${topicName}.
Sources: src/events/interactions/interactionCreate.ts:121, src/handlers/ticket/setup/topics.ts:321
Handler Structure
Handlers are organized into feature-based modules within the src/handlers/ directory. Each module typically contains a "main hub" file and other files for specific sub-features or UI components.
Example: Developer Hub (/dev)
The /dev command provides a hub for developer-only operations. Its interaction flow demonstrates the modular handler pattern.
- Entry Point: The
showDevHubfunction is called, which creates an embed and aStringSelectMenuwith thecustomIdofdev:menu:category. - User Selection: The user selects a category from the menu (e.g., "presence management").
- Routing: The
interactionCreaterouter detects theStringSelectMenuInteractionwith thedev:menu:categoryID and callshandleCategoryMenuinsrc/handlers/dev/main-hub.ts. - Delegation:
handleCategoryMenureads the selected value (presence) and dynamically imports and calls the appropriate sub-handler (showPresenceMenufrom./presence). - Navigation: Most sub-menus include a "back" button with a
customIdlikedev:btn:back, which callshandleDevBackButtonto return the user to the mainshowDevHubview.
Sources: src/handlers/dev/main-hub.ts:13-145
The following sequence diagram illustrates this flow:
This diagram shows the sequence of calls when a user navigates into and back out of a sub-menu within the Developer Hub.
Sources: src/events/interactions/interactionCreate.ts:630-634, src/handlers/dev/main-hub.ts:106-111, 140-145
State Management in Interactions
For multi-step workflows, data must be passed between interactions. The system primarily uses the customId field to encode this state.
Example: Add Roles to User
The "Add Roles to User" feature is a three-step process that demonstrates state encoding:
- User Selection: A
UserSelectMenuwithcustom_id: 'roles:user:select'is shown. - Role Selection: After users are selected,
handleUserSelectis triggered. It encodes the selected user IDs into a Base64 string and appends it to thecustomIdof the next component, aRoleSelectMenu. The new ID becomesroles:role:assign|{userDataB64}. - Confirmation: After roles are selected,
handleRoleSelectis triggered. It decodes the user IDs from thecustomIdand encodes both user and role IDs into thecustomIdof the final confirmation button:roles:btn:assign_confirm|{assignmentDataB64}. - Execution:
handleAssignConfirmdecodes the final data packet and performs the role assignments.
// src/handlers/roles/add-to-user/index.ts:100-103
// Encode user IDs in custom_id
const userDataB64 = Buffer.from(JSON.stringify(selectedUsers)).toString(
'base64'
)
// src/handlers/roles/add-to-user/index.ts:133-136
// RoleSelect custom_id with encoded user data
{
type: 6, // RoleSelect
custom_id: `roles:role:assign|${userDataB64}`,
...
}
// src/handlers/roles/add-to-user/index.ts:249-254
// Encode final assignment data for the confirm button
const assignmentData = { userIds, roleIds: selectedRoleIds }
const assignmentDataB64 = Buffer.from(
JSON.stringify(assignmentData)
).toString('base64')
const confirmBtn = MinaButtons.custom(
`roles:btn:assign_confirm|${assignmentDataB64}`,
...
)
Sources: src/handlers/roles/add-to-user/index.ts:101, src/handlers/roles/add-to-user/index.ts:135, src/handlers/roles/add-to-user/index.ts:253
Handler Interfaces
To enforce consistency and type safety across the various handler modules, TypeScript interfaces are defined in types/handlers.d.ts. These interfaces declare the functions that a specific handler module is expected to export.
For example, the TicketHandlers interface defines the contract for all functions related to the ticket system's interactive components.
// types/handlers.d.ts:74-93
declare module '@handlers/ticket' {
export interface TicketHandlers {
handleTicketCategoryMenu: (interaction: StringSelectMenuInteraction) => Promise<void>
handleTicketBackButton: (interaction: ButtonInteraction) => Promise<void>
handleSetupMenu: (interaction: StringSelectMenuInteraction) => Promise<void>
// ... more handler functions
handleAddTopicModal: (interaction: ModalSubmitInteraction) => Promise<void>
handleRemoveTopicSelect: (interaction: StringSelectMenuInteraction) => Promise<void>
handleRemoveTopicConfirm: (interaction: ButtonInteraction) => Promise<void>
// ... etc.
}
export type TicketCategory = 'setup' | 'manage'
export type TicketSetupOption = 'message' | 'log' | 'limit' | 'topics'
export type TicketManageOption = 'close' | 'closeall' | 'add' | 'remove'
export type TicketTopicsOption = 'list' | 'add' | 'remove'
}
This practice ensures that the main interactionCreate router can reliably call functions from different modules, as they all adhere to a predefined structure.
Sources: types/handlers.d.ts:74-93
Conclusion
The Interaction Handler System is a robust and scalable solution for managing complex user interactions in the Amina bot. By leveraging a centralized router (interactionCreate.ts), a strict customId naming convention, and a modular handler structure, the system remains organized and easy to extend. The use of dynamic imports optimizes performance, while TypeScript interfaces ensure type safety and structural consistency across different feature modules. This architecture is fundamental to providing the bot's rich, interactive user experience.