Event System
This content is not available in your language yet.
TomoriBot routes Discord gateway events and REST rate-limit notifications through one dispatcher in src/handlers/eventHandler.ts.
Dispatcher model
Section titled “Dispatcher model”The dispatcher maps Discord event names to folder names under src/events/ using eventFolderMap.
- Shallow module discovery: At startup,
getHandlerFiles()scans for*.tsfiles directly inside each mapped folder. Subdirectories are ignored by the loader, reserving nested folders undersrc/events/<eventName>/for event-local helper modules. - Lexical execution order: Handlers within a folder are imported dynamically and sorted alphabetically by filename before caching. When an event fires, its handlers execute sequentially in that order.
- Isolated execution: Each handler runs in its own
try...catchblock. A thrown error in one handler logs structured metadata (EventHandlerError) without preventing subsequent handlers from executing for that event. - Activity tracking: Dispatched gateway events call
healthTracker.recordActivity(). The health endpoint reports activity age as a diagnostic; readiness and WebSocket ping determine its verdict. - Fan-in mapping: Multiple Discord events can map to a single handler folder. For example,
emojiCreate,emojiDelete, andemojiUpdateall route toguildEmojisUpdate, whilestickerCreate,stickerDelete, andstickerUpdateroute toguildStickersUpdate.
REST rate limits
Section titled “REST rate limits”REST rate limits arrive on client.rest.on("rateLimited", ...). In discord.js v14, the REST rate limiter is owned by the REST client. setupEventListeners() checks for the rateLimit folder separately and registers the listener on client.rest rather than the gateway client.
Primary event flows
Section titled “Primary event flows”Slash commands and interactions
Section titled “Slash commands and interactions”The gateway event interactionCreate routes to src/events/interactionCreate/handleCommands.ts. The handler separates chat-input commands, autocomplete requests, and globally routed component or modal interactions, dispatching each to its respective runner. See Command System for interaction lifecycle and timing constraints.
Chat messages
Section titled “Chat messages”The messageCreate event routes to src/events/messageCreate/tomoriChat.ts. The handler normalizes incoming messages, evaluates server admission and ignore rules, manages per-channel execution queues, and runs turn planning and generation. See Normalize Invocation for chat normalization and admission details.
src/events/messageCreate/activeNudge.ts sorts first and feeds the Natural Nudges active-nudge counter. It starts the counter without awaiting it, because handlers run one after another and a cold settings read must never delay tomoriChat. A tripped check runs detached as well. See Natural Nudges.
Startup initialization
Section titled “Startup initialization”The clientReady event runs startup initialization tasks, including registering application slash commands with Discord (src/events/clientReady/01_registercommands.ts) and initializing Model Context Protocol (MCP) servers.
Member joins and the welcome gate
Section titled “Member joins and the welcome gate”The guildMemberAdd event routes to src/events/guildMemberAdd/newUser.ts:
- Registers the user row in the database via
userRepository.register. - Evaluates guild welcome configuration. If the server has Membership Screening or Onboarding enabled,
helpers/welcomeGate.tsretains an in-memory waiter per member until screening clears and onboarding completes (CompletedOnboardingflag onguildMemberUpdate). - If the member leaves before clearing screening,
guildMemberRemovecancels the waiter. Waiters that never resolve are dropped after one hour (WELCOME_GATE_MAX_WAIT_MS). - Once screening clears, the handler verifies the member is still in the guild and invokes the welcome greeting through the standard chat coordinator pipeline. The greeting borrows the channel’s latest message only for placement; that speaker’s blacklist status, role restrictions, and personal credentials do not control the welcome. Like other system triggers (
textQuotaSource: "system"), it gets no reply directive or native reply to that message when it waits in a busy channel’s queue (answersQueuedTriggerMessage()insrc/utils/chat/contextDirectives.ts). - While the greeting is being prepared,
src/utils/chat/welcomeHandoff.tsholds a handoff window keyed by welcome channel and member. If the member triggers Tomori in that channel first, chat admission injects the welcome context into their own turn and the standalone greeting is skipped, so they get one reply instead of two. Once the greeting turn claims the window, a member turn for its anchor message is dropped, and later member messages queue behind the greeting as usual.
Extending event handling
Section titled “Extending event handling”Adding a new event handler or mapping a new Discord event is documented in the contributor guide Adding an Event Handler.
Source pointers
Section titled “Source pointers”src/handlers/eventHandler.ts: single event dispatcher and listener setup.src/events/interactionCreate/handleCommands.ts: slash command, autocomplete, and interaction dispatch.src/events/messageCreate/tomoriChat.ts: chat pipeline entry point.src/events/messageCreate/activeNudge.ts: Natural Nudges active-nudge counter.src/events/guildMemberAdd/newUser.ts: member registration and welcome gate runner.src/utils/misc/healthTracker.ts: liveness tracking.