diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c1e122f5..74dd21972 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ Docs: https://docs.openclaw.ai - Feishu/Typing backoff: re-throw Feishu typing add/remove rate-limit and quota errors (`429`, `99991400`, `99991403`) and detect SDK non-throwing backoff responses so the typing keepalive circuit breaker can stop retries instead of looping indefinitely. (#28494) - Feishu/Probe status caching: cache successful `probeFeishu()` bot-info results for 10 minutes (bounded cache with per-account keying) to reduce repeated status/onboarding probe API calls, while bypassing cache for failures and exceptions. (#28907) Thanks @Glucksberg. - Feishu/Opus media send type: send `.opus` attachments with `msg_type: "audio"` (instead of `"media"`) so Feishu voice messages deliver correctly while `.mp4` remains `msg_type: "media"` and documents remain `msg_type: "file"`. (#28269) Thanks @Glucksberg. +- Feishu/Mobile video media type: treat inbound `message_type: "media"` as video-equivalent for media key extraction, placeholder inference, and media download resolution so mobile-app video sends ingest correctly. (#25502) Thanks @4ier. - Feishu/Inbound rich-text parsing: preserve `share_chat` payload summaries when available and add explicit parsing for rich-text `code`/`code_block`/`pre` tags so forwarded and code-heavy messages keep useful context in agent input. (#28591) Thanks @kevinWangSheng. - Feishu/Local media sends: propagate `mediaLocalRoots` through Feishu outbound media sending into `loadWebMedia` so local path attachments work with post-CVE local-root enforcement. (#27884) Thanks @joelnishanth. - Feishu/Group sender allowlist fallback: add global `channels.feishu.groupSenderAllowFrom` sender authorization for group chats, with per-group `groups..allowFrom` precedence and regression coverage for allow/block/precedence behavior. (#29174) Thanks @1MoreBuild. diff --git a/extensions/feishu/src/bot.test.ts b/extensions/feishu/src/bot.test.ts index 6fbe8225a..b679bd71d 100644 --- a/extensions/feishu/src/bot.test.ts +++ b/extensions/feishu/src/bot.test.ts @@ -571,6 +571,54 @@ describe("handleFeishuMessage command authorization", () => { ); }); + it("uses media message_type file_key (not thumbnail image_key) for inbound mobile video download", async () => { + mockShouldComputeCommandAuthorized.mockReturnValue(false); + + const cfg: ClawdbotConfig = { + channels: { + feishu: { + dmPolicy: "open", + }, + }, + } as ClawdbotConfig; + + const event: FeishuMessageEvent = { + sender: { + sender_id: { + open_id: "ou-sender", + }, + }, + message: { + message_id: "msg-media-inbound", + chat_id: "oc-dm", + chat_type: "p2p", + message_type: "media", + content: JSON.stringify({ + file_key: "file_media_payload", + image_key: "img_media_thumb", + file_name: "mobile.mp4", + }), + }, + }; + + await dispatchMessage({ cfg, event }); + + expect(mockDownloadMessageResourceFeishu).toHaveBeenCalledWith( + expect.objectContaining({ + messageId: "msg-media-inbound", + fileKey: "file_media_payload", + type: "file", + }), + ); + expect(mockSaveMediaBuffer).toHaveBeenCalledWith( + expect.any(Buffer), + "video/mp4", + "inbound", + expect.any(Number), + "clip.mp4", + ); + }); + it("includes message_id in BodyForAgent on its own line", async () => { mockShouldComputeCommandAuthorized.mockReturnValue(false); diff --git a/extensions/feishu/src/bot.ts b/extensions/feishu/src/bot.ts index 6dcfa6eb0..2486e6a96 100644 --- a/extensions/feishu/src/bot.ts +++ b/extensions/feishu/src/bot.ts @@ -371,7 +371,8 @@ function parseMediaKeys( case "audio": return { fileKey }; case "video": - // Video has both file_key (video) and image_key (thumbnail) + case "media": + // Video/media has both file_key (video) and image_key (thumbnail) return { fileKey, imageKey }; case "sticker": return { fileKey }; @@ -471,6 +472,7 @@ function inferPlaceholder(messageType: string): string { case "audio": return ""; case "video": + case "media": return ""; case "sticker": return ""; @@ -495,7 +497,7 @@ async function resolveFeishuMediaList(params: { const { cfg, messageId, messageType, content, maxBytes, log, accountId } = params; // Only process media message types (including post for embedded images) - const mediaTypes = ["image", "file", "audio", "video", "sticker", "post"]; + const mediaTypes = ["image", "file", "audio", "video", "media", "sticker", "post"]; if (!mediaTypes.includes(messageType)) { return []; }