mirror of
https://github.com/farcasclaudiu/openclaw.git
synced 2026-06-28 23:02:02 +03:00
fix(feishu): use msg_type 'media' for video/audio messages (#14648)
Merged via /review-pr -> /prepare-pr -> /merge-pr. Prepared head SHA: e8044cb2085cc77ac2b9e819a09dc7e1c21bc8da Co-authored-by: 0xRaini <190923101+0xRaini@users.noreply.github.com> Co-authored-by: steipete <58493+steipete@users.noreply.github.com> Reviewed-by: @steipete
This commit is contained in:
@@ -0,0 +1,151 @@
|
|||||||
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
|
const createFeishuClientMock = vi.hoisted(() => vi.fn());
|
||||||
|
const resolveFeishuAccountMock = vi.hoisted(() => vi.fn());
|
||||||
|
const normalizeFeishuTargetMock = vi.hoisted(() => vi.fn());
|
||||||
|
const resolveReceiveIdTypeMock = vi.hoisted(() => vi.fn());
|
||||||
|
|
||||||
|
const fileCreateMock = vi.hoisted(() => vi.fn());
|
||||||
|
const messageCreateMock = vi.hoisted(() => vi.fn());
|
||||||
|
const messageReplyMock = vi.hoisted(() => vi.fn());
|
||||||
|
|
||||||
|
vi.mock("./client.js", () => ({
|
||||||
|
createFeishuClient: createFeishuClientMock,
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock("./accounts.js", () => ({
|
||||||
|
resolveFeishuAccount: resolveFeishuAccountMock,
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock("./targets.js", () => ({
|
||||||
|
normalizeFeishuTarget: normalizeFeishuTargetMock,
|
||||||
|
resolveReceiveIdType: resolveReceiveIdTypeMock,
|
||||||
|
}));
|
||||||
|
|
||||||
|
import { sendMediaFeishu } from "./media.js";
|
||||||
|
|
||||||
|
describe("sendMediaFeishu msg_type routing", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
|
||||||
|
resolveFeishuAccountMock.mockReturnValue({
|
||||||
|
configured: true,
|
||||||
|
accountId: "main",
|
||||||
|
appId: "app_id",
|
||||||
|
appSecret: "app_secret",
|
||||||
|
domain: "feishu",
|
||||||
|
});
|
||||||
|
|
||||||
|
normalizeFeishuTargetMock.mockReturnValue("ou_target");
|
||||||
|
resolveReceiveIdTypeMock.mockReturnValue("open_id");
|
||||||
|
|
||||||
|
createFeishuClientMock.mockReturnValue({
|
||||||
|
im: {
|
||||||
|
file: {
|
||||||
|
create: fileCreateMock,
|
||||||
|
},
|
||||||
|
message: {
|
||||||
|
create: messageCreateMock,
|
||||||
|
reply: messageReplyMock,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
fileCreateMock.mockResolvedValue({
|
||||||
|
code: 0,
|
||||||
|
data: { file_key: "file_key_1" },
|
||||||
|
});
|
||||||
|
|
||||||
|
messageCreateMock.mockResolvedValue({
|
||||||
|
code: 0,
|
||||||
|
data: { message_id: "msg_1" },
|
||||||
|
});
|
||||||
|
|
||||||
|
messageReplyMock.mockResolvedValue({
|
||||||
|
code: 0,
|
||||||
|
data: { message_id: "reply_1" },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("uses msg_type=media for mp4", async () => {
|
||||||
|
await sendMediaFeishu({
|
||||||
|
cfg: {} as any,
|
||||||
|
to: "user:ou_target",
|
||||||
|
mediaBuffer: Buffer.from("video"),
|
||||||
|
fileName: "clip.mp4",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(fileCreateMock).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
data: expect.objectContaining({ file_type: "mp4" }),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(messageCreateMock).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
data: expect.objectContaining({ msg_type: "media" }),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("uses msg_type=media for opus", async () => {
|
||||||
|
await sendMediaFeishu({
|
||||||
|
cfg: {} as any,
|
||||||
|
to: "user:ou_target",
|
||||||
|
mediaBuffer: Buffer.from("audio"),
|
||||||
|
fileName: "voice.opus",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(fileCreateMock).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
data: expect.objectContaining({ file_type: "opus" }),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(messageCreateMock).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
data: expect.objectContaining({ msg_type: "media" }),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("uses msg_type=file for documents", async () => {
|
||||||
|
await sendMediaFeishu({
|
||||||
|
cfg: {} as any,
|
||||||
|
to: "user:ou_target",
|
||||||
|
mediaBuffer: Buffer.from("doc"),
|
||||||
|
fileName: "paper.pdf",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(fileCreateMock).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
data: expect.objectContaining({ file_type: "pdf" }),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(messageCreateMock).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
data: expect.objectContaining({ msg_type: "file" }),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("uses msg_type=media when replying with mp4", async () => {
|
||||||
|
await sendMediaFeishu({
|
||||||
|
cfg: {} as any,
|
||||||
|
to: "user:ou_target",
|
||||||
|
mediaBuffer: Buffer.from("video"),
|
||||||
|
fileName: "reply.mp4",
|
||||||
|
replyToMessageId: "om_parent",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(messageReplyMock).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
path: { message_id: "om_parent" },
|
||||||
|
data: expect.objectContaining({ msg_type: "media" }),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(messageCreateMock).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -359,10 +359,13 @@ export async function sendFileFeishu(params: {
|
|||||||
cfg: ClawdbotConfig;
|
cfg: ClawdbotConfig;
|
||||||
to: string;
|
to: string;
|
||||||
fileKey: string;
|
fileKey: string;
|
||||||
|
/** Use "media" for audio/video files, "file" for documents */
|
||||||
|
msgType?: "file" | "media";
|
||||||
replyToMessageId?: string;
|
replyToMessageId?: string;
|
||||||
accountId?: string;
|
accountId?: string;
|
||||||
}): Promise<SendMediaResult> {
|
}): Promise<SendMediaResult> {
|
||||||
const { cfg, to, fileKey, replyToMessageId, accountId } = params;
|
const { cfg, to, fileKey, replyToMessageId, accountId } = params;
|
||||||
|
const msgType = params.msgType ?? "file";
|
||||||
const account = resolveFeishuAccount({ cfg, accountId });
|
const account = resolveFeishuAccount({ cfg, accountId });
|
||||||
if (!account.configured) {
|
if (!account.configured) {
|
||||||
throw new Error(`Feishu account "${account.accountId}" not configured`);
|
throw new Error(`Feishu account "${account.accountId}" not configured`);
|
||||||
@@ -382,7 +385,7 @@ export async function sendFileFeishu(params: {
|
|||||||
path: { message_id: replyToMessageId },
|
path: { message_id: replyToMessageId },
|
||||||
data: {
|
data: {
|
||||||
content,
|
content,
|
||||||
msg_type: "file",
|
msg_type: msgType,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -401,7 +404,7 @@ export async function sendFileFeishu(params: {
|
|||||||
data: {
|
data: {
|
||||||
receive_id: receiveId,
|
receive_id: receiveId,
|
||||||
content,
|
content,
|
||||||
msg_type: "file",
|
msg_type: msgType,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -524,6 +527,15 @@ export async function sendMediaFeishu(params: {
|
|||||||
fileType,
|
fileType,
|
||||||
accountId,
|
accountId,
|
||||||
});
|
});
|
||||||
return sendFileFeishu({ cfg, to, fileKey, replyToMessageId, accountId });
|
// Feishu requires msg_type "media" for audio/video, "file" for documents
|
||||||
|
const isMedia = fileType === "mp4" || fileType === "opus";
|
||||||
|
return sendFileFeishu({
|
||||||
|
cfg,
|
||||||
|
to,
|
||||||
|
fileKey,
|
||||||
|
msgType: isMedia ? "media" : "file",
|
||||||
|
replyToMessageId,
|
||||||
|
accountId,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user