import "server-only"; import { getDefaultStorageConfig } from "@/lib/db/queries"; import { findImageArchiveDownloadTask } from "./image-archive-db"; import { extractImageProxySourceUrl, inferOriginalImageUrl } from "@/lib/image-url"; export async function resolveCanvasCdnDownload(urls: string[], filename: string) { const sources = Array.from(new Set(urls.flatMap(url => { const source = extractImageProxySourceUrl(url) || url; return [source, inferOriginalImageUrl(source) || source]; }))); const task = await findImageArchiveDownloadTask(sources, true); const asset = (task?.metadata as { downloadAsset?: { status?: string; url?: string; contentType?: string; bytes?: number } } | null)?.downloadAsset; if (!task) return { task, direct: null, canPrepareDirect: false }; const config = await getDefaultStorageConfig(); if (config?.type !== "qiniu-kodo" || !config.publicUrl || !config.isEnabled) return { task, direct: null, canPrepareDirect: false }; const ready = asset?.status === "ready" && asset.url && asset.contentType === "image/png"; const source = ready ? asset.url : task.targetUrl; if (!source) return { task, direct: null, canPrepareDirect: false }; const url = new URL(source); const base = new URL(config.publicUrl); const origin = process.env.CANVAS_QINIU_ORIGIN_URL; if (origin && url.origin === new URL(origin).origin) url.host = base.host; if (base.protocol !== "https:" || url.origin !== base.origin || !(url.pathname.startsWith(base.pathname.replace(/\/$/, "") + "/"))) return { task, direct: null, canPrepareDirect: false }; if (!ready) return { task, direct: null, canPrepareDirect: true }; // attname is interpreted by Kodo/CDN as Content-Disposition, including Chinese // filenames. Keep all processing/auth parameters intact for correct cache keys. url.searchParams.set("attname", filename); return { task, direct: { delivery: "cdn", downloadUrl: url.toString(), filename, bytes: asset?.bytes || 0 }, canPrepareDirect: true }; }