Files
curriculum-project-hub/hub/src/feishu/download.ts
T

65 lines
2.2 KiB
TypeScript

import { randomUUID } from "node:crypto";
import { join } from "node:path";
import type { ToolContext } from "../agent/tools.js";
import { downloadMessageFile, type FeishuRuntime } from "./client.js";
export interface FeishuMessageResourceArgs {
readonly messageId: string;
readonly fileKey: string;
readonly resourceType: "image" | "file";
}
export interface DownloadedFeishuMessageResource extends FeishuMessageResourceArgs {
readonly path: string;
}
type DownloadContext = Pick<ToolContext, "boundChatId" | "workspaceDir"> & { readonly workspaceRoot: string };
interface MessageLookupResult {
readonly data?: {
readonly message?: { readonly chat_id?: string | undefined } | undefined;
readonly items?: ReadonlyArray<{ readonly chat_id?: string | undefined }> | undefined;
} | undefined;
}
/** Download one resource after proving its message belongs to the run's bound chat. */
export async function downloadFeishuMessageResource(
args: FeishuMessageResourceArgs,
context: DownloadContext,
rt: FeishuRuntime,
): Promise<DownloadedFeishuMessageResource> {
const response = await rt.client.im.v1.message.get({
path: { message_id: args.messageId },
}) as MessageLookupResult;
const message = response.data?.message ?? response.data?.items?.[0];
if (message === undefined) {
throw new Error(`Feishu message not found: ${args.messageId}`);
}
if (message.chat_id !== context.boundChatId) {
rt.logger.warn(
{ messageId: args.messageId, boundChatId: context.boundChatId, returnedChatId: message.chat_id },
"Feishu resource download refused outside bound chat",
);
throw new Error(
`refused Feishu resource download: message ${args.messageId} is not in the current project's bound chat`,
);
}
const extension = args.resourceType === "image" ? ".png" : ".bin";
const workspaceRelativePath = join(
".cph",
"inbox",
`feishu-${args.resourceType}-${randomUUID()}${extension}`,
);
const savePath = await downloadMessageFile(
rt,
args.messageId,
args.fileKey,
context.workspaceRoot,
context.workspaceDir,
workspaceRelativePath,
args.resourceType,
);
return { ...args, path: savePath };
}