import { DEFAULT_ACCOUNT_ID, getChatChannelMeta, type ChannelPlugin, } from "../../../src/plugin-sdk/index.js"; // Import feishu functions directly from source import { getStartupChatIds, listFeishuAccountIds, resolveFeishuAccount, resolveDefaultFeishuAccountId, type ResolvedFeishuAccount, } from "../../../src/feishu/accounts.js"; import { sendMessageFeishu, type SendFeishuMessageParams } from "../../../src/feishu/send.js"; import { monitorFeishuProvider, type FeishuMessageContext } from "../../../src/feishu/monitor.js"; import { createFeishuClient } from "../../../src/feishu/client.js"; import { dispatchFeishuMessage } from "../../../src/feishu/message-dispatch.js"; const meta = getChatChannelMeta("feishu"); /** * Detect the appropriate receiveIdType from Feishu target ID prefix * - ou_xxx β†’ open_id (user) * - oc_xxx β†’ chat_id (group chat) * - on_xxx β†’ union_id (cross-app user id) */ function detectFeishuReceiveIdType( target: string, ): SendFeishuMessageParams["receiveIdType"] { const trimmed = target.trim().toLowerCase(); if (trimmed.startsWith("ou_")) return "open_id"; if (trimmed.startsWith("on_")) return "union_id"; // Default to chat_id for oc_ prefix or unknown formats return "chat_id"; } export const feishuPlugin: ChannelPlugin = { id: "feishu", meta: { ...meta, aliases: ["lark"], }, capabilities: { chatTypes: ["direct", "channel"], reactions: true, media: true, }, config: { listAccountIds: (cfg) => listFeishuAccountIds(cfg), resolveAccount: (cfg, accountId) => resolveFeishuAccount({ cfg, accountId }), defaultAccountId: (cfg) => resolveDefaultFeishuAccountId(cfg), isConfigured: (account) => account.credentials.source !== "none", describeAccount: (account) => ({ accountId: account.accountId, name: account.name, enabled: account.enabled, configured: account.credentials.source !== "none", }), }, status: { defaultRuntime: { accountId: DEFAULT_ACCOUNT_ID, running: false, lastStartAt: null, lastStopAt: null, lastError: null, }, buildChannelSummary: ({ snapshot }) => ({ configured: snapshot.configured ?? false, running: snapshot.running ?? false, lastStartAt: snapshot.lastStartAt ?? null, lastStopAt: snapshot.lastStopAt ?? null, lastError: snapshot.lastError ?? null, }), buildAccountSnapshot: ({ account, runtime }) => ({ accountId: account.accountId, name: account.name, enabled: account.enabled, configured: account.credentials.source !== "none", running: runtime?.running ?? false, lastStartAt: runtime?.lastStartAt ?? null, lastStopAt: runtime?.lastStopAt ?? null, lastError: runtime?.lastError ?? null, }), }, messaging: { targetResolver: { hint: "Use ou_xxx (open_id), oc_xxx (chat_id), or on_xxx (union_id)", // Recognize Feishu ID patterns: ou_ (open_id), oc_ (chat_id), on_ (union_id) looksLikeId: (raw: string) => { const trimmed = raw.trim(); return /^(ou_|oc_|on_)[a-z0-9]+$/i.test(trimmed); }, }, }, outbound: { deliveryMode: "direct", chunker: null, textChunkLimit: 4000, sendText: async ({ to, text, accountId, cfg }) => { // Detect receiveIdType from target prefix const receiveIdType = detectFeishuReceiveIdType(to); const result = await sendMessageFeishu({ to, text, accountId: accountId ?? undefined, config: cfg, receiveIdType, }); if (!result.success || !result.messageId) { throw new Error(result.error ?? "Failed to send Feishu message"); } return { channel: "feishu" as const, messageId: result.messageId }; }, sendMedia: async ({ to, text, accountId, cfg }) => { // Feishu media sending requires separate API - for now just send text const receiveIdType = detectFeishuReceiveIdType(to); const result = await sendMessageFeishu({ to, text, accountId: accountId ?? undefined, config: cfg, receiveIdType, }); if (!result.success || !result.messageId) { throw new Error(result.error ?? "Failed to send Feishu message"); } return { channel: "feishu" as const, messageId: result.messageId }; }, }, gateway: { startAccount: async (ctx) => { const account = ctx.account; ctx.log?.info(`[${account.accountId}] starting Feishu provider`); // Create client for startup message const client = createFeishuClient(account.credentials, { timeoutMs: (account.config.timeoutSeconds ?? 30) * 1000, }); // Message handler that dispatches to the agent system const onMessage = async (msgCtx: FeishuMessageContext) => { await dispatchFeishuMessage({ ctx: msgCtx, cfg: ctx.cfg, runtime: ctx.runtime, account, }); }; // Start the monitor with message handler const monitorPromise = monitorFeishuProvider({ accountId: account.accountId, config: ctx.cfg, runtime: ctx.runtime, abortSignal: ctx.abortSignal, onMessage, }); // Send startup message to all configured startup chat IDs const startupChatIds = getStartupChatIds(account.config); const timestamp = new Date().toLocaleString("zh-CN", { timeZone: "Asia/Shanghai" }); for (const chatId of startupChatIds) { try { await client.sendTextMessage( chatId, `πŸš€ Clawdbot ι£žδΉ¦η½‘ε…³ε·²ε―εŠ¨ (${timestamp})`, "chat_id", ); ctx.log?.info(`[${account.accountId}] sent startup message to ${chatId}`); } catch (err) { ctx.log?.warn(`[${account.accountId}] failed to send startup message to ${chatId}: ${err}`); } } return monitorPromise; }, }, };