import { ensureSchema, getRawDb, getUploadBucket, uid, } from "../../../lib/mvp-db"; import { partnerOptions, withPartnerCors, } from "../../../lib/partner-cors"; import { MAX_RESULT_SCREENSHOTS, parseResultScreenshotKeys, serializeResultScreenshotKeys, } from "../../../lib/result-screenshots"; import { creatorScreenshotMcpUrl } from "../../../lib/creator-screenshot-access"; import { extractCreatorMetricsFromMcp, resolveCollectionMcpConfig, type CollectionMcpBindings, } from "../../../lib/mcp-collection-client"; import { getRuntimeEnv } from "../../../lib/runtime-env"; async function readUpload(request: Request) { const contentType = request.headers.get("content-type") ?? ""; if (contentType.startsWith("multipart/form-data")) { const form = await request.formData(); const file = form.get("file"); if (!(file instanceof File)) return null; return { taskToken: String(form.get("taskToken") ?? "").trim(), claimToken: String(form.get("claimToken") ?? "").trim(), delegationToken: String(form.get("delegationToken") ?? "").trim(), distributionId: String(form.get("distributionId") ?? "").trim(), uploadKind: String(form.get("uploadKind") ?? "publish").trim(), fileName: file.name, fileType: file.type, fileBytes: await file.arrayBuffer(), }; } const encodedName = request.headers.get("x-koc-file-name") ?? "screenshot.jpg"; let fileName = "screenshot.jpg"; try { fileName = decodeURIComponent(encodedName); } catch { fileName = "screenshot.jpg"; } return { taskToken: String(request.headers.get("x-koc-task") ?? "").trim(), claimToken: String(request.headers.get("x-koc-claim") ?? "").trim(), delegationToken: String( request.headers.get("x-koc-delegation") ?? "", ).trim(), distributionId: String( request.headers.get("x-koc-distribution") ?? "", ).trim(), uploadKind: String( request.headers.get("x-koc-upload-kind") ?? "publish", ).trim(), fileName, fileType: contentType.split(";")[0].trim(), fileBytes: await request.arrayBuffer(), }; } async function handlePost(request: Request) { try { await ensureSchema(); const upload = await readUpload(request); if ( !upload || (!upload.claimToken && !upload.delegationToken) || (!upload.delegationToken && !upload.taskToken) || !upload.distributionId || upload.fileBytes.byteLength === 0 ) { return Response.json({ error: "请选择需要上传的截图" }, { status: 400 }); } if ( !upload.fileType.startsWith("image/") || upload.fileBytes.byteLength > 8_000_000 ) { return Response.json( { error: "仅支持8MB以内的图片" }, { status: 400 }, ); } const isCreatorCenter = upload.uploadKind === "creator-center"; const isTaskResult = upload.uploadKind === "task-result"; if (!["publish", "creator-center", "task-result"].includes(upload.uploadKind)) { return Response.json({ error: "不支持的截图类型" }, { status: 400 }); } const assignment = upload.delegationToken ? await getRawDb() .prepare( `SELECT d.id, d.publish_url, d.result_screenshot_key, t.task_type FROM distributions d JOIN delegation_bundles b ON b.id = d.delegation_bundle_id JOIN tasks t ON t.id = d.task_id WHERE d.id = ? AND b.share_token = ? AND b.status = 'active'`, ) .bind(upload.distributionId, upload.delegationToken) .first<{ id: string; publish_url: string | null; result_screenshot_key: string | null; task_type: string }>() : await getRawDb() .prepare( `SELECT d.id, d.publish_url, d.result_screenshot_key, t.task_type FROM distributions d JOIN claims c ON c.id = d.claim_id JOIN tasks t ON t.id = d.task_id WHERE d.id = ? AND c.claim_token = ? AND t.share_token = ? AND c.task_id = t.id`, ) .bind(upload.distributionId, upload.claimToken, upload.taskToken) .first<{ id: string; publish_url: string | null; result_screenshot_key: string | null; task_type: string }>(); if (!assignment) { return Response.json({ error: "笔记与领取凭证不匹配" }, { status: 403 }); } if (isCreatorCenter && !assignment.publish_url) { return Response.json( { error: "请先回填这篇笔记的发布信息" }, { status: 409 }, ); } if (isTaskResult && assignment.task_type !== "screenshot_collect") { return Response.json({ error: "当前任务不支持截图结果回填" }, { status: 400 }); } if (!isTaskResult && assignment.task_type === "screenshot_collect") { return Response.json({ error: "截图回收任务请上传任务结果截图" }, { status: 400 }); } const existingResultKeys = isTaskResult ? parseResultScreenshotKeys(assignment.result_screenshot_key) : []; if (isTaskResult && existingResultKeys.length >= MAX_RESULT_SCREENSHOTS) { return Response.json( { error: `每份任务最多上传${MAX_RESULT_SCREENSHOTS}张截图` }, { status: 409 }, ); } const extension = upload.fileName.split(".").at(-1)?.replace(/[^a-zA-Z0-9]/g, "") || "jpg"; const key = isTaskResult ? `task-results/${upload.distributionId}/${uid("shot")}.${extension}` : isCreatorCenter ? `creator-center/${upload.distributionId}/${uid("shot")}.${extension}` : `publish-evidence/${upload.distributionId}/${uid("shot")}.${extension}`; await getUploadBucket().put(key, upload.fileBytes, { httpMetadata: { contentType: upload.fileType }, }); if (isTaskResult) { await getRawDb() .prepare( `UPDATE distributions SET result_screenshot_key = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?`, ) .bind( serializeResultScreenshotKeys([...existingResultKeys, key]), upload.distributionId, ) .run(); } else if (isCreatorCenter) { await getRawDb() .prepare( `UPDATE distributions SET screenshot_key = ?, ocr_status = CASE WHEN exposure IS NOT NULL AND views IS NOT NULL THEN ocr_status ELSE 'processing' END, updated_at = CURRENT_TIMESTAMP WHERE id = ?`, ) .bind(key, upload.distributionId) .run(); try { const metrics = await extractCreatorMetricsFromMcp( creatorScreenshotMcpUrl(request, upload.distributionId, key), resolveCollectionMcpConfig( getRuntimeEnv() as unknown as CollectionMcpBindings, ), ); await getRawDb() .prepare( `UPDATE distributions SET exposure = ?, views = ?, ocr_status = 'success', updated_at = CURRENT_TIMESTAMP WHERE id = ?`, ) .bind(metrics.exposure, metrics.views, upload.distributionId) .run(); return Response.json({ uploaded: true, exposure: metrics.exposure, views: metrics.views, ocrStatus: "success", kind: "creator-center", }); } catch (error) { console.error("[KOC LOOP] creator screenshot OCR failed", { distributionId: upload.distributionId, detail: error instanceof Error ? error.message : String(error), }); await getRawDb() .prepare( `UPDATE distributions SET ocr_status = 'failed', updated_at = CURRENT_TIMESTAMP WHERE id = ?`, ) .bind(upload.distributionId) .run(); } } else { await getRawDb() .prepare( `UPDATE distributions SET publish_screenshot_key = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?`, ) .bind(key, upload.distributionId) .run(); } return Response.json({ uploaded: true, screenshotCount: isTaskResult ? existingResultKeys.length + 1 : undefined, kind: isTaskResult ? "task-result" : isCreatorCenter ? "creator-center" : "publish", ocrStatus: isCreatorCenter ? "failed" : undefined, }); } catch (error) { return Response.json( { error: error instanceof Error ? error.message : "上传失败" }, { status: 500 }, ); } } export async function POST(request: Request) { return withPartnerCors(request, await handlePost(request)); } export async function OPTIONS(request: Request) { return partnerOptions(request); }