import { getRuntimeEnv } from "../../../lib/runtime-env"; import { ensureSchema, getRawDb, getUploadBucket, } from "../../../lib/mvp-db"; import { downloadFeishuMedia, type FeishuBindings, } from "../../../lib/feishu-client"; import { partnerOptions, withPartnerCors, } from "../../../lib/partner-cors"; import { parseResultScreenshotKeys } from "../../../lib/result-screenshots"; import { hasMp4FileSignature } from "../../../lib/video-file"; const env = getRuntimeEnv(); type StoredAsset = { index: number; key: string; fileToken?: string; }; function textValue(value: string | null, maxLength = 100) { return String(value ?? "").trim().slice(0, maxLength); } function findAsset( value: string, imageIndex: number, prefixes = ["content-assets/", "task-assets/"], ) { try { const assets = JSON.parse(value) as StoredAsset[]; return Array.isArray(assets) ? assets.find( (asset) => asset.index === imageIndex && typeof asset.key === "string" && prefixes.some((prefix) => asset.key.startsWith(prefix)) && (asset.fileToken === undefined || typeof asset.fileToken === "string"), ) : undefined; } catch { return undefined; } } async function handleGet(request: Request) { try { await ensureSchema(); const url = new URL(request.url); const taskToken = textValue(url.searchParams.get("task")); const claimToken = textValue(url.searchParams.get("claim")); const delegationToken = textValue(url.searchParams.get("share")); const distributionId = textValue(url.searchParams.get("distribution")); const imageIndex = Number(url.searchParams.get("index")); const imageKind = textValue(url.searchParams.get("kind"), 20); const downloadRequested = url.searchParams.get("download") === "1"; if ( (!delegationToken && (!taskToken || !claimToken)) || !distributionId || !Number.isInteger(imageIndex) || imageIndex < 1 ) { return Response.json({ error: "素材链接不完整" }, { status: 400 }); } const row = delegationToken ? await getRawDb() .prepare( `SELECT c.image_assets, c.video_assets, d.result_screenshot_key, d.publish_screenshot_key, d.screenshot_key FROM distributions d JOIN contents c ON c.id = d.content_id JOIN delegation_bundles b ON b.id = d.delegation_bundle_id WHERE d.id = ? AND b.share_token = ? AND b.status = 'active'`, ) .bind(distributionId, delegationToken) .first<{ image_assets: string; video_assets: string; result_screenshot_key: string | null; publish_screenshot_key: string | null; screenshot_key: string | null; }>() : await getRawDb() .prepare( `SELECT c.image_assets, c.video_assets, d.result_screenshot_key, d.publish_screenshot_key, d.screenshot_key FROM distributions d JOIN contents c ON c.id = d.content_id JOIN claims cl ON cl.id = d.claim_id JOIN tasks t ON t.id = d.task_id WHERE d.id = ? AND cl.claim_token = ? AND t.share_token = ? AND cl.task_id = t.id`, ) .bind(distributionId, claimToken, taskToken) .first<{ image_assets: string; video_assets: string; result_screenshot_key: string | null; publish_screenshot_key: string | null; screenshot_key: string | null; }>(); const asset = imageKind === "result" ? row ? { index: imageIndex, key: parseResultScreenshotKeys(row.result_screenshot_key)[ imageIndex - 1 ], } : undefined : imageKind === "publish" ? row?.publish_screenshot_key?.startsWith("publish-evidence/") ? { index: 1, key: row.publish_screenshot_key } : undefined : imageKind === "creator" ? row?.screenshot_key?.startsWith("creator-center/") ? { index: 1, key: row.screenshot_key } : undefined : imageKind === "video" ? row ? findAsset(row.video_assets, imageIndex, ["content-videos/"]) : undefined : row ? findAsset(row.image_assets, imageIndex) : undefined; if (!asset?.key) { return Response.json({ error: "没有找到这个素材" }, { status: 404 }); } const bucket = getUploadBucket(); let object = await bucket.get(asset.key); let objectBytes = object ? await object.arrayBuffer() : null; const invalidStoredVideo = imageKind === "video" && objectBytes !== null && !hasMp4FileSignature(objectBytes); if ((!object || invalidStoredVideo) && asset.fileToken) { const media = await downloadFeishuMedia( asset.fileToken, env as unknown as FeishuBindings, fetch, { maxBytes: imageKind === "video" ? 500 * 1024 * 1024 : undefined, label: imageKind === "video" ? "视频" : "图片", }, ); if (imageKind === "video" && !hasMp4FileSignature(media.bytes)) { return Response.json( { error: "源视频不是可下载的 MP4 文件,请重新上传 MP4 视频" }, { status: 422 }, ); } await bucket.put(asset.key, media.bytes, { httpMetadata: { contentType: imageKind === "video" ? "video/mp4" : media.contentType, }, customMetadata: { source: "feishu-api" }, }); object = await bucket.get(asset.key); objectBytes = object ? await object.arrayBuffer() : media.bytes; } if (!object || !objectBytes) { return Response.json({ error: "素材同步失败,请稍后重试" }, { status: 404 }); } if (imageKind === "video" && !hasMp4FileSignature(objectBytes)) { return Response.json( { error: "已存储的视频不是有效的 MP4 文件,请联系运营重新同步" }, { status: 422 }, ); } const headers = new Headers(); object.writeHttpMetadata(headers); const isMutableEvidence = imageKind === "publish" || imageKind === "creator"; headers.set( "Cache-Control", isMutableEvidence ? "private, no-store" : "private, max-age=3600", ); if (imageKind === "video") { headers.set("Content-Type", "video/mp4"); } headers.set("Content-Length", String(objectBytes.byteLength)); headers.set("X-Content-Type-Options", "nosniff"); const fileName = imageKind === "video" ? `video-${imageIndex}.mp4` : `image-${imageIndex}`; headers.set( "Content-Disposition", `${downloadRequested ? "attachment" : "inline"}; filename="${fileName}"; filename*=UTF-8''${encodeURIComponent(fileName)}`, ); return new Response(objectBytes, { headers }); } catch (error) { return Response.json( { error: error instanceof Error ? error.message : "素材读取失败" }, { status: 500 }, ); } } export async function GET(request: Request) { return withPartnerCors(request, await handleGet(request)); } export async function OPTIONS(request: Request) { return partnerOptions(request); }