/** Per-account override block. Same shape as the top-level DingTalkConfig * but every field is optional — operators only override what differs * from the global defaults. Each entry gets its own DWClient instance. */ export interface DingTalkAccountConfig { /** Human-readable name (optional). Shown in CLI / Web listings. */ name?: string; /** Account-specific credentials. Required when defined. */ clientId?: string; clientSecret?: string; /** Per-account access-policy overrides (fall back to global if absent). */ dmPolicy?: 'open' | 'pairing' | 'allowlist'; groupPolicy?: 'open' | 'allowlist' | 'disabled'; allowFrom?: string[]; groupAllowFrom?: string[]; ackReaction?: 'off' | 'emoji' | 'kaomoji'; /** Disable this account without removing the entry. */ enabled?: boolean; } export interface DingTalkConfig { /** AppKey from the DingTalk developer console (also serves as robotCode). */ clientId: string; /** AppSecret — used by the SDK to obtain access tokens automatically. */ clientSecret: string; /** Optional channel id (forwarded to MessageContext); we don't drive * routing on it but a few downstream consumers tag logs by it. */ channelId?: string; /** Batch 4 (#3) — multi-account / multi-bot. When this map is present * the adapter starts one DWClient per entry, in addition to (or * instead of) the top-level single-account credentials. Existing * single-account deployments are unaffected because top-level fields * remain the canonical default account ("default" key reserved). */ accounts?: Record; /** Batch 1 (#4) — DM access policy. Defaults to 'open' (current behavior). * - `open` : any user can DM the bot (existing default) * - `pairing` : only users seen in earlier `allowFrom`-acked messages * - `allowlist` : only senderStaffId in `allowFrom` may DM the bot */ dmPolicy?: 'open' | 'pairing' | 'allowlist'; /** Batch 1 (#4) — Group access policy. Defaults to 'open'. * - `open` : any group @-mention is accepted * - `allowlist` : only sender in `groupAllowFrom` is accepted * - `disabled` : no group chat handling at all */ groupPolicy?: 'open' | 'allowlist' | 'disabled'; /** senderStaffId allowed when dmPolicy = allowlist (or seeded for pairing). */ allowFrom?: string[]; /** senderStaffId allowed when groupPolicy = allowlist. */ groupAllowFrom?: string[]; /** Batch 1 (#7) — ack-reaction mode: render a quick "received / working / done" * emoji marker right after inbound. Defaults to 'off' (no change to today's * behavior). `emoji` uses ⏳ → 🤖 → ✅; `kaomoji` uses pure-text faces for * fonts that don't render emoji (some legacy corp clients). */ ackReaction?: 'off' | 'emoji' | 'kaomoji'; } /** Inbound message payload from the Stream SDK's TOPIC_ROBOT callback. * Empirically observed msgtype values (community docs say `image`/`voice` * but DingTalk actually wire-formats them as `picture`/`audio`): * 'text' | 'picture' | 'audio' | 'file' | 'link' | 'markdown' | 'action_card' * The official Node SDK only types `text`; we keep msgtype as a loose * string and snapshot known shapes for the rest. Unknown shapes fall * through to a debug-logging path that dumps the full payload — that's * how we discovered the `picture`/`audio` naming + the `content` block * shape (community docs document those fields under `msg.picture` etc., * but real deliveries put them inside a single `content` object). */ export interface DingTalkInboundMessage { /** Unique message id for dedup. */ msgId: string; /** One of the empirically-confirmed msgtype values, or something else. */ msgtype: string; /** Present on msgtype='text'. */ text?: { content: string; }; /** Present on msgtype='link'. picUrl is the thumbnail; messageUrl is * the deep link / URL the user actually shared. Title/text are * display preview. */ link?: { title?: string; text?: string; picUrl?: string; messageUrl?: string; }; /** Catch-all `content` block — used by every non-text/non-link msgtype. * Different msgtype values use different subsets of the fields below: * picture: { pictureDownloadCode (full), downloadCode (thumb) } * audio: { downloadCode, recognition (DingTalk's server-side ASR!) } * file: { downloadCode, fileName } (best-effort from docs) * markdown / action_card: { text, title } (best-effort) */ content?: { pictureDownloadCode?: string; downloadCode?: string; /** DingTalk's built-in voice-to-text for `audio` messages. Prefer * this over running whisper.cpp locally — it's typically more * accurate for Mandarin and zero-cost on our side. */ recognition?: string; title?: string; text?: string; fileName?: string; duration?: string | number; }; /** Conversation id in OpenAPI's `openConversationId` shape — usable * directly when calling /v1.0/robot/groupMessages/send. */ conversationId: string; /** '1' = single chat (1:1), '2' = group chat. */ conversationType: '1' | '2' | string; /** Internal staff id of the message sender (use in 1:1 send userIds). */ senderStaffId: string; senderNick?: string; senderCorpId?: string; /** Bot's own ids (chatbotUserId = robot's user id inside the corp). */ chatbotUserId?: string; chatbotCorpId?: string; /** The bot's robotCode — same value as the configured AppKey/clientId * for stream-mode internal apps, but we read it from the message so * we don't have to hard-code the relationship. */ robotCode: string; /** Temporary webhook URL the bot can POST to without an access token, * valid until `sessionWebhookExpiredTime` (ms epoch). 30s in practice. */ sessionWebhook?: string; sessionWebhookExpiredTime?: number; createAt?: number; isAdmin?: boolean; /** Batch 3 (#8) — when the inbound is a "reply to ..." quote, the * Stream SDK may populate these fields. Names follow the older * OpenAPI shapes seen in the wild; we read them defensively. */ originalMessageId?: string; originalContent?: { text?: string; }; /** Catch-all for unknown fields — we never index this in code, but * preserving it lets the debug-log path dump the full payload. */ [extra: string]: unknown; } //# sourceMappingURL=types.d.ts.map