@colinlu50/openclaw-lark-stream 2026.3.17
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/LICENSE +21 -0
- package/README.md +141 -0
- package/README.zh.md +70 -0
- package/bin/openclaw-lark.js +48 -0
- package/index.d.ts +36 -0
- package/index.js +118 -0
- package/openclaw.plugin.json +10 -0
- package/package.json +66 -0
- package/skills/feishu-bitable/SKILL.md +248 -0
- package/skills/feishu-bitable/references/examples.md +813 -0
- package/skills/feishu-bitable/references/field-properties.md +763 -0
- package/skills/feishu-bitable/references/record-values.md +911 -0
- package/skills/feishu-calendar/SKILL.md +244 -0
- package/skills/feishu-channel-rules/SKILL.md +24 -0
- package/skills/feishu-channel-rules/references/markdown-syntax.md +138 -0
- package/skills/feishu-create-doc/SKILL.md +719 -0
- package/skills/feishu-fetch-doc/SKILL.md +93 -0
- package/skills/feishu-im-read/SKILL.md +163 -0
- package/skills/feishu-task/SKILL.md +293 -0
- package/skills/feishu-troubleshoot/SKILL.md +70 -0
- package/skills/feishu-update-doc/SKILL.md +285 -0
- package/src/card/builder.d.ts +106 -0
- package/src/card/builder.js +443 -0
- package/src/card/cardkit.d.ts +90 -0
- package/src/card/cardkit.js +181 -0
- package/src/card/flush-controller.d.ts +45 -0
- package/src/card/flush-controller.js +134 -0
- package/src/card/image-resolver.d.ts +45 -0
- package/src/card/image-resolver.js +112 -0
- package/src/card/markdown-style.d.ts +16 -0
- package/src/card/markdown-style.js +97 -0
- package/src/card/reply-dispatcher-types.d.ts +120 -0
- package/src/card/reply-dispatcher-types.js +57 -0
- package/src/card/reply-dispatcher.d.ts +15 -0
- package/src/card/reply-dispatcher.js +299 -0
- package/src/card/reply-mode.d.ts +38 -0
- package/src/card/reply-mode.js +65 -0
- package/src/card/streaming-card-controller.d.ts +101 -0
- package/src/card/streaming-card-controller.js +810 -0
- package/src/card/unavailable-guard.d.ts +35 -0
- package/src/card/unavailable-guard.js +83 -0
- package/src/channel/abort-detect.d.ts +34 -0
- package/src/channel/abort-detect.js +124 -0
- package/src/channel/chat-queue.d.ts +41 -0
- package/src/channel/chat-queue.js +58 -0
- package/src/channel/config-adapter.d.ts +23 -0
- package/src/channel/config-adapter.js +101 -0
- package/src/channel/directory.d.ts +57 -0
- package/src/channel/directory.js +191 -0
- package/src/channel/event-handlers.d.ts +15 -0
- package/src/channel/event-handlers.js +221 -0
- package/src/channel/monitor.d.ts +17 -0
- package/src/channel/monitor.js +129 -0
- package/src/channel/onboarding-config.d.ts +17 -0
- package/src/channel/onboarding-config.js +88 -0
- package/src/channel/onboarding-migrate.d.ts +25 -0
- package/src/channel/onboarding-migrate.js +67 -0
- package/src/channel/onboarding.d.ts +12 -0
- package/src/channel/onboarding.js +296 -0
- package/src/channel/plugin.d.ts +13 -0
- package/src/channel/plugin.js +278 -0
- package/src/channel/probe.d.ts +14 -0
- package/src/channel/probe.js +21 -0
- package/src/channel/types.d.ts +36 -0
- package/src/channel/types.js +7 -0
- package/src/commands/auth.d.ts +21 -0
- package/src/commands/auth.js +161 -0
- package/src/commands/diagnose.d.ts +69 -0
- package/src/commands/diagnose.js +807 -0
- package/src/commands/doctor.d.ts +26 -0
- package/src/commands/doctor.js +584 -0
- package/src/commands/index.d.ts +25 -0
- package/src/commands/index.js +212 -0
- package/src/commands/locale.d.ts +7 -0
- package/src/commands/locale.js +7 -0
- package/src/core/accounts.d.ts +37 -0
- package/src/core/accounts.js +163 -0
- package/src/core/agent-config.d.ts +100 -0
- package/src/core/agent-config.js +139 -0
- package/src/core/api-error.d.ts +48 -0
- package/src/core/api-error.js +112 -0
- package/src/core/app-owner-fallback.d.ts +21 -0
- package/src/core/app-owner-fallback.js +38 -0
- package/src/core/app-scope-checker.d.ts +87 -0
- package/src/core/app-scope-checker.js +190 -0
- package/src/core/auth-errors.d.ts +144 -0
- package/src/core/auth-errors.js +154 -0
- package/src/core/chat-info-cache.d.ts +57 -0
- package/src/core/chat-info-cache.js +152 -0
- package/src/core/config-schema.d.ts +448 -0
- package/src/core/config-schema.js +200 -0
- package/src/core/device-flow.d.ts +77 -0
- package/src/core/device-flow.js +212 -0
- package/src/core/domains.d.ts +18 -0
- package/src/core/domains.js +28 -0
- package/src/core/feishu-fetch.d.ts +18 -0
- package/src/core/feishu-fetch.js +25 -0
- package/src/core/footer-config.d.ts +24 -0
- package/src/core/footer-config.js +39 -0
- package/src/core/lark-client.d.ts +108 -0
- package/src/core/lark-client.js +353 -0
- package/src/core/lark-logger.d.ts +23 -0
- package/src/core/lark-logger.js +154 -0
- package/src/core/lark-ticket.d.ts +29 -0
- package/src/core/lark-ticket.js +35 -0
- package/src/core/message-unavailable.d.ts +53 -0
- package/src/core/message-unavailable.js +130 -0
- package/src/core/owner-policy.d.ts +31 -0
- package/src/core/owner-policy.js +52 -0
- package/src/core/permission-url.d.ts +22 -0
- package/src/core/permission-url.js +72 -0
- package/src/core/raw-request.d.ts +27 -0
- package/src/core/raw-request.js +62 -0
- package/src/core/scope-manager.d.ts +168 -0
- package/src/core/scope-manager.js +213 -0
- package/src/core/security-check.d.ts +72 -0
- package/src/core/security-check.js +174 -0
- package/src/core/shutdown-hooks.d.ts +22 -0
- package/src/core/shutdown-hooks.js +56 -0
- package/src/core/targets.d.ts +60 -0
- package/src/core/targets.js +164 -0
- package/src/core/token-store.d.ts +54 -0
- package/src/core/token-store.js +314 -0
- package/src/core/tool-client.d.ts +176 -0
- package/src/core/tool-client.js +380 -0
- package/src/core/tool-scopes.d.ts +153 -0
- package/src/core/tool-scopes.js +326 -0
- package/src/core/tools-config.d.ts +55 -0
- package/src/core/tools-config.js +137 -0
- package/src/core/types.d.ts +87 -0
- package/src/core/types.js +11 -0
- package/src/core/uat-client.d.ts +46 -0
- package/src/core/uat-client.js +187 -0
- package/src/core/version.d.ts +25 -0
- package/src/core/version.js +49 -0
- package/src/messaging/converters/audio.d.ts +8 -0
- package/src/messaging/converters/audio.js +21 -0
- package/src/messaging/converters/calendar.d.ts +13 -0
- package/src/messaging/converters/calendar.js +50 -0
- package/src/messaging/converters/content-converter.d.ts +41 -0
- package/src/messaging/converters/content-converter.js +106 -0
- package/src/messaging/converters/file.d.ts +8 -0
- package/src/messaging/converters/file.js +20 -0
- package/src/messaging/converters/folder.d.ts +8 -0
- package/src/messaging/converters/folder.js +20 -0
- package/src/messaging/converters/hongbao.d.ts +8 -0
- package/src/messaging/converters/hongbao.js +16 -0
- package/src/messaging/converters/image.d.ts +8 -0
- package/src/messaging/converters/image.js +18 -0
- package/src/messaging/converters/index.d.ts +8 -0
- package/src/messaging/converters/index.js +50 -0
- package/src/messaging/converters/interactive/card-converter.d.ts +76 -0
- package/src/messaging/converters/interactive/card-converter.js +1173 -0
- package/src/messaging/converters/interactive/card-utils.d.ts +9 -0
- package/src/messaging/converters/interactive/card-utils.js +42 -0
- package/src/messaging/converters/interactive/index.d.ts +8 -0
- package/src/messaging/converters/interactive/index.js +21 -0
- package/src/messaging/converters/interactive/legacy.d.ts +11 -0
- package/src/messaging/converters/interactive/legacy.js +57 -0
- package/src/messaging/converters/interactive/types.d.ts +23 -0
- package/src/messaging/converters/interactive/types.js +24 -0
- package/src/messaging/converters/location.d.ts +8 -0
- package/src/messaging/converters/location.js +19 -0
- package/src/messaging/converters/merge-forward.d.ts +32 -0
- package/src/messaging/converters/merge-forward.js +225 -0
- package/src/messaging/converters/post.d.ts +11 -0
- package/src/messaging/converters/post.js +135 -0
- package/src/messaging/converters/share.d.ts +9 -0
- package/src/messaging/converters/share.js +23 -0
- package/src/messaging/converters/sticker.d.ts +8 -0
- package/src/messaging/converters/sticker.js +18 -0
- package/src/messaging/converters/system.d.ts +12 -0
- package/src/messaging/converters/system.js +32 -0
- package/src/messaging/converters/text.d.ts +8 -0
- package/src/messaging/converters/text.js +14 -0
- package/src/messaging/converters/todo.d.ts +8 -0
- package/src/messaging/converters/todo.js +41 -0
- package/src/messaging/converters/types.d.ts +107 -0
- package/src/messaging/converters/types.js +7 -0
- package/src/messaging/converters/unknown.d.ts +8 -0
- package/src/messaging/converters/unknown.js +16 -0
- package/src/messaging/converters/utils.d.ts +22 -0
- package/src/messaging/converters/utils.js +51 -0
- package/src/messaging/converters/video-chat.d.ts +8 -0
- package/src/messaging/converters/video-chat.js +23 -0
- package/src/messaging/converters/video.d.ts +8 -0
- package/src/messaging/converters/video.js +32 -0
- package/src/messaging/converters/vote.d.ts +8 -0
- package/src/messaging/converters/vote.js +24 -0
- package/src/messaging/inbound/dedup.d.ts +59 -0
- package/src/messaging/inbound/dedup.js +116 -0
- package/src/messaging/inbound/dispatch-builders.d.ts +84 -0
- package/src/messaging/inbound/dispatch-builders.js +152 -0
- package/src/messaging/inbound/dispatch-commands.d.ts +27 -0
- package/src/messaging/inbound/dispatch-commands.js +112 -0
- package/src/messaging/inbound/dispatch-context.d.ts +67 -0
- package/src/messaging/inbound/dispatch-context.js +136 -0
- package/src/messaging/inbound/dispatch.d.ts +47 -0
- package/src/messaging/inbound/dispatch.js +264 -0
- package/src/messaging/inbound/enrich.d.ts +102 -0
- package/src/messaging/inbound/enrich.js +227 -0
- package/src/messaging/inbound/gate-effects.d.ts +23 -0
- package/src/messaging/inbound/gate-effects.js +43 -0
- package/src/messaging/inbound/gate.d.ts +60 -0
- package/src/messaging/inbound/gate.js +233 -0
- package/src/messaging/inbound/handler.d.ts +35 -0
- package/src/messaging/inbound/handler.js +173 -0
- package/src/messaging/inbound/media-resolver.d.ts +32 -0
- package/src/messaging/inbound/media-resolver.js +87 -0
- package/src/messaging/inbound/mention.d.ts +39 -0
- package/src/messaging/inbound/mention.js +81 -0
- package/src/messaging/inbound/parse-io.d.ts +50 -0
- package/src/messaging/inbound/parse-io.js +81 -0
- package/src/messaging/inbound/parse.d.ts +28 -0
- package/src/messaging/inbound/parse.js +106 -0
- package/src/messaging/inbound/permission.d.ts +17 -0
- package/src/messaging/inbound/permission.js +40 -0
- package/src/messaging/inbound/policy.d.ts +94 -0
- package/src/messaging/inbound/policy.js +160 -0
- package/src/messaging/inbound/reaction-handler.d.ts +61 -0
- package/src/messaging/inbound/reaction-handler.js +221 -0
- package/src/messaging/inbound/user-name-cache.d.ts +82 -0
- package/src/messaging/inbound/user-name-cache.js +241 -0
- package/src/messaging/outbound/actions.d.ts +16 -0
- package/src/messaging/outbound/actions.js +309 -0
- package/src/messaging/outbound/chat-manage.d.ts +64 -0
- package/src/messaging/outbound/chat-manage.js +111 -0
- package/src/messaging/outbound/deliver.d.ts +155 -0
- package/src/messaging/outbound/deliver.js +298 -0
- package/src/messaging/outbound/fetch.d.ts +12 -0
- package/src/messaging/outbound/fetch.js +12 -0
- package/src/messaging/outbound/forward.d.ts +26 -0
- package/src/messaging/outbound/forward.js +48 -0
- package/src/messaging/outbound/media-url-utils.d.ts +29 -0
- package/src/messaging/outbound/media-url-utils.js +130 -0
- package/src/messaging/outbound/media.d.ts +260 -0
- package/src/messaging/outbound/media.js +758 -0
- package/src/messaging/outbound/outbound.d.ts +89 -0
- package/src/messaging/outbound/outbound.js +121 -0
- package/src/messaging/outbound/reactions.d.ts +124 -0
- package/src/messaging/outbound/reactions.js +378 -0
- package/src/messaging/outbound/send.d.ts +152 -0
- package/src/messaging/outbound/send.js +355 -0
- package/src/messaging/outbound/typing.d.ts +71 -0
- package/src/messaging/outbound/typing.js +179 -0
- package/src/messaging/shared/message-lookup.d.ts +54 -0
- package/src/messaging/shared/message-lookup.js +117 -0
- package/src/messaging/types.d.ts +176 -0
- package/src/messaging/types.js +10 -0
- package/src/tools/auto-auth.d.ts +56 -0
- package/src/tools/auto-auth.js +919 -0
- package/src/tools/helpers.d.ts +260 -0
- package/src/tools/helpers.js +364 -0
- package/src/tools/mcp/doc/create.d.ts +12 -0
- package/src/tools/mcp/doc/create.js +44 -0
- package/src/tools/mcp/doc/fetch.d.ts +12 -0
- package/src/tools/mcp/doc/fetch.js +36 -0
- package/src/tools/mcp/doc/index.d.ts +12 -0
- package/src/tools/mcp/doc/index.js +41 -0
- package/src/tools/mcp/doc/update.d.ts +12 -0
- package/src/tools/mcp/doc/update.js +61 -0
- package/src/tools/mcp/shared.d.ts +59 -0
- package/src/tools/mcp/shared.js +226 -0
- package/src/tools/oapi/bitable/app-table-field.d.ts +16 -0
- package/src/tools/oapi/bitable/app-table-field.js +222 -0
- package/src/tools/oapi/bitable/app-table-record.d.ts +20 -0
- package/src/tools/oapi/bitable/app-table-record.js +436 -0
- package/src/tools/oapi/bitable/app-table-view.d.ts +17 -0
- package/src/tools/oapi/bitable/app-table-view.js +195 -0
- package/src/tools/oapi/bitable/app-table.d.ts +19 -0
- package/src/tools/oapi/bitable/app-table.js +247 -0
- package/src/tools/oapi/bitable/app.d.ts +18 -0
- package/src/tools/oapi/bitable/app.js +186 -0
- package/src/tools/oapi/bitable/index.d.ts +9 -0
- package/src/tools/oapi/bitable/index.js +9 -0
- package/src/tools/oapi/calendar/calendar.d.ts +15 -0
- package/src/tools/oapi/calendar/calendar.js +122 -0
- package/src/tools/oapi/calendar/event-attendee.d.ts +16 -0
- package/src/tools/oapi/calendar/event-attendee.js +263 -0
- package/src/tools/oapi/calendar/event.d.ts +16 -0
- package/src/tools/oapi/calendar/event.js +709 -0
- package/src/tools/oapi/calendar/freebusy.d.ts +13 -0
- package/src/tools/oapi/calendar/freebusy.js +111 -0
- package/src/tools/oapi/calendar/index.d.ts +8 -0
- package/src/tools/oapi/calendar/index.js +8 -0
- package/src/tools/oapi/chat/chat.d.ts +16 -0
- package/src/tools/oapi/chat/chat.js +124 -0
- package/src/tools/oapi/chat/index.d.ts +10 -0
- package/src/tools/oapi/chat/index.js +15 -0
- package/src/tools/oapi/chat/members.d.ts +11 -0
- package/src/tools/oapi/chat/members.js +81 -0
- package/src/tools/oapi/common/get-user.d.ts +12 -0
- package/src/tools/oapi/common/get-user.js +106 -0
- package/src/tools/oapi/common/index.d.ts +6 -0
- package/src/tools/oapi/common/index.js +6 -0
- package/src/tools/oapi/common/search-user.d.ts +11 -0
- package/src/tools/oapi/common/search-user.js +73 -0
- package/src/tools/oapi/drive/doc-comments.d.ts +15 -0
- package/src/tools/oapi/drive/doc-comments.js +279 -0
- package/src/tools/oapi/drive/doc-media.d.ts +19 -0
- package/src/tools/oapi/drive/doc-media.js +335 -0
- package/src/tools/oapi/drive/file.d.ts +19 -0
- package/src/tools/oapi/drive/file.js +483 -0
- package/src/tools/oapi/drive/index.d.ts +12 -0
- package/src/tools/oapi/drive/index.js +36 -0
- package/src/tools/oapi/helpers.d.ts +182 -0
- package/src/tools/oapi/helpers.js +354 -0
- package/src/tools/oapi/im/format-messages.d.ts +50 -0
- package/src/tools/oapi/im/format-messages.js +165 -0
- package/src/tools/oapi/im/index.d.ts +10 -0
- package/src/tools/oapi/im/index.js +17 -0
- package/src/tools/oapi/im/message-read.d.ts +13 -0
- package/src/tools/oapi/im/message-read.js +411 -0
- package/src/tools/oapi/im/message.d.ts +16 -0
- package/src/tools/oapi/im/message.js +149 -0
- package/src/tools/oapi/im/resource.d.ts +13 -0
- package/src/tools/oapi/im/resource.js +150 -0
- package/src/tools/oapi/im/time-utils.d.ts +46 -0
- package/src/tools/oapi/im/time-utils.js +201 -0
- package/src/tools/oapi/im/user-name-uat.d.ts +26 -0
- package/src/tools/oapi/im/user-name-uat.js +140 -0
- package/src/tools/oapi/index.d.ts +11 -0
- package/src/tools/oapi/index.js +58 -0
- package/src/tools/oapi/sdk-types.d.ts +96 -0
- package/src/tools/oapi/sdk-types.js +12 -0
- package/src/tools/oapi/search/doc-search.d.ts +13 -0
- package/src/tools/oapi/search/doc-search.js +191 -0
- package/src/tools/oapi/search/index.d.ts +12 -0
- package/src/tools/oapi/search/index.js +33 -0
- package/src/tools/oapi/sheets/index.d.ts +12 -0
- package/src/tools/oapi/sheets/index.js +31 -0
- package/src/tools/oapi/sheets/sheet.d.ts +16 -0
- package/src/tools/oapi/sheets/sheet.js +652 -0
- package/src/tools/oapi/task/comment.d.ts +15 -0
- package/src/tools/oapi/task/comment.js +140 -0
- package/src/tools/oapi/task/index.d.ts +8 -0
- package/src/tools/oapi/task/index.js +8 -0
- package/src/tools/oapi/task/subtask.d.ts +14 -0
- package/src/tools/oapi/task/subtask.js +162 -0
- package/src/tools/oapi/task/task.d.ts +16 -0
- package/src/tools/oapi/task/task.js +344 -0
- package/src/tools/oapi/task/tasklist.d.ts +21 -0
- package/src/tools/oapi/task/tasklist.js +321 -0
- package/src/tools/oapi/wiki/index.d.ts +12 -0
- package/src/tools/oapi/wiki/index.js +34 -0
- package/src/tools/oapi/wiki/space-node.d.ts +17 -0
- package/src/tools/oapi/wiki/space-node.js +230 -0
- package/src/tools/oapi/wiki/space.d.ts +15 -0
- package/src/tools/oapi/wiki/space.js +130 -0
- package/src/tools/oauth-batch-auth.d.ts +11 -0
- package/src/tools/oauth-batch-auth.js +142 -0
- package/src/tools/oauth-cards.d.ts +39 -0
- package/src/tools/oauth-cards.js +315 -0
- package/src/tools/oauth.d.ts +47 -0
- package/src/tools/oauth.js +620 -0
- package/src/tools/onboarding-auth.d.ts +27 -0
- package/src/tools/onboarding-auth.js +130 -0
- package/src/tools/tat/im/index.d.ts +15 -0
- package/src/tools/tat/im/index.js +18 -0
- package/src/tools/tat/im/resource.d.ts +15 -0
- package/src/tools/tat/im/resource.js +157 -0
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026 ByteDance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*
|
|
5
|
+
* 消息格式化公共函数
|
|
6
|
+
*
|
|
7
|
+
* 将飞书 IM API 返回的原始消息对象转换为 AI 可读的 JSON 格式。
|
|
8
|
+
* 由 feishu_im_user_get_messages 和 feishu_im_user_get_thread_messages 共享。
|
|
9
|
+
*
|
|
10
|
+
* 所有 API 调用均通过 UAT(用户身份)进行。
|
|
11
|
+
*/
|
|
12
|
+
import { larkLogger } from '../../../core/lark-logger';
|
|
13
|
+
import { convertMessageContent, buildConvertContextFromItem, extractMentionOpenId, } from '../../../messaging/converters/content-converter';
|
|
14
|
+
import { getUATUserName, setUATUserNames, batchResolveUserNamesAsUser } from './user-name-uat';
|
|
15
|
+
import { millisStringToDateTime } from './time-utils';
|
|
16
|
+
const log = larkLogger('oapi/im/format-messages');
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
// UAT callbacks for merge_forward expansion
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
/** 通过 UAT 获取合并转发子消息 */
|
|
21
|
+
function createUATFetchSubMessages(client) {
|
|
22
|
+
return async (messageId) => {
|
|
23
|
+
const res = await client.invokeByPath('feishu_im_user_get_messages.default', `/open-apis/im/v1/messages/${messageId}`, {
|
|
24
|
+
method: 'GET',
|
|
25
|
+
query: { user_id_type: 'open_id', card_msg_content_type: 'raw_card_content' },
|
|
26
|
+
as: 'user',
|
|
27
|
+
});
|
|
28
|
+
if (res.code !== 0) {
|
|
29
|
+
throw new Error(`API error: code=${res.code} msg=${res.msg}`);
|
|
30
|
+
}
|
|
31
|
+
return res.data?.items ?? [];
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
// Public API
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
/**
|
|
38
|
+
* 格式化单条消息对象。
|
|
39
|
+
*
|
|
40
|
+
* 使用 convertMessageContent 将 body.content 转为 AI 可读文本,
|
|
41
|
+
* 并过滤掉 AI 不需要的字段(upper_message_id、tenant_key 等)。
|
|
42
|
+
*/
|
|
43
|
+
export async function formatMessageItem(item, accountId, nameResolver, ctxOverrides) {
|
|
44
|
+
const messageId = item.message_id ?? '';
|
|
45
|
+
const msgType = item.msg_type ?? 'unknown';
|
|
46
|
+
// 使用 converter 处理消息内容
|
|
47
|
+
let content = '';
|
|
48
|
+
try {
|
|
49
|
+
const rawContent = item.body?.content ?? '';
|
|
50
|
+
if (rawContent) {
|
|
51
|
+
const ctx = {
|
|
52
|
+
...buildConvertContextFromItem(item, messageId, accountId),
|
|
53
|
+
...ctxOverrides,
|
|
54
|
+
};
|
|
55
|
+
const result = await convertMessageContent(rawContent, msgType, ctx);
|
|
56
|
+
content = result.content;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
log.warn('converter failed, falling back to raw content', {
|
|
61
|
+
messageId,
|
|
62
|
+
msgType,
|
|
63
|
+
error: err instanceof Error ? err.message : String(err),
|
|
64
|
+
});
|
|
65
|
+
content = item.body?.content ?? '';
|
|
66
|
+
}
|
|
67
|
+
// 构建 sender(从 UAT 缓存中读取名字)
|
|
68
|
+
const senderId = item.sender?.id ?? '';
|
|
69
|
+
const senderType = item.sender?.sender_type ?? 'unknown';
|
|
70
|
+
let senderName;
|
|
71
|
+
if (senderId && senderType === 'user') {
|
|
72
|
+
senderName = nameResolver(senderId);
|
|
73
|
+
}
|
|
74
|
+
const sender = {
|
|
75
|
+
id: senderId,
|
|
76
|
+
sender_type: senderType,
|
|
77
|
+
};
|
|
78
|
+
if (senderName) {
|
|
79
|
+
sender.name = senderName;
|
|
80
|
+
}
|
|
81
|
+
// 构建 mentions(简化格式)
|
|
82
|
+
let mentions;
|
|
83
|
+
if (item.mentions && item.mentions.length > 0) {
|
|
84
|
+
mentions = item.mentions.map((m) => ({
|
|
85
|
+
key: m.key ?? '',
|
|
86
|
+
id: extractMentionOpenId(m.id),
|
|
87
|
+
name: m.name ?? '',
|
|
88
|
+
}));
|
|
89
|
+
}
|
|
90
|
+
// 转换 create_time(飞书 API 返回毫秒时间戳字符串 → ISO 8601 +08:00)
|
|
91
|
+
const createTime = item.create_time ? millisStringToDateTime(item.create_time) : '';
|
|
92
|
+
const formatted = {
|
|
93
|
+
message_id: messageId,
|
|
94
|
+
msg_type: msgType,
|
|
95
|
+
content,
|
|
96
|
+
sender,
|
|
97
|
+
create_time: createTime,
|
|
98
|
+
deleted: item.deleted ?? false,
|
|
99
|
+
updated: item.updated ?? false,
|
|
100
|
+
};
|
|
101
|
+
// 可选字段
|
|
102
|
+
// reply_to(parent_id)和 thread_id 的展示逻辑参考 Go MCP:
|
|
103
|
+
// - 有 thread_id 时只展示 thread_id,省略 reply_to(话题上下文可推断)
|
|
104
|
+
// - 无 thread_id 但有 parent_id 时,展示为 reply_to
|
|
105
|
+
if (item.thread_id) {
|
|
106
|
+
formatted.thread_id = item.thread_id;
|
|
107
|
+
}
|
|
108
|
+
else if (item.parent_id) {
|
|
109
|
+
formatted.reply_to = item.parent_id;
|
|
110
|
+
}
|
|
111
|
+
if (mentions) {
|
|
112
|
+
formatted.mentions = mentions;
|
|
113
|
+
}
|
|
114
|
+
return formatted;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* 批量格式化消息列表(UAT 路径)。
|
|
118
|
+
*
|
|
119
|
+
* 先批量解析所有 sender 的名字(写入 UAT 缓存),再逐条格式化。
|
|
120
|
+
* 这样 formatMessageItem 中的 sender.name 和 converter 的
|
|
121
|
+
* resolveUserName 都能从 UAT 缓存中读到名字。
|
|
122
|
+
*/
|
|
123
|
+
export async function formatMessageList(items, account, log, client) {
|
|
124
|
+
const accountId = account.accountId;
|
|
125
|
+
const nameResolver = (openId) => getUATUserName(accountId, openId);
|
|
126
|
+
// 1. 把 mention 自带的名字写入 UAT 缓存(免费信息)
|
|
127
|
+
const mentionNames = new Map();
|
|
128
|
+
for (const item of items) {
|
|
129
|
+
for (const m of item.mentions ?? []) {
|
|
130
|
+
const openId = extractMentionOpenId(m.id);
|
|
131
|
+
if (openId && m.name) {
|
|
132
|
+
mentionNames.set(openId, m.name);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
if (mentionNames.size > 0) {
|
|
137
|
+
setUATUserNames(accountId, mentionNames);
|
|
138
|
+
}
|
|
139
|
+
// 2. 收集所有 user 类型 sender 的 open_id
|
|
140
|
+
const senderIds = [
|
|
141
|
+
...new Set(items
|
|
142
|
+
.map((item) => (item.sender?.sender_type === 'user' ? item.sender.id : undefined))
|
|
143
|
+
.filter((id) => !!id)),
|
|
144
|
+
];
|
|
145
|
+
// 3. 批量解析 UAT 缓存中缺失的名字
|
|
146
|
+
if (senderIds.length > 0) {
|
|
147
|
+
const missing = senderIds.filter((id) => getUATUserName(accountId, id) === undefined);
|
|
148
|
+
if (missing.length > 0) {
|
|
149
|
+
await batchResolveUserNamesAsUser({ client, openIds: missing, log });
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
// 4. 构建 merge_forward 展开所需的回调
|
|
153
|
+
const uatBatchResolve = async (openIds) => {
|
|
154
|
+
await batchResolveUserNamesAsUser({ client, openIds, log });
|
|
155
|
+
};
|
|
156
|
+
const ctxOverrides = {
|
|
157
|
+
account,
|
|
158
|
+
accountId,
|
|
159
|
+
resolveUserName: nameResolver,
|
|
160
|
+
batchResolveNames: uatBatchResolve,
|
|
161
|
+
fetchSubMessages: createUATFetchSubMessages(client),
|
|
162
|
+
};
|
|
163
|
+
// 5. 逐条格式化
|
|
164
|
+
return Promise.all(items.map((item) => formatMessageItem(item, accountId, nameResolver, ctxOverrides)));
|
|
165
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026 ByteDance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*
|
|
5
|
+
* IM Tools Index
|
|
6
|
+
*
|
|
7
|
+
* 即时通讯相关工具
|
|
8
|
+
*/
|
|
9
|
+
import type { OpenClawPluginApi } from 'openclaw/plugin-sdk';
|
|
10
|
+
export declare function registerFeishuImTools(api: OpenClawPluginApi): void;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026 ByteDance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*
|
|
5
|
+
* IM Tools Index
|
|
6
|
+
*
|
|
7
|
+
* 即时通讯相关工具
|
|
8
|
+
*/
|
|
9
|
+
import { registerFeishuImUserMessageTool } from './message';
|
|
10
|
+
import { registerFeishuImUserFetchResourceTool } from './resource';
|
|
11
|
+
import { registerMessageReadTools } from './message-read';
|
|
12
|
+
export function registerFeishuImTools(api) {
|
|
13
|
+
registerFeishuImUserMessageTool(api);
|
|
14
|
+
registerFeishuImUserFetchResourceTool(api);
|
|
15
|
+
registerMessageReadTools(api);
|
|
16
|
+
api.logger.info?.('feishu_im: Registered feishu_im_user_message, feishu_im_user_fetch_resource, feishu_im_user_get_messages, feishu_im_user_get_thread_messages, feishu_im_user_search_messages');
|
|
17
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026 ByteDance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*
|
|
5
|
+
* 消息读取工具集 -- 以用户身份获取/搜索飞书消息
|
|
6
|
+
*
|
|
7
|
+
* 包含:
|
|
8
|
+
* - feishu_im_user_get_messages (chat_id / open_id → 会话消息)
|
|
9
|
+
* - feishu_im_user_get_thread_messages (thread_id → 话题消息)
|
|
10
|
+
* - feishu_im_user_search_messages (跨会话关键词搜索)
|
|
11
|
+
*/
|
|
12
|
+
import type { OpenClawPluginApi } from 'openclaw/plugin-sdk';
|
|
13
|
+
export declare function registerMessageReadTools(api: OpenClawPluginApi): void;
|
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026 ByteDance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*
|
|
5
|
+
* 消息读取工具集 -- 以用户身份获取/搜索飞书消息
|
|
6
|
+
*
|
|
7
|
+
* 包含:
|
|
8
|
+
* - feishu_im_user_get_messages (chat_id / open_id → 会话消息)
|
|
9
|
+
* - feishu_im_user_get_thread_messages (thread_id → 话题消息)
|
|
10
|
+
* - feishu_im_user_search_messages (跨会话关键词搜索)
|
|
11
|
+
*/
|
|
12
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
13
|
+
import { Type } from '@sinclair/typebox';
|
|
14
|
+
import { assertLarkOk, createToolContext, getFirstAccount, handleInvokeErrorWithAutoAuth, json, registerTool, StringEnum } from '../helpers';
|
|
15
|
+
import { dateTimeToSecondsString, parseTimeRangeToSeconds } from './time-utils';
|
|
16
|
+
import { formatMessageList } from './format-messages';
|
|
17
|
+
import { getUATUserName, batchResolveUserNamesAsUser } from './user-name-uat';
|
|
18
|
+
// ===========================================================================
|
|
19
|
+
// Shared helpers
|
|
20
|
+
// ===========================================================================
|
|
21
|
+
function sortRuleToSortType(rule) {
|
|
22
|
+
return rule === 'create_time_asc' ? 'ByCreateTimeAsc' : 'ByCreateTimeDesc';
|
|
23
|
+
}
|
|
24
|
+
/** open_id → chat_id (P2P 单聊) */
|
|
25
|
+
async function resolveP2PChatId(client, openId, log) {
|
|
26
|
+
const res = await client.invokeByPath('feishu_im_user_get_messages.default', '/open-apis/im/v1/chat_p2p/batch_query', {
|
|
27
|
+
method: 'POST',
|
|
28
|
+
body: { chatter_ids: [openId] },
|
|
29
|
+
query: { user_id_type: 'open_id' },
|
|
30
|
+
as: 'user',
|
|
31
|
+
});
|
|
32
|
+
const chats = res.data?.p2p_chats;
|
|
33
|
+
if (!chats?.length) {
|
|
34
|
+
log.info(`batch_query: no p2p chat found for open_id=${openId}`);
|
|
35
|
+
throw new Error(`no 1-on-1 chat found with open_id=${openId}. You may not have chat history with this user.`);
|
|
36
|
+
}
|
|
37
|
+
log.info(`batch_query: resolved chat_id=${chats[0].chat_id}`);
|
|
38
|
+
return chats[0].chat_id;
|
|
39
|
+
}
|
|
40
|
+
/** 解析时间参数,返回秒级时间戳字符串 */
|
|
41
|
+
function resolveTimeRange(p, logInfo) {
|
|
42
|
+
if (p.relative_time) {
|
|
43
|
+
const range = parseTimeRangeToSeconds(p.relative_time);
|
|
44
|
+
logInfo(`relative_time="${p.relative_time}" → start=${range.start}, end=${range.end}`);
|
|
45
|
+
return range;
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
start: p.start_time ? dateTimeToSecondsString(p.start_time) : undefined,
|
|
49
|
+
end: p.end_time ? dateTimeToSecondsString(p.end_time) : undefined,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
/** 格式化 message.list 结果并返回 */
|
|
53
|
+
async function formatAndReturn(res, config, log, client) {
|
|
54
|
+
const items = res.data?.items ?? [];
|
|
55
|
+
const account = getFirstAccount(config);
|
|
56
|
+
const messages = await formatMessageList(items, account, (...args) => log.info(args.map(String).join(' ')), client);
|
|
57
|
+
const hasMore = res.data?.has_more ?? false;
|
|
58
|
+
const pageToken = res.data?.page_token;
|
|
59
|
+
log.info(`list: returned ${messages.length} messages, has_more=${hasMore}`);
|
|
60
|
+
return json({ messages, has_more: hasMore, page_token: pageToken });
|
|
61
|
+
}
|
|
62
|
+
// ===========================================================================
|
|
63
|
+
// feishu_im_user_get_messages
|
|
64
|
+
// ===========================================================================
|
|
65
|
+
const GetMessagesSchema = Type.Object({
|
|
66
|
+
open_id: Type.Optional(Type.String({
|
|
67
|
+
description: '用户 open_id(ou_xxx),获取与该用户的单聊消息。与 chat_id 互斥',
|
|
68
|
+
})),
|
|
69
|
+
chat_id: Type.Optional(Type.String({
|
|
70
|
+
description: '会话 ID(oc_xxx),支持单聊和群聊。与 open_id 互斥',
|
|
71
|
+
})),
|
|
72
|
+
sort_rule: Type.Optional(StringEnum(['create_time_asc', 'create_time_desc'], {
|
|
73
|
+
description: '排序方式,默认 create_time_desc(最新消息在前)',
|
|
74
|
+
})),
|
|
75
|
+
page_size: Type.Optional(Type.Number({ description: '每页消息数(1-50),默认 50', minimum: 1, maximum: 50 })),
|
|
76
|
+
page_token: Type.Optional(Type.String({ description: '分页标记,用于获取下一页' })),
|
|
77
|
+
relative_time: Type.Optional(Type.String({
|
|
78
|
+
description: '相对时间范围:today / yesterday / day_before_yesterday / this_week / last_week / this_month / last_month / last_{N}_{unit}(unit: minutes/hours/days)。与 start_time/end_time 互斥',
|
|
79
|
+
})),
|
|
80
|
+
start_time: Type.Optional(Type.String({
|
|
81
|
+
description: '起始时间(ISO 8601 格式,如 2026-02-27T00:00:00+08:00)。与 relative_time 互斥',
|
|
82
|
+
})),
|
|
83
|
+
end_time: Type.Optional(Type.String({
|
|
84
|
+
description: '结束时间(ISO 8601 格式,如 2026-02-27T23:59:59+08:00)。与 relative_time 互斥',
|
|
85
|
+
})),
|
|
86
|
+
});
|
|
87
|
+
function registerGetMessages(api) {
|
|
88
|
+
if (!api.config)
|
|
89
|
+
return;
|
|
90
|
+
const config = api.config;
|
|
91
|
+
const { toolClient, log } = createToolContext(api, 'feishu_im_user_get_messages');
|
|
92
|
+
registerTool(api, {
|
|
93
|
+
name: 'feishu_im_user_get_messages',
|
|
94
|
+
label: 'Feishu: Get IM Messages',
|
|
95
|
+
description: '【以用户身份】获取群聊或单聊的历史消息。' +
|
|
96
|
+
'\n\n用法:' +
|
|
97
|
+
'\n- 通过 chat_id 获取群聊/单聊消息' +
|
|
98
|
+
'\n- 通过 open_id 获取与指定用户的单聊消息(自动解析 chat_id)' +
|
|
99
|
+
'\n- 支持时间范围过滤:relative_time(如 today、last_3_days)或 start_time/end_time(ISO 8601 格式)' +
|
|
100
|
+
'\n- 支持分页:page_size + page_token' +
|
|
101
|
+
'\n\n【参数约束】' +
|
|
102
|
+
'\n- open_id 和 chat_id 必须二选一,不能同时提供' +
|
|
103
|
+
'\n- relative_time 和 start_time/end_time 不能同时使用' +
|
|
104
|
+
'\n- page_size 范围 1-50,默认 50' +
|
|
105
|
+
'\n\n返回消息列表,每条消息包含 message_id、msg_type、content(AI 可读文本)、sender、create_time 等字段。',
|
|
106
|
+
parameters: GetMessagesSchema,
|
|
107
|
+
async execute(_toolCallId, params) {
|
|
108
|
+
const p = params;
|
|
109
|
+
try {
|
|
110
|
+
if (p.open_id && p.chat_id) {
|
|
111
|
+
return json({ error: 'cannot provide both open_id and chat_id, please provide only one' });
|
|
112
|
+
}
|
|
113
|
+
if (!p.open_id && !p.chat_id) {
|
|
114
|
+
return json({ error: 'either open_id or chat_id is required' });
|
|
115
|
+
}
|
|
116
|
+
if (p.relative_time && (p.start_time || p.end_time)) {
|
|
117
|
+
return json({ error: 'cannot use both relative_time and start_time/end_time' });
|
|
118
|
+
}
|
|
119
|
+
const client = toolClient();
|
|
120
|
+
let chatId = p.chat_id ?? '';
|
|
121
|
+
if (p.open_id) {
|
|
122
|
+
log.info(`resolving P2P chat for open_id=${p.open_id}`);
|
|
123
|
+
chatId = await resolveP2PChatId(client, p.open_id, log);
|
|
124
|
+
}
|
|
125
|
+
const time = resolveTimeRange(p, log.info);
|
|
126
|
+
log.info(`list: chat_id=${chatId}, sort=${p.sort_rule ?? 'create_time_desc'}, page_size=${p.page_size ?? 50}`);
|
|
127
|
+
const res = await client.invoke('feishu_im_user_get_messages.default', (sdk, opts) => sdk.im.v1.message.list({
|
|
128
|
+
params: {
|
|
129
|
+
container_id_type: 'chat',
|
|
130
|
+
container_id: chatId,
|
|
131
|
+
start_time: time.start,
|
|
132
|
+
end_time: time.end,
|
|
133
|
+
sort_type: sortRuleToSortType(p.sort_rule),
|
|
134
|
+
page_size: p.page_size ?? 50,
|
|
135
|
+
page_token: p.page_token,
|
|
136
|
+
card_msg_content_type: 'raw_card_content',
|
|
137
|
+
},
|
|
138
|
+
}, opts), {
|
|
139
|
+
as: 'user',
|
|
140
|
+
});
|
|
141
|
+
assertLarkOk(res);
|
|
142
|
+
return await formatAndReturn(res, config, log, client);
|
|
143
|
+
}
|
|
144
|
+
catch (err) {
|
|
145
|
+
return await handleInvokeErrorWithAutoAuth(err, config);
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
}, { name: 'feishu_im_user_get_messages' });
|
|
149
|
+
}
|
|
150
|
+
// ===========================================================================
|
|
151
|
+
// feishu_im_user_get_thread_messages
|
|
152
|
+
// ===========================================================================
|
|
153
|
+
const GetThreadMessagesSchema = Type.Object({
|
|
154
|
+
thread_id: Type.String({ description: '话题 ID(omt_xxx 格式)' }),
|
|
155
|
+
sort_rule: Type.Optional(StringEnum(['create_time_asc', 'create_time_desc'], {
|
|
156
|
+
description: '排序方式,默认 create_time_desc(最新消息在前)',
|
|
157
|
+
})),
|
|
158
|
+
page_size: Type.Optional(Type.Number({ description: '每页消息数(1-50),默认 50', minimum: 1, maximum: 50 })),
|
|
159
|
+
page_token: Type.Optional(Type.String({ description: '分页标记,用于获取下一页' })),
|
|
160
|
+
});
|
|
161
|
+
function registerGetThreadMessages(api) {
|
|
162
|
+
if (!api.config)
|
|
163
|
+
return;
|
|
164
|
+
const config = api.config;
|
|
165
|
+
const { toolClient, log } = createToolContext(api, 'feishu_im_user_get_thread_messages');
|
|
166
|
+
registerTool(api, {
|
|
167
|
+
name: 'feishu_im_user_get_thread_messages',
|
|
168
|
+
label: 'Feishu: Get Thread Messages',
|
|
169
|
+
description: '【以用户身份】获取话题(thread)内的消息列表。' +
|
|
170
|
+
'\n\n用法:' +
|
|
171
|
+
'\n- 通过 thread_id(omt_xxx)获取话题内的所有消息' +
|
|
172
|
+
'\n- 支持分页:page_size + page_token' +
|
|
173
|
+
'\n\n【注意】话题消息不支持时间范围过滤(飞书 API 限制)' +
|
|
174
|
+
'\n\n返回消息列表,格式同 feishu_im_user_get_messages。',
|
|
175
|
+
parameters: GetThreadMessagesSchema,
|
|
176
|
+
async execute(_toolCallId, params) {
|
|
177
|
+
const p = params;
|
|
178
|
+
try {
|
|
179
|
+
const client = toolClient();
|
|
180
|
+
log.info(`list: thread_id=${p.thread_id}, sort=${p.sort_rule ?? 'create_time_desc'}, page_size=${p.page_size ?? 50}`);
|
|
181
|
+
const res = await client.invoke('feishu_im_user_get_messages.default', (sdk, opts) => sdk.im.v1.message.list({
|
|
182
|
+
params: {
|
|
183
|
+
container_id_type: 'thread',
|
|
184
|
+
container_id: p.thread_id,
|
|
185
|
+
sort_type: sortRuleToSortType(p.sort_rule),
|
|
186
|
+
page_size: p.page_size ?? 50,
|
|
187
|
+
page_token: p.page_token,
|
|
188
|
+
card_msg_content_type: 'raw_card_content',
|
|
189
|
+
},
|
|
190
|
+
}, opts), {
|
|
191
|
+
as: 'user',
|
|
192
|
+
});
|
|
193
|
+
assertLarkOk(res);
|
|
194
|
+
return await formatAndReturn(res, config, log, client);
|
|
195
|
+
}
|
|
196
|
+
catch (err) {
|
|
197
|
+
return await handleInvokeErrorWithAutoAuth(err, config);
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
}, { name: 'feishu_im_user_get_thread_messages' });
|
|
201
|
+
}
|
|
202
|
+
// ===========================================================================
|
|
203
|
+
// feishu_im_user_search_messages
|
|
204
|
+
// ===========================================================================
|
|
205
|
+
const SearchMessagesSchema = Type.Object({
|
|
206
|
+
query: Type.Optional(Type.String({ description: '搜索关键词,匹配消息内容。可为空字符串表示不按内容过滤' })),
|
|
207
|
+
sender_ids: Type.Optional(Type.Array(Type.String({ description: '发送者的 open_id(ou_xxx)' }), {
|
|
208
|
+
description: '发送者 open_id 列表。如需根据用户名查找 open_id,请先使用 search_user 工具',
|
|
209
|
+
})),
|
|
210
|
+
chat_id: Type.Optional(Type.String({ description: '限定搜索范围的会话 ID(oc_xxx)' })),
|
|
211
|
+
mention_ids: Type.Optional(Type.Array(Type.String({ description: '被@用户的 open_id(ou_xxx)' }), { description: '被@用户的 open_id 列表' })),
|
|
212
|
+
message_type: Type.Optional(StringEnum(['file', 'image', 'media'], {
|
|
213
|
+
description: '消息类型过滤:file / image / media。为空则搜索所有类型',
|
|
214
|
+
})),
|
|
215
|
+
sender_type: Type.Optional(StringEnum(['user', 'bot', 'all'], {
|
|
216
|
+
description: '发送者类型:user / bot / all。默认 user',
|
|
217
|
+
})),
|
|
218
|
+
chat_type: Type.Optional(StringEnum(['group', 'p2p'], {
|
|
219
|
+
description: '会话类型:group(群聊)/ p2p(单聊)',
|
|
220
|
+
})),
|
|
221
|
+
relative_time: Type.Optional(Type.String({
|
|
222
|
+
description: '相对时间范围:today / yesterday / day_before_yesterday / this_week / last_week / this_month / last_month / last_{N}_{unit}(unit: minutes/hours/days)。与 start_time/end_time 互斥',
|
|
223
|
+
})),
|
|
224
|
+
start_time: Type.Optional(Type.String({
|
|
225
|
+
description: '起始时间(ISO 8601 格式,如 2026-02-27T00:00:00+08:00)。与 relative_time 互斥',
|
|
226
|
+
})),
|
|
227
|
+
end_time: Type.Optional(Type.String({
|
|
228
|
+
description: '结束时间(ISO 8601 格式,如 2026-02-27T23:59:59+08:00)。与 relative_time 互斥',
|
|
229
|
+
})),
|
|
230
|
+
page_size: Type.Optional(Type.Number({ description: '每页消息数(1-50),默认 50', minimum: 1, maximum: 50 })),
|
|
231
|
+
page_token: Type.Optional(Type.String({ description: '分页标记,用于获取下一页' })),
|
|
232
|
+
});
|
|
233
|
+
function buildSearchData(p, time) {
|
|
234
|
+
const data = {
|
|
235
|
+
query: p.query ?? '',
|
|
236
|
+
start_time: time.start,
|
|
237
|
+
end_time: time.end,
|
|
238
|
+
};
|
|
239
|
+
if (p.sender_ids?.length)
|
|
240
|
+
data.from_ids = p.sender_ids;
|
|
241
|
+
if (p.chat_id)
|
|
242
|
+
data.chat_ids = [p.chat_id];
|
|
243
|
+
if (p.mention_ids?.length)
|
|
244
|
+
data.at_chatter_ids = p.mention_ids;
|
|
245
|
+
if (p.message_type)
|
|
246
|
+
data.message_type = p.message_type;
|
|
247
|
+
if (p.sender_type && p.sender_type !== 'all')
|
|
248
|
+
data.from_type = p.sender_type;
|
|
249
|
+
if (p.chat_type)
|
|
250
|
+
data.chat_type = p.chat_type === 'group' ? 'group_chat' : 'p2p_chat';
|
|
251
|
+
return data;
|
|
252
|
+
}
|
|
253
|
+
async function fetchChatContexts(client, chatIds, logInfo, logWarn) {
|
|
254
|
+
const map = new Map();
|
|
255
|
+
if (chatIds.length === 0)
|
|
256
|
+
return map;
|
|
257
|
+
try {
|
|
258
|
+
logInfo(`batch_query: requesting ${chatIds.length} chat_ids: ${chatIds.join(', ')}`);
|
|
259
|
+
const res = await client.invokeByPath('feishu_im_user_search_messages.default', '/open-apis/im/v1/chats/batch_query', {
|
|
260
|
+
method: 'POST',
|
|
261
|
+
body: { chat_ids: chatIds },
|
|
262
|
+
query: { user_id_type: 'open_id' },
|
|
263
|
+
as: 'user',
|
|
264
|
+
});
|
|
265
|
+
logInfo(`batch_query: response code=${res.code}, msg=${res.msg}, items=${res.data?.items?.length ?? 0}`);
|
|
266
|
+
if (res.code !== 0) {
|
|
267
|
+
logWarn(`batch_query: API returned error code=${res.code}, msg=${res.msg}`);
|
|
268
|
+
}
|
|
269
|
+
for (const c of res.data?.items ?? []) {
|
|
270
|
+
if (c.chat_id) {
|
|
271
|
+
map.set(c.chat_id, {
|
|
272
|
+
name: c.name ?? '',
|
|
273
|
+
chat_mode: c.chat_mode ?? '',
|
|
274
|
+
p2p_target_id: c.p2p_target_id,
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
catch (err) {
|
|
280
|
+
logInfo(`batch_query chats failed, skipping: ${err}`);
|
|
281
|
+
}
|
|
282
|
+
return map;
|
|
283
|
+
}
|
|
284
|
+
async function resolveP2PTargetNames(chatMap, client, logFn) {
|
|
285
|
+
const ids = [...new Set([...chatMap.values()].map((c) => c.p2p_target_id).filter((id) => !!id))];
|
|
286
|
+
if (ids.length > 0) {
|
|
287
|
+
await batchResolveUserNamesAsUser({ client, openIds: ids, log: logFn });
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
function enrichMessages(messages, items, chatMap, nameResolver) {
|
|
291
|
+
return messages.map((msg, idx) => {
|
|
292
|
+
const chatId = items[idx]?.chat_id;
|
|
293
|
+
const ctx = chatId ? chatMap.get(chatId) : undefined;
|
|
294
|
+
if (!chatId || !ctx)
|
|
295
|
+
return { ...msg, chat_id: chatId };
|
|
296
|
+
if (ctx.chat_mode === 'p2p' && ctx.p2p_target_id) {
|
|
297
|
+
const name = nameResolver(ctx.p2p_target_id);
|
|
298
|
+
return {
|
|
299
|
+
...msg,
|
|
300
|
+
chat_id: chatId,
|
|
301
|
+
chat_type: 'p2p',
|
|
302
|
+
chat_name: name || undefined,
|
|
303
|
+
chat_partner: { open_id: ctx.p2p_target_id, name: name || undefined },
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
return {
|
|
307
|
+
...msg,
|
|
308
|
+
chat_id: chatId,
|
|
309
|
+
chat_type: ctx.chat_mode,
|
|
310
|
+
chat_name: ctx.name || undefined,
|
|
311
|
+
};
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
function registerSearchMessages(api) {
|
|
315
|
+
if (!api.config)
|
|
316
|
+
return;
|
|
317
|
+
const config = api.config;
|
|
318
|
+
const { toolClient, log } = createToolContext(api, 'feishu_im_user_search_messages');
|
|
319
|
+
registerTool(api, {
|
|
320
|
+
name: 'feishu_im_user_search_messages',
|
|
321
|
+
label: 'Feishu: Search Messages',
|
|
322
|
+
description: '【以用户身份】跨会话搜索飞书消息。' +
|
|
323
|
+
'\n\n用法:' +
|
|
324
|
+
'\n- 按关键词搜索消息内容' +
|
|
325
|
+
'\n- 按发送者、被@用户、消息类型过滤' +
|
|
326
|
+
'\n- 按时间范围过滤:relative_time 或 start_time/end_time' +
|
|
327
|
+
'\n- 限定在某个会话内搜索(chat_id)' +
|
|
328
|
+
'\n- 支持分页:page_size + page_token' +
|
|
329
|
+
'\n\n【参数约束】' +
|
|
330
|
+
'\n- 所有参数均可选,但至少应提供一个过滤条件' +
|
|
331
|
+
'\n- relative_time 和 start_time/end_time 不能同时使用' +
|
|
332
|
+
'\n- page_size 范围 1-50,默认 50' +
|
|
333
|
+
'\n\n返回消息列表,每条消息包含 message_id、msg_type、content、sender、create_time 等字段。' +
|
|
334
|
+
'\n每条消息还包含 chat_id、chat_type(p2p/group)、chat_name(群名或单聊对方名字)。' +
|
|
335
|
+
'\n单聊消息额外包含 chat_partner(对方 open_id 和名字)。' +
|
|
336
|
+
'\n搜索结果中的 chat_id 和 thread_id 可配合 feishu_im_user_get_messages / feishu_im_user_get_thread_messages 查看上下文。',
|
|
337
|
+
parameters: SearchMessagesSchema,
|
|
338
|
+
async execute(_toolCallId, params) {
|
|
339
|
+
const p = params;
|
|
340
|
+
try {
|
|
341
|
+
if (p.relative_time && (p.start_time || p.end_time)) {
|
|
342
|
+
return json({ error: 'cannot use both relative_time and start_time/end_time' });
|
|
343
|
+
}
|
|
344
|
+
const client = toolClient();
|
|
345
|
+
const account = getFirstAccount(config);
|
|
346
|
+
const logFn = (...args) => log.info(args.map(String).join(' '));
|
|
347
|
+
// 1. 搜索消息 ID
|
|
348
|
+
const time = resolveTimeRange(p, log.info);
|
|
349
|
+
const searchData = buildSearchData(p, {
|
|
350
|
+
start: time.start ?? '978307200',
|
|
351
|
+
end: time.end ?? Math.floor(Date.now() / 1000).toString(),
|
|
352
|
+
});
|
|
353
|
+
log.info(`search: query="${p.query ?? ''}", page_size=${p.page_size ?? 50}`);
|
|
354
|
+
const searchRes = await client.invoke('feishu_im_user_search_messages.default', (sdk, opts) => sdk.search.message.create({
|
|
355
|
+
data: searchData,
|
|
356
|
+
params: {
|
|
357
|
+
user_id_type: 'open_id',
|
|
358
|
+
page_size: p.page_size ?? 50,
|
|
359
|
+
page_token: p.page_token,
|
|
360
|
+
},
|
|
361
|
+
}, opts), {
|
|
362
|
+
as: 'user',
|
|
363
|
+
});
|
|
364
|
+
assertLarkOk(searchRes);
|
|
365
|
+
const messageIds = searchRes.data?.items ?? [];
|
|
366
|
+
const hasMore = searchRes.data?.has_more ?? false;
|
|
367
|
+
const pageToken = searchRes.data?.page_token;
|
|
368
|
+
log.info(`search: found ${messageIds.length} IDs, has_more=${hasMore}`);
|
|
369
|
+
if (messageIds.length === 0) {
|
|
370
|
+
return json({ messages: [], has_more: hasMore, page_token: pageToken });
|
|
371
|
+
}
|
|
372
|
+
// 2. 批量获取消息详情
|
|
373
|
+
const queryStr = messageIds.map((id) => `message_ids=${encodeURIComponent(id)}`).join('&');
|
|
374
|
+
const mgetRes = await client.invokeByPath('feishu_im_user_search_messages.default', `/open-apis/im/v1/messages/mget?${queryStr}`, {
|
|
375
|
+
method: 'GET',
|
|
376
|
+
query: { user_id_type: 'open_id', card_msg_content_type: 'raw_card_content' },
|
|
377
|
+
as: 'user',
|
|
378
|
+
});
|
|
379
|
+
const items = mgetRes.data?.items ?? [];
|
|
380
|
+
log.info(`mget: ${items.length} details`);
|
|
381
|
+
// 3. 批量获取会话信息
|
|
382
|
+
const chatIds = [
|
|
383
|
+
...new Set(items.map((i) => i.chat_id).filter(Boolean)),
|
|
384
|
+
];
|
|
385
|
+
const chatMap = await fetchChatContexts(client, chatIds, log.info, log.warn);
|
|
386
|
+
const p2pChats = [...chatMap.entries()].filter(([, v]) => v.chat_mode === 'p2p');
|
|
387
|
+
log.info(`chats: ${chatMap.size}/${chatIds.length} resolved, p2p=${p2pChats.length}`);
|
|
388
|
+
// 4. 格式化消息(填充 sender 名字缓存,使用 UAT)
|
|
389
|
+
const messages = await formatMessageList(items, account, logFn, client);
|
|
390
|
+
// 5. 解析 p2p 对方用户名(使用 UAT)
|
|
391
|
+
await resolveP2PTargetNames(chatMap, client, logFn);
|
|
392
|
+
// 6. 拼装返回
|
|
393
|
+
const uatNameResolver = (id) => getUATUserName(account.accountId, id);
|
|
394
|
+
const result = enrichMessages(messages, items, chatMap, uatNameResolver);
|
|
395
|
+
log.info(`result: ${result.length} messages, has_more=${hasMore}`);
|
|
396
|
+
return json({ messages: result, has_more: hasMore, page_token: pageToken });
|
|
397
|
+
}
|
|
398
|
+
catch (err) {
|
|
399
|
+
return await handleInvokeErrorWithAutoAuth(err, config);
|
|
400
|
+
}
|
|
401
|
+
},
|
|
402
|
+
}, { name: 'feishu_im_user_search_messages' });
|
|
403
|
+
}
|
|
404
|
+
// ===========================================================================
|
|
405
|
+
// Unified registration
|
|
406
|
+
// ===========================================================================
|
|
407
|
+
export function registerMessageReadTools(api) {
|
|
408
|
+
registerGetMessages(api);
|
|
409
|
+
registerGetThreadMessages(api);
|
|
410
|
+
registerSearchMessages(api);
|
|
411
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026 ByteDance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*
|
|
5
|
+
* feishu_im_user_message tool -- 以用户身份发送/回复 IM 消息
|
|
6
|
+
*
|
|
7
|
+
* Actions: send, reply
|
|
8
|
+
*
|
|
9
|
+
* Uses the Feishu IM API:
|
|
10
|
+
* - send: POST /open-apis/im/v1/messages?receive_id_type=...
|
|
11
|
+
* - reply: POST /open-apis/im/v1/messages/:message_id/reply
|
|
12
|
+
*
|
|
13
|
+
* 全部以用户身份(user_access_token)调用,scope 来自 real-scope.json。
|
|
14
|
+
*/
|
|
15
|
+
import type { OpenClawPluginApi } from 'openclaw/plugin-sdk';
|
|
16
|
+
export declare function registerFeishuImUserMessageTool(api: OpenClawPluginApi): void;
|