@openclaw/feishu 2026.7.2-beta.7 → 2026.8.1-beta.2
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/accounts-CCCdMen2.js +203 -0
- package/dist/api.js +52 -51
- package/dist/{channel-CScE82zY.js → channel-Dzb5jv3i.js} +53 -30
- package/dist/channel-plugin-api.js +1 -1
- package/dist/{channel.runtime-B21e1E0r.js → channel.runtime-CoPaHQQ6.js} +181 -142
- package/dist/{client-Dcbs6vml.js → client-Hp7uo_cl.js} +27 -16
- package/dist/contract-api.js +2 -2
- package/dist/{conversation-id-BeJL-wq7.js → conversation-id-VYgGQ-GX.js} +11 -15
- package/dist/doctor-contract-api.js +1 -1
- package/dist/doctor-contract-bEQXIXyP.js +158 -0
- package/dist/{drive-hSi_Utp0.js → drive-B0JkoiRj.js} +318 -41
- package/dist/{media-CEUraFOR.js → media-BjSy8fiy.js} +298 -182
- package/dist/{monitor-DngbaA6a.js → monitor-CeHCfROt.js} +3 -3
- package/dist/{monitor.account-BnTWCw55.js → monitor.account-DwdqexLU.js} +237 -158
- package/dist/{monitor.startup-CqH2tiJq.js → monitor.startup-BGErejNH.js} +1 -1
- package/dist/{probe-p3POS2RN.js → probe-DVpy58s0.js} +2 -2
- package/dist/security-audit-D6Fz2h6p.js +23 -0
- package/dist/{send-result-B9_BpUPx.js → send-result-DEAycmOk.js} +18 -9
- package/dist/session-binding-contract-api.js +1 -1
- package/dist/{session-conversation-BksWrfzm.js → session-conversation-DFCIvQK-.js} +1 -1
- package/dist/session-key-api.js +1 -1
- package/dist/setup-api.js +1 -1
- package/dist/{subagent-hooks-Cx1cX7rW.js → subagent-hooks-B867acTt.js} +2 -2
- package/dist/subagent-hooks-api.js +1 -1
- package/dist/{thread-bindings-N3wkgkIN.js → thread-bindings-Itvfmx6_.js} +6 -3
- package/openclaw.plugin.json +143 -1
- package/package.json +4 -4
- package/skills/feishu-doc/SKILL.md +20 -195
- package/skills/feishu-doc/references/block-types.md +9 -14
- package/skills/feishu-drive/SKILL.md +16 -102
- package/skills/feishu-perm/SKILL.md +10 -110
- package/skills/feishu-wiki/SKILL.md +13 -109
- package/dist/accounts-u9X5Wsan.js +0 -469
- package/dist/doctor-contract-BiD9tyIv.js +0 -102
- package/dist/security-audit-D7WK_BHh.js +0 -11
|
@@ -1,21 +1,22 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { s as resolveFeishuRuntimeAccount } from "./accounts-CCCdMen2.js";
|
|
2
2
|
import { i as resolveReceiveIdType, r as normalizeFeishuTarget } from "./targets-BUjQ1TcA.js";
|
|
3
|
-
import { c as assertFeishuPostWithinEnvelope, f as materializeFeishuPostMarkdownSoftBreaks, i as toFeishuSendResult, l as buildFeishuPostMessageContent, o as resolveFeishuCardTemplate, r as resolveFeishuReceiptKind, t as assertFeishuMessageApiSuccess } from "./send-result-
|
|
4
|
-
import { r as createFeishuClient } from "./client-
|
|
3
|
+
import { c as assertFeishuPostWithinEnvelope, f as materializeFeishuPostMarkdownSoftBreaks, i as toFeishuSendResult, l as buildFeishuPostMessageContent, o as resolveFeishuCardTemplate, r as resolveFeishuReceiptKind, t as assertFeishuMessageApiSuccess } from "./send-result-DEAycmOk.js";
|
|
4
|
+
import { r as createFeishuClient } from "./client-Hp7uo_cl.js";
|
|
5
5
|
import { t as getFeishuRuntime } from "./runtime-C5JxBWZp.js";
|
|
6
|
+
import { m as requestFeishuApi } from "./drive-B0JkoiRj.js";
|
|
7
|
+
import { PlatformMessageNotDispatchedError } from "openclaw/plugin-sdk/error-runtime";
|
|
6
8
|
import { parseStrictNonNegativeInteger } from "openclaw/plugin-sdk/number-runtime";
|
|
7
9
|
import { isRecord, normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
8
10
|
import { convertMarkdownTables } from "openclaw/plugin-sdk/text-chunking";
|
|
9
11
|
import fs from "node:fs";
|
|
10
12
|
import path from "node:path";
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
+
import { MEDIA_FFMPEG_MAX_AUDIO_DURATION_SECS, buildOutboundMediaLoadOptions, runFfmpeg, runFfprobe } from "openclaw/plugin-sdk/media-runtime";
|
|
14
|
+
import { readRegularFile, writeExternalFileWithinRoot } from "openclaw/plugin-sdk/security-runtime";
|
|
13
15
|
import { logVerbose } from "openclaw/plugin-sdk/runtime-env";
|
|
14
16
|
import { isBlockedHostnameOrIp, resolvePinnedHostnameWithPolicy } from "openclaw/plugin-sdk/ssrf-runtime";
|
|
15
17
|
import { resolveMarkdownTableMode } from "openclaw/plugin-sdk/markdown-table-runtime";
|
|
16
|
-
import { readRegularFile, writeExternalFileWithinRoot } from "openclaw/plugin-sdk/security-runtime";
|
|
17
18
|
import { Readable } from "node:stream";
|
|
18
|
-
import { mediaKindFromMime } from "openclaw/plugin-sdk/media-mime";
|
|
19
|
+
import { detectMime, mediaKindFromMime } from "openclaw/plugin-sdk/media-mime";
|
|
19
20
|
import { saveMediaBuffer, saveMediaStream } from "openclaw/plugin-sdk/media-store";
|
|
20
21
|
import { resolvePreferredOpenClawTmpDir, withTempDownloadPath, withTempWorkspace } from "openclaw/plugin-sdk/temp-path";
|
|
21
22
|
//#region extensions/feishu/src/identity-header.ts
|
|
@@ -147,66 +148,6 @@ function resolveFeishuSendTarget(params) {
|
|
|
147
148
|
};
|
|
148
149
|
}
|
|
149
150
|
//#endregion
|
|
150
|
-
//#region extensions/feishu/src/types.ts
|
|
151
|
-
function isFeishuGroupChatType(chatType) {
|
|
152
|
-
return chatType === "group" || chatType === "topic_group";
|
|
153
|
-
}
|
|
154
|
-
//#endregion
|
|
155
|
-
//#region extensions/feishu/src/mention.ts
|
|
156
|
-
function isFeishuBroadcastMention(mention) {
|
|
157
|
-
const normalizedKey = mention.key?.trim().toLowerCase();
|
|
158
|
-
if (normalizedKey === "@all" || normalizedKey === "@_all") return true;
|
|
159
|
-
return [
|
|
160
|
-
mention.id?.open_id,
|
|
161
|
-
mention.id?.user_id,
|
|
162
|
-
mention.id?.union_id
|
|
163
|
-
].some((id) => id?.trim().toLowerCase() === "all");
|
|
164
|
-
}
|
|
165
|
-
/**
|
|
166
|
-
* Extract mention targets from message event (excluding the bot itself)
|
|
167
|
-
*/
|
|
168
|
-
function extractMentionTargets(event, botOpenId) {
|
|
169
|
-
return (event.message.mentions ?? []).filter((m) => {
|
|
170
|
-
if (isFeishuBroadcastMention(m)) return false;
|
|
171
|
-
if (m.id.open_id === botOpenId) return false;
|
|
172
|
-
return Boolean(m.id.open_id);
|
|
173
|
-
}).map((m) => ({
|
|
174
|
-
openId: m.id.open_id,
|
|
175
|
-
name: m.name,
|
|
176
|
-
key: m.key
|
|
177
|
-
}));
|
|
178
|
-
}
|
|
179
|
-
/**
|
|
180
|
-
* Check if message is a mention forward request
|
|
181
|
-
* Rules:
|
|
182
|
-
* - Group: message mentions bot + at least one other user
|
|
183
|
-
* - DM: message mentions any user (no need to mention bot)
|
|
184
|
-
*/
|
|
185
|
-
function isMentionForwardRequest(event, botOpenId) {
|
|
186
|
-
const mentions = event.message.mentions ?? [];
|
|
187
|
-
if (mentions.length === 0) return false;
|
|
188
|
-
const normalizedBotOpenId = botOpenId?.trim();
|
|
189
|
-
if (!normalizedBotOpenId) return false;
|
|
190
|
-
const isDirectMessage = !isFeishuGroupChatType(event.message.chat_type);
|
|
191
|
-
const userMentions = mentions.filter((m) => !isFeishuBroadcastMention(m));
|
|
192
|
-
const hasOtherMention = userMentions.some((m) => m.id.open_id !== normalizedBotOpenId);
|
|
193
|
-
if (isDirectMessage) return hasOtherMention;
|
|
194
|
-
return userMentions.some((m) => m.id.open_id === normalizedBotOpenId) && hasOtherMention;
|
|
195
|
-
}
|
|
196
|
-
/**
|
|
197
|
-
* Format @mention for card message (lark_md)
|
|
198
|
-
*/
|
|
199
|
-
function formatMentionForCard(target) {
|
|
200
|
-
return `<at id=${target.openId}></at>`;
|
|
201
|
-
}
|
|
202
|
-
/**
|
|
203
|
-
* Build card content with @mentions (Markdown format)
|
|
204
|
-
*/
|
|
205
|
-
function buildMentionedCardContent(targets, message) {
|
|
206
|
-
if (targets.length === 0) return message;
|
|
207
|
-
return `${targets.map((t) => formatMentionForCard(t)).join(" ")} ${message}`;
|
|
208
|
-
}
|
|
209
|
-
//#endregion
|
|
210
151
|
//#region extensions/feishu/src/post.ts
|
|
211
152
|
const FALLBACK_POST_TEXT = "[Rich text message]";
|
|
212
153
|
const MARKDOWN_SPECIAL_CHARS = /([\\`*_{}[\]()#+\-!|>~])/g;
|
|
@@ -233,7 +174,7 @@ function sanitizeFenceLanguage(language) {
|
|
|
233
174
|
}
|
|
234
175
|
function renderTextElement(element) {
|
|
235
176
|
const text = toStringOrEmpty(element.text);
|
|
236
|
-
const style = isRecord
|
|
177
|
+
const style = isRecord(element.style) ? element.style : void 0;
|
|
237
178
|
if (isStyleEnabled(style, "code")) return wrapInlineCode(text);
|
|
238
179
|
let rendered = escapeMarkdownText(text);
|
|
239
180
|
if (!rendered) return "";
|
|
@@ -264,7 +205,7 @@ function renderCodeBlockElement(element) {
|
|
|
264
205
|
return `\`\`\`${language}\n${code}${code.endsWith("\n") ? "" : "\n"}\`\`\``;
|
|
265
206
|
}
|
|
266
207
|
function renderElement(element, imageKeys, mediaKeys, mentionedOpenIds, renderMediaPlaceholders) {
|
|
267
|
-
if (!isRecord
|
|
208
|
+
if (!isRecord(element)) return escapeMarkdownText(toStringOrEmpty(element));
|
|
268
209
|
switch (normalizeLowercaseStringOrEmpty(toStringOrEmpty(element.tag))) {
|
|
269
210
|
case "text": return renderTextElement(element);
|
|
270
211
|
case "a": return renderLinkElement(element);
|
|
@@ -305,7 +246,7 @@ function renderElement(element, imageKeys, mediaKeys, mentionedOpenIds, renderMe
|
|
|
305
246
|
}
|
|
306
247
|
}
|
|
307
248
|
function toPostPayload(candidate) {
|
|
308
|
-
if (!isRecord
|
|
249
|
+
if (!isRecord(candidate) || !Array.isArray(candidate.content)) return null;
|
|
309
250
|
return {
|
|
310
251
|
title: toStringOrEmpty(candidate.title),
|
|
311
252
|
content: candidate.content
|
|
@@ -314,7 +255,7 @@ function toPostPayload(candidate) {
|
|
|
314
255
|
function resolveLocalePayload(candidate) {
|
|
315
256
|
const direct = toPostPayload(candidate);
|
|
316
257
|
if (direct) return direct;
|
|
317
|
-
if (!isRecord
|
|
258
|
+
if (!isRecord(candidate)) return null;
|
|
318
259
|
for (const value of Object.values(candidate)) {
|
|
319
260
|
const localePayload = toPostPayload(value);
|
|
320
261
|
if (localePayload) return localePayload;
|
|
@@ -324,7 +265,7 @@ function resolveLocalePayload(candidate) {
|
|
|
324
265
|
function resolvePostPayload(parsed) {
|
|
325
266
|
const direct = toPostPayload(parsed);
|
|
326
267
|
if (direct) return direct;
|
|
327
|
-
if (!isRecord
|
|
268
|
+
if (!isRecord(parsed)) return null;
|
|
328
269
|
const wrappedPost = resolveLocalePayload(parsed.post);
|
|
329
270
|
if (wrappedPost) return wrappedPost;
|
|
330
271
|
return resolveLocalePayload(parsed);
|
|
@@ -364,64 +305,9 @@ function parsePostContent(content, options = {}) {
|
|
|
364
305
|
}
|
|
365
306
|
}
|
|
366
307
|
//#endregion
|
|
367
|
-
//#region extensions/feishu/src/
|
|
368
|
-
const WITHDRAWN_REPLY_ERROR_CODES = /* @__PURE__ */ new Set([230011, 231003]);
|
|
308
|
+
//#region extensions/feishu/src/interactive-message-content.ts
|
|
369
309
|
const INTERACTIVE_CARD_FALLBACK_TEXT = "[Interactive Card]";
|
|
370
310
|
const POST_FALLBACK_TEXT = "[Rich text message]";
|
|
371
|
-
function shouldFallbackFromReplyTarget(response) {
|
|
372
|
-
if (response.code !== void 0 && WITHDRAWN_REPLY_ERROR_CODES.has(response.code)) return true;
|
|
373
|
-
const msg = normalizeLowercaseStringOrEmpty(response.msg);
|
|
374
|
-
return msg.includes("withdrawn") || msg.includes("not found");
|
|
375
|
-
}
|
|
376
|
-
/** Check whether a thrown error indicates a withdrawn/not-found reply target. */
|
|
377
|
-
function isWithdrawnReplyError(err) {
|
|
378
|
-
if (typeof err !== "object" || err === null) return false;
|
|
379
|
-
const code = err.code;
|
|
380
|
-
if (typeof code === "number" && WITHDRAWN_REPLY_ERROR_CODES.has(code)) return true;
|
|
381
|
-
const response = err.response;
|
|
382
|
-
if (typeof response?.data?.code === "number" && WITHDRAWN_REPLY_ERROR_CODES.has(response.data.code)) return true;
|
|
383
|
-
const cause = err.cause;
|
|
384
|
-
if (cause && cause !== err) return isWithdrawnReplyError(cause);
|
|
385
|
-
return false;
|
|
386
|
-
}
|
|
387
|
-
/** Send a direct message as a fallback when a reply target is unavailable. */
|
|
388
|
-
async function sendFallbackDirect(client, params, errorPrefix) {
|
|
389
|
-
const response = await requestFeishuApi(() => client.im.message.create({
|
|
390
|
-
params: { receive_id_type: params.receiveIdType },
|
|
391
|
-
data: {
|
|
392
|
-
receive_id: params.receiveId,
|
|
393
|
-
content: params.content,
|
|
394
|
-
msg_type: params.msgType
|
|
395
|
-
}
|
|
396
|
-
}), errorPrefix, { includeNestedErrorLogId: true });
|
|
397
|
-
assertFeishuMessageApiSuccess(response, errorPrefix);
|
|
398
|
-
return toFeishuSendResult(response, params.receiveId, resolveFeishuReceiptKind(params.msgType));
|
|
399
|
-
}
|
|
400
|
-
async function sendReplyOrFallbackDirect(client, params) {
|
|
401
|
-
if (!params.replyToMessageId) return sendFallbackDirect(client, params.directParams, params.directErrorPrefix);
|
|
402
|
-
const replyTargetFallbackError = params.replyInThread && params.allowTopLevelReplyFallback !== true ? /* @__PURE__ */ new Error("Feishu thread reply failed: reply target is unavailable and cannot safely fall back to a top-level send.") : null;
|
|
403
|
-
let response;
|
|
404
|
-
try {
|
|
405
|
-
response = await requestFeishuApi(() => client.im.message.reply({
|
|
406
|
-
path: { message_id: params.replyToMessageId },
|
|
407
|
-
data: {
|
|
408
|
-
content: params.content,
|
|
409
|
-
msg_type: params.msgType,
|
|
410
|
-
...params.replyInThread ? { reply_in_thread: true } : {}
|
|
411
|
-
}
|
|
412
|
-
}), params.replyErrorPrefix, { includeNestedErrorLogId: true });
|
|
413
|
-
} catch (err) {
|
|
414
|
-
if (!isWithdrawnReplyError(err)) throw err;
|
|
415
|
-
if (replyTargetFallbackError) throw replyTargetFallbackError;
|
|
416
|
-
return sendFallbackDirect(client, params.directParams, params.directErrorPrefix);
|
|
417
|
-
}
|
|
418
|
-
if (shouldFallbackFromReplyTarget(response)) {
|
|
419
|
-
if (replyTargetFallbackError) throw replyTargetFallbackError;
|
|
420
|
-
return sendFallbackDirect(client, params.directParams, params.directErrorPrefix);
|
|
421
|
-
}
|
|
422
|
-
assertFeishuMessageApiSuccess(response, params.replyErrorPrefix);
|
|
423
|
-
return toFeishuSendResult(response, params.directParams.receiveId, resolveFeishuReceiptKind(params.msgType));
|
|
424
|
-
}
|
|
425
311
|
function normalizeCardTemplateVariable(value) {
|
|
426
312
|
if (typeof value === "string") return value;
|
|
427
313
|
if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") return String(value);
|
|
@@ -444,17 +330,79 @@ function applyCardTemplateVariables(text, variables) {
|
|
|
444
330
|
return variables.get(variableName) ?? match;
|
|
445
331
|
});
|
|
446
332
|
}
|
|
333
|
+
function normalizeInteractiveValue(value, variables) {
|
|
334
|
+
const scalar = normalizeCardTemplateVariable(value);
|
|
335
|
+
if (scalar !== void 0) return applyCardTemplateVariables(scalar, variables);
|
|
336
|
+
if (Array.isArray(value)) return value.map((entry) => normalizeInteractiveValue(entry, variables)).filter(Boolean).join(", ");
|
|
337
|
+
if (!isRecord(value)) return "";
|
|
338
|
+
for (const key of [
|
|
339
|
+
"content",
|
|
340
|
+
"text",
|
|
341
|
+
"label",
|
|
342
|
+
"name",
|
|
343
|
+
"display_name",
|
|
344
|
+
"user_name",
|
|
345
|
+
"user_id",
|
|
346
|
+
"open_id",
|
|
347
|
+
"id",
|
|
348
|
+
"value"
|
|
349
|
+
]) {
|
|
350
|
+
const text = normalizeInteractiveValue(value[key], variables);
|
|
351
|
+
if (text) return text;
|
|
352
|
+
}
|
|
353
|
+
return "";
|
|
354
|
+
}
|
|
355
|
+
function extractInteractiveTableText(element, variables) {
|
|
356
|
+
if (!Array.isArray(element.columns) || !Array.isArray(element.rows)) return;
|
|
357
|
+
const columns = element.columns.flatMap((column) => {
|
|
358
|
+
if (!isRecord(column) || typeof column.name !== "string") return [];
|
|
359
|
+
return [{
|
|
360
|
+
name: column.name,
|
|
361
|
+
title: typeof column.display_name === "string" ? column.display_name : column.name
|
|
362
|
+
}];
|
|
363
|
+
});
|
|
364
|
+
if (columns.length === 0) return;
|
|
365
|
+
const lines = [columns.map((column) => applyCardTemplateVariables(column.title, variables)).join(" | ")];
|
|
366
|
+
for (const row of element.rows) {
|
|
367
|
+
if (!isRecord(row)) continue;
|
|
368
|
+
const cells = columns.map((column) => normalizeInteractiveValue(row[column.name], variables));
|
|
369
|
+
if (cells.some(Boolean)) lines.push(cells.join(" | "));
|
|
370
|
+
}
|
|
371
|
+
return lines.join("\n");
|
|
372
|
+
}
|
|
447
373
|
function extractInteractiveElementText(element, variables) {
|
|
448
374
|
if (!isRecord(element)) return;
|
|
449
375
|
const tag = typeof element.tag === "string" ? element.tag : "";
|
|
450
376
|
const text = isRecord(element.text) ? element.text : void 0;
|
|
451
|
-
if (tag === "div"
|
|
377
|
+
if (tag === "div") {
|
|
378
|
+
const parts = [normalizeInteractiveValue(element.text, variables)];
|
|
379
|
+
if (Array.isArray(element.fields)) parts.push(extractInteractiveElementsText(element.fields, variables));
|
|
380
|
+
return parts.filter(Boolean).join("\n") || void 0;
|
|
381
|
+
}
|
|
452
382
|
if ((tag === "markdown" || tag === "lark_md") && typeof element.content === "string") return applyCardTemplateVariables(element.content, variables);
|
|
383
|
+
if ((tag === "text" || tag === "a" || tag === "button") && element.text !== void 0) return normalizeInteractiveValue(element.text, variables) || void 0;
|
|
384
|
+
if (tag === "at") {
|
|
385
|
+
const mention = normalizeInteractiveValue(element.user_name ?? element.user_id, variables);
|
|
386
|
+
return mention ? mention.startsWith("@") ? mention : `@${mention}` : void 0;
|
|
387
|
+
}
|
|
453
388
|
if (tag === "plain_text" && typeof element.content === "string") return applyCardTemplateVariables(element.content, variables);
|
|
389
|
+
if (tag === "table") return extractInteractiveTableText(element, variables);
|
|
390
|
+
return [
|
|
391
|
+
element.elements,
|
|
392
|
+
element.columns,
|
|
393
|
+
element.children,
|
|
394
|
+
element.fields,
|
|
395
|
+
element.actions
|
|
396
|
+
].filter(Array.isArray).map((children) => extractInteractiveElementsText(children, variables)).filter(Boolean).join("\n") || (typeof text?.content === "string" ? text.content : void 0);
|
|
454
397
|
}
|
|
455
398
|
function extractInteractiveElementsText(elements, variables) {
|
|
456
399
|
const texts = [];
|
|
457
400
|
for (const element of elements) {
|
|
401
|
+
if (Array.isArray(element)) {
|
|
402
|
+
const row = element.map((part) => extractInteractiveElementText(part, variables)).filter((part) => Boolean(part)).join(" ").trim();
|
|
403
|
+
if (row) texts.push(row);
|
|
404
|
+
continue;
|
|
405
|
+
}
|
|
458
406
|
const text = extractInteractiveElementText(element, variables);
|
|
459
407
|
if (text !== void 0) texts.push(text);
|
|
460
408
|
}
|
|
@@ -470,18 +418,140 @@ function readInteractiveElementArrays(parsed) {
|
|
|
470
418
|
}
|
|
471
419
|
return elementArrays;
|
|
472
420
|
}
|
|
473
|
-
function
|
|
474
|
-
|
|
475
|
-
|
|
421
|
+
function readInteractiveCardTitle(parsed, variables) {
|
|
422
|
+
if (typeof parsed.title === "string") return applyCardTemplateVariables(parsed.title, variables).trim();
|
|
423
|
+
const header = isRecord(parsed.header) ? parsed.header : void 0;
|
|
424
|
+
const title = isRecord(header?.title) ? header.title : void 0;
|
|
425
|
+
return typeof title?.content === "string" ? applyCardTemplateVariables(title.content, variables).trim() : "";
|
|
476
426
|
}
|
|
477
427
|
function parseInteractiveCardContent(parsed) {
|
|
478
428
|
if (!isRecord(parsed)) return INTERACTIVE_CARD_FALLBACK_TEXT;
|
|
479
429
|
const variables = readCardTemplateVariables(parsed);
|
|
430
|
+
const title = readInteractiveCardTitle(parsed, variables);
|
|
480
431
|
for (const elements of readInteractiveElementArrays(parsed)) {
|
|
481
432
|
const text = extractInteractiveElementsText(elements, variables);
|
|
482
|
-
if (text) return text;
|
|
433
|
+
if (text) return title ? `${title}\n${text}` : text;
|
|
483
434
|
}
|
|
484
|
-
|
|
435
|
+
const postText = parsePostContent(JSON.stringify(parsed)).textContent.trim();
|
|
436
|
+
if (postText && postText !== POST_FALLBACK_TEXT) return postText;
|
|
437
|
+
return title || INTERACTIVE_CARD_FALLBACK_TEXT;
|
|
438
|
+
}
|
|
439
|
+
//#endregion
|
|
440
|
+
//#region extensions/feishu/src/types.ts
|
|
441
|
+
function isFeishuGroupChatType(chatType) {
|
|
442
|
+
return chatType === "group" || chatType === "topic_group";
|
|
443
|
+
}
|
|
444
|
+
//#endregion
|
|
445
|
+
//#region extensions/feishu/src/mention.ts
|
|
446
|
+
function isFeishuBroadcastMention(mention) {
|
|
447
|
+
const normalizedKey = mention.key?.trim().toLowerCase();
|
|
448
|
+
if (normalizedKey === "@all" || normalizedKey === "@_all") return true;
|
|
449
|
+
return [
|
|
450
|
+
mention.id?.open_id,
|
|
451
|
+
mention.id?.user_id,
|
|
452
|
+
mention.id?.union_id
|
|
453
|
+
].some((id) => id?.trim().toLowerCase() === "all");
|
|
454
|
+
}
|
|
455
|
+
/**
|
|
456
|
+
* Extract mention targets from message event (excluding the bot itself)
|
|
457
|
+
*/
|
|
458
|
+
function extractMentionTargets(event, botOpenId) {
|
|
459
|
+
return (event.message.mentions ?? []).filter((m) => {
|
|
460
|
+
if (isFeishuBroadcastMention(m)) return false;
|
|
461
|
+
if (m.id.open_id === botOpenId) return false;
|
|
462
|
+
return Boolean(m.id.open_id);
|
|
463
|
+
}).map((m) => ({
|
|
464
|
+
openId: m.id.open_id,
|
|
465
|
+
name: m.name,
|
|
466
|
+
key: m.key
|
|
467
|
+
}));
|
|
468
|
+
}
|
|
469
|
+
/**
|
|
470
|
+
* Check if message is a mention forward request
|
|
471
|
+
* Rules:
|
|
472
|
+
* - Group: message mentions bot + at least one other user
|
|
473
|
+
* - DM: message mentions any user (no need to mention bot)
|
|
474
|
+
*/
|
|
475
|
+
function isMentionForwardRequest(event, botOpenId) {
|
|
476
|
+
const mentions = event.message.mentions ?? [];
|
|
477
|
+
if (mentions.length === 0) return false;
|
|
478
|
+
const normalizedBotOpenId = botOpenId?.trim();
|
|
479
|
+
if (!normalizedBotOpenId) return false;
|
|
480
|
+
const isDirectMessage = !isFeishuGroupChatType(event.message.chat_type);
|
|
481
|
+
const userMentions = mentions.filter((m) => !isFeishuBroadcastMention(m));
|
|
482
|
+
const hasOtherMention = userMentions.some((m) => m.id.open_id !== normalizedBotOpenId);
|
|
483
|
+
if (isDirectMessage) return hasOtherMention;
|
|
484
|
+
return userMentions.some((m) => m.id.open_id === normalizedBotOpenId) && hasOtherMention;
|
|
485
|
+
}
|
|
486
|
+
/**
|
|
487
|
+
* Format @mention for card message (lark_md)
|
|
488
|
+
*/
|
|
489
|
+
function formatMentionForCard(target) {
|
|
490
|
+
return `<at id=${target.openId}></at>`;
|
|
491
|
+
}
|
|
492
|
+
/**
|
|
493
|
+
* Build card content with @mentions (Markdown format)
|
|
494
|
+
*/
|
|
495
|
+
function buildMentionedCardContent(targets, message) {
|
|
496
|
+
if (targets.length === 0) return message;
|
|
497
|
+
return `${targets.map((t) => formatMentionForCard(t)).join(" ")} ${message}`;
|
|
498
|
+
}
|
|
499
|
+
//#endregion
|
|
500
|
+
//#region extensions/feishu/src/send.ts
|
|
501
|
+
const WITHDRAWN_REPLY_ERROR_CODES = /* @__PURE__ */ new Set([230011, 231003]);
|
|
502
|
+
function shouldFallbackFromReplyTarget(response) {
|
|
503
|
+
if (response.code !== void 0 && WITHDRAWN_REPLY_ERROR_CODES.has(response.code)) return true;
|
|
504
|
+
const msg = normalizeLowercaseStringOrEmpty(response.msg);
|
|
505
|
+
return msg.includes("withdrawn") || msg.includes("not found");
|
|
506
|
+
}
|
|
507
|
+
/** Check whether a thrown error indicates a withdrawn/not-found reply target. */
|
|
508
|
+
function isWithdrawnReplyError(err) {
|
|
509
|
+
if (typeof err !== "object" || err === null) return false;
|
|
510
|
+
const code = err.code;
|
|
511
|
+
if (typeof code === "number" && WITHDRAWN_REPLY_ERROR_CODES.has(code)) return true;
|
|
512
|
+
const response = err.response;
|
|
513
|
+
if (typeof response?.data?.code === "number" && WITHDRAWN_REPLY_ERROR_CODES.has(response.data.code)) return true;
|
|
514
|
+
const cause = err.cause;
|
|
515
|
+
if (cause && cause !== err) return isWithdrawnReplyError(cause);
|
|
516
|
+
return false;
|
|
517
|
+
}
|
|
518
|
+
/** Send a direct message as a fallback when a reply target is unavailable. */
|
|
519
|
+
async function sendFallbackDirect(client, params, errorPrefix) {
|
|
520
|
+
const response = await requestFeishuApi(() => client.im.message.create({
|
|
521
|
+
params: { receive_id_type: params.receiveIdType },
|
|
522
|
+
data: {
|
|
523
|
+
receive_id: params.receiveId,
|
|
524
|
+
content: params.content,
|
|
525
|
+
msg_type: params.msgType
|
|
526
|
+
}
|
|
527
|
+
}), errorPrefix, { includeNestedErrorLogId: true });
|
|
528
|
+
assertFeishuMessageApiSuccess(response, errorPrefix);
|
|
529
|
+
return toFeishuSendResult(response, params.receiveId, resolveFeishuReceiptKind(params.msgType), errorPrefix);
|
|
530
|
+
}
|
|
531
|
+
async function sendReplyOrFallbackDirect(client, params) {
|
|
532
|
+
if (!params.replyToMessageId) return sendFallbackDirect(client, params.directParams, params.directErrorPrefix);
|
|
533
|
+
const replyTargetFallbackError = params.replyInThread && params.allowTopLevelReplyFallback !== true ? /* @__PURE__ */ new Error("Feishu thread reply failed: reply target is unavailable and cannot safely fall back to a top-level send.") : null;
|
|
534
|
+
let response;
|
|
535
|
+
try {
|
|
536
|
+
response = await requestFeishuApi(() => client.im.message.reply({
|
|
537
|
+
path: { message_id: params.replyToMessageId },
|
|
538
|
+
data: {
|
|
539
|
+
content: params.content,
|
|
540
|
+
msg_type: params.msgType,
|
|
541
|
+
...params.replyInThread ? { reply_in_thread: true } : {}
|
|
542
|
+
}
|
|
543
|
+
}), params.replyErrorPrefix, { includeNestedErrorLogId: true });
|
|
544
|
+
} catch (err) {
|
|
545
|
+
if (!isWithdrawnReplyError(err)) throw err;
|
|
546
|
+
if (replyTargetFallbackError) throw replyTargetFallbackError;
|
|
547
|
+
return sendFallbackDirect(client, params.directParams, params.directErrorPrefix);
|
|
548
|
+
}
|
|
549
|
+
if (shouldFallbackFromReplyTarget(response)) {
|
|
550
|
+
if (replyTargetFallbackError) throw replyTargetFallbackError;
|
|
551
|
+
return sendFallbackDirect(client, params.directParams, params.directErrorPrefix);
|
|
552
|
+
}
|
|
553
|
+
assertFeishuMessageApiSuccess(response, params.replyErrorPrefix);
|
|
554
|
+
return toFeishuSendResult(response, params.directParams.receiveId, resolveFeishuReceiptKind(params.msgType), params.replyErrorPrefix);
|
|
485
555
|
}
|
|
486
556
|
function parseFeishuMessageContent(rawContent, msgType, messageId) {
|
|
487
557
|
if (!rawContent) return "";
|
|
@@ -518,6 +588,7 @@ function parseFeishuMessageItem(item, fallbackMessageId) {
|
|
|
518
588
|
content: parseFeishuMessageContent(rawContent, msgType, item.message_id),
|
|
519
589
|
contentType: msgType,
|
|
520
590
|
createTime: parseStrictNonNegativeInteger(item.create_time),
|
|
591
|
+
...item.root_id ? { rootId: item.root_id } : {},
|
|
521
592
|
threadId: item.thread_id || void 0
|
|
522
593
|
};
|
|
523
594
|
}
|
|
@@ -559,29 +630,40 @@ async function listFeishuThreadMessages(params) {
|
|
|
559
630
|
accountId
|
|
560
631
|
});
|
|
561
632
|
if (!account.configured) throw new Error(`Feishu account "${account.accountId}" not configured`);
|
|
562
|
-
const
|
|
563
|
-
container_id_type: "thread",
|
|
564
|
-
container_id: threadId,
|
|
565
|
-
sort_type: "ByCreateTimeDesc",
|
|
566
|
-
page_size: Math.min(limit + 1, 50),
|
|
567
|
-
card_msg_content_type: "user_card_content"
|
|
568
|
-
} });
|
|
569
|
-
if (response.code !== 0) throw new Error(`Feishu thread list failed: code=${response.code} msg=${response.msg ?? "unknown"}`);
|
|
570
|
-
const items = response.data?.items ?? [];
|
|
633
|
+
const client = createFeishuClient(account);
|
|
571
634
|
const results = [];
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
});
|
|
584
|
-
if (
|
|
635
|
+
const seenMessageIds = /* @__PURE__ */ new Set();
|
|
636
|
+
const seenPageTokens = /* @__PURE__ */ new Set();
|
|
637
|
+
let pageToken;
|
|
638
|
+
while (results.length < limit) {
|
|
639
|
+
const response = await client.im.message.list({ params: {
|
|
640
|
+
container_id_type: "thread",
|
|
641
|
+
container_id: threadId,
|
|
642
|
+
sort_type: "ByCreateTimeDesc",
|
|
643
|
+
page_size: Math.min(limit + 1, 50),
|
|
644
|
+
...pageToken ? { page_token: pageToken } : {},
|
|
645
|
+
card_msg_content_type: "user_card_content"
|
|
646
|
+
} });
|
|
647
|
+
if (response.code !== 0) throw new Error(`Feishu thread list failed: code=${response.code} msg=${response.msg ?? "unknown"}`);
|
|
648
|
+
for (const item of response.data?.items ?? []) {
|
|
649
|
+
if (currentMessageId && item.message_id === currentMessageId || rootMessageId && item.message_id === rootMessageId || item.message_id && seenMessageIds.has(item.message_id)) continue;
|
|
650
|
+
const parsed = parseFeishuMessageItem(item);
|
|
651
|
+
if (parsed.messageId) seenMessageIds.add(parsed.messageId);
|
|
652
|
+
results.push({
|
|
653
|
+
messageId: parsed.messageId,
|
|
654
|
+
senderId: parsed.senderId,
|
|
655
|
+
senderType: parsed.senderType,
|
|
656
|
+
content: parsed.content,
|
|
657
|
+
contentType: parsed.contentType,
|
|
658
|
+
createTime: parsed.createTime
|
|
659
|
+
});
|
|
660
|
+
if (results.length >= limit) break;
|
|
661
|
+
}
|
|
662
|
+
if (results.length >= limit || response.data?.has_more !== true) break;
|
|
663
|
+
const nextPageToken = response.data.page_token?.trim();
|
|
664
|
+
if (!nextPageToken || seenPageTokens.has(nextPageToken)) throw new Error(`Feishu thread history pagination returned a ${nextPageToken ? "repeated" : "missing"} page token`);
|
|
665
|
+
seenPageTokens.add(nextPageToken);
|
|
666
|
+
pageToken = nextPageToken;
|
|
585
667
|
}
|
|
586
668
|
results.reverse();
|
|
587
669
|
return results;
|
|
@@ -765,9 +847,26 @@ async function sendMarkdownCardFeishu(params) {
|
|
|
765
847
|
//#endregion
|
|
766
848
|
//#region extensions/feishu/src/media.ts
|
|
767
849
|
const FEISHU_MEDIA_HTTP_TIMEOUT_MS = 12e4;
|
|
850
|
+
const FEISHU_MAX_FILE_UPLOAD_BYTES = 30 * 1024 * 1024;
|
|
851
|
+
const FEISHU_MAX_IMAGE_UPLOAD_BYTES = 10 * 1024 * 1024;
|
|
768
852
|
const FEISHU_VOICE_FILE_NAME = "voice.ogg";
|
|
769
853
|
const FEISHU_VOICE_SAMPLE_RATE_HZ = 48e3;
|
|
770
854
|
const FEISHU_VOICE_BITRATE = "64k";
|
|
855
|
+
const FEISHU_SUPPORTED_IMAGE_CONTENT_TYPES = /* @__PURE__ */ new Set([
|
|
856
|
+
"image/jpeg",
|
|
857
|
+
"image/jpg",
|
|
858
|
+
"image/png",
|
|
859
|
+
"image/gif",
|
|
860
|
+
"image/webp",
|
|
861
|
+
"image/bmp",
|
|
862
|
+
"image/x-ms-bmp",
|
|
863
|
+
"image/tiff",
|
|
864
|
+
"image/tif",
|
|
865
|
+
"image/heic",
|
|
866
|
+
"image/x-icon",
|
|
867
|
+
"image/ico",
|
|
868
|
+
"image/vnd.microsoft.icon"
|
|
869
|
+
]);
|
|
771
870
|
const FEISHU_TRANSCODABLE_AUDIO_EXTS = /* @__PURE__ */ new Set([
|
|
772
871
|
".aac",
|
|
773
872
|
".aiff",
|
|
@@ -1035,7 +1134,7 @@ async function sendImageFeishu(params) {
|
|
|
1035
1134
|
}
|
|
1036
1135
|
}), "Feishu image send failed", { includeNestedErrorLogId: true });
|
|
1037
1136
|
assertFeishuMessageApiSuccess(response, "Feishu image send failed");
|
|
1038
|
-
return toFeishuSendResult(response, receiveId, "media");
|
|
1137
|
+
return toFeishuSendResult(response, receiveId, "media", "Feishu image send failed");
|
|
1039
1138
|
}
|
|
1040
1139
|
/**
|
|
1041
1140
|
* Send a file message using a file_key
|
|
@@ -1073,7 +1172,7 @@ async function sendFileFeishu(params) {
|
|
|
1073
1172
|
}
|
|
1074
1173
|
}), "Feishu file send failed", { includeNestedErrorLogId: true });
|
|
1075
1174
|
assertFeishuMessageApiSuccess(response, "Feishu file send failed");
|
|
1076
|
-
return toFeishuSendResult(response, receiveId, resolveFeishuReceiptKind(msgType));
|
|
1175
|
+
return toFeishuSendResult(response, receiveId, resolveFeishuReceiptKind(msgType), "Feishu file send failed");
|
|
1077
1176
|
}
|
|
1078
1177
|
/**
|
|
1079
1178
|
* Helper to detect file type from extension
|
|
@@ -1095,20 +1194,11 @@ function detectFileType(fileName) {
|
|
|
1095
1194
|
default: return "stream";
|
|
1096
1195
|
}
|
|
1097
1196
|
}
|
|
1098
|
-
function resolveFeishuOutboundMediaKind(params) {
|
|
1099
|
-
const { fileName, contentType } = params;
|
|
1197
|
+
async function resolveFeishuOutboundMediaKind(params) {
|
|
1198
|
+
const { buffer, fileName, contentType } = params;
|
|
1100
1199
|
const ext = normalizeLowercaseStringOrEmpty(path.extname(fileName));
|
|
1101
|
-
const
|
|
1102
|
-
if (
|
|
1103
|
-
".jpg",
|
|
1104
|
-
".jpeg",
|
|
1105
|
-
".png",
|
|
1106
|
-
".gif",
|
|
1107
|
-
".webp",
|
|
1108
|
-
".bmp",
|
|
1109
|
-
".ico",
|
|
1110
|
-
".tiff"
|
|
1111
|
-
].includes(ext) || mimeKind === "image") return { msgType: "image" };
|
|
1200
|
+
const detectedContentType = await detectMime({ buffer }) ?? "";
|
|
1201
|
+
if (FEISHU_SUPPORTED_IMAGE_CONTENT_TYPES.has(detectedContentType)) return { msgType: "image" };
|
|
1112
1202
|
if (ext === ".opus" || ext === ".ogg" || contentType === "audio/ogg" || contentType === "audio/opus") return {
|
|
1113
1203
|
fileType: "opus",
|
|
1114
1204
|
msgType: "audio"
|
|
@@ -1127,6 +1217,14 @@ function resolveFeishuOutboundMediaKind(params) {
|
|
|
1127
1217
|
msgType: fileType === "stream" ? "file" : fileType === "opus" ? "audio" : fileType === "mp4" ? "media" : "file"
|
|
1128
1218
|
};
|
|
1129
1219
|
}
|
|
1220
|
+
function assertFeishuUploadWithinEnvelope(params) {
|
|
1221
|
+
if (params.buffer.byteLength === 0) throw new Error("Feishu attachments cannot be empty");
|
|
1222
|
+
const maxBytes = params.msgType === "image" ? Math.min(params.mediaMaxBytes, FEISHU_MAX_IMAGE_UPLOAD_BYTES) : params.mediaMaxBytes;
|
|
1223
|
+
if (params.buffer.byteLength > maxBytes) {
|
|
1224
|
+
const label = params.msgType === "image" ? "image" : "file";
|
|
1225
|
+
throw new Error(`Feishu ${label} exceeds its ${String(maxBytes)}-byte upload limit`);
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1130
1228
|
function isFeishuNativeVoiceAudio(params) {
|
|
1131
1229
|
const ext = normalizeLowercaseStringOrEmpty(path.extname(params.fileName));
|
|
1132
1230
|
const contentType = normalizeLowercaseStringOrEmpty(params.contentType);
|
|
@@ -1244,19 +1342,19 @@ async function maybeProbeUploadDurationMs(params) {
|
|
|
1244
1342
|
}
|
|
1245
1343
|
/**
|
|
1246
1344
|
* Upload and send media (image or file) from URL, local path, or buffer.
|
|
1247
|
-
*
|
|
1248
|
-
* must be passed so loadWebMedia allows the path (post CVE-2026-26321).
|
|
1345
|
+
* Local paths require host-owned mediaAccess or approved legacy roots/readers.
|
|
1249
1346
|
*/
|
|
1250
1347
|
async function sendMediaFeishu(params) {
|
|
1251
1348
|
const { cfg, to, mediaUrl, mediaBuffer, fileName, replyToMessageId, replyInThread, allowTopLevelReplyFallback, accountId, mediaLocalRoots, audioAsVoice } = params;
|
|
1252
|
-
const
|
|
1349
|
+
const account = await runBeforeFeishuMessageDispatch(() => {
|
|
1253
1350
|
const resolved = resolveFeishuRuntimeAccount({
|
|
1254
1351
|
cfg,
|
|
1255
1352
|
accountId
|
|
1256
1353
|
});
|
|
1257
1354
|
if (!resolved.configured) throw new Error(`Feishu account "${resolved.accountId}" not configured`);
|
|
1258
1355
|
return resolved;
|
|
1259
|
-
})
|
|
1356
|
+
});
|
|
1357
|
+
const mediaMaxBytes = Math.min((account.config?.mediaMaxMb ?? 30) * 1024 * 1024, FEISHU_MAX_FILE_UPLOAD_BYTES);
|
|
1260
1358
|
let buffer;
|
|
1261
1359
|
let name;
|
|
1262
1360
|
let contentType;
|
|
@@ -1267,11 +1365,13 @@ async function sendMediaFeishu(params) {
|
|
|
1267
1365
|
contentType: void 0
|
|
1268
1366
|
};
|
|
1269
1367
|
if (mediaUrl) {
|
|
1270
|
-
const media = await getFeishuRuntime().media.loadWebMedia(mediaUrl, {
|
|
1368
|
+
const media = await getFeishuRuntime().media.loadWebMedia(mediaUrl, buildOutboundMediaLoadOptions({
|
|
1271
1369
|
maxBytes: mediaMaxBytes,
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1370
|
+
mediaAccess: params.mediaAccess,
|
|
1371
|
+
mediaLocalRoots,
|
|
1372
|
+
mediaReadFile: params.mediaReadFile,
|
|
1373
|
+
optimizeImages: false
|
|
1374
|
+
}));
|
|
1275
1375
|
return {
|
|
1276
1376
|
buffer: media.buffer,
|
|
1277
1377
|
name: fileName ?? media.fileName ?? "file",
|
|
@@ -1283,6 +1383,16 @@ async function sendMediaFeishu(params) {
|
|
|
1283
1383
|
buffer = loaded.buffer;
|
|
1284
1384
|
name = loaded.name;
|
|
1285
1385
|
contentType = loaded.contentType;
|
|
1386
|
+
const loadedRouting = await runBeforeFeishuMessageDispatch(() => resolveFeishuOutboundMediaKind({
|
|
1387
|
+
buffer,
|
|
1388
|
+
fileName: name,
|
|
1389
|
+
contentType
|
|
1390
|
+
}));
|
|
1391
|
+
await runBeforeFeishuMessageDispatch(() => assertFeishuUploadWithinEnvelope({
|
|
1392
|
+
buffer,
|
|
1393
|
+
mediaMaxBytes,
|
|
1394
|
+
msgType: loadedRouting.msgType
|
|
1395
|
+
}));
|
|
1286
1396
|
const prepared = await runBeforeFeishuMessageDispatch(() => prepareFeishuVoiceMedia({
|
|
1287
1397
|
buffer,
|
|
1288
1398
|
fileName: name,
|
|
@@ -1292,11 +1402,17 @@ async function sendMediaFeishu(params) {
|
|
|
1292
1402
|
buffer = prepared.buffer;
|
|
1293
1403
|
name = prepared.fileName;
|
|
1294
1404
|
contentType = prepared.contentType;
|
|
1295
|
-
const routing = resolveFeishuOutboundMediaKind({
|
|
1405
|
+
const routing = prepared.buffer === loaded.buffer && prepared.fileName === loaded.name && prepared.contentType === loaded.contentType ? loadedRouting : await runBeforeFeishuMessageDispatch(() => resolveFeishuOutboundMediaKind({
|
|
1406
|
+
buffer,
|
|
1296
1407
|
fileName: name,
|
|
1297
1408
|
contentType
|
|
1298
|
-
});
|
|
1409
|
+
}));
|
|
1299
1410
|
const voiceIntentDegradedToFile = audioAsVoice === true && routing.msgType !== "audio";
|
|
1411
|
+
await runBeforeFeishuMessageDispatch(() => assertFeishuUploadWithinEnvelope({
|
|
1412
|
+
buffer,
|
|
1413
|
+
mediaMaxBytes,
|
|
1414
|
+
msgType: routing.msgType
|
|
1415
|
+
}));
|
|
1300
1416
|
if (routing.msgType === "image") {
|
|
1301
1417
|
const { imageKey } = await runBeforeFeishuMessageDispatch(() => uploadImageFeishu({
|
|
1302
1418
|
cfg,
|
|
@@ -1345,4 +1461,4 @@ async function sendMediaFeishu(params) {
|
|
|
1345
1461
|
};
|
|
1346
1462
|
}
|
|
1347
1463
|
//#endregion
|
|
1348
|
-
export {
|
|
1464
|
+
export { normalizeFeishuExternalKey as _, getMessageFeishu as a, resolveFeishuIdentityHeaderTitle as b, sendMarkdownCardFeishu as c, extractMentionTargets as d, isFeishuBroadcastMention as f, parsePostContent as g, parseInteractiveCardContent as h, editMessageFeishu as i, sendMessageFeishu as l, isFeishuGroupChatType as m, sendMediaFeishu as n, listFeishuThreadMessages as o, isMentionForwardRequest as p, shouldSuppressFeishuTextForVoiceMedia as r, sendCardFeishu as s, saveMessageResourceFeishu as t, sendStructuredCardFeishu as u, buildFeishuMediaFallbackText as v, resolveFeishuIdentityEmoji as y };
|