UNPKG

chat

Version:

Unified chat abstraction for Slack, Teams, Google Chat, and Discord

408 lines (312 loc) 13.7 kB
--- title: Handling Events description: Register handlers for mentions, messages, reactions, member joins, and platform-specific events. type: guide prerequisites: - /docs/getting-started related: - /docs/slash-commands - /docs/actions - /docs/modals --- Chat SDK uses an event-driven architecture. You register handlers for different event types, and the SDK routes incoming webhooks to the appropriate handler. ## How routing works When a message arrives, the SDK evaluates handlers in this order: 1. **Direct messages**if the thread is a DM and any `onDirectMessage` handlers are registered, they fire before `onSubscribedMessage`, `onNewMention`, and pattern handlers. 2. **Subscribed threads**if the thread is subscribed, `onSubscribedMessage` fires and no other message handler runs. DMs only reach this step when no `onDirectMessage` handlers are registered. 3. **Mentions**if the bot is @-mentioned in an unsubscribed thread, `onNewMention` fires. Unsubscribed DMs without direct handlers are treated as mentions for backward compatibility. 4. **Pattern matches**if the message text matches any `onNewMessage` regex patterns, those handlers fire. Reactions, slash commands, actions, and modals have their own dedicated routing and are not affected by subscription state. ## Handling @-mentions `onNewMention` fires when your bot is @-mentioned in a thread it hasn't subscribed to. This is the primary entry point for new conversations. ```typescript title="lib/bot.ts" lineNumbers bot.onNewMention(async (thread, message) => { await thread.subscribe(); await thread.post("Hello! I'm now listening to this thread."); }); ``` The handler receives a [`Thread`](/docs/api/thread) and a [`Message`](/docs/api/message). Once you call `thread.subscribe()`, future messages in that thread route to `onSubscribedMessage` instead. ### When to use - **AI assistants** — subscribe on first mention, then respond to all follow-up messages in the thread. - **Ticket bots** — create a ticket when mentioned, then track the conversation. - **One-shot commands** — respond to a mention without subscribing for bots that don't need ongoing context. ### Example: AI assistant with context ```typescript title="lib/bot.ts" lineNumbers bot.onNewMention(async (thread, message) => { await thread.subscribe(); await thread.startTyping(); const response = await generateAIResponse(message.text); await thread.post(response); }); ``` ### Example: Triage bot ```typescript title="lib/bot.ts" lineNumbers bot.onNewMention(async (thread, message) => { const ticket = await createTicket({ title: message.text.slice(0, 100), reporter: message.author.fullName, }); await thread.post(`Ticket created: ${ticket.url}`); }); ``` ## Handling subscribed messages `onSubscribedMessage` fires for every new message in a non-DM thread your bot has subscribed to. Once subscribed, messages (including @-mentions) route here instead of `onNewMention`. If an `onDirectMessage` handler is registered, DM messages route there before subscription routing. Without a direct handler, subscribed DMs route to `onSubscribedMessage`. ```typescript title="lib/bot.ts" lineNumbers bot.onSubscribedMessage(async (thread, message) => { if (message.isMention) { await thread.post("You mentioned me!"); return; } await thread.post(`Got your message: ${message.text}`); }); ``` <Callout type="info"> Messages sent by the bot itself do not trigger this handler. You don't need to filter out your own messages. </Callout> ### When to use - **Conversational AI** — maintain a back-and-forth conversation with message history. - **Thread monitoring** — watch a thread for updates and react to specific keywords or patterns. - **Collaborative workflows** — track all messages in a thread to update external systems. ### Example: Conversational AI with history ```typescript title="lib/bot.ts" lineNumbers import { toAiMessages } from "chat/ai"; bot.onSubscribedMessage(async (thread, message) => { await thread.startTyping(); // Build conversation history from thread messages const messages = []; for await (const msg of thread.allMessages) { messages.push(msg); } const history = await toAiMessages(messages); const response = await generateAIResponse(history); await thread.post(response); }); ``` See [`toAiMessages`](/docs/ai/to-ai-messages) for all options including multi-user name prefixing, message transforms, and attachment handling. ### Example: Unsubscribe on keyword ```typescript title="lib/bot.ts" lineNumbers bot.onSubscribedMessage(async (thread, message) => { if (message.text.toLowerCase().includes("stop")) { await thread.unsubscribe(); await thread.post("Got it, I'll stop watching this thread."); return; } // Handle other messages... }); ``` ### Example: Thread state for multi-step flows ```typescript title="lib/bot.ts" lineNumbers interface ThreadState { step: "awaiting_name" | "awaiting_email" | "done"; } const bot = new Chat<typeof adapters, ThreadState>({ /* ...config */ }); bot.onNewMention(async (thread) => { await thread.subscribe(); await thread.setState({ step: "awaiting_name" }); await thread.post("Let's get you set up. What's your name?"); }); bot.onSubscribedMessage(async (thread, message) => { const state = await thread.state; switch (state?.step) { case "awaiting_name": await thread.setState({ step: "awaiting_email" }); await thread.post(`Thanks, ${message.text}! What's your email?`); break; case "awaiting_email": await thread.setState({ step: "done" }); await thread.post("All set! Your account has been created."); await thread.unsubscribe(); break; } }); ``` ## Handling pattern matches `onNewMessage` fires for messages matching a regex pattern in threads the bot is **not** subscribed to. Use it for keyword-triggered responses without requiring an @-mention. ```typescript title="lib/bot.ts" lineNumbers bot.onNewMessage(/^help$/i, async (thread, message) => { await thread.post("Here's how I can help..."); }); ``` The first argument is a `RegExp` that's tested against the message text. Only messages in unsubscribed threads are evaluated. ### When to use - **Keyword triggers** — respond to specific words or phrases without requiring a mention. - **Auto-responders** — detect common questions and provide instant answers. - **Escalation detection** — watch for urgent language and alert the right people. ### Example: FAQ auto-responder ```typescript title="lib/bot.ts" lineNumbers bot.onNewMessage(/\b(deploy|deployment|ship)\b/i, async (thread, message) => { await thread.post( "Deployments run automatically on push to `main`. " + "Check status at https://dashboard.example.com/deploys" ); }); ``` ### Example: Incident detection ```typescript title="lib/bot.ts" lineNumbers bot.onNewMessage(/\b(outage|down|incident|p[01])\b/i, async (thread, message) => { await thread.subscribe(); await thread.post( "I've flagged this as a potential incident and I'm monitoring this thread." ); await notifyOnCallTeam({ channel: thread.channel.id, reporter: message.author.fullName, text: message.text, }); }); ``` ## Handling reactions `onReaction` fires when users add or remove emoji reactions to messages. You can handle all reactions or filter by specific emoji. ```typescript title="lib/bot.ts" lineNumbers import { emoji } from "chat"; // Handle specific emoji bot.onReaction(["thumbs_up", "heart"], async (event) => { if (!event.added) return; await event.adapter.addReaction( event.threadId, event.messageId, emoji.raised_hands ); }); // Handle all reactions bot.onReaction(async (event) => { console.log(`${event.user.userName} ${event.added ? "added" : "removed"} ${event.emoji}`); }); ``` ### ReactionEvent | Property | Type | Description | |----------|------|-------------| | `emoji` | `EmojiValue` | Normalized emoji name for cross-platform comparison | | `rawEmoji` | `string` | Platform-specific emoji string | | `added` | `boolean` | `true` if added, `false` if removed | | `user` | `Author` | The user who reacted | | `message` | `Message` (optional) | The message that was reacted to | | `thread` | `Thread` | Thread for posting replies | | `messageId` | `string` | ID of the message that was reacted to | | `threadId` | `string` | Thread ID | | `adapter` | `Adapter` | The platform adapter | | `raw` | `unknown` | Platform-specific event payload | ### When to use - **Approval workflows** — use thumbs up/down as lightweight approve/reject signals. - **Bookmarking** — save messages to an external system when a specific emoji is added. - **Polls and voting** — count reactions as votes. ### Example: Approval workflow ```typescript title="lib/bot.ts" lineNumbers bot.onReaction(["thumbs_up", "thumbs_down"], async (event) => { if (!event.added) return; const approved = event.emoji === "thumbs_up"; const status = approved ? "approved" : "rejected"; await event.thread.post( `Request ${status} by ${event.user.fullName}` ); await updateRequestStatus(event.messageId, status, event.user.userId); }); ``` ### Example: Save to external system ```typescript title="lib/bot.ts" lineNumbers bot.onReaction(["bookmark"], async (event) => { if (!event.added || !event.message) return; await saveToDatabase({ text: event.message.text, savedBy: event.user.userId, source: event.threadId, }); await event.thread.post( `Bookmarked by ${event.user.fullName}` ); }); ``` ## Handling interactions For button clicks, slash commands, and modal forms, see the dedicated guides: - **[Slash Commands](/docs/slash-commands)** — handle `/command` invocations from the message composer. - **[Actions](/docs/actions)** — handle button clicks and interactive card events. - **[Modals](/docs/modals)** — collect structured input through modal dialogs with validation. ## Handling Slack-specific events These handlers are specific to the Slack platform and require the Slack adapter. ### Handling assistant threads `onAssistantThreadStarted` fires when a user opens a new assistant thread in Slack. Use it with the [Slack Assistants API](/adapters/official/slack#slack-assistants-api) to set suggested prompts and status indicators. ```typescript title="lib/bot.ts" lineNumbers bot.onAssistantThreadStarted(async (event) => { const slack = bot.getAdapter("slack") as SlackAdapter; await slack.setSuggestedPrompts(event.channelId, event.threadTs, [ { title: "Get started", message: "What can you help me with?" }, { title: "Summarize", message: "Summarize the current channel" }, ]); }); ``` The `event` object includes: | Property | Type | Description | |----------|------|-------------| | `channelId` | `string` | The assistant thread's channel | | `threadTs` | `string` | Thread timestamp | | `threadId` | `string` | Thread ID | | `userId` | `string` | User who started the thread | | `context` | `object` | Assistant context (channelId, teamId, enterpriseId, threadEntryPoint) | | `adapter` | `Adapter` | The Slack adapter | ### Handling assistant context changes `onAssistantContextChanged` fires when the assistant context changes, for example when a user navigates to a different channel while the assistant thread is open. ```typescript title="lib/bot.ts" lineNumbers bot.onAssistantContextChanged(async (event) => { const slack = bot.getAdapter("slack") as SlackAdapter; await slack.setStatus(event.channelId, event.threadTs, "Updating context..."); // Update prompts based on new context const channelName = event.context.channelId ?? "general"; await slack.setSuggestedPrompts(event.channelId, event.threadTs, [ { title: "Summarize", message: `Summarize #${channelName}` }, ]); }); ``` ### Handling App Home opens `onAppHomeOpened` fires when a user opens your bot's Home tab in Slack. Use it to publish a dynamic view. ```typescript title="lib/bot.ts" lineNumbers bot.onAppHomeOpened(async (event) => { const slack = bot.getAdapter("slack") as SlackAdapter; await slack.publishHomeView(event.userId, { type: "home", blocks: [ { type: "section", text: { type: "mrkdwn", text: `Welcome, <@${event.userId}>!` }, }, { type: "actions", elements: [ { type: "button", text: { type: "plain_text", text: "Open Dashboard" }, url: "https://dashboard.example.com", }, ], }, ], }); }); ``` The `event` object includes: | Property | Type | Description | |----------|------|-------------| | `userId` | `string` | User who opened the Home tab | | `channelId` | `string` | Channel context | | `adapter` | `Adapter` | The Slack adapter | ### Handling member joined channel `onMemberJoinedChannel` fires when a user joins a Slack channel. Use it to post welcome messages or onboard users automatically. ```typescript title="lib/bot.ts" lineNumbers bot.onMemberJoinedChannel(async (event) => { // Only post when the bot itself joins if (event.userId !== event.adapter.botUserId) { return; } await event.adapter.postMessage( event.channelId, "Hello! I'm now available in this channel. Mention me to get started." ); }); ``` The `event` object includes: | Property | Type | Description | |----------|------|-------------| | `adapter` | `Adapter` | The Slack adapter | | `channelId` | `string` | The channel that was joined | | `userId` | `string` | The user who joined | | `inviterId` | `string` (optional) | The user who invited them |