@openclaw/feishu 2026.3.12 → 2026.5.1-beta.1
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/api.ts +31 -0
- package/channel-entry.ts +20 -0
- package/channel-plugin-api.ts +1 -0
- package/contract-api.ts +16 -0
- package/index.ts +70 -53
- package/openclaw.plugin.json +1653 -4
- package/package.json +32 -7
- package/runtime-api.ts +55 -0
- package/secret-contract-api.ts +5 -0
- package/security-contract-api.ts +1 -0
- package/session-key-api.ts +1 -0
- package/setup-api.ts +3 -0
- package/setup-entry.test.ts +14 -0
- package/setup-entry.ts +13 -0
- package/src/accounts.test.ts +115 -22
- package/src/accounts.ts +199 -117
- package/src/app-registration.ts +331 -0
- package/src/approval-auth.test.ts +24 -0
- package/src/approval-auth.ts +25 -0
- package/src/async.test.ts +35 -0
- package/src/async.ts +43 -1
- package/src/audio-preflight.runtime.ts +9 -0
- package/src/bitable.test.ts +131 -0
- package/src/bitable.ts +59 -22
- package/src/bot-content.ts +474 -0
- package/src/bot-group-name.test.ts +108 -0
- package/src/bot-runtime-api.ts +12 -0
- package/src/bot-sender-name.ts +125 -0
- package/src/bot.broadcast.test.ts +463 -0
- package/src/bot.card-action.test.ts +519 -5
- package/src/bot.checkBotMentioned.test.ts +92 -20
- package/src/bot.helpers.test.ts +118 -0
- package/src/bot.stripBotMention.test.ts +13 -21
- package/src/bot.test.ts +1334 -401
- package/src/bot.ts +798 -786
- package/src/card-action.ts +408 -40
- package/src/card-interaction.test.ts +129 -0
- package/src/card-interaction.ts +159 -0
- package/src/card-test-helpers.ts +47 -0
- package/src/card-ux-approval.ts +65 -0
- package/src/card-ux-launcher.test.ts +99 -0
- package/src/card-ux-launcher.ts +121 -0
- package/src/card-ux-shared.ts +33 -0
- package/src/channel-runtime-api.ts +16 -0
- package/src/channel.runtime.ts +47 -0
- package/src/channel.test.ts +914 -3
- package/src/channel.ts +1252 -309
- package/src/chat-schema.ts +5 -4
- package/src/chat.test.ts +84 -28
- package/src/chat.ts +68 -10
- package/src/client.test.ts +212 -103
- package/src/client.ts +115 -21
- package/src/comment-dispatcher-runtime-api.ts +6 -0
- package/src/comment-dispatcher.test.ts +169 -0
- package/src/comment-dispatcher.ts +107 -0
- package/src/comment-handler-runtime-api.ts +3 -0
- package/src/comment-handler.test.ts +486 -0
- package/src/comment-handler.ts +309 -0
- package/src/comment-reaction.test.ts +166 -0
- package/src/comment-reaction.ts +259 -0
- package/src/comment-shared.test.ts +182 -0
- package/src/comment-shared.ts +365 -0
- package/src/comment-target.ts +44 -0
- package/src/config-schema.test.ts +77 -25
- package/src/config-schema.ts +31 -4
- package/src/conversation-id.test.ts +18 -0
- package/src/conversation-id.ts +199 -0
- package/src/dedup-runtime-api.ts +1 -0
- package/src/dedup.ts +76 -35
- package/src/directory.static.ts +61 -0
- package/src/directory.test.ts +119 -20
- package/src/directory.ts +61 -91
- package/src/doc-schema.ts +1 -1
- package/src/docx-batch-insert.test.ts +39 -38
- package/src/docx-batch-insert.ts +55 -19
- package/src/docx-color-text.ts +9 -4
- package/src/docx-table-ops.test.ts +53 -0
- package/src/docx-table-ops.ts +52 -34
- package/src/docx-types.ts +38 -0
- package/src/docx.account-selection.test.ts +12 -3
- package/src/docx.test.ts +314 -74
- package/src/docx.ts +278 -122
- package/src/drive-schema.ts +47 -1
- package/src/drive.test.ts +1219 -0
- package/src/drive.ts +614 -13
- package/src/dynamic-agent.ts +10 -4
- package/src/event-types.ts +45 -0
- package/src/external-keys.ts +1 -1
- package/src/lifecycle.test-support.ts +220 -0
- package/src/media.test.ts +413 -87
- package/src/media.ts +488 -154
- package/src/mention-target.types.ts +5 -0
- package/src/mention.ts +32 -51
- package/src/message-action-contract.ts +13 -0
- package/src/monitor-state-runtime-api.ts +7 -0
- package/src/monitor-transport-runtime-api.ts +7 -0
- package/src/monitor.account.ts +220 -313
- package/src/monitor.acp-init-failure.lifecycle.test-support.ts +219 -0
- package/src/monitor.bot-identity.ts +86 -0
- package/src/monitor.bot-menu-handler.ts +165 -0
- package/src/monitor.bot-menu.lifecycle.test-support.ts +224 -0
- package/src/monitor.bot-menu.test.ts +178 -0
- package/src/monitor.broadcast.reply-once.lifecycle.test-support.ts +264 -0
- package/src/monitor.card-action.lifecycle.test-support.ts +373 -0
- package/src/monitor.cleanup.test.ts +376 -0
- package/src/monitor.comment-notice-handler.ts +105 -0
- package/src/monitor.comment.test.ts +937 -0
- package/src/monitor.comment.ts +1386 -0
- package/src/monitor.lifecycle.test.ts +4 -0
- package/src/monitor.message-handler.ts +339 -0
- package/src/monitor.reaction.lifecycle.test-support.ts +68 -0
- package/src/monitor.reaction.test.ts +194 -92
- package/src/monitor.reply-once.lifecycle.test-support.ts +190 -0
- package/src/monitor.startup.test.ts +24 -36
- package/src/monitor.startup.ts +26 -16
- package/src/monitor.state.ts +20 -5
- package/src/monitor.synthetic-error.ts +18 -0
- package/src/monitor.test-mocks.ts +2 -2
- package/src/monitor.transport.ts +297 -39
- package/src/monitor.ts +15 -10
- package/src/monitor.webhook-e2e.test.ts +272 -0
- package/src/monitor.webhook-security.test.ts +125 -91
- package/src/monitor.webhook.test-helpers.ts +116 -0
- package/src/outbound-runtime-api.ts +1 -0
- package/src/outbound.test.ts +627 -53
- package/src/outbound.ts +623 -81
- package/src/perm-schema.ts +1 -1
- package/src/perm.ts +1 -7
- package/src/pins.ts +108 -0
- package/src/policy.test.ts +297 -117
- package/src/policy.ts +142 -29
- package/src/post.ts +7 -6
- package/src/probe.test.ts +122 -118
- package/src/probe.ts +26 -16
- package/src/processing-claims.ts +59 -0
- package/src/qr-terminal.ts +1 -0
- package/src/reactions.ts +23 -60
- package/src/reasoning-preview.test.ts +59 -0
- package/src/reasoning-preview.ts +20 -0
- package/src/reply-dispatcher-runtime-api.ts +7 -0
- package/src/reply-dispatcher.test.ts +721 -168
- package/src/reply-dispatcher.ts +422 -172
- package/src/runtime.ts +6 -3
- package/src/secret-contract.ts +145 -0
- package/src/secret-input.ts +1 -13
- package/src/security-audit-shared.ts +69 -0
- package/src/security-audit.test.ts +61 -0
- package/src/security-audit.ts +1 -0
- package/src/send-result.ts +1 -1
- package/src/send-target.test.ts +9 -3
- package/src/send-target.ts +10 -4
- package/src/send.reply-fallback.test.ts +127 -42
- package/src/send.test.ts +386 -4
- package/src/send.ts +486 -164
- package/src/sequential-key.test.ts +72 -0
- package/src/sequential-key.ts +28 -0
- package/src/sequential-queue.test.ts +92 -0
- package/src/sequential-queue.ts +16 -0
- package/src/session-conversation.ts +42 -0
- package/src/session-route.ts +48 -0
- package/src/setup-core.ts +51 -0
- package/src/{onboarding.test.ts → setup-surface.test.ts} +52 -21
- package/src/setup-surface.ts +581 -0
- package/src/streaming-card.test.ts +138 -2
- package/src/streaming-card.ts +134 -18
- package/src/subagent-hooks.test.ts +603 -0
- package/src/subagent-hooks.ts +397 -0
- package/src/targets.ts +3 -13
- package/src/test-support/lifecycle-test-support.ts +479 -0
- package/src/thread-bindings.test.ts +143 -0
- package/src/thread-bindings.ts +330 -0
- package/src/tool-account-routing.test.ts +66 -8
- package/src/tool-account.test.ts +44 -0
- package/src/tool-account.ts +40 -17
- package/src/tool-factory-test-harness.ts +11 -8
- package/src/tool-result.ts +3 -1
- package/src/tools-config.ts +1 -1
- package/src/types.ts +16 -15
- package/src/typing.ts +10 -6
- package/src/wiki-schema.ts +1 -1
- package/src/wiki.ts +1 -7
- package/subagent-hooks-api.ts +31 -0
- package/tsconfig.json +16 -0
- package/src/feishu-command-handler.ts +0 -59
- package/src/onboarding.status.test.ts +0 -25
- package/src/onboarding.ts +0 -489
- package/src/send-message.ts +0 -71
- package/src/targets.test.ts +0 -70
package/src/send.ts
CHANGED
|
@@ -1,21 +1,43 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
1
|
+
import { resolveMarkdownTableMode } from "openclaw/plugin-sdk/markdown-table-runtime";
|
|
2
|
+
import {
|
|
3
|
+
convertMarkdownTables,
|
|
4
|
+
normalizeLowercaseStringOrEmpty,
|
|
5
|
+
normalizeOptionalLowercaseString,
|
|
6
|
+
} from "openclaw/plugin-sdk/text-runtime";
|
|
7
|
+
import type { ClawdbotConfig } from "../runtime-api.js";
|
|
8
|
+
import { resolveFeishuRuntimeAccount } from "./accounts.js";
|
|
3
9
|
import { createFeishuClient } from "./client.js";
|
|
4
|
-
import type { MentionTarget } from "./mention.js";
|
|
5
|
-
import {
|
|
10
|
+
import type { MentionTarget } from "./mention-target.types.js";
|
|
11
|
+
import { buildMentionedCardContent, buildMentionedMessage } from "./mention.js";
|
|
6
12
|
import { parsePostContent } from "./post.js";
|
|
7
|
-
import { getFeishuRuntime } from "./runtime.js";
|
|
8
13
|
import { assertFeishuMessageApiSuccess, toFeishuSendResult } from "./send-result.js";
|
|
9
14
|
import { resolveFeishuSendTarget } from "./send-target.js";
|
|
10
15
|
import type { FeishuChatType, FeishuMessageInfo, FeishuSendResult } from "./types.js";
|
|
11
16
|
|
|
12
17
|
const WITHDRAWN_REPLY_ERROR_CODES = new Set([230011, 231003]);
|
|
18
|
+
const INTERACTIVE_CARD_FALLBACK_TEXT = "[Interactive Card]";
|
|
19
|
+
const POST_FALLBACK_TEXT = "[Rich text message]";
|
|
20
|
+
const FEISHU_CARD_TEMPLATES = new Set([
|
|
21
|
+
"blue",
|
|
22
|
+
"green",
|
|
23
|
+
"red",
|
|
24
|
+
"orange",
|
|
25
|
+
"purple",
|
|
26
|
+
"indigo",
|
|
27
|
+
"wathet",
|
|
28
|
+
"turquoise",
|
|
29
|
+
"yellow",
|
|
30
|
+
"grey",
|
|
31
|
+
"carmine",
|
|
32
|
+
"violet",
|
|
33
|
+
"lime",
|
|
34
|
+
]);
|
|
13
35
|
|
|
14
36
|
function shouldFallbackFromReplyTarget(response: { code?: number; msg?: string }): boolean {
|
|
15
37
|
if (response.code !== undefined && WITHDRAWN_REPLY_ERROR_CODES.has(response.code)) {
|
|
16
38
|
return true;
|
|
17
39
|
}
|
|
18
|
-
const msg = response.msg
|
|
40
|
+
const msg = normalizeLowercaseStringOrEmpty(response.msg);
|
|
19
41
|
return msg.includes("withdrawn") || msg.includes("not found");
|
|
20
42
|
}
|
|
21
43
|
|
|
@@ -40,9 +62,17 @@ function isWithdrawnReplyError(err: unknown): boolean {
|
|
|
40
62
|
return false;
|
|
41
63
|
}
|
|
42
64
|
|
|
65
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
66
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
67
|
+
}
|
|
68
|
+
|
|
43
69
|
type FeishuCreateMessageClient = {
|
|
44
70
|
im: {
|
|
45
71
|
message: {
|
|
72
|
+
reply: (opts: {
|
|
73
|
+
path: { message_id: string };
|
|
74
|
+
data: { content: string; msg_type: string; reply_in_thread?: true };
|
|
75
|
+
}) => Promise<{ code?: number; msg?: string; data?: { message_id?: string } }>;
|
|
46
76
|
create: (opts: {
|
|
47
77
|
params: { receive_id_type: "chat_id" | "email" | "open_id" | "union_id" | "user_id" };
|
|
48
78
|
data: { receive_id: string; content: string; msg_type: string };
|
|
@@ -51,6 +81,31 @@ type FeishuCreateMessageClient = {
|
|
|
51
81
|
};
|
|
52
82
|
};
|
|
53
83
|
|
|
84
|
+
type FeishuMessageSender = {
|
|
85
|
+
id?: string;
|
|
86
|
+
id_type?: string;
|
|
87
|
+
sender_type?: string;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
type FeishuMessageGetItem = {
|
|
91
|
+
message_id?: string;
|
|
92
|
+
chat_id?: string;
|
|
93
|
+
chat_type?: FeishuChatType;
|
|
94
|
+
thread_id?: string;
|
|
95
|
+
msg_type?: string;
|
|
96
|
+
body?: { content?: string };
|
|
97
|
+
sender?: FeishuMessageSender;
|
|
98
|
+
create_time?: string;
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
type FeishuGetMessageResponse = {
|
|
102
|
+
code?: number;
|
|
103
|
+
msg?: string;
|
|
104
|
+
data?: FeishuMessageGetItem & {
|
|
105
|
+
items?: FeishuMessageGetItem[];
|
|
106
|
+
};
|
|
107
|
+
};
|
|
108
|
+
|
|
54
109
|
/** Send a direct message as a fallback when a reply target is unavailable. */
|
|
55
110
|
async function sendFallbackDirect(
|
|
56
111
|
client: FeishuCreateMessageClient,
|
|
@@ -74,38 +129,180 @@ async function sendFallbackDirect(
|
|
|
74
129
|
return toFeishuSendResult(response, params.receiveId);
|
|
75
130
|
}
|
|
76
131
|
|
|
77
|
-
function
|
|
78
|
-
|
|
79
|
-
|
|
132
|
+
async function sendReplyOrFallbackDirect(
|
|
133
|
+
client: FeishuCreateMessageClient,
|
|
134
|
+
params: {
|
|
135
|
+
replyToMessageId?: string;
|
|
136
|
+
replyInThread?: boolean;
|
|
137
|
+
content: string;
|
|
138
|
+
msgType: string;
|
|
139
|
+
directParams: {
|
|
140
|
+
receiveId: string;
|
|
141
|
+
receiveIdType: "chat_id" | "email" | "open_id" | "union_id" | "user_id";
|
|
142
|
+
content: string;
|
|
143
|
+
msgType: string;
|
|
144
|
+
};
|
|
145
|
+
directErrorPrefix: string;
|
|
146
|
+
replyErrorPrefix: string;
|
|
147
|
+
},
|
|
148
|
+
): Promise<FeishuSendResult> {
|
|
149
|
+
if (!params.replyToMessageId) {
|
|
150
|
+
return sendFallbackDirect(client, params.directParams, params.directErrorPrefix);
|
|
80
151
|
}
|
|
81
152
|
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
153
|
+
const threadReplyFallbackError = params.replyInThread
|
|
154
|
+
? new Error(
|
|
155
|
+
"Feishu thread reply failed: reply target is unavailable and cannot safely fall back to a top-level send.",
|
|
156
|
+
)
|
|
157
|
+
: null;
|
|
158
|
+
|
|
159
|
+
let response: { code?: number; msg?: string; data?: { message_id?: string } };
|
|
160
|
+
try {
|
|
161
|
+
response = await client.im.message.reply({
|
|
162
|
+
path: { message_id: params.replyToMessageId },
|
|
163
|
+
data: {
|
|
164
|
+
content: params.content,
|
|
165
|
+
msg_type: params.msgType,
|
|
166
|
+
...(params.replyInThread ? { reply_in_thread: true } : {}),
|
|
167
|
+
},
|
|
168
|
+
});
|
|
169
|
+
} catch (err) {
|
|
170
|
+
if (!isWithdrawnReplyError(err)) {
|
|
171
|
+
throw err;
|
|
172
|
+
}
|
|
173
|
+
if (threadReplyFallbackError) {
|
|
174
|
+
throw threadReplyFallbackError;
|
|
175
|
+
}
|
|
176
|
+
return sendFallbackDirect(client, params.directParams, params.directErrorPrefix);
|
|
177
|
+
}
|
|
178
|
+
if (shouldFallbackFromReplyTarget(response)) {
|
|
179
|
+
if (threadReplyFallbackError) {
|
|
180
|
+
throw threadReplyFallbackError;
|
|
181
|
+
}
|
|
182
|
+
return sendFallbackDirect(client, params.directParams, params.directErrorPrefix);
|
|
85
183
|
}
|
|
184
|
+
assertFeishuMessageApiSuccess(response, params.replyErrorPrefix);
|
|
185
|
+
return toFeishuSendResult(response, params.directParams.receiveId);
|
|
186
|
+
}
|
|
86
187
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
188
|
+
function normalizeCardTemplateVariable(value: unknown): string | undefined {
|
|
189
|
+
if (typeof value === "string") {
|
|
190
|
+
return value;
|
|
191
|
+
}
|
|
192
|
+
if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
|
|
193
|
+
return String(value);
|
|
194
|
+
}
|
|
195
|
+
return undefined;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function readCardTemplateVariables(parsed: Record<string, unknown>): Map<string, string> {
|
|
199
|
+
const variables = new Map<string, string>();
|
|
200
|
+
for (const source of [parsed.template_variable, parsed.template_variables]) {
|
|
201
|
+
if (!isRecord(source)) {
|
|
90
202
|
continue;
|
|
91
203
|
}
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
204
|
+
for (const [key, value] of Object.entries(source)) {
|
|
205
|
+
const normalized = normalizeCardTemplateVariable(value);
|
|
206
|
+
if (normalized !== undefined) {
|
|
207
|
+
variables.set(key, normalized);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return variables;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function applyCardTemplateVariables(text: string, variables: Map<string, string>): string {
|
|
215
|
+
if (variables.size === 0) {
|
|
216
|
+
return text;
|
|
217
|
+
}
|
|
218
|
+
return text.replace(/\$\{([A-Za-z0-9_.-]+)\}|\{\{\s*([A-Za-z0-9_.-]+)\s*\}\}/g, (match, a, b) => {
|
|
219
|
+
const variableName = typeof a === "string" ? a : b;
|
|
220
|
+
return variables.get(variableName) ?? match;
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function extractInteractiveElementText(
|
|
225
|
+
element: unknown,
|
|
226
|
+
variables: Map<string, string>,
|
|
227
|
+
): string | undefined {
|
|
228
|
+
if (!isRecord(element)) {
|
|
229
|
+
return undefined;
|
|
230
|
+
}
|
|
231
|
+
const tag = typeof element.tag === "string" ? element.tag : "";
|
|
232
|
+
const text = isRecord(element.text) ? element.text : undefined;
|
|
233
|
+
|
|
234
|
+
if (tag === "div" && typeof text?.content === "string") {
|
|
235
|
+
return applyCardTemplateVariables(text.content, variables);
|
|
236
|
+
}
|
|
237
|
+
if ((tag === "markdown" || tag === "lark_md") && typeof element.content === "string") {
|
|
238
|
+
return applyCardTemplateVariables(element.content, variables);
|
|
239
|
+
}
|
|
240
|
+
if (tag === "plain_text" && typeof element.content === "string") {
|
|
241
|
+
return applyCardTemplateVariables(element.content, variables);
|
|
242
|
+
}
|
|
243
|
+
return undefined;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function extractInteractiveElementsText(
|
|
247
|
+
elements: unknown[],
|
|
248
|
+
variables: Map<string, string>,
|
|
249
|
+
): string {
|
|
250
|
+
const texts: string[] = [];
|
|
251
|
+
for (const element of elements) {
|
|
252
|
+
const text = extractInteractiveElementText(element, variables);
|
|
253
|
+
if (text !== undefined) {
|
|
254
|
+
texts.push(text);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
return texts.join("\n").trim();
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function readInteractiveElementArrays(parsed: Record<string, unknown>): unknown[][] {
|
|
261
|
+
const body = isRecord(parsed.body) ? parsed.body : undefined;
|
|
262
|
+
const elementArrays: unknown[][] = [];
|
|
263
|
+
|
|
264
|
+
for (const candidate of [parsed.elements, body?.elements]) {
|
|
265
|
+
if (Array.isArray(candidate)) {
|
|
266
|
+
elementArrays.push(candidate);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
for (const candidate of [parsed.i18n_elements, body?.i18n_elements]) {
|
|
271
|
+
if (!isRecord(candidate)) {
|
|
99
272
|
continue;
|
|
100
273
|
}
|
|
101
|
-
|
|
102
|
-
|
|
274
|
+
for (const localeElements of Object.values(candidate)) {
|
|
275
|
+
if (Array.isArray(localeElements)) {
|
|
276
|
+
elementArrays.push(localeElements);
|
|
277
|
+
}
|
|
103
278
|
}
|
|
104
279
|
}
|
|
105
|
-
|
|
280
|
+
|
|
281
|
+
return elementArrays;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function parseInteractivePostFallback(parsed: unknown): string | undefined {
|
|
285
|
+
const textContent = parsePostContent(JSON.stringify(parsed)).textContent.trim();
|
|
286
|
+
return textContent && textContent !== POST_FALLBACK_TEXT ? textContent : undefined;
|
|
106
287
|
}
|
|
107
288
|
|
|
108
|
-
function
|
|
289
|
+
function parseInteractiveCardContent(parsed: unknown): string {
|
|
290
|
+
if (!isRecord(parsed)) {
|
|
291
|
+
return INTERACTIVE_CARD_FALLBACK_TEXT;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const variables = readCardTemplateVariables(parsed);
|
|
295
|
+
for (const elements of readInteractiveElementArrays(parsed)) {
|
|
296
|
+
const text = extractInteractiveElementsText(elements, variables);
|
|
297
|
+
if (text) {
|
|
298
|
+
return text;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return parseInteractivePostFallback(parsed) ?? INTERACTIVE_CARD_FALLBACK_TEXT;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function parseFeishuMessageContent(rawContent: string, msgType: string): string {
|
|
109
306
|
if (!rawContent) {
|
|
110
307
|
return "";
|
|
111
308
|
}
|
|
@@ -146,6 +343,33 @@ function parseQuotedMessageContent(rawContent: string, msgType: string): string
|
|
|
146
343
|
return `[${msgType || "unknown"} message]`;
|
|
147
344
|
}
|
|
148
345
|
|
|
346
|
+
function parseFeishuMessageItem(
|
|
347
|
+
item: FeishuMessageGetItem,
|
|
348
|
+
fallbackMessageId?: string,
|
|
349
|
+
): FeishuMessageInfo {
|
|
350
|
+
const msgType = item.msg_type ?? "text";
|
|
351
|
+
const rawContent = item.body?.content ?? "";
|
|
352
|
+
|
|
353
|
+
return {
|
|
354
|
+
messageId: item.message_id ?? fallbackMessageId ?? "",
|
|
355
|
+
chatId: item.chat_id ?? "",
|
|
356
|
+
chatType:
|
|
357
|
+
item.chat_type === "group" ||
|
|
358
|
+
item.chat_type === "topic_group" ||
|
|
359
|
+
item.chat_type === "private" ||
|
|
360
|
+
item.chat_type === "p2p"
|
|
361
|
+
? item.chat_type
|
|
362
|
+
: undefined,
|
|
363
|
+
senderId: item.sender?.id,
|
|
364
|
+
senderOpenId: item.sender?.id_type === "open_id" ? item.sender?.id : undefined,
|
|
365
|
+
senderType: item.sender?.sender_type,
|
|
366
|
+
content: parseFeishuMessageContent(rawContent, msgType),
|
|
367
|
+
contentType: msgType,
|
|
368
|
+
createTime: item.create_time ? Number.parseInt(item.create_time, 10) : undefined,
|
|
369
|
+
threadId: item.thread_id || undefined,
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
|
|
149
373
|
/**
|
|
150
374
|
* Get a message by its ID.
|
|
151
375
|
* Useful for fetching quoted/replied message content.
|
|
@@ -156,7 +380,7 @@ export async function getMessageFeishu(params: {
|
|
|
156
380
|
accountId?: string;
|
|
157
381
|
}): Promise<FeishuMessageInfo | null> {
|
|
158
382
|
const { cfg, messageId, accountId } = params;
|
|
159
|
-
const account =
|
|
383
|
+
const account = resolveFeishuRuntimeAccount({ cfg, accountId });
|
|
160
384
|
if (!account.configured) {
|
|
161
385
|
throw new Error(`Feishu account "${account.accountId}" not configured`);
|
|
162
386
|
}
|
|
@@ -166,36 +390,7 @@ export async function getMessageFeishu(params: {
|
|
|
166
390
|
try {
|
|
167
391
|
const response = (await client.im.message.get({
|
|
168
392
|
path: { message_id: messageId },
|
|
169
|
-
})) as
|
|
170
|
-
code?: number;
|
|
171
|
-
msg?: string;
|
|
172
|
-
data?: {
|
|
173
|
-
items?: Array<{
|
|
174
|
-
message_id?: string;
|
|
175
|
-
chat_id?: string;
|
|
176
|
-
chat_type?: FeishuChatType;
|
|
177
|
-
msg_type?: string;
|
|
178
|
-
body?: { content?: string };
|
|
179
|
-
sender?: {
|
|
180
|
-
id?: string;
|
|
181
|
-
id_type?: string;
|
|
182
|
-
sender_type?: string;
|
|
183
|
-
};
|
|
184
|
-
create_time?: string;
|
|
185
|
-
}>;
|
|
186
|
-
message_id?: string;
|
|
187
|
-
chat_id?: string;
|
|
188
|
-
chat_type?: FeishuChatType;
|
|
189
|
-
msg_type?: string;
|
|
190
|
-
body?: { content?: string };
|
|
191
|
-
sender?: {
|
|
192
|
-
id?: string;
|
|
193
|
-
id_type?: string;
|
|
194
|
-
sender_type?: string;
|
|
195
|
-
};
|
|
196
|
-
create_time?: string;
|
|
197
|
-
};
|
|
198
|
-
};
|
|
393
|
+
})) as FeishuGetMessageResponse;
|
|
199
394
|
|
|
200
395
|
if (response.code !== 0) {
|
|
201
396
|
return null;
|
|
@@ -212,29 +407,104 @@ export async function getMessageFeishu(params: {
|
|
|
212
407
|
return null;
|
|
213
408
|
}
|
|
214
409
|
|
|
215
|
-
|
|
216
|
-
const rawContent = item.body?.content ?? "";
|
|
217
|
-
const content = parseQuotedMessageContent(rawContent, msgType);
|
|
218
|
-
|
|
219
|
-
return {
|
|
220
|
-
messageId: item.message_id ?? messageId,
|
|
221
|
-
chatId: item.chat_id ?? "",
|
|
222
|
-
chatType:
|
|
223
|
-
item.chat_type === "group" || item.chat_type === "private" || item.chat_type === "p2p"
|
|
224
|
-
? item.chat_type
|
|
225
|
-
: undefined,
|
|
226
|
-
senderId: item.sender?.id,
|
|
227
|
-
senderOpenId: item.sender?.id_type === "open_id" ? item.sender?.id : undefined,
|
|
228
|
-
senderType: item.sender?.sender_type,
|
|
229
|
-
content,
|
|
230
|
-
contentType: msgType,
|
|
231
|
-
createTime: item.create_time ? parseInt(String(item.create_time), 10) : undefined,
|
|
232
|
-
};
|
|
410
|
+
return parseFeishuMessageItem(item, messageId);
|
|
233
411
|
} catch {
|
|
234
412
|
return null;
|
|
235
413
|
}
|
|
236
414
|
}
|
|
237
415
|
|
|
416
|
+
export type FeishuThreadMessageInfo = {
|
|
417
|
+
messageId: string;
|
|
418
|
+
senderId?: string;
|
|
419
|
+
senderType?: string;
|
|
420
|
+
content: string;
|
|
421
|
+
contentType: string;
|
|
422
|
+
createTime?: number;
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* List messages in a Feishu thread (topic).
|
|
427
|
+
* Uses container_id_type=thread to directly query thread messages,
|
|
428
|
+
* which includes both the root message and all replies (including bot replies).
|
|
429
|
+
*/
|
|
430
|
+
export async function listFeishuThreadMessages(params: {
|
|
431
|
+
cfg: ClawdbotConfig;
|
|
432
|
+
threadId: string;
|
|
433
|
+
currentMessageId?: string;
|
|
434
|
+
/** Exclude the root message (already provided separately as ThreadStarterBody). */
|
|
435
|
+
rootMessageId?: string;
|
|
436
|
+
limit?: number;
|
|
437
|
+
accountId?: string;
|
|
438
|
+
}): Promise<FeishuThreadMessageInfo[]> {
|
|
439
|
+
const { cfg, threadId, currentMessageId, rootMessageId, limit = 20, accountId } = params;
|
|
440
|
+
const account = resolveFeishuRuntimeAccount({ cfg, accountId });
|
|
441
|
+
if (!account.configured) {
|
|
442
|
+
throw new Error(`Feishu account "${account.accountId}" not configured`);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
const client = createFeishuClient(account);
|
|
446
|
+
|
|
447
|
+
const response = (await client.im.message.list({
|
|
448
|
+
params: {
|
|
449
|
+
container_id_type: "thread",
|
|
450
|
+
container_id: threadId,
|
|
451
|
+
// Fetch newest messages first so long threads keep the most recent turns.
|
|
452
|
+
// Results are reversed below to restore chronological order.
|
|
453
|
+
sort_type: "ByCreateTimeDesc",
|
|
454
|
+
page_size: Math.min(limit + 1, 50),
|
|
455
|
+
},
|
|
456
|
+
})) as {
|
|
457
|
+
code?: number;
|
|
458
|
+
msg?: string;
|
|
459
|
+
data?: {
|
|
460
|
+
items?: Array<
|
|
461
|
+
{
|
|
462
|
+
message_id?: string;
|
|
463
|
+
root_id?: string;
|
|
464
|
+
parent_id?: string;
|
|
465
|
+
} & FeishuMessageGetItem
|
|
466
|
+
>;
|
|
467
|
+
};
|
|
468
|
+
};
|
|
469
|
+
|
|
470
|
+
if (response.code !== 0) {
|
|
471
|
+
throw new Error(
|
|
472
|
+
`Feishu thread list failed: code=${response.code} msg=${response.msg ?? "unknown"}`,
|
|
473
|
+
);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
const items = response.data?.items ?? [];
|
|
477
|
+
const results: FeishuThreadMessageInfo[] = [];
|
|
478
|
+
|
|
479
|
+
for (const item of items) {
|
|
480
|
+
if (currentMessageId && item.message_id === currentMessageId) {
|
|
481
|
+
continue;
|
|
482
|
+
}
|
|
483
|
+
if (rootMessageId && item.message_id === rootMessageId) {
|
|
484
|
+
continue;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
const parsed = parseFeishuMessageItem(item);
|
|
488
|
+
|
|
489
|
+
results.push({
|
|
490
|
+
messageId: parsed.messageId,
|
|
491
|
+
senderId: parsed.senderId,
|
|
492
|
+
senderType: parsed.senderType,
|
|
493
|
+
content: parsed.content,
|
|
494
|
+
contentType: parsed.contentType,
|
|
495
|
+
createTime: parsed.createTime,
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
if (results.length >= limit) {
|
|
499
|
+
break;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// Restore chronological order (oldest first) since we fetched newest-first.
|
|
504
|
+
results.reverse();
|
|
505
|
+
return results;
|
|
506
|
+
}
|
|
507
|
+
|
|
238
508
|
export type SendFeishuMessageParams = {
|
|
239
509
|
cfg: ClawdbotConfig;
|
|
240
510
|
to: string;
|
|
@@ -248,7 +518,7 @@ export type SendFeishuMessageParams = {
|
|
|
248
518
|
accountId?: string;
|
|
249
519
|
};
|
|
250
520
|
|
|
251
|
-
function buildFeishuPostMessagePayload(params: { messageText: string }): {
|
|
521
|
+
export function buildFeishuPostMessagePayload(params: { messageText: string }): {
|
|
252
522
|
content: string;
|
|
253
523
|
msgType: string;
|
|
254
524
|
} {
|
|
@@ -275,7 +545,7 @@ export async function sendMessageFeishu(
|
|
|
275
545
|
): Promise<FeishuSendResult> {
|
|
276
546
|
const { cfg, to, text, replyToMessageId, replyInThread, mentions, accountId } = params;
|
|
277
547
|
const { client, receiveId, receiveIdType } = resolveFeishuSendTarget({ cfg, to, accountId });
|
|
278
|
-
const tableMode =
|
|
548
|
+
const tableMode = resolveMarkdownTableMode({
|
|
279
549
|
cfg,
|
|
280
550
|
channel: "feishu",
|
|
281
551
|
});
|
|
@@ -285,37 +555,20 @@ export async function sendMessageFeishu(
|
|
|
285
555
|
if (mentions && mentions.length > 0) {
|
|
286
556
|
rawText = buildMentionedMessage(mentions, rawText);
|
|
287
557
|
}
|
|
288
|
-
const messageText =
|
|
558
|
+
const messageText = convertMarkdownTables(rawText, tableMode);
|
|
289
559
|
|
|
290
560
|
const { content, msgType } = buildFeishuPostMessagePayload({ messageText });
|
|
291
561
|
|
|
292
562
|
const directParams = { receiveId, receiveIdType, content, msgType };
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
...(replyInThread ? { reply_in_thread: true } : {}),
|
|
303
|
-
},
|
|
304
|
-
});
|
|
305
|
-
} catch (err) {
|
|
306
|
-
if (!isWithdrawnReplyError(err)) {
|
|
307
|
-
throw err;
|
|
308
|
-
}
|
|
309
|
-
return sendFallbackDirect(client, directParams, "Feishu send failed");
|
|
310
|
-
}
|
|
311
|
-
if (shouldFallbackFromReplyTarget(response)) {
|
|
312
|
-
return sendFallbackDirect(client, directParams, "Feishu send failed");
|
|
313
|
-
}
|
|
314
|
-
assertFeishuMessageApiSuccess(response, "Feishu reply failed");
|
|
315
|
-
return toFeishuSendResult(response, receiveId);
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
return sendFallbackDirect(client, directParams, "Feishu send failed");
|
|
563
|
+
return sendReplyOrFallbackDirect(client, {
|
|
564
|
+
replyToMessageId,
|
|
565
|
+
replyInThread,
|
|
566
|
+
content,
|
|
567
|
+
msgType,
|
|
568
|
+
directParams,
|
|
569
|
+
directErrorPrefix: "Feishu send failed",
|
|
570
|
+
replyErrorPrefix: "Feishu reply failed",
|
|
571
|
+
});
|
|
319
572
|
}
|
|
320
573
|
|
|
321
574
|
export type SendFeishuCardParams = {
|
|
@@ -334,32 +587,68 @@ export async function sendCardFeishu(params: SendFeishuCardParams): Promise<Feis
|
|
|
334
587
|
const content = JSON.stringify(card);
|
|
335
588
|
|
|
336
589
|
const directParams = { receiveId, receiveIdType, content, msgType: "interactive" };
|
|
590
|
+
return sendReplyOrFallbackDirect(client, {
|
|
591
|
+
replyToMessageId,
|
|
592
|
+
replyInThread,
|
|
593
|
+
content,
|
|
594
|
+
msgType: "interactive",
|
|
595
|
+
directParams,
|
|
596
|
+
directErrorPrefix: "Feishu card send failed",
|
|
597
|
+
replyErrorPrefix: "Feishu card reply failed",
|
|
598
|
+
});
|
|
599
|
+
}
|
|
337
600
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
601
|
+
export async function editMessageFeishu(params: {
|
|
602
|
+
cfg: ClawdbotConfig;
|
|
603
|
+
messageId: string;
|
|
604
|
+
text?: string;
|
|
605
|
+
card?: Record<string, unknown>;
|
|
606
|
+
accountId?: string;
|
|
607
|
+
}): Promise<{ messageId: string; contentType: "post" | "interactive" }> {
|
|
608
|
+
const { cfg, messageId, text, card, accountId } = params;
|
|
609
|
+
const account = resolveFeishuRuntimeAccount({ cfg, accountId });
|
|
610
|
+
if (!account.configured) {
|
|
611
|
+
throw new Error(`Feishu account "${account.accountId}" not configured`);
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
const hasText = typeof text === "string" && text.trim().length > 0;
|
|
615
|
+
const hasCard = Boolean(card);
|
|
616
|
+
if (hasText === hasCard) {
|
|
617
|
+
throw new Error("Feishu edit requires exactly one of text or card.");
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
const client = createFeishuClient(account);
|
|
621
|
+
|
|
622
|
+
if (card) {
|
|
623
|
+
const content = JSON.stringify(card);
|
|
624
|
+
const response = await client.im.message.patch({
|
|
625
|
+
path: { message_id: messageId },
|
|
626
|
+
data: { content },
|
|
627
|
+
});
|
|
628
|
+
|
|
629
|
+
if (response.code !== 0) {
|
|
630
|
+
throw new Error(`Feishu message edit failed: ${response.msg || `code ${response.code}`}`);
|
|
357
631
|
}
|
|
358
|
-
|
|
359
|
-
return
|
|
632
|
+
|
|
633
|
+
return { messageId, contentType: "interactive" };
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
const tableMode = resolveMarkdownTableMode({
|
|
637
|
+
cfg,
|
|
638
|
+
channel: "feishu",
|
|
639
|
+
});
|
|
640
|
+
const messageText = convertMarkdownTables(text!, tableMode);
|
|
641
|
+
const payload = buildFeishuPostMessagePayload({ messageText });
|
|
642
|
+
const response = await client.im.message.patch({
|
|
643
|
+
path: { message_id: messageId },
|
|
644
|
+
data: { content: payload.content },
|
|
645
|
+
});
|
|
646
|
+
|
|
647
|
+
if (response.code !== 0) {
|
|
648
|
+
throw new Error(`Feishu message edit failed: ${response.msg || `code ${response.code}`}`);
|
|
360
649
|
}
|
|
361
650
|
|
|
362
|
-
return
|
|
651
|
+
return { messageId, contentType: "post" };
|
|
363
652
|
}
|
|
364
653
|
|
|
365
654
|
export async function updateCardFeishu(params: {
|
|
@@ -369,7 +658,7 @@ export async function updateCardFeishu(params: {
|
|
|
369
658
|
accountId?: string;
|
|
370
659
|
}): Promise<void> {
|
|
371
660
|
const { cfg, messageId, card, accountId } = params;
|
|
372
|
-
const account =
|
|
661
|
+
const account = resolveFeishuRuntimeAccount({ cfg, accountId });
|
|
373
662
|
if (!account.configured) {
|
|
374
663
|
throw new Error(`Feishu account "${account.accountId}" not configured`);
|
|
375
664
|
}
|
|
@@ -396,7 +685,7 @@ export function buildMarkdownCard(text: string): Record<string, unknown> {
|
|
|
396
685
|
return {
|
|
397
686
|
schema: "2.0",
|
|
398
687
|
config: {
|
|
399
|
-
|
|
688
|
+
width_mode: "fill",
|
|
400
689
|
},
|
|
401
690
|
body: {
|
|
402
691
|
elements: [
|
|
@@ -409,64 +698,97 @@ export function buildMarkdownCard(text: string): Record<string, unknown> {
|
|
|
409
698
|
};
|
|
410
699
|
}
|
|
411
700
|
|
|
701
|
+
/** Header configuration for structured Feishu cards. */
|
|
702
|
+
export type CardHeaderConfig = {
|
|
703
|
+
/** Header title text, e.g. "💻 Coder" */
|
|
704
|
+
title: string;
|
|
705
|
+
/** Feishu header color template (blue, green, red, orange, purple, grey, etc.). Defaults to "blue". */
|
|
706
|
+
template?: string;
|
|
707
|
+
};
|
|
708
|
+
|
|
709
|
+
export function resolveFeishuCardTemplate(template?: string): string | undefined {
|
|
710
|
+
const normalized = normalizeOptionalLowercaseString(template);
|
|
711
|
+
if (!normalized || !FEISHU_CARD_TEMPLATES.has(normalized)) {
|
|
712
|
+
return undefined;
|
|
713
|
+
}
|
|
714
|
+
return normalized;
|
|
715
|
+
}
|
|
716
|
+
|
|
412
717
|
/**
|
|
413
|
-
*
|
|
414
|
-
*
|
|
718
|
+
* Build a Feishu interactive card with optional header and note footer.
|
|
719
|
+
* When header/note are omitted, behaves identically to buildMarkdownCard.
|
|
415
720
|
*/
|
|
416
|
-
export
|
|
721
|
+
export function buildStructuredCard(
|
|
722
|
+
text: string,
|
|
723
|
+
options?: {
|
|
724
|
+
header?: CardHeaderConfig;
|
|
725
|
+
note?: string;
|
|
726
|
+
},
|
|
727
|
+
): Record<string, unknown> {
|
|
728
|
+
const elements: Record<string, unknown>[] = [{ tag: "markdown", content: text }];
|
|
729
|
+
if (options?.note) {
|
|
730
|
+
elements.push({ tag: "hr" });
|
|
731
|
+
elements.push({ tag: "markdown", content: `<font color='grey'>${options.note}</font>` });
|
|
732
|
+
}
|
|
733
|
+
const card: Record<string, unknown> = {
|
|
734
|
+
schema: "2.0",
|
|
735
|
+
config: { width_mode: "fill" },
|
|
736
|
+
body: { elements },
|
|
737
|
+
};
|
|
738
|
+
if (options?.header) {
|
|
739
|
+
card.header = {
|
|
740
|
+
title: { tag: "plain_text", content: options.header.title },
|
|
741
|
+
template: resolveFeishuCardTemplate(options.header.template) ?? "blue",
|
|
742
|
+
};
|
|
743
|
+
}
|
|
744
|
+
return card;
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
/**
|
|
748
|
+
* Send a message as a structured card with optional header and note.
|
|
749
|
+
*/
|
|
750
|
+
export async function sendStructuredCardFeishu(params: {
|
|
417
751
|
cfg: ClawdbotConfig;
|
|
418
752
|
to: string;
|
|
419
753
|
text: string;
|
|
420
754
|
replyToMessageId?: string;
|
|
421
755
|
/** When true, reply creates a Feishu topic thread instead of an inline reply */
|
|
422
756
|
replyInThread?: boolean;
|
|
423
|
-
/** Mention target users */
|
|
424
757
|
mentions?: MentionTarget[];
|
|
425
758
|
accountId?: string;
|
|
759
|
+
header?: CardHeaderConfig;
|
|
760
|
+
note?: string;
|
|
426
761
|
}): Promise<FeishuSendResult> {
|
|
427
|
-
const { cfg, to, text, replyToMessageId, replyInThread, mentions, accountId } =
|
|
762
|
+
const { cfg, to, text, replyToMessageId, replyInThread, mentions, accountId, header, note } =
|
|
763
|
+
params;
|
|
428
764
|
let cardText = text;
|
|
429
765
|
if (mentions && mentions.length > 0) {
|
|
430
766
|
cardText = buildMentionedCardContent(mentions, text);
|
|
431
767
|
}
|
|
432
|
-
const card =
|
|
768
|
+
const card = buildStructuredCard(cardText, { header, note });
|
|
433
769
|
return sendCardFeishu({ cfg, to, card, replyToMessageId, replyInThread, accountId });
|
|
434
770
|
}
|
|
435
771
|
|
|
436
772
|
/**
|
|
437
|
-
*
|
|
438
|
-
*
|
|
773
|
+
* Send a message as a markdown card (interactive message).
|
|
774
|
+
* This renders markdown properly in Feishu (code blocks, tables, bold/italic, etc.)
|
|
439
775
|
*/
|
|
440
|
-
export async function
|
|
776
|
+
export async function sendMarkdownCardFeishu(params: {
|
|
441
777
|
cfg: ClawdbotConfig;
|
|
442
|
-
|
|
778
|
+
to: string;
|
|
443
779
|
text: string;
|
|
780
|
+
replyToMessageId?: string;
|
|
781
|
+
/** When true, reply creates a Feishu topic thread instead of an inline reply */
|
|
782
|
+
replyInThread?: boolean;
|
|
783
|
+
/** Mention target users */
|
|
784
|
+
mentions?: MentionTarget[];
|
|
444
785
|
accountId?: string;
|
|
445
|
-
}): Promise<
|
|
446
|
-
const { cfg,
|
|
447
|
-
|
|
448
|
-
if (
|
|
449
|
-
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
const client = createFeishuClient(account);
|
|
453
|
-
const tableMode = getFeishuRuntime().channel.text.resolveMarkdownTableMode({
|
|
454
|
-
cfg,
|
|
455
|
-
channel: "feishu",
|
|
456
|
-
});
|
|
457
|
-
const messageText = getFeishuRuntime().channel.text.convertMarkdownTables(text ?? "", tableMode);
|
|
458
|
-
|
|
459
|
-
const { content, msgType } = buildFeishuPostMessagePayload({ messageText });
|
|
460
|
-
|
|
461
|
-
const response = await client.im.message.update({
|
|
462
|
-
path: { message_id: messageId },
|
|
463
|
-
data: {
|
|
464
|
-
msg_type: msgType,
|
|
465
|
-
content,
|
|
466
|
-
},
|
|
467
|
-
});
|
|
468
|
-
|
|
469
|
-
if (response.code !== 0) {
|
|
470
|
-
throw new Error(`Feishu message edit failed: ${response.msg || `code ${response.code}`}`);
|
|
786
|
+
}): Promise<FeishuSendResult> {
|
|
787
|
+
const { cfg, to, text, replyToMessageId, replyInThread, mentions, accountId } = params;
|
|
788
|
+
let cardText = text;
|
|
789
|
+
if (mentions && mentions.length > 0) {
|
|
790
|
+
cardText = buildMentionedCardContent(mentions, text);
|
|
471
791
|
}
|
|
792
|
+
const card = buildMarkdownCard(cardText);
|
|
793
|
+
return sendCardFeishu({ cfg, to, card, replyToMessageId, replyInThread, accountId });
|
|
472
794
|
}
|