Skip to main content

stacks-chat

Use when implementing chat messaging in Stacks - sending messages to Slack (webhooks, bot tokens, block kit), Discord (webhooks, bot tokens, embeds), Microsoft Teams (adaptive cards, webhooks), the BaseChatDriver abstraction, retry logic, multi-channel chat routing, or READING a person's conversations from iMessage, WhatsApp, Slack and Discord and archiving them there (the inbox drivers). Covers @stacksjs/chat.

معلومات المصدر

المستودع
stacksjs/stacks
آخر نشاط في المصدر
٦ أكتوبر ٢٠٢٦ في ٠٠:٠٣
لغة SKILL.md المكتشفة
الإنجليزية
النجوم
٦٣١
التفرعات
٢٠

خيارات التثبيت

يُحدَّد Prompt الذي يراجع المصدر أولًا بشكل افتراضي. يمكنك التبديل إلى أمر مباشر أو تنزيل نسخة محلية.

مراجعة ملفات المصدر

اقرأ SKILL.md وأي ملفات مرافقة يعرضها SkillsMP قبل أن تقرر التثبيت.

عرض SKILL.md

SKILL.md
تعليمات المصدر · معاينة للقراءة فقط
name
stacks-chat
description
Use when implementing chat messaging in Stacks - sending messages to Slack (webhooks, bot tokens, block kit), Discord (webhooks, bot tokens, embeds), Microsoft Teams (adaptive cards, webhooks), the BaseChatDriver abstraction, retry logic, multi-channel chat routing, or READING a person's conversations from iMessage, WhatsApp, Slack and Discord and archiving them there (the inbox drivers). Covers @stacksjs/chat.
license
MIT
compatibility
Bun >= 1.3.0, TypeScript
allowed-tools
Read Edit Write Bash Grep Glob
# Stacks Chat Multi-driver chat messaging with Slack, Discord, and Microsoft Teams support. Each driver supports both webhook and bot token modes, with retry logic and structured message formats. ## Key Paths - Core package: `storage/framework/core/chat/src/` - Types: `storage/framework/core/types/src/chat.ts` ## Source Files ``` chat/src/ ├── index.ts # router, configure functions, type re-exports └── drivers/ ├── base.ts # BaseChatDriver abstract class ├── slack.ts # SlackDriver + sendWebhook() ├── discord.ts # DiscordDriver + sendWebhook() + sendEmbed() └── teams.ts # TeamsDriver + sendWebhook() + sendCard() ``` ## Router (index.ts) The main `send()` function routes to the specified driver. Default driver is `'slack'`: ```typescript import { send, sendToSlack, sendToDiscord, sendToTeams } from '@stacksjs/chat' // Route to driver (default: 'slack') await send(message, { driver: 'slack' }) await send(message, { driver: 'discord' }) await send(message, { driver: 'teams' }) // Direct webhook helpers await sendToSlack(webhookUrl, text, options?) await sendToDiscord(webhookUrl, content, options?) await sendToTeams(webhookUrl, text) ``` Settings come from `config/services.ts` by default, read on each send after the app's config has loaded: `SLACK_WEBHOOK_URL` / `SLACK_BOT_TOKEN`, `DISCORD_WEBHOOK_URL` / `DISCORD_BOT_TOKEN`, `TEAMS_WEBHOOK_URL`, and each driver's `*_MAX_RETRIES` / `*_RETRY_TIMEOUT`. A webhook URL from config passes the same HTTPS and host checks as one given to `configure*()`. The configure functions override config, for an app that keeps these elsewhere: ```typescript import { configureSlack, configureDiscord, configureTeams } from '@stacksjs/chat' configureSlack({ webhookUrl: '...', botToken: '...' }) configureDiscord({ webhookUrl: '...', botToken: '...' }) configureTeams({ webhookUrl: '...' }) ``` Exported types: `ChatDriver` (union), `SendOptions`, `SlackConfig`, `SlackMessage`, `SlackBlock`, `SlackAttachment`, `DiscordConfig`, `DiscordMessage`, `DiscordEmbed`, `TeamsConfig`, `TeamsMessage`, `TeamsAdaptiveCard`, `TeamsCardElement`, `TeamsCardAction` ## BaseChatDriver (base.ts) Abstract base class all drivers extend. Implements `ChatDriver` interface from `@stacksjs/types`: ```typescript abstract class BaseChatDriver implements ChatDriver { public abstract name: string protected config: Required<ChatDriverConfig> constructor(config?: ChatDriverConfig) // defaults: maxRetries=3, retryTimeout=1000 configure(config: ChatDriverConfig): void abstract send(message: ChatMessage, options?: RenderOptions): Promise<ChatResult> // Protected helpers protected validateMessage(message: ChatMessage): boolean // throws if no `to` or no `content`/`template` protected async handleError(error: unknown, message: ChatMessage): Promise<ChatResult> protected async handleSuccess(message: ChatMessage, messageId?: string): Promise<ChatResult> } ``` - `handleError()` logs the error, then calls `message.onError()` if defined, returns `ChatResult` with `success: false` - `handleSuccess()` calls `message.handle()` first, then `message.onSuccess()` if defined, returns `ChatResult` with `success: true` - Both handlers merge any partial result from callbacks into the final `ChatResult` ## ChatMessage Interface (from @stacksjs/types) ```typescript interface ChatMessage { to: string | string[] // recipient (channel ID, user ID, etc.) from?: { id?: string, name?: string, avatar?: string } subject?: string // title for rich messages content?: string // plain text content template?: string // template for rich message rendering data?: Record<string, any> // template data attachments?: ChatAttachment[] onSuccess?: () => void | Promise<void> | Partial<ChatResult> onError?: (error: Error) => void | Promise<void> | Partial<ChatResult> handle?: () => void | Promise<void> | Partial<ChatResult> [key: string]: any // custom platform-specific fields } ``` ## ChatResult Interface (from @stacksjs/types) ```typescript interface ChatResult { success: boolean message: string provider: string // 'slack' | 'discord' | 'teams' messageId?: string data?: Record<string, any> } ``` ## Slack Driver ### SlackConfig ```typescript interface SlackConfig { webhookUrl?: string botToken?: string maxRetries?: number // default: 3 retryTimeout?: number // default: 1000ms } ``` ### SlackMessage (for webhook payloads) ```typescript interface SlackMessage { channel?: string text?: string blocks?: SlackBlock[] attachments?: SlackAttachment[] username?: string iconEmoji?: string iconUrl?: string threadTs?: string mrkdwn?: boolean } ``` ### SlackBlock ```typescript interface SlackBlock { type: 'section' | 'divider' | 'header' | 'context' | 'actions' | 'image' text?: { type: 'plain_text' | 'mrkdwn', text: string, emoji?: boolean } accessory?: any elements?: any[] block_id?: string } ``` ### SlackAttachment ```typescript interface SlackAttachment { color?: string pretext?: string author_name?: string author_link?: string author_icon?: string title?: string title_link?: string text?: string fields?: Array<{ title: string, value: string, short?: boolean }> image_url?: string thumb_url?: string footer?: string footer_icon?: string ts?: number } ``` ### Slack Sending Modes 1. **Webhook mode**: If `config.webhookUrl` is set, sends via `POST` to webhook URL. Payload includes `text`, `username`, `mrkdwn: true`. If `message.template` exists, builds Block Kit blocks. 2. **Bot token mode**: If `config.botToken` is set, sends via Slack API `https://slack.com/api/chat.postMessage` with `Authorization: Bearer <token>`. Returns `ts` (timestamp) as message ID. 3. Throws `Error('Slack not configured: provide webhookUrl or botToken')` if neither is set. ### Direct Webhook Function ```typescript async function sendWebhook(webhookUrl: string, text: string, options?: Partial<SlackMessage>): Promise<ChatResult> ``` Merges `options` into payload, sends POST to the provided webhook URL. ### Block Building When `message.template` is set, `buildBlocks()` creates: - A `header` block from `message.subject` (if present) - A `section` block from `message.content` with `mrkdwn` type ### Exports - `SlackDriver` class (also as `Driver`) - `driver` -- pre-instantiated `SlackDriver` singleton - `send()`, `sendWebhook()`, `configure()` functions ## Discord Driver ### DiscordConfig ```typescript interface DiscordConfig { webhookUrl?: string botToken?: string maxRetries?: number // default: 3 retryTimeout?: number // default: 1000ms } ``` ### DiscordEmbed ```typescript interface DiscordEmbed { title?: string description?: string url?: string color?: number // integer color value (e.g. 0x5865F2) timestamp?: string // ISO 8601 footer?: { text: string, icon_url?: string } image?: { url: string } thumbnail?: { url: string } author?: { name: string, url?: string, icon_url?: string } fields?: Array<{ name: string, value: string, inline?: boolean }> } ``` ### DiscordMessage ```typescript interface DiscordMessage { content?: string username?: string avatarUrl?: string tts?: boolean embeds?: DiscordEmbed[] allowedMentions?: { parse?: Array<'roles' | 'users' | 'everyone'> roles?: string[] users?: string[] } } ``` ### Discord Sending Modes 1. **Webhook mode**: POST to `config.webhookUrl`. Handles `204` (empty success) responses. Returns `id` from response. 2. **Bot token mode**: POST to `https://discord.com/api/v10/channels/{channelId}/messages` with `Authorization: Bot <token>`. Uses `message.to` as the channel ID. ### Embed Building When `message.subject` or `message.template` is set, `buildEmbed()` creates an embed with: - `title` from `message.subject` - `description` from `message.content` - Default color: `0x5865F2` (Discord blurple) - `timestamp`: current ISO date ### Extra Functions ```typescript async function sendWebhook(webhookUrl: string, content: string, options?: Partial<DiscordMessage>): Promise<ChatResult> async function sendEmbed(webhookUrl: string, embed: DiscordEmbed, options?: Partial<DiscordMessage>): Promise<ChatResult> ``` `sendEmbed()` wraps the embed inside `sendWebhook()` with empty content. ### Exports - `DiscordDriver` class (also as `Driver`) - `driver` -- pre-instantiated singleton - `send()`, `sendWebhook()`, `sendEmbed()`, `configure()` functions ## Teams Driver ### TeamsConfig ```typescript interface TeamsConfig { webhookUrl?: string maxRetries?: number // default: 3 retryTimeout?: number // default: 1000ms } ``` ### TeamsAdaptiveCard ```typescript interface TeamsAdaptiveCard { type: 'AdaptiveCard' version: string // '1.4' body: TeamsCardElement[] actions?: TeamsCardAction[] $schema?: string // 'http://adaptivecards.io/schemas/adaptive-card.json' } ``` ### TeamsCardElement ```typescript interface TeamsCardElement { type: 'TextBlock' | 'Image' | 'Container' | 'ColumnSet' | 'Column' | 'FactSet' | 'ImageSet' text?: string size?: 'Small' | 'Default' | 'Medium' | 'Large' | 'ExtraLarge' weight?: 'Lighter' | 'Default' | 'Bolder' color?: 'Default' | 'Dark' | 'Light' | 'Accent' | 'Good' | 'Warning' | 'Attention' wrap?: boolean url?: string altText?: string items?: TeamsCardElement[] columns?: TeamsCardElement[] width?: string facts?: Array<{ title: string, value: string }> images?: Array<{ type: 'Image', url: string, size?: string }> } ``` ### TeamsCardAction ```typescript interface TeamsCardAction { type: 'Action.OpenUrl' | 'Action.Submit' | 'Action.ShowCard' title: string url?: string data?: Record<string, any> card?: TeamsAdaptiveCard } ``` ### TeamsMessage ```typescript interface TeamsMessage { type?: 'message' summary?: string text?: string attachments?: Array<{ contentType: 'application/vnd.microsoft.card.adaptive' content: TeamsAdaptiveCard }> } ``` ### Teams Sending - Uses `config.webhookUrl` or falls back to `message.to` as the webhook URL - Validates the URL as a Teams Workflows (Power Automate) webhook on https: `*.environment.api.powerplatform.com`, `*.api.powerautomate.com`, `flow.microsoft.com` or `*.logic.azure.com` (`.us` / `.cn` too). An Office 365 connector URL (`*.webhook.office.com`) is refused with a migration message - Microsoft retired those connectors in May 2026 - Every message is an Adaptive Card in `attachments`, which the Workflows webhook template requires; a plain message is a card holding its text - Rich messages (with subject or template): the Adaptive Card v1.4 has: - Title as `TextBlock` (size: Large, weight: Bolder) - Content as `TextBlock` (wrap: true) - Timestamp as `TextBlock` (size: Small, color: Dark) ### Extra Functions ```typescript async function sendWebhook(webhookUrl: string, text: string): Promise<ChatResult> async function sendCard(webhookUrl: string, card: TeamsAdaptiveCard, summary?: string): Promise<ChatResult> ``` ### Exports - `TeamsDriver` class (also as `Driver`) - `driver` -- pre-instantiated singleton - `send()`, `sendWebhook()`, `sendCard()`, `configure()` functions ## Inbox drivers (reading and archiving) `send()` pushes messages out. The inbox drivers go the other way: they list a person's conversations, read messages, notice new ones (a per-conversation `cursor`), and archive a conversation *in the real app* as far as the provider allows. Source: `chat/src/inbox/`. One interface, `InboxDriver`: ```typescript
عرض على GitHub
ملف SKILL.md هذا كبير جدا، لذلك يعرض SkillsMP القسم الاول فقط هنا. عرض على GitHub