@nextclaw/channel-extension-feishu 0.1.5 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/main.js +2 -0
- package/dist/main.js.map +1 -0
- package/dist/services/feishu-auth-capability.service.d.ts +2 -1
- package/dist/services/feishu-auth-capability.service.d.ts.map +1 -0
- package/dist/services/feishu-auth-capability.service.js +2 -0
- package/dist/services/feishu-auth-capability.service.js.map +1 -0
- package/dist/services/feishu-channel-adapter.service.d.ts +2 -1
- package/dist/services/feishu-channel-adapter.service.d.ts.map +1 -0
- package/dist/services/feishu-channel-adapter.service.js +2 -0
- package/dist/services/feishu-channel-adapter.service.js.map +1 -0
- package/dist/services/feishu-registration.service.d.ts +2 -1
- package/dist/services/feishu-registration.service.d.ts.map +1 -0
- package/dist/services/feishu-registration.service.js +2 -0
- package/dist/services/feishu-registration.service.js.map +1 -0
- package/dist/services/feishu-reply-chat.service.js +2 -0
- package/dist/services/feishu-reply-chat.service.js.map +1 -0
- package/dist/services/feishu-sdk.service.d.ts +2 -1
- package/dist/services/feishu-sdk.service.d.ts.map +1 -0
- package/dist/services/feishu-sdk.service.js +2 -0
- package/dist/services/feishu-sdk.service.js.map +1 -0
- package/dist/stores/feishu-account.store.d.ts +2 -1
- package/dist/stores/feishu-account.store.d.ts.map +1 -0
- package/dist/stores/feishu-account.store.js +2 -0
- package/dist/stores/feishu-account.store.js.map +1 -0
- package/dist/types/feishu-extension.types.d.ts +2 -1
- package/dist/types/feishu-extension.types.d.ts.map +1 -0
- package/dist/utils/feishu-config.utils.js +2 -0
- package/dist/utils/feishu-config.utils.js.map +1 -0
- package/dist/utils/feishu-message.utils.js +2 -0
- package/dist/utils/feishu-message.utils.js.map +1 -0
- package/dist/utils/feishu-session-route.utils.js +2 -0
- package/dist/utils/feishu-session-route.utils.js.map +1 -0
- package/dist/utils/feishu-submitted-message.utils.js +2 -0
- package/dist/utils/feishu-submitted-message.utils.js.map +1 -0
- package/package.json +5 -5
package/dist/main.js
CHANGED
package/dist/main.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"main.js","names":[],"sources":["../src/main.ts"],"sourcesContent":["import { startChannelExtension, warnNcpEventError } from \"@nextclaw/extension-sdk\";\nimport { FeishuAuthCapability } from \"./services/feishu-auth-capability.service.js\";\nimport { FeishuChannelAdapter } from \"./services/feishu-channel-adapter.service.js\";\nimport { toFeishuSubmittedMessage } from \"./utils/feishu-submitted-message.utils.js\";\n\nawait startChannelExtension({\n channelId: \"feishu\",\n createAdapter: () => new FeishuChannelAdapter(),\n mapInbound: toFeishuSubmittedMessage,\n createAuthCapability: ({ channel }) => new FeishuAuthCapability({ channel }),\n onNcpEventError: warnNcpEventError(\"feishu\"),\n});\n"],"mappings":";;;;;AAKA,MAAM,sBAAsB;CAC1B,WAAW;CACX,qBAAqB,IAAI,sBAAsB;CAC/C,YAAY;CACZ,uBAAuB,EAAE,cAAc,IAAI,qBAAqB,EAAE,SAAS,CAAC;CAC5E,iBAAiB,kBAAkB,SAAS;CAC7C,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"feishu-auth-capability.service.d.ts","names":[],"sources":["../../src/services/feishu-auth-capability.service.ts"],"mappings":";;;;KAOK,wBAAA;EACH,OAAA,EAAS,gBAAA;EACT,mBAAA,GAAsB,IAAA,CAAK,yBAAA;AAAA;AAAA,cAGhB,oBAAA;EAAA,iBACM,OAAA;EAAA,iBACA,mBAAA;cAEL,IAAA,EAAM,wBAAA;EAAA,SAKT,KAAA,GACP,OAAA,EAAS,0BAAA,KACR,OAAA;EAAA,SAQM,IAAA,GACP,OAAA,EAAS,0BAAA,KACR,OAAA;EAAA,iBAQc,iBAAA;EAAA,iBAOA,UAAA;EAAA,iBAGA,UAAA;AAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"feishu-auth-capability.service.js","names":[],"sources":["../../src/services/feishu-auth-capability.service.ts"],"sourcesContent":["import type {\n ExtensionChannel,\n ExtensionCapabilityPayload,\n} from \"@nextclaw/extension-sdk\";\nimport { FeishuRegistrationService } from \"./feishu-registration.service.js\";\nimport type { FeishuDomain } from \"../types/feishu-extension.types.js\";\n\ntype FeishuAuthCapabilityDeps = {\n channel: ExtensionChannel;\n registrationService?: Pick<FeishuRegistrationService, \"start\" | \"poll\">;\n};\n\nexport class FeishuAuthCapability {\n private readonly channel: ExtensionChannel;\n private readonly registrationService: Pick<FeishuRegistrationService, \"start\" | \"poll\">;\n\n constructor(deps: FeishuAuthCapabilityDeps) {\n this.channel = deps.channel;\n this.registrationService = deps.registrationService ?? new FeishuRegistrationService();\n }\n\n readonly start = async (\n request: ExtensionCapabilityPayload,\n ): Promise<unknown> =>\n await this.registrationService.start({\n pluginConfig: await this.readCurrentConfig(),\n requestedAccountId: this.readString(request.accountId),\n domain: this.readDomain(request.domain),\n verbose: request.verbose === true,\n });\n\n readonly poll = async (\n request: ExtensionCapabilityPayload,\n ): Promise<unknown> => {\n const sessionId = this.readString(request.sessionId);\n if (!sessionId) {\n throw new Error(\"sessionId is required\");\n }\n return await this.registrationService.poll({ sessionId });\n };\n\n private readonly readCurrentConfig = async (): Promise<Record<string, unknown>> => {\n const config = await this.channel.config.get();\n return config && typeof config === \"object\" && !Array.isArray(config)\n ? config as Record<string, unknown>\n : {};\n };\n\n private readonly readString = (value: unknown): string | null =>\n typeof value === \"string\" && value.trim() ? value.trim() : null;\n\n private readonly readDomain = (value: unknown): FeishuDomain | null => {\n const text = this.readString(value);\n return text === \"feishu\" || text === \"lark\" ? text : null;\n };\n}\n"],"mappings":";;AAYA,IAAa,uBAAb,MAAkC;CAChC;CACA;CAEA,YAAY,MAAgC;AAC1C,OAAK,UAAU,KAAK;AACpB,OAAK,sBAAsB,KAAK,uBAAuB,IAAI,2BAA2B;;CAGxF,QAAiB,OACf,YAEA,MAAM,KAAK,oBAAoB,MAAM;EACnC,cAAc,MAAM,KAAK,mBAAmB;EAC5C,oBAAoB,KAAK,WAAW,QAAQ,UAAU;EACtD,QAAQ,KAAK,WAAW,QAAQ,OAAO;EACvC,SAAS,QAAQ,YAAY;EAC9B,CAAC;CAEJ,OAAgB,OACd,YACqB;EACrB,MAAM,YAAY,KAAK,WAAW,QAAQ,UAAU;AACpD,MAAI,CAAC,UACH,OAAM,IAAI,MAAM,wBAAwB;AAE1C,SAAO,MAAM,KAAK,oBAAoB,KAAK,EAAE,WAAW,CAAC;;CAG3D,oBAAqC,YAA8C;EACjF,MAAM,SAAS,MAAM,KAAK,QAAQ,OAAO,KAAK;AAC9C,SAAO,UAAU,OAAO,WAAW,YAAY,CAAC,MAAM,QAAQ,OAAO,GACjE,SACA,EAAE;;CAGR,cAA+B,UAC7B,OAAO,UAAU,YAAY,MAAM,MAAM,GAAG,MAAM,MAAM,GAAG;CAE7D,cAA+B,UAAwC;EACrE,MAAM,OAAO,KAAK,WAAW,MAAM;AACnC,SAAO,SAAS,YAAY,SAAS,SAAS,OAAO"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"feishu-channel-adapter.service.d.ts","names":[],"sources":["../../src/services/feishu-channel-adapter.service.ts"],"mappings":";;;;;;KAgDK,iBAAA;EACH,GAAA,GAAM,gBAAA;EACN,KAAA,GAAQ,kBAAA;EACR,MAAA,GAAS,IAAA,QAAY,OAAA;AAAA;AAAA,cA0BV,oBAAA,YAAgC,4BAAA;EAAA,QACnC,cAAA;EAAA,iBACS,GAAA;EAAA,iBACA,KAAA;EAAA,iBACA,MAAA;EAAA,iBACA,aAAA;EAAA,iBACA,SAAA;EAAA,iBACA,aAAA;EAAA,iBACA,eAAA;EAAA,iBACA,mBAAA;EAAA,QACT,OAAA;EAAA,QACA,MAAA;cAEI,IAAA,GAAM,iBAAA;EAAA,SAYT,SAAA,GAAmB,MAAA,EAAQ,mBAAA,KAAsB,OAAA;EAAA,SASjD,KAAA,QAAkB,OAAA;EAAA,SAalB,IAAA,QAAiB,OAAA;EAAA,SAiBjB,SAAA,GACP,OAAA,GAAU,OAAA,EAAS,oBAAA,YAAgC,OAAA;EAAA,SAU5C,YAAA,GAAsB,KAAA,EAAO,gBAAA,KAAmB,OAAA;EAAA,SAuBhD,kBAAA,GAA4B,OAAA,EAAS,oBAAA,KAAuB,OAAA;EAAA,iBAIpD,YAAA;EAAA,iBAaA,uBAAA;EAAA,iBAQA,qBAAA;EAAA,iBAuBA,wBAAA;EAAA,iBAcA,kBAAA;EAAA,iBAQA,mBAAA;EAAA,iBAqBA,qBAAA;EAAA,iBAaA,kBAAA;EAAA,iBAsCA,uBAAA;EAAA,iBA2BA,0BAAA;EAAA,iBAoBA,wBAAA;EAAA,iBAoBA,iBAAA;EAAA,iBAiBA,QAAA;EAAA,SAYR,gBAAA,GAA0B,MAAA;IACjC,EAAA;IACA,IAAA;IACA,SAAA;EAAA,MACE,OAAA;AAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"feishu-channel-adapter.service.js","names":[],"sources":["../../src/services/feishu-channel-adapter.service.ts"],"sourcesContent":["import type * as Lark from \"@larksuiteoapi/node-sdk\";\nimport { NcpEventType, type NcpEndpointEvent } from \"@nextclaw/ncp\";\nimport { NcpReplyConsumer, type ChatTarget } from \"@nextclaw/ncp-toolkit\";\nimport {\n FileFeishuAccountStore,\n type FeishuAccountStore,\n} from \"../stores/feishu-account.store.js\";\nimport {\n FeishuReplyChat,\n NcpEventQueue,\n TERMINAL_NCP_EVENT_TYPES,\n type FeishuReplySession,\n} from \"./feishu-reply-chat.service.js\";\nimport { FeishuSdkService } from \"./feishu-sdk.service.js\";\nimport {\n isFeishuBotMentioned,\n isFeishuGroupChat,\n parseFeishuInboundMessage,\n} from \"../utils/feishu-message.utils.js\";\nimport {\n readFeishuEventSessionId,\n resolveFeishuSessionRoute,\n} from \"../utils/feishu-session-route.utils.js\";\nimport type {\n FeishuChannelAdapterContract,\n FeishuChannelConfig,\n FeishuInboundMessage,\n FeishuRuntimeAccount,\n} from \"../types/feishu-extension.types.js\";\n\ntype FeishuCanonicalRoute = {\n conversationId: string;\n accountId?: string;\n};\n\ntype FeishuProcessingReactionState = {\n accountId: string;\n messageId: string;\n reactionId: string | null;\n};\n\nconst FEISHU_PROCESSING_REACTION = \"Typing\";\nconst FEISHU_FAILED_REACTION = \"CrossMark\";\nconst FAILED_NCP_EVENT_TYPES = new Set<NcpEndpointEvent[\"type\"]>([\n NcpEventType.MessageFailed,\n NcpEventType.RunError,\n]);\n\ntype FeishuAdapterDeps = {\n sdk?: FeishuSdkService;\n store?: FeishuAccountStore;\n logger?: Pick<typeof console, \"warn\" | \"log\">;\n};\n\ntype FeishuWsHandle = {\n start: (params: { eventDispatcher: Lark.EventDispatcher }) => void;\n close?: () => void;\n stop?: () => void;\n};\n\nfunction readStringArray(value: unknown): string[] {\n if (!Array.isArray(value)) {\n return [];\n }\n return value\n .map((entry) => (typeof entry === \"string\" ? entry.trim() : \"\"))\n .filter(Boolean);\n}\n\nfunction normalizeRouteKey(conversationId: string): string {\n return conversationId.toLowerCase();\n}\n\nfunction isAllowedSender(allowFrom: string[], senderId: string): boolean {\n return allowFrom.length === 0 || allowFrom.includes(senderId);\n}\n\nexport class FeishuChannelAdapter implements FeishuChannelAdapterContract {\n private messageHandler: ((message: FeishuInboundMessage) => void | Promise<void>) | null = null;\n private readonly sdk: FeishuSdkService;\n private readonly store: FeishuAccountStore;\n private readonly logger: Pick<typeof console, \"warn\" | \"log\">;\n private readonly replyConsumer: NcpReplyConsumer;\n private readonly wsClients = new Map<string, FeishuWsHandle>();\n private readonly replySessions = new Map<string, FeishuReplySession>();\n private readonly canonicalRoutes = new Map<string, FeishuCanonicalRoute>();\n private readonly processingReactions = new Map<string, FeishuProcessingReactionState>();\n private running = false;\n private config: FeishuChannelConfig = {};\n\n constructor(deps: FeishuAdapterDeps = {}) {\n this.sdk = deps.sdk ?? new FeishuSdkService();\n this.store = deps.store ?? new FileFeishuAccountStore();\n this.logger = deps.logger ?? console;\n this.replyConsumer = new NcpReplyConsumer(\n new FeishuReplyChat({\n resolveAccount: this.resolveSendAccount,\n sendText: this.sendText,\n }),\n );\n }\n\n readonly configure = async (config: FeishuChannelConfig): Promise<void> => {\n this.config = config;\n if (!this.running) {\n return;\n }\n await this.stop();\n await this.start();\n };\n\n readonly start = async (): Promise<void> => {\n if (this.running) {\n return;\n }\n this.running = true;\n for (const accountId of this.listAvailableAccountIds()) {\n const account = this.resolveRuntimeAccount(accountId);\n if (account?.enabled) {\n this.startAccount(account);\n }\n }\n };\n\n readonly stop = async (): Promise<void> => {\n if (!this.running) {\n return;\n }\n this.running = false;\n for (const client of this.wsClients.values()) {\n client.close?.();\n client.stop?.();\n }\n this.wsClients.clear();\n for (const session of this.replySessions.values()) {\n session.queue.close();\n }\n this.replySessions.clear();\n this.processingReactions.clear();\n };\n\n readonly onMessage = (\n handler: (message: FeishuInboundMessage) => void | Promise<void>,\n ): (() => void) => {\n this.messageHandler = handler;\n return () => {\n if (this.messageHandler === handler) {\n this.messageHandler = null;\n }\n };\n };\n\n readonly sendNcpEvent = async (event: NcpEndpointEvent): Promise<void> => {\n if (!this.running) {\n return;\n }\n const route = resolveFeishuSessionRoute(event);\n if (!route) {\n return;\n }\n const sessionId = readFeishuEventSessionId(event);\n if (!sessionId) {\n return;\n }\n const canonicalRoute = this.resolveCanonicalRoute(route);\n const session = this.resolveReplySession(sessionId, canonicalRoute);\n session.queue.push(event);\n if (TERMINAL_NCP_EVENT_TYPES.has(event.type)) {\n session.queue.close();\n this.replySessions.delete(sessionId);\n await session.consuming;\n await this.finalizeProcessingReaction(canonicalRoute, FAILED_NCP_EVENT_TYPES.has(event.type));\n }\n };\n\n readonly emitMessageForTest = async (message: FeishuInboundMessage): Promise<void> => {\n await this.messageHandler?.(message);\n };\n\n private readonly startAccount = (account: FeishuRuntimeAccount): void => {\n const dispatcher = this.sdk.createEventDispatcher();\n dispatcher.register({\n \"im.message.receive_v1\": async (data: unknown) => {\n await this.handleInboundEvent(account, data);\n },\n });\n const wsClient = this.sdk.createWsClient(account) as unknown as FeishuWsHandle;\n wsClient.start({ eventDispatcher: dispatcher });\n this.wsClients.set(account.accountId, wsClient);\n this.logger.log?.(`[feishu] websocket started for ${account.accountId}`);\n };\n\n private readonly listAvailableAccountIds = (): string[] => {\n return Array.from(new Set([\n ...(this.config.defaultAccountId ? [this.config.defaultAccountId] : []),\n ...Object.keys(this.config.accounts ?? {}),\n ...this.store.listAccountIds(),\n ]));\n };\n\n private readonly resolveRuntimeAccount = (accountId: string): FeishuRuntimeAccount | null => {\n const stored = this.store.loadAccount(accountId);\n if (!stored?.appId || !stored.appSecret) {\n return null;\n }\n const accountConfig = this.config.accounts?.[accountId] ?? {};\n return {\n accountId,\n appId: stored.appId,\n appSecret: stored.appSecret,\n domain: accountConfig.domain ?? stored.domain ?? this.config.domain ?? \"feishu\",\n enabled: accountConfig.enabled !== false && this.config.enabled !== false,\n name: accountConfig.name ?? stored.botName,\n botOpenId: stored.botOpenId,\n allowFrom: Array.from(new Set([\n ...readStringArray(this.config.allowFrom),\n ...readStringArray(accountConfig.allowFrom),\n ])),\n groupPolicy: accountConfig.groupPolicy ?? this.config.groupPolicy ?? \"open\",\n requireMention: accountConfig.requireMention ?? this.config.requireMention ?? true,\n };\n };\n\n private readonly resolveSelectedAccountId = (requestedAccountId?: string): string => {\n if (requestedAccountId) {\n return requestedAccountId;\n }\n if (this.config.defaultAccountId) {\n return this.config.defaultAccountId;\n }\n const accountIds = this.listAvailableAccountIds();\n if (accountIds.length === 1 && accountIds[0]) {\n return accountIds[0];\n }\n throw new Error(\"feishu send failed: accountId is required when multiple accounts are configured\");\n };\n\n private readonly resolveSendAccount = (target: ChatTarget): FeishuRuntimeAccount => {\n const account = this.resolveRuntimeAccount(this.resolveSelectedAccountId(target.accountId));\n if (!account?.enabled) {\n throw new Error(`feishu send failed: account \"${target.accountId ?? this.config.defaultAccountId ?? \"\"}\" is not connected`);\n }\n return account;\n };\n\n private readonly resolveReplySession = (\n sessionId: string,\n route: FeishuCanonicalRoute,\n ): FeishuReplySession => {\n const existing = this.replySessions.get(sessionId);\n if (existing) {\n return existing;\n }\n const queue = new NcpEventQueue();\n const consuming = this.replyConsumer.consume({\n target: {\n conversationId: route.conversationId,\n ...(route.accountId ? { accountId: route.accountId } : {}),\n },\n eventStream: queue,\n });\n const session = { queue, consuming };\n this.replySessions.set(sessionId, session);\n return session;\n };\n\n private readonly resolveCanonicalRoute = (\n route: FeishuCanonicalRoute,\n ): FeishuCanonicalRoute => {\n const remembered = this.canonicalRoutes.get(normalizeRouteKey(route.conversationId));\n if (remembered) {\n return {\n ...remembered,\n ...(route.accountId ? { accountId: route.accountId } : {}),\n };\n }\n return route;\n };\n\n private readonly handleInboundEvent = async (\n account: FeishuRuntimeAccount,\n rawEvent: unknown,\n ): Promise<void> => {\n const parsed = parseFeishuInboundMessage(rawEvent);\n if (!parsed || !isAllowedSender(account.allowFrom, parsed.senderOpenId)) {\n return;\n }\n if (isFeishuGroupChat(parsed.chatType)) {\n if (account.groupPolicy === \"disabled\") {\n return;\n }\n if (account.groupPolicy === \"allowlist\" && !account.allowFrom.includes(parsed.chatId)) {\n return;\n }\n if (account.requireMention && !isFeishuBotMentioned({\n botOpenId: account.botOpenId,\n mentionedOpenIds: parsed.mentionedOpenIds,\n })) {\n return;\n }\n }\n this.canonicalRoutes.set(normalizeRouteKey(parsed.chatId), {\n accountId: account.accountId,\n conversationId: parsed.chatId,\n });\n await this.startProcessingReaction(account, parsed.messageId, parsed.chatId);\n await this.messageHandler?.({\n conversationId: parsed.chatId,\n senderId: parsed.senderOpenId,\n text: parsed.text,\n accountId: account.accountId,\n peerKind: isFeishuGroupChat(parsed.chatType) ? \"group\" : \"direct\",\n messageId: parsed.messageId,\n raw: rawEvent,\n });\n };\n\n private readonly startProcessingReaction = async (\n account: FeishuRuntimeAccount,\n messageId: string | undefined,\n conversationId: string,\n ): Promise<void> => {\n if (!messageId) {\n return;\n }\n const routeKey = normalizeRouteKey(conversationId);\n try {\n const reactionId = await this.sdk.addReaction({\n account,\n messageId,\n emojiType: FEISHU_PROCESSING_REACTION,\n });\n this.processingReactions.set(routeKey, {\n accountId: account.accountId,\n messageId,\n reactionId,\n });\n } catch (error) {\n this.logger.warn?.(\n `[feishu] failed to add processing reaction for ${messageId}: ${String(error)}`,\n );\n }\n };\n\n private readonly finalizeProcessingReaction = async (\n route: FeishuCanonicalRoute,\n failed: boolean,\n ): Promise<void> => {\n const routeKey = normalizeRouteKey(route.conversationId);\n const state = this.processingReactions.get(routeKey);\n if (!state) {\n return;\n }\n this.processingReactions.delete(routeKey);\n const account = this.resolveRuntimeAccount(state.accountId);\n if (!account?.enabled) {\n return;\n }\n await this.deleteProcessingReaction(account, state);\n if (failed) {\n await this.addFailedReaction(account, state.messageId);\n }\n };\n\n private readonly deleteProcessingReaction = async (\n account: FeishuRuntimeAccount,\n state: FeishuProcessingReactionState,\n ): Promise<void> => {\n if (!state.reactionId) {\n return;\n }\n try {\n await this.sdk.deleteReaction({\n account,\n messageId: state.messageId,\n reactionId: state.reactionId,\n });\n } catch (error) {\n this.logger.warn?.(\n `[feishu] failed to remove processing reaction for ${state.messageId}: ${String(error)}`,\n );\n }\n };\n\n private readonly addFailedReaction = async (\n account: FeishuRuntimeAccount,\n messageId: string,\n ): Promise<void> => {\n try {\n await this.sdk.addReaction({\n account,\n messageId,\n emojiType: FEISHU_FAILED_REACTION,\n });\n } catch (error) {\n this.logger.warn?.(\n `[feishu] failed to add failure reaction for ${messageId}: ${String(error)}`,\n );\n }\n };\n\n private readonly sendText = async (params: {\n account: FeishuRuntimeAccount;\n conversationId: string;\n text: string;\n }): Promise<void> => {\n await this.sdk.sendText({\n account: params.account,\n chatId: params.conversationId,\n text: params.text,\n });\n };\n\n readonly sendOutboundText = async (params: {\n to: string;\n text: string;\n accountId?: string | null;\n }): Promise<void> => {\n const { accountId, text, to } = params;\n const account = this.resolveSendAccount({\n conversationId: to,\n ...(accountId ? { accountId } : {}),\n });\n await this.sendText({\n account,\n conversationId: to,\n text,\n });\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"feishu-registration.service.d.ts","names":[],"sources":["../../src/services/feishu-registration.service.ts"],"mappings":";;;;KAaY,6BAAA;EACV,YAAA,GAAe,MAAA;EACf,kBAAA;EACA,MAAA,GAAS,YAAA;EACT,OAAA;AAAA;AAAA,KAGU,qBAAA;EACV,OAAA;EACA,IAAA;EACA,SAAA;EACA,MAAA;EACA,SAAA;EACA,SAAA;EACA,UAAA;EACA,IAAA;AAAA;AAAA,KAGU,oBAAA;EACV,OAAA;EACA,MAAA;EACA,OAAA;EACA,UAAA;EACA,SAAA;EACA,KAAA;EACA,YAAA,GAAe,MAAA;AAAA;AAAA,KAcZ,6BAAA;EACH,KAAA,GAAQ,kBAAA;EACR,SAAA,UAAmB,KAAA;AAAA;AAAA,cA8BR,yBAAA;EAAA,iBACM,KAAA;EAAA,iBACA,SAAA;EAAA,iBACA,QAAA;cAEL,IAAA,GAAM,6BAAA;EAAA,SAKT,KAAA,GACP,MAAA,EAAQ,6BAAA,KACP,OAAA,CAAQ,qBAAA;EAAA,SA6BF,IAAA;IAAc;EAAA;IAAiB,SAAA;EAAA,MAAsB,OAAA,CAAQ,oBAAA;EAAA,iBAsErD,2BAAA;EAAA,iBAUA,iBAAA;EAAA,iBA2BA,gBAAA;EAAA,iBAgBA,mBAAA;EAAA,iBA6CA,QAAA;EAAA,iBAiCA,UAAA;EAAA,iBAOA,sBAAA;AAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"feishu-registration.service.js","names":[],"sources":["../../src/services/feishu-registration.service.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport {\n buildRegisteredFeishuChannelConfig,\n DEFAULT_FEISHU_DOMAIN,\n FEISHU_CHANNEL_ID,\n normalizeFeishuChannelConfig,\n} from \"../utils/feishu-config.utils.js\";\nimport {\n FileFeishuAccountStore,\n type FeishuAccountStore,\n} from \"../stores/feishu-account.store.js\";\nimport type { FeishuChannelConfig, FeishuDomain } from \"../types/feishu-extension.types.js\";\n\nexport type FeishuRegistrationStartParams = {\n pluginConfig?: Record<string, unknown>;\n requestedAccountId?: string | null;\n domain?: FeishuDomain | null;\n verbose?: boolean;\n};\n\nexport type FeishuAuthStartResult = {\n channel: string;\n kind: \"qr_code\";\n sessionId: string;\n qrCode: string;\n qrCodeUrl: string;\n expiresAt: string;\n intervalMs: number;\n note?: string;\n};\n\nexport type FeishuAuthPollResult = {\n channel: string;\n status: \"pending\" | \"scanned\" | \"authorized\" | \"expired\" | \"error\";\n message?: string;\n nextPollMs?: number;\n accountId?: string | null;\n notes?: string[];\n pluginConfig?: Record<string, unknown>;\n};\n\ntype FeishuRegistrationSession = {\n currentConfig: FeishuChannelConfig;\n requestedAccountId?: string | null;\n deviceCode: string;\n domain: FeishuDomain;\n intervalMs: number;\n expiresAtMs: number;\n};\n\ntype FeishuRegistrationResponse = Record<string, unknown>;\n\ntype FeishuRegistrationServiceDeps = {\n store?: FeishuAccountStore;\n fetchImpl?: typeof fetch;\n};\n\nconst REGISTRATION_PATH = \"/oauth/v1/app/registration\";\nconst FEISHU_ACCOUNTS_BASE_URLS: Record<FeishuDomain, string> = {\n feishu: \"https://accounts.feishu.cn\",\n lark: \"https://accounts.larksuite.com\",\n};\nconst FEISHU_OPEN_BASE_URLS: Record<FeishuDomain, string> = {\n feishu: \"https://open.feishu.cn\",\n lark: \"https://open.larksuite.com\",\n};\nconst FEISHU_AUTH_POLL_INTERVAL_MS = 5_000;\nconst FEISHU_REGISTRATION_TIMEOUT_MS = 10 * 60_000;\n\nfunction readString(value: unknown): string | undefined {\n return typeof value === \"string\" && value.trim() ? value.trim() : undefined;\n}\n\nfunction readDomain(value: unknown): FeishuDomain | undefined {\n return value === \"feishu\" || value === \"lark\" ? value : undefined;\n}\n\nfunction appendHermesQrHints(url: string): string {\n if (!url.trim()) {\n return url;\n }\n return `${url}${url.includes(\"?\") ? \"&\" : \"?\"}from=nextclaw&tp=nextclaw`;\n}\n\nexport class FeishuRegistrationService {\n private readonly store: FeishuAccountStore;\n private readonly fetchImpl: typeof fetch;\n private readonly sessions = new Map<string, FeishuRegistrationSession>();\n\n constructor(deps: FeishuRegistrationServiceDeps = {}) {\n this.store = deps.store ?? new FileFeishuAccountStore();\n this.fetchImpl = deps.fetchImpl ?? fetch;\n }\n\n readonly start = async (\n params: FeishuRegistrationStartParams,\n ): Promise<FeishuAuthStartResult> => {\n this.cleanupExpiredSessions();\n const currentConfig = normalizeFeishuChannelConfig(params.pluginConfig);\n const domain = params.domain ?? currentConfig.domain ?? DEFAULT_FEISHU_DOMAIN;\n await this.assertRegistrationSupported(domain);\n const begin = await this.beginRegistration(domain);\n const sessionId = randomUUID();\n const expiresAtMs = Date.now() + Math.min(begin.expiresInMs, FEISHU_REGISTRATION_TIMEOUT_MS);\n this.sessions.set(sessionId, {\n currentConfig,\n requestedAccountId: params.requestedAccountId,\n deviceCode: begin.deviceCode,\n domain,\n intervalMs: begin.intervalMs,\n expiresAtMs,\n });\n\n return {\n channel: FEISHU_CHANNEL_ID,\n kind: \"qr_code\",\n sessionId,\n qrCode: begin.qrUrl,\n qrCodeUrl: begin.qrUrl,\n expiresAt: new Date(expiresAtMs).toISOString(),\n intervalMs: begin.intervalMs,\n note: \"请使用飞书或 Lark 扫码授权,NextClaw 会自动创建机器人应用并保存连接信息。\",\n };\n };\n\n readonly poll = async ({ sessionId }: { sessionId: string }): Promise<FeishuAuthPollResult | null> => {\n this.cleanupExpiredSessions();\n const session = this.sessions.get(sessionId);\n if (!session) {\n return null;\n }\n if (session.expiresAtMs <= Date.now()) {\n this.sessions.delete(sessionId);\n return {\n channel: FEISHU_CHANNEL_ID,\n status: \"expired\",\n message: \"飞书扫码授权已过期,请重新开始。\",\n };\n }\n\n try {\n const response = await this.postRegistration(session.domain, {\n action: \"poll\",\n device_code: session.deviceCode,\n tp: \"ob_app\",\n });\n const userInfo = this.readRecord(response.user_info) ?? {};\n const switchedDomain = readDomain(userInfo.tenant_brand) ?? session.domain;\n if (switchedDomain !== session.domain) {\n session.domain = switchedDomain;\n }\n const clientId = readString(response.client_id);\n const clientSecret = readString(response.client_secret);\n if (clientId && clientSecret) {\n const result = await this.confirmRegistration({\n session,\n appId: clientId,\n appSecret: clientSecret,\n ownerOpenId: readString(userInfo.open_id),\n });\n this.sessions.delete(sessionId);\n return {\n channel: FEISHU_CHANNEL_ID,\n status: \"authorized\",\n message: \"飞书已连接。\",\n nextPollMs: 0,\n accountId: result.accountId,\n notes: result.notes,\n pluginConfig: result.pluginConfig,\n };\n }\n\n const error = readString(response.error);\n if (error === \"access_denied\" || error === \"expired_token\") {\n this.sessions.delete(sessionId);\n return {\n channel: FEISHU_CHANNEL_ID,\n status: \"expired\",\n message: error === \"access_denied\" ? \"飞书授权已取消。\" : \"飞书扫码授权已过期。\",\n };\n }\n return {\n channel: FEISHU_CHANNEL_ID,\n status: \"pending\",\n nextPollMs: session.intervalMs,\n };\n } catch (error) {\n return {\n channel: FEISHU_CHANNEL_ID,\n status: \"error\",\n message: error instanceof Error ? error.message : String(error),\n };\n }\n };\n\n private readonly assertRegistrationSupported = async (domain: FeishuDomain): Promise<void> => {\n const response = await this.postRegistration(domain, { action: \"init\" });\n const methods = Array.isArray(response.supported_auth_methods)\n ? response.supported_auth_methods\n : [];\n if (!methods.includes(\"client_secret\")) {\n throw new Error(`Feishu registration does not support client_secret auth. Supported: ${methods.join(\", \")}`);\n }\n };\n\n private readonly beginRegistration = async (\n domain: FeishuDomain,\n ): Promise<{\n deviceCode: string;\n qrUrl: string;\n intervalMs: number;\n expiresInMs: number;\n }> => {\n const response = await this.postRegistration(domain, {\n action: \"begin\",\n archetype: \"PersonalAgent\",\n auth_method: \"client_secret\",\n request_user_info: \"open_id\",\n });\n const deviceCode = readString(response.device_code);\n const qrUrl = readString(response.verification_uri_complete);\n if (!deviceCode || !qrUrl) {\n throw new Error(\"Feishu registration did not return a device code and QR URL.\");\n }\n return {\n deviceCode,\n qrUrl: appendHermesQrHints(qrUrl),\n intervalMs: Math.max(1, Number(response.interval ?? 5)) * 1000 || FEISHU_AUTH_POLL_INTERVAL_MS,\n expiresInMs: Math.max(60, Number(response.expire_in ?? 600)) * 1000,\n };\n };\n\n private readonly postRegistration = async (\n domain: FeishuDomain,\n body: Record<string, string>,\n ): Promise<FeishuRegistrationResponse> => {\n const response = await this.fetchImpl(`${FEISHU_ACCOUNTS_BASE_URLS[domain]}${REGISTRATION_PATH}`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body: new URLSearchParams(body).toString(),\n });\n const data = await response.json() as FeishuRegistrationResponse;\n if (!response.ok && !data.error) {\n throw new Error(`Feishu registration failed: HTTP ${response.status}`);\n }\n return data;\n };\n\n private readonly confirmRegistration = async ({\n appId,\n appSecret,\n ownerOpenId,\n session,\n }: {\n session: FeishuRegistrationSession;\n appId: string;\n appSecret: string;\n ownerOpenId?: string;\n }): Promise<{ pluginConfig: Record<string, unknown>; accountId: string; notes: string[] }> => {\n const botInfo = await this.probeBot({\n appId,\n appSecret,\n domain: session.domain,\n });\n const accountId = session.requestedAccountId?.trim() || appId;\n this.store.saveAccount({\n accountId,\n appId,\n appSecret,\n domain: session.domain,\n botName: botInfo.botName,\n botOpenId: botInfo.botOpenId,\n ownerOpenId,\n savedAt: new Date().toISOString(),\n });\n\n const notes = [\n ...(botInfo.botName ? [`Connected bot: ${botInfo.botName}`] : []),\n ...(ownerOpenId ? [`Authorized initial user: ${ownerOpenId}`] : []),\n ];\n return {\n accountId,\n notes,\n pluginConfig: buildRegisteredFeishuChannelConfig({\n config: session.currentConfig,\n accountId,\n domain: session.domain,\n botName: botInfo.botName,\n allowOpenId: ownerOpenId,\n }) as Record<string, unknown>,\n };\n };\n\n private readonly probeBot = async ({\n appId,\n appSecret,\n domain,\n }: {\n appId: string;\n appSecret: string;\n domain: FeishuDomain;\n }): Promise<{ botName?: string; botOpenId?: string }> => {\n const tokenResponse = await this.fetchImpl(`${FEISHU_OPEN_BASE_URLS[domain]}/open-apis/auth/v3/tenant_access_token/internal`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ app_id: appId, app_secret: appSecret }),\n });\n const tokenData = await tokenResponse.json() as Record<string, unknown>;\n const accessToken = readString(tokenData.tenant_access_token);\n if (!accessToken) {\n return {};\n }\n const botResponse = await this.fetchImpl(`${FEISHU_OPEN_BASE_URLS[domain]}/open-apis/bot/v3/info`, {\n headers: {\n Authorization: `Bearer ${accessToken}`,\n \"Content-Type\": \"application/json\",\n },\n });\n const botData = await botResponse.json() as Record<string, unknown>;\n const bot = this.readRecord(botData.bot) ?? this.readRecord(this.readRecord(botData.data)?.bot);\n return {\n botName: readString(bot?.app_name) ?? readString(bot?.bot_name),\n botOpenId: readString(bot?.open_id),\n };\n };\n\n private readonly readRecord = (value: unknown): Record<string, unknown> | undefined => {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) {\n return undefined;\n }\n return value as Record<string, unknown>;\n };\n\n private readonly cleanupExpiredSessions = (now = Date.now()): void => {\n for (const [sessionId, session] of this.sessions.entries()) {\n if (session.expiresAtMs <= now) {\n this.sessions.delete(sessionId);\n }\n }\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"feishu-reply-chat.service.js","names":[],"sources":["../../src/services/feishu-reply-chat.service.ts"],"sourcesContent":["import { NcpEventType, type NcpEndpointEvent, type NcpMessagePart } from \"@nextclaw/ncp\";\nimport { type Chat, type ChatTarget } from \"@nextclaw/ncp-toolkit\";\nimport type { FeishuRuntimeAccount } from \"../types/feishu-extension.types.js\";\n\nexport const TERMINAL_NCP_EVENT_TYPES = new Set<NcpEndpointEvent[\"type\"]>([\n NcpEventType.MessageCompleted,\n NcpEventType.MessageFailed,\n NcpEventType.RunFinished,\n NcpEventType.RunError,\n]);\n\nexport type FeishuReplySession = {\n queue: NcpEventQueue;\n consuming: Promise<void>;\n};\n\nexport class NcpEventQueue implements AsyncIterable<NcpEndpointEvent> {\n private readonly events: NcpEndpointEvent[] = [];\n private waiting: ((result: IteratorResult<NcpEndpointEvent>) => void) | null = null;\n private closed = false;\n\n readonly push = (event: NcpEndpointEvent): void => {\n if (this.closed) {\n return;\n }\n const waiting = this.waiting;\n if (waiting) {\n this.waiting = null;\n waiting({ value: event, done: false });\n return;\n }\n this.events.push(event);\n };\n\n readonly close = (): void => {\n this.closed = true;\n const waiting = this.waiting;\n if (waiting) {\n this.waiting = null;\n waiting({ value: undefined, done: true });\n }\n };\n\n [Symbol.asyncIterator] = (): AsyncIterator<NcpEndpointEvent> => ({\n next: async () => {\n const event = this.events.shift();\n if (event) {\n return { value: event, done: false };\n }\n if (this.closed) {\n return { value: undefined, done: true };\n }\n return await new Promise<IteratorResult<NcpEndpointEvent>>((resolve) => {\n this.waiting = resolve;\n });\n },\n });\n}\n\nfunction renderPartText(part: NcpMessagePart): string {\n switch (part.type) {\n case \"text\":\n case \"rich-text\":\n return part.text;\n case \"source\":\n return [part.title ?? \"\", part.url ?? \"\", part.snippet ?? \"\"].filter(Boolean).join(\"\\n\");\n case \"card\": {\n const title = typeof part.payload.title === \"string\" ? part.payload.title.trim() : \"\";\n return title || JSON.stringify(part.payload);\n }\n case \"action\":\n return part.label.trim();\n case \"step-start\":\n return part.title?.trim() ?? \"\";\n case \"file\":\n return part.name ? `[file] ${part.name}` : \"[file]\";\n default:\n return \"\";\n }\n}\n\nexport class FeishuReplyChat implements Chat {\n constructor(\n private readonly deps: {\n resolveAccount: (target: ChatTarget) => FeishuRuntimeAccount;\n sendText: (params: {\n account: FeishuRuntimeAccount;\n conversationId: string;\n text: string;\n }) => Promise<void>;\n },\n ) {}\n\n readonly startTyping = async (_target: ChatTarget): Promise<void> => {};\n\n readonly stopTyping = async (_target: ChatTarget): Promise<void> => {};\n\n readonly sendError = async (target: ChatTarget, message: string): Promise<void> => {\n if (message.trim()) {\n await this.sendText(target, message);\n }\n };\n\n readonly sendPart = async (target: ChatTarget, part: NcpMessagePart): Promise<void> => {\n const text = renderPartText(part);\n if (text.trim()) {\n await this.sendText(target, text);\n }\n };\n\n private readonly sendText = async (target: ChatTarget, text: string): Promise<void> => {\n const account = this.deps.resolveAccount(target);\n await this.deps.sendText({\n account,\n conversationId: target.conversationId,\n text,\n });\n };\n}\n"],"mappings":";;;AAIA,MAAa,2BAA2B,IAAI,IAA8B;CACxE,aAAa;CACb,aAAa;CACb,aAAa;CACb,aAAa;CACd,CAAC;AAOF,IAAa,gBAAb,MAAsE;CACpE,SAA8C,EAAE;CAChD,UAA+E;CAC/E,SAAiB;CAEjB,QAAiB,UAAkC;AACjD,MAAI,KAAK,OACP;EAEF,MAAM,UAAU,KAAK;AACrB,MAAI,SAAS;AACX,QAAK,UAAU;AACf,WAAQ;IAAE,OAAO;IAAO,MAAM;IAAO,CAAC;AACtC;;AAEF,OAAK,OAAO,KAAK,MAAM;;CAGzB,cAA6B;AAC3B,OAAK,SAAS;EACd,MAAM,UAAU,KAAK;AACrB,MAAI,SAAS;AACX,QAAK,UAAU;AACf,WAAQ;IAAE,OAAO,KAAA;IAAW,MAAM;IAAM,CAAC;;;CAI7C,CAAC,OAAO,wBAAyD,EAC/D,MAAM,YAAY;EAChB,MAAM,QAAQ,KAAK,OAAO,OAAO;AACjC,MAAI,MACF,QAAO;GAAE,OAAO;GAAO,MAAM;GAAO;AAEtC,MAAI,KAAK,OACP,QAAO;GAAE,OAAO,KAAA;GAAW,MAAM;GAAM;AAEzC,SAAO,MAAM,IAAI,SAA2C,YAAY;AACtE,QAAK,UAAU;IACf;IAEL;;AAGH,SAAS,eAAe,MAA8B;AACpD,SAAQ,KAAK,MAAb;EACE,KAAK;EACL,KAAK,YACH,QAAO,KAAK;EACd,KAAK,SACH,QAAO;GAAC,KAAK,SAAS;GAAI,KAAK,OAAO;GAAI,KAAK,WAAW;GAAG,CAAC,OAAO,QAAQ,CAAC,KAAK,KAAK;EAC1F,KAAK,OAEH,SADc,OAAO,KAAK,QAAQ,UAAU,WAAW,KAAK,QAAQ,MAAM,MAAM,GAAG,OACnE,KAAK,UAAU,KAAK,QAAQ;EAE9C,KAAK,SACH,QAAO,KAAK,MAAM,MAAM;EAC1B,KAAK,aACH,QAAO,KAAK,OAAO,MAAM,IAAI;EAC/B,KAAK,OACH,QAAO,KAAK,OAAO,UAAU,KAAK,SAAS;EAC7C,QACE,QAAO;;;AAIb,IAAa,kBAAb,MAA6C;CAC3C,YACE,MAQA;AARiB,OAAA,OAAA;;CAUnB,cAAuB,OAAO,YAAuC;CAErE,aAAsB,OAAO,YAAuC;CAEpE,YAAqB,OAAO,QAAoB,YAAmC;AACjF,MAAI,QAAQ,MAAM,CAChB,OAAM,KAAK,SAAS,QAAQ,QAAQ;;CAIxC,WAAoB,OAAO,QAAoB,SAAwC;EACrF,MAAM,OAAO,eAAe,KAAK;AACjC,MAAI,KAAK,MAAM,CACb,OAAM,KAAK,SAAS,QAAQ,KAAK;;CAIrC,WAA4B,OAAO,QAAoB,SAAgC;EACrF,MAAM,UAAU,KAAK,KAAK,eAAe,OAAO;AAChD,QAAM,KAAK,KAAK,SAAS;GACvB;GACA,gBAAgB,OAAO;GACvB;GACD,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"feishu-sdk.service.d.ts","names":[],"sources":["../../src/services/feishu-sdk.service.ts"],"mappings":";;;;KAGK,mBAAA;EACH,EAAA;IACE,OAAA;MACE,MAAA,GAAS,MAAA;QACP,MAAA;UAAU,eAAA;QAAA;QACV,IAAA;UAAQ,UAAA;UAAoB,OAAA;UAAiB,QAAA;QAAA;MAAA,MACzC,OAAA;QAAU,IAAA;QAAe,GAAA;QAAc,IAAA;UAAS,UAAA;QAAA;MAAA;IAAA;IAExD,eAAA;MACE,MAAA,GAAS,MAAA;QACP,IAAA;UAAQ,UAAA;QAAA;QACR,IAAA;UAAQ,aAAA;YAAiB,UAAA;UAAA;QAAA;MAAA,MACrB,OAAA;QAAU,IAAA;QAAe,GAAA;QAAc,IAAA;UAAS,WAAA;QAAA;MAAA;MACtD,MAAA,GAAS,MAAA;QACP,IAAA;UAAQ,UAAA;UAAoB,WAAA;QAAA;MAAA,MACxB,OAAA;QAAU,IAAA;QAAe,GAAA;MAAA;IAAA;EAAA;AAAA;AAAA,cAgBxB,gBAAA;EAAA,SACF,YAAA,GAAgB,OAAA,EAAS,oBAAA,KAAuB,mBAAA;EAAA,SAQhD,qBAAA,QAA4B,IAAA,CAAK,eAAA;EAAA,SAGjC,cAAA,GAAkB,OAAA,EAAS,oBAAA,KAAuB,IAAA,CAAK,QAAA;EAAA,SAQvD,QAAA,GAAkB,MAAA;IACzB,OAAA,EAAS,oBAAA;IACT,MAAA;IACA,IAAA;EAAA,MACE,OAAA;EAAA,SAYK,WAAA,GAAqB,MAAA;IAC5B,OAAA,EAAS,oBAAA;IACT,SAAA;IACA,SAAA;EAAA,MACE,OAAA;EAAA,SAWK,cAAA,GAAwB,MAAA;IAC/B,OAAA,EAAS,oBAAA;IACT,SAAA;IACA,UAAA;EAAA,MACE,OAAA;AAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"feishu-sdk.service.js","names":[],"sources":["../../src/services/feishu-sdk.service.ts"],"sourcesContent":["import * as Lark from \"@larksuiteoapi/node-sdk\";\nimport type { FeishuDomain, FeishuRuntimeAccount } from \"../types/feishu-extension.types.js\";\n\ntype FeishuMessageClient = {\n im: {\n message: {\n create: (params: {\n params: { receive_id_type: \"chat_id\" };\n data: { receive_id: string; content: string; msg_type: \"text\" };\n }) => Promise<{ code?: number; msg?: string; data?: { message_id?: string } }>;\n };\n messageReaction: {\n create: (params: {\n path: { message_id: string };\n data: { reaction_type: { emoji_type: string } };\n }) => Promise<{ code?: number; msg?: string; data?: { reaction_id?: string } }>;\n delete: (params: {\n path: { message_id: string; reaction_id: string };\n }) => Promise<{ code?: number; msg?: string }>;\n };\n };\n};\n\nfunction resolveDomain(domain: FeishuDomain): Lark.Domain {\n return domain === \"lark\" ? Lark.Domain.Lark : Lark.Domain.Feishu;\n}\n\nfunction assertFeishuSuccess(response: { code?: number; msg?: string }, fallback: string): void {\n if (response.code === 0 || response.code === undefined) {\n return;\n }\n throw new Error(`${fallback}: ${response.msg ?? `code ${response.code}`}`);\n}\n\nexport class FeishuSdkService {\n readonly createClient = (account: FeishuRuntimeAccount): FeishuMessageClient =>\n new Lark.Client({\n appId: account.appId,\n appSecret: account.appSecret,\n appType: Lark.AppType.SelfBuild,\n domain: resolveDomain(account.domain),\n }) as unknown as FeishuMessageClient;\n\n readonly createEventDispatcher = (): Lark.EventDispatcher =>\n new Lark.EventDispatcher({});\n\n readonly createWsClient = (account: FeishuRuntimeAccount): Lark.WSClient =>\n new Lark.WSClient({\n appId: account.appId,\n appSecret: account.appSecret,\n domain: resolveDomain(account.domain),\n loggerLevel: Lark.LoggerLevel.info,\n });\n\n readonly sendText = async (params: {\n account: FeishuRuntimeAccount;\n chatId: string;\n text: string;\n }): Promise<void> => {\n const response = await this.createClient(params.account).im.message.create({\n params: { receive_id_type: \"chat_id\" },\n data: {\n receive_id: params.chatId,\n content: JSON.stringify({ text: params.text }),\n msg_type: \"text\",\n },\n });\n assertFeishuSuccess(response, \"Feishu send failed\");\n };\n\n readonly addReaction = async (params: {\n account: FeishuRuntimeAccount;\n messageId: string;\n emojiType: string;\n }): Promise<string | null> => {\n const response = await this.createClient(params.account).im.messageReaction.create({\n path: { message_id: params.messageId },\n data: {\n reaction_type: { emoji_type: params.emojiType },\n },\n });\n assertFeishuSuccess(response, \"Feishu reaction failed\");\n return response.data?.reaction_id ?? null;\n };\n\n readonly deleteReaction = async (params: {\n account: FeishuRuntimeAccount;\n messageId: string;\n reactionId: string;\n }): Promise<void> => {\n const response = await this.createClient(params.account).im.messageReaction.delete({\n path: {\n message_id: params.messageId,\n reaction_id: params.reactionId,\n },\n });\n assertFeishuSuccess(response, \"Feishu reaction delete failed\");\n };\n}\n"],"mappings":";;AAuBA,SAAS,cAAc,QAAmC;AACxD,QAAO,WAAW,SAAS,KAAK,OAAO,OAAO,KAAK,OAAO;;AAG5D,SAAS,oBAAoB,UAA2C,UAAwB;AAC9F,KAAI,SAAS,SAAS,KAAK,SAAS,SAAS,KAAA,EAC3C;AAEF,OAAM,IAAI,MAAM,GAAG,SAAS,IAAI,SAAS,OAAO,QAAQ,SAAS,SAAS;;AAG5E,IAAa,mBAAb,MAA8B;CAC5B,gBAAyB,YACvB,IAAI,KAAK,OAAO;EACd,OAAO,QAAQ;EACf,WAAW,QAAQ;EACnB,SAAS,KAAK,QAAQ;EACtB,QAAQ,cAAc,QAAQ,OAAO;EACtC,CAAC;CAEJ,8BACE,IAAI,KAAK,gBAAgB,EAAE,CAAC;CAE9B,kBAA2B,YACzB,IAAI,KAAK,SAAS;EAChB,OAAO,QAAQ;EACf,WAAW,QAAQ;EACnB,QAAQ,cAAc,QAAQ,OAAO;EACrC,aAAa,KAAK,YAAY;EAC/B,CAAC;CAEJ,WAAoB,OAAO,WAIN;AASnB,sBARiB,MAAM,KAAK,aAAa,OAAO,QAAQ,CAAC,GAAG,QAAQ,OAAO;GACzE,QAAQ,EAAE,iBAAiB,WAAW;GACtC,MAAM;IACJ,YAAY,OAAO;IACnB,SAAS,KAAK,UAAU,EAAE,MAAM,OAAO,MAAM,CAAC;IAC9C,UAAU;IACX;GACF,CAAC,EAC4B,qBAAqB;;CAGrD,cAAuB,OAAO,WAIA;EAC5B,MAAM,WAAW,MAAM,KAAK,aAAa,OAAO,QAAQ,CAAC,GAAG,gBAAgB,OAAO;GACjF,MAAM,EAAE,YAAY,OAAO,WAAW;GACtC,MAAM,EACJ,eAAe,EAAE,YAAY,OAAO,WAAW,EAChD;GACF,CAAC;AACF,sBAAoB,UAAU,yBAAyB;AACvD,SAAO,SAAS,MAAM,eAAe;;CAGvC,iBAA0B,OAAO,WAIZ;AAOnB,sBANiB,MAAM,KAAK,aAAa,OAAO,QAAQ,CAAC,GAAG,gBAAgB,OAAO,EACjF,MAAM;GACJ,YAAY,OAAO;GACnB,aAAa,OAAO;GACrB,EACF,CAAC,EAC4B,gCAAgC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"feishu-account.store.d.ts","names":[],"sources":["../../src/stores/feishu-account.store.ts"],"mappings":";;;KAKY,mBAAA;EACV,SAAA;EACA,KAAA;EACA,SAAA;EACA,MAAA,EAAQ,YAAA;EACR,OAAA;EACA,SAAA;EACA,WAAA;EACA,OAAA;AAAA;AAAA,KAGU,kBAAA;EACV,cAAA;EACA,WAAA,GAAc,SAAA,aAAsB,mBAAA;EACpC,WAAA,GAAc,OAAA,EAAS,mBAAA;EACvB,aAAA,GAAgB,SAAA;AAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"feishu-account.store.js","names":[],"sources":["../../src/stores/feishu-account.store.ts"],"sourcesContent":["import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join, resolve } from \"node:path\";\nimport type { FeishuDomain } from \"../types/feishu-extension.types.js\";\n\nexport type StoredFeishuAccount = {\n accountId: string;\n appId: string;\n appSecret: string;\n domain: FeishuDomain;\n botName?: string;\n botOpenId?: string;\n ownerOpenId?: string;\n savedAt?: string;\n};\n\nexport type FeishuAccountStore = {\n listAccountIds: () => string[];\n loadAccount: (accountId: string) => StoredFeishuAccount | null;\n saveAccount: (account: StoredFeishuAccount) => void;\n deleteAccount: (accountId: string) => void;\n};\n\nconst NEXTCLAW_HOME_ENV_KEY = \"NEXTCLAW_HOME\";\nconst NEXTCLAW_DEFAULT_HOME_DIR = \".nextclaw\";\n\nfunction resolveNextclawHome(): string {\n const override = process.env[NEXTCLAW_HOME_ENV_KEY]?.trim();\n return resolve(override || join(homedir(), NEXTCLAW_DEFAULT_HOME_DIR));\n}\n\nfunction resolveAccountsDir(): string {\n return join(resolveNextclawHome(), \"channels\", \"feishu\", \"accounts\");\n}\n\nfunction toFileName(accountId: string): string {\n return `${encodeURIComponent(accountId)}.json`;\n}\n\nfunction readJsonFile<T>(filePath: string): T | null {\n if (!existsSync(filePath)) {\n return null;\n }\n try {\n return JSON.parse(readFileSync(filePath, \"utf-8\")) as T;\n } catch {\n return null;\n }\n}\n\nexport class FileFeishuAccountStore implements FeishuAccountStore {\n readonly listAccountIds = (): string[] => {\n if (!existsSync(resolveAccountsDir())) {\n return [];\n }\n return readdirSync(resolveAccountsDir())\n .filter((entry) => entry.endsWith(\".json\"))\n .map((entry) => decodeURIComponent(entry.slice(0, -5)))\n .filter(Boolean);\n };\n\n readonly loadAccount = (accountId: string): StoredFeishuAccount | null => {\n return readJsonFile<StoredFeishuAccount>(join(resolveAccountsDir(), toFileName(accountId)));\n };\n\n readonly saveAccount = (account: StoredFeishuAccount): void => {\n mkdirSync(resolveAccountsDir(), { recursive: true });\n writeFileSync(\n join(resolveAccountsDir(), toFileName(account.accountId)),\n JSON.stringify(account, null, 2),\n );\n };\n\n readonly deleteAccount = (accountId: string): void => {\n rmSync(join(resolveAccountsDir(), toFileName(accountId)), { force: true });\n };\n}\n"],"mappings":";;;;AAuBA,MAAM,wBAAwB;AAC9B,MAAM,4BAA4B;AAElC,SAAS,sBAA8B;CACrC,MAAM,WAAW,QAAQ,IAAI,wBAAwB,MAAM;AAC3D,QAAO,QAAQ,YAAY,KAAK,SAAS,EAAE,0BAA0B,CAAC;;AAGxE,SAAS,qBAA6B;AACpC,QAAO,KAAK,qBAAqB,EAAE,YAAY,UAAU,WAAW;;AAGtE,SAAS,WAAW,WAA2B;AAC7C,QAAO,GAAG,mBAAmB,UAAU,CAAC;;AAG1C,SAAS,aAAgB,UAA4B;AACnD,KAAI,CAAC,WAAW,SAAS,CACvB,QAAO;AAET,KAAI;AACF,SAAO,KAAK,MAAM,aAAa,UAAU,QAAQ,CAAC;SAC5C;AACN,SAAO;;;AAIX,IAAa,yBAAb,MAAkE;CAChE,uBAA0C;AACxC,MAAI,CAAC,WAAW,oBAAoB,CAAC,CACnC,QAAO,EAAE;AAEX,SAAO,YAAY,oBAAoB,CAAC,CACrC,QAAQ,UAAU,MAAM,SAAS,QAAQ,CAAC,CAC1C,KAAK,UAAU,mBAAmB,MAAM,MAAM,GAAG,GAAG,CAAC,CAAC,CACtD,OAAO,QAAQ;;CAGpB,eAAwB,cAAkD;AACxE,SAAO,aAAkC,KAAK,oBAAoB,EAAE,WAAW,UAAU,CAAC,CAAC;;CAG7F,eAAwB,YAAuC;AAC7D,YAAU,oBAAoB,EAAE,EAAE,WAAW,MAAM,CAAC;AACpD,gBACE,KAAK,oBAAoB,EAAE,WAAW,QAAQ,UAAU,CAAC,EACzD,KAAK,UAAU,SAAS,MAAM,EAAE,CACjC;;CAGH,iBAA0B,cAA4B;AACpD,SAAO,KAAK,oBAAoB,EAAE,WAAW,UAAU,CAAC,EAAE,EAAE,OAAO,MAAM,CAAC"}
|
|
@@ -53,4 +53,5 @@ type FeishuChannelAdapterContract = {
|
|
|
53
53
|
}) => Promise<void>;
|
|
54
54
|
};
|
|
55
55
|
//#endregion
|
|
56
|
-
export { FeishuAccountConfig, FeishuChannelAdapterContract, FeishuChannelConfig, FeishuDomain, FeishuInboundMessage, FeishuRuntimeAccount };
|
|
56
|
+
export { FeishuAccountConfig, FeishuChannelAdapterContract, FeishuChannelConfig, FeishuDomain, FeishuInboundMessage, FeishuRuntimeAccount };
|
|
57
|
+
//# sourceMappingURL=feishu-extension.types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"feishu-extension.types.d.ts","names":[],"sources":["../../src/types/feishu-extension.types.ts"],"mappings":";;;KAEY,YAAA;AAAA,KAEA,mBAAA;EACV,OAAA;EACA,IAAA;EACA,MAAA,GAAS,YAAA;EACT,SAAA;EACA,WAAA;EACA,cAAA;AAAA;AAAA,KAGU,mBAAA;EACV,OAAA;EACA,gBAAA;EACA,MAAA,GAAS,YAAA;EACT,SAAA;EACA,WAAA;EACA,cAAA;EACA,QAAA,GAAW,MAAA,SAAe,mBAAA;AAAA;AAAA,KAGhB,oBAAA;EACV,cAAA;EACA,QAAA;EACA,IAAA;EACA,SAAA;EACA,QAAA;EACA,SAAA;EACA,GAAA;AAAA;AAAA,KAGU,oBAAA;EACV,SAAA;EACA,KAAA;EACA,SAAA;EACA,MAAA,EAAQ,YAAA;EACR,OAAA;EACA,IAAA;EACA,SAAA;EACA,SAAA;EACA,WAAA;EACA,cAAA;AAAA;AAAA,KAGU,4BAAA;EACV,SAAA,GAAY,MAAA,EAAQ,mBAAA,KAAwB,OAAA;EAC5C,KAAA,QAAa,OAAA;EACb,IAAA,QAAY,OAAA;EACZ,SAAA,GAAY,OAAA,GAAU,OAAA,EAAS,oBAAA,YAAgC,OAAA;EAC/D,YAAA,GAAe,KAAA,EAAO,gBAAA,KAAqB,OAAA;EAC3C,gBAAA,GAAmB,MAAA;IACjB,EAAA;IACA,IAAA;IACA,SAAA;EAAA,MACI,OAAA;AAAA"}
|
|
@@ -77,3 +77,5 @@ function buildRegisteredFeishuChannelConfig({ accountId, allowOpenId, botName, c
|
|
|
77
77
|
}
|
|
78
78
|
//#endregion
|
|
79
79
|
export { DEFAULT_FEISHU_DOMAIN, FEISHU_CHANNEL_ID, buildRegisteredFeishuChannelConfig, normalizeFeishuChannelConfig };
|
|
80
|
+
|
|
81
|
+
//# sourceMappingURL=feishu-config.utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"feishu-config.utils.js","names":[],"sources":["../../src/utils/feishu-config.utils.ts"],"sourcesContent":["import type {\n FeishuAccountConfig,\n FeishuChannelConfig,\n FeishuDomain,\n} from \"../types/feishu-extension.types.js\";\n\nexport const FEISHU_CHANNEL_ID = \"feishu\";\nexport const DEFAULT_FEISHU_DOMAIN: FeishuDomain = \"feishu\";\n\nfunction toRecord(value: unknown): Record<string, unknown> | undefined {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) {\n return undefined;\n }\n return value as Record<string, unknown>;\n}\n\nfunction readString(value: unknown): string | undefined {\n if (typeof value !== \"string\") {\n return undefined;\n }\n const trimmed = value.trim();\n return trimmed || undefined;\n}\n\nfunction readStringArray(value: unknown): string[] | undefined {\n if (!Array.isArray(value)) {\n return undefined;\n }\n const values = value\n .map((entry) => readString(entry))\n .filter((entry): entry is string => Boolean(entry));\n return values.length > 0 ? values : undefined;\n}\n\nfunction readDomain(value: unknown): FeishuDomain | undefined {\n return value === \"lark\" || value === \"feishu\" ? value : undefined;\n}\n\nfunction readGroupPolicy(value: unknown): FeishuAccountConfig[\"groupPolicy\"] {\n return value === \"open\" || value === \"allowlist\" || value === \"disabled\"\n ? value\n : undefined;\n}\n\nfunction normalizeAccountConfig(value: unknown): FeishuAccountConfig | undefined {\n const record = toRecord(value);\n if (!record) {\n return undefined;\n }\n return {\n enabled: typeof record.enabled === \"boolean\" ? record.enabled : undefined,\n name: readString(record.name),\n domain: readDomain(record.domain),\n allowFrom: readStringArray(record.allowFrom),\n groupPolicy: readGroupPolicy(record.groupPolicy),\n requireMention: typeof record.requireMention === \"boolean\" ? record.requireMention : undefined,\n };\n}\n\nexport function normalizeFeishuChannelConfig(value: unknown): FeishuChannelConfig {\n const record = toRecord(value);\n if (!record) {\n return {};\n }\n\n const accounts: Record<string, FeishuAccountConfig> = {};\n for (const [accountId, rawAccountConfig] of Object.entries(toRecord(record.accounts) ?? {})) {\n const normalized = normalizeAccountConfig(rawAccountConfig);\n if (normalized) {\n accounts[accountId] = normalized;\n }\n }\n\n return {\n enabled: typeof record.enabled === \"boolean\" ? record.enabled : undefined,\n defaultAccountId: readString(record.defaultAccountId),\n domain: readDomain(record.domain),\n allowFrom: readStringArray(record.allowFrom),\n groupPolicy: readGroupPolicy(record.groupPolicy),\n requireMention: typeof record.requireMention === \"boolean\" ? record.requireMention : undefined,\n accounts: Object.keys(accounts).length > 0 ? accounts : undefined,\n };\n}\n\nexport function buildRegisteredFeishuChannelConfig({\n accountId,\n allowOpenId,\n botName,\n config,\n domain,\n}: {\n config: FeishuChannelConfig;\n accountId: string;\n domain: FeishuDomain;\n botName?: string;\n allowOpenId?: string;\n}): FeishuChannelConfig {\n const current = normalizeFeishuChannelConfig(config);\n const currentAccount = current.accounts?.[accountId] ?? {};\n const allowFrom = new Set([\n ...(current.allowFrom ?? []),\n ...(currentAccount.allowFrom ?? []),\n ...(allowOpenId ? [allowOpenId] : []),\n ]);\n\n return {\n ...current,\n enabled: true,\n defaultAccountId: accountId,\n domain: current.domain ?? domain,\n accounts: {\n ...(current.accounts ?? {}),\n [accountId]: {\n ...currentAccount,\n enabled: true,\n domain,\n name: currentAccount.name ?? botName,\n allowFrom: allowFrom.size > 0 ? [...allowFrom] : undefined,\n },\n },\n };\n}\n"],"mappings":";AAMA,MAAa,oBAAoB;AACjC,MAAa,wBAAsC;AAEnD,SAAS,SAAS,OAAqD;AACrE,KAAI,CAAC,SAAS,OAAO,UAAU,YAAY,MAAM,QAAQ,MAAM,CAC7D;AAEF,QAAO;;AAGT,SAAS,WAAW,OAAoC;AACtD,KAAI,OAAO,UAAU,SACnB;AAGF,QADgB,MAAM,MAAM,IACV,KAAA;;AAGpB,SAAS,gBAAgB,OAAsC;AAC7D,KAAI,CAAC,MAAM,QAAQ,MAAM,CACvB;CAEF,MAAM,SAAS,MACZ,KAAK,UAAU,WAAW,MAAM,CAAC,CACjC,QAAQ,UAA2B,QAAQ,MAAM,CAAC;AACrD,QAAO,OAAO,SAAS,IAAI,SAAS,KAAA;;AAGtC,SAAS,WAAW,OAA0C;AAC5D,QAAO,UAAU,UAAU,UAAU,WAAW,QAAQ,KAAA;;AAG1D,SAAS,gBAAgB,OAAoD;AAC3E,QAAO,UAAU,UAAU,UAAU,eAAe,UAAU,aAC1D,QACA,KAAA;;AAGN,SAAS,uBAAuB,OAAiD;CAC/E,MAAM,SAAS,SAAS,MAAM;AAC9B,KAAI,CAAC,OACH;AAEF,QAAO;EACL,SAAS,OAAO,OAAO,YAAY,YAAY,OAAO,UAAU,KAAA;EAChE,MAAM,WAAW,OAAO,KAAK;EAC7B,QAAQ,WAAW,OAAO,OAAO;EACjC,WAAW,gBAAgB,OAAO,UAAU;EAC5C,aAAa,gBAAgB,OAAO,YAAY;EAChD,gBAAgB,OAAO,OAAO,mBAAmB,YAAY,OAAO,iBAAiB,KAAA;EACtF;;AAGH,SAAgB,6BAA6B,OAAqC;CAChF,MAAM,SAAS,SAAS,MAAM;AAC9B,KAAI,CAAC,OACH,QAAO,EAAE;CAGX,MAAM,WAAgD,EAAE;AACxD,MAAK,MAAM,CAAC,WAAW,qBAAqB,OAAO,QAAQ,SAAS,OAAO,SAAS,IAAI,EAAE,CAAC,EAAE;EAC3F,MAAM,aAAa,uBAAuB,iBAAiB;AAC3D,MAAI,WACF,UAAS,aAAa;;AAI1B,QAAO;EACL,SAAS,OAAO,OAAO,YAAY,YAAY,OAAO,UAAU,KAAA;EAChE,kBAAkB,WAAW,OAAO,iBAAiB;EACrD,QAAQ,WAAW,OAAO,OAAO;EACjC,WAAW,gBAAgB,OAAO,UAAU;EAC5C,aAAa,gBAAgB,OAAO,YAAY;EAChD,gBAAgB,OAAO,OAAO,mBAAmB,YAAY,OAAO,iBAAiB,KAAA;EACrF,UAAU,OAAO,KAAK,SAAS,CAAC,SAAS,IAAI,WAAW,KAAA;EACzD;;AAGH,SAAgB,mCAAmC,EACjD,WACA,aACA,SACA,QACA,UAOsB;CACtB,MAAM,UAAU,6BAA6B,OAAO;CACpD,MAAM,iBAAiB,QAAQ,WAAW,cAAc,EAAE;CAC1D,MAAM,YAAY,IAAI,IAAI;EACxB,GAAI,QAAQ,aAAa,EAAE;EAC3B,GAAI,eAAe,aAAa,EAAE;EAClC,GAAI,cAAc,CAAC,YAAY,GAAG,EAAE;EACrC,CAAC;AAEF,QAAO;EACL,GAAG;EACH,SAAS;EACT,kBAAkB;EAClB,QAAQ,QAAQ,UAAU;EAC1B,UAAU;GACR,GAAI,QAAQ,YAAY,EAAE;IACzB,YAAY;IACX,GAAG;IACH,SAAS;IACT;IACA,MAAM,eAAe,QAAQ;IAC7B,WAAW,UAAU,OAAO,IAAI,CAAC,GAAG,UAAU,GAAG,KAAA;IAClD;GACF;EACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"feishu-message.utils.js","names":[],"sources":["../../src/utils/feishu-message.utils.ts"],"sourcesContent":["export type FeishuMessageEvent = {\n sender?: {\n sender_id?: {\n open_id?: string;\n user_id?: string;\n };\n sender_type?: string;\n };\n message?: {\n chat_id?: string;\n chat_type?: string;\n content?: string;\n mentions?: Array<{\n id?: {\n open_id?: string;\n user_id?: string;\n };\n }>;\n message_id?: string;\n message_type?: string;\n };\n};\n\nexport type ParsedFeishuInboundMessage = {\n chatId: string;\n senderOpenId: string;\n text: string;\n messageId?: string;\n chatType: string;\n mentionedOpenIds: string[];\n};\n\nfunction readRecord(value: unknown): Record<string, unknown> | undefined {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) {\n return undefined;\n }\n return value as Record<string, unknown>;\n}\n\nfunction readString(value: unknown): string | undefined {\n return typeof value === \"string\" && value.trim() ? value.trim() : undefined;\n}\n\nfunction parseTextContent(raw: string | undefined): string {\n if (!raw) {\n return \"\";\n }\n try {\n const parsed = JSON.parse(raw) as Record<string, unknown>;\n return readString(parsed.text) ?? \"\";\n } catch {\n return raw;\n }\n}\n\nfunction readMentionOpenIds(value: unknown): string[] {\n if (!Array.isArray(value)) {\n return [];\n }\n return value\n .map((entry) => readString(readRecord(readRecord(entry)?.id)?.open_id))\n .filter((entry): entry is string => Boolean(entry));\n}\n\nexport function parseFeishuInboundMessage(value: unknown): ParsedFeishuInboundMessage | null {\n const event = value as FeishuMessageEvent;\n const chatId = readString(event.message?.chat_id);\n const senderOpenId = readString(event.sender?.sender_id?.open_id);\n if (!chatId || !senderOpenId || event.sender?.sender_type === \"app\") {\n return null;\n }\n const text = parseTextContent(event.message?.content);\n if (!text.trim()) {\n return null;\n }\n return {\n chatId,\n senderOpenId,\n text,\n messageId: readString(event.message?.message_id),\n chatType: readString(event.message?.chat_type) ?? \"p2p\",\n mentionedOpenIds: readMentionOpenIds(event.message?.mentions),\n };\n}\n\nexport function isFeishuGroupChat(chatType: string): boolean {\n return chatType !== \"p2p\";\n}\n\nexport function isFeishuBotMentioned(params: {\n botOpenId?: string;\n mentionedOpenIds: string[];\n}): boolean {\n return Boolean(params.botOpenId && params.mentionedOpenIds.includes(params.botOpenId));\n}\n"],"mappings":";AAgCA,SAAS,WAAW,OAAqD;AACvE,KAAI,CAAC,SAAS,OAAO,UAAU,YAAY,MAAM,QAAQ,MAAM,CAC7D;AAEF,QAAO;;AAGT,SAAS,WAAW,OAAoC;AACtD,QAAO,OAAO,UAAU,YAAY,MAAM,MAAM,GAAG,MAAM,MAAM,GAAG,KAAA;;AAGpE,SAAS,iBAAiB,KAAiC;AACzD,KAAI,CAAC,IACH,QAAO;AAET,KAAI;AAEF,SAAO,WADQ,KAAK,MAAM,IAAI,CACL,KAAK,IAAI;SAC5B;AACN,SAAO;;;AAIX,SAAS,mBAAmB,OAA0B;AACpD,KAAI,CAAC,MAAM,QAAQ,MAAM,CACvB,QAAO,EAAE;AAEX,QAAO,MACJ,KAAK,UAAU,WAAW,WAAW,WAAW,MAAM,EAAE,GAAG,EAAE,QAAQ,CAAC,CACtE,QAAQ,UAA2B,QAAQ,MAAM,CAAC;;AAGvD,SAAgB,0BAA0B,OAAmD;CAC3F,MAAM,QAAQ;CACd,MAAM,SAAS,WAAW,MAAM,SAAS,QAAQ;CACjD,MAAM,eAAe,WAAW,MAAM,QAAQ,WAAW,QAAQ;AACjE,KAAI,CAAC,UAAU,CAAC,gBAAgB,MAAM,QAAQ,gBAAgB,MAC5D,QAAO;CAET,MAAM,OAAO,iBAAiB,MAAM,SAAS,QAAQ;AACrD,KAAI,CAAC,KAAK,MAAM,CACd,QAAO;AAET,QAAO;EACL;EACA;EACA;EACA,WAAW,WAAW,MAAM,SAAS,WAAW;EAChD,UAAU,WAAW,MAAM,SAAS,UAAU,IAAI;EAClD,kBAAkB,mBAAmB,MAAM,SAAS,SAAS;EAC9D;;AAGH,SAAgB,kBAAkB,UAA2B;AAC3D,QAAO,aAAa;;AAGtB,SAAgB,qBAAqB,QAGzB;AACV,QAAO,QAAQ,OAAO,aAAa,OAAO,iBAAiB,SAAS,OAAO,UAAU,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"feishu-session-route.utils.js","names":[],"sources":["../../src/utils/feishu-session-route.utils.ts"],"sourcesContent":["export type FeishuSessionRoute = {\n conversationId: string;\n accountId?: string;\n};\n\nconst PEER_KINDS = new Set([\"direct\", \"group\", \"channel\"]);\n\nfunction readPayload(value: unknown): unknown {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) {\n return undefined;\n }\n return (value as { payload?: unknown }).payload;\n}\n\nfunction readNestedMessageSessionId(payload: Record<string, unknown>): string | undefined {\n const message = payload.message;\n if (!message || typeof message !== \"object\" || Array.isArray(message)) {\n return undefined;\n }\n const sessionId = (message as { sessionId?: unknown }).sessionId;\n return typeof sessionId === \"string\" && sessionId.trim() ? sessionId.trim() : undefined;\n}\n\nexport function readFeishuEventSessionId(event: unknown): string | undefined {\n const payload = readPayload(event);\n if (!payload || typeof payload !== \"object\" || Array.isArray(payload)) {\n return undefined;\n }\n const sessionId = (payload as { sessionId?: unknown }).sessionId;\n if (typeof sessionId === \"string\" && sessionId.trim()) {\n return sessionId.trim();\n }\n return readNestedMessageSessionId(payload as Record<string, unknown>);\n}\n\nexport function resolveFeishuSessionRoute(event: unknown): FeishuSessionRoute | null {\n const sessionId = readFeishuEventSessionId(event);\n if (!sessionId) {\n return null;\n }\n const parts = sessionId.split(\":\");\n const normalizedParts = parts.map((part) => part.toLowerCase());\n if (normalizedParts[0] !== \"agent\" || parts.length < 5) {\n return null;\n }\n\n if (normalizedParts[2] === \"feishu\" && PEER_KINDS.has(normalizedParts[3] ?? \"\") && parts.length >= 5) {\n return {\n conversationId: parts.slice(4).join(\":\"),\n };\n }\n\n if (normalizedParts[2] === \"feishu\" && PEER_KINDS.has(normalizedParts[4] ?? \"\") && parts.length >= 6) {\n return {\n accountId: parts[3],\n conversationId: parts.slice(5).join(\":\"),\n };\n }\n\n return null;\n}\n"],"mappings":";AAKA,MAAM,aAAa,IAAI,IAAI;CAAC;CAAU;CAAS;CAAU,CAAC;AAE1D,SAAS,YAAY,OAAyB;AAC5C,KAAI,CAAC,SAAS,OAAO,UAAU,YAAY,MAAM,QAAQ,MAAM,CAC7D;AAEF,QAAQ,MAAgC;;AAG1C,SAAS,2BAA2B,SAAsD;CACxF,MAAM,UAAU,QAAQ;AACxB,KAAI,CAAC,WAAW,OAAO,YAAY,YAAY,MAAM,QAAQ,QAAQ,CACnE;CAEF,MAAM,YAAa,QAAoC;AACvD,QAAO,OAAO,cAAc,YAAY,UAAU,MAAM,GAAG,UAAU,MAAM,GAAG,KAAA;;AAGhF,SAAgB,yBAAyB,OAAoC;CAC3E,MAAM,UAAU,YAAY,MAAM;AAClC,KAAI,CAAC,WAAW,OAAO,YAAY,YAAY,MAAM,QAAQ,QAAQ,CACnE;CAEF,MAAM,YAAa,QAAoC;AACvD,KAAI,OAAO,cAAc,YAAY,UAAU,MAAM,CACnD,QAAO,UAAU,MAAM;AAEzB,QAAO,2BAA2B,QAAmC;;AAGvE,SAAgB,0BAA0B,OAA2C;CACnF,MAAM,YAAY,yBAAyB,MAAM;AACjD,KAAI,CAAC,UACH,QAAO;CAET,MAAM,QAAQ,UAAU,MAAM,IAAI;CAClC,MAAM,kBAAkB,MAAM,KAAK,SAAS,KAAK,aAAa,CAAC;AAC/D,KAAI,gBAAgB,OAAO,WAAW,MAAM,SAAS,EACnD,QAAO;AAGT,KAAI,gBAAgB,OAAO,YAAY,WAAW,IAAI,gBAAgB,MAAM,GAAG,IAAI,MAAM,UAAU,EACjG,QAAO,EACL,gBAAgB,MAAM,MAAM,EAAE,CAAC,KAAK,IAAI,EACzC;AAGH,KAAI,gBAAgB,OAAO,YAAY,WAAW,IAAI,gBAAgB,MAAM,GAAG,IAAI,MAAM,UAAU,EACjG,QAAO;EACL,WAAW,MAAM;EACjB,gBAAgB,MAAM,MAAM,EAAE,CAAC,KAAK,IAAI;EACzC;AAGH,QAAO"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"feishu-submitted-message.utils.js","names":[],"sources":["../../src/utils/feishu-submitted-message.utils.ts"],"sourcesContent":["import type { ChannelSubmittedMessageInput } from \"@nextclaw/extension-sdk\";\nimport type { FeishuInboundMessage } from \"../types/feishu-extension.types.js\";\n\nexport function toFeishuSubmittedMessage(\n message: FeishuInboundMessage,\n): ChannelSubmittedMessageInput {\n return {\n conversationId: message.conversationId,\n senderId: message.senderId,\n content: {\n type: \"text\",\n text: message.text,\n },\n metadata: {\n accountId: message.accountId,\n account_id: message.accountId,\n peerId: message.conversationId,\n peer_id: message.conversationId,\n peerKind: message.peerKind,\n peer_kind: message.peerKind,\n ...(message.messageId ? { message_id: message.messageId } : {}),\n ...(message.raw === undefined ? {} : { raw: message.raw }),\n },\n };\n}\n"],"mappings":";AAGA,SAAgB,yBACd,SAC8B;AAC9B,QAAO;EACL,gBAAgB,QAAQ;EACxB,UAAU,QAAQ;EAClB,SAAS;GACP,MAAM;GACN,MAAM,QAAQ;GACf;EACD,UAAU;GACR,WAAW,QAAQ;GACnB,YAAY,QAAQ;GACpB,QAAQ,QAAQ;GAChB,SAAS,QAAQ;GACjB,UAAU,QAAQ;GAClB,WAAW,QAAQ;GACnB,GAAI,QAAQ,YAAY,EAAE,YAAY,QAAQ,WAAW,GAAG,EAAE;GAC9D,GAAI,QAAQ,QAAQ,KAAA,IAAY,EAAE,GAAG,EAAE,KAAK,QAAQ,KAAK;GAC1D;EACF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nextclaw/channel-extension-feishu",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "NextClaw Feishu/Lark lightweight channel extension process.",
|
|
6
6
|
"type": "module",
|
|
@@ -19,9 +19,9 @@
|
|
|
19
19
|
],
|
|
20
20
|
"dependencies": {
|
|
21
21
|
"@larksuiteoapi/node-sdk": "^1.59.0",
|
|
22
|
-
"@nextclaw/extension-sdk": "0.1.
|
|
23
|
-
"@nextclaw/ncp-toolkit": "0.5.
|
|
24
|
-
"@nextclaw/ncp": "0.5.
|
|
22
|
+
"@nextclaw/extension-sdk": "0.1.10",
|
|
23
|
+
"@nextclaw/ncp-toolkit": "0.5.21",
|
|
24
|
+
"@nextclaw/ncp": "0.5.16"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
27
|
"@types/node": "^20.17.6",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"vitest": "^4.1.2"
|
|
30
30
|
},
|
|
31
31
|
"scripts": {
|
|
32
|
-
"build": "tsdown src/index.ts src/main.ts --dts --clean --target es2022 --no-fixedExtension --unbundle",
|
|
32
|
+
"build": "tsdown src/index.ts src/main.ts --dts.sourcemap --clean --target es2022 --no-fixedExtension --unbundle",
|
|
33
33
|
"lint": "eslint src --max-warnings=0",
|
|
34
34
|
"test": "vitest run",
|
|
35
35
|
"tsc": "tsc -p tsconfig.json"
|