fix: download canvas images from original source
This commit is contained in:
parent
71dcd5026b
commit
c631f906b1
@ -1,3 +1,5 @@
|
||||
import { extractImageProxySourceUrl, inferOriginalImageUrl } from "@/lib/image-url";
|
||||
|
||||
type DownloadFileItem = {
|
||||
url: string;
|
||||
name?: string;
|
||||
@ -85,6 +87,45 @@ function buildRemoteFetchHeaders(fileUrl: string, requestOrigin: string, cookie:
|
||||
return headers;
|
||||
}
|
||||
|
||||
function resolveHttpUrl(value: string, requestOrigin: string): string {
|
||||
const raw = String(value || "").trim();
|
||||
if (!raw) return "";
|
||||
|
||||
try {
|
||||
const parsed = new URL(raw, requestOrigin);
|
||||
if (!/^https?:$/i.test(parsed.protocol)) return "";
|
||||
return parsed.toString();
|
||||
} catch {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
function buildCanvasDownloadSourceCandidates(fileUrl: string, requestOrigin: string) {
|
||||
const candidates: string[] = [];
|
||||
const seen = new Set<string>();
|
||||
|
||||
const add = (value: string) => {
|
||||
const resolved = resolveHttpUrl(value, requestOrigin);
|
||||
if (!resolved || seen.has(resolved)) return;
|
||||
seen.add(resolved);
|
||||
candidates.push(resolved);
|
||||
};
|
||||
|
||||
const resolvedFileUrl = resolveHttpUrl(fileUrl, requestOrigin);
|
||||
const proxySourceUrl = extractImageProxySourceUrl(resolvedFileUrl || fileUrl);
|
||||
if (proxySourceUrl) {
|
||||
add(inferOriginalImageUrl(proxySourceUrl) || proxySourceUrl);
|
||||
add(proxySourceUrl);
|
||||
}
|
||||
|
||||
if (resolvedFileUrl) {
|
||||
add(inferOriginalImageUrl(resolvedFileUrl) || resolvedFileUrl);
|
||||
add(resolvedFileUrl);
|
||||
}
|
||||
|
||||
return candidates;
|
||||
}
|
||||
|
||||
function getContentTypeMime(contentType: string): string {
|
||||
return String(contentType || "")
|
||||
.split(";")[0]
|
||||
@ -257,41 +298,47 @@ export async function fetchCanvasDownloadAsset(params: {
|
||||
let lastError = "";
|
||||
const timeoutMs = params.timeoutMs || DEFAULT_FETCH_TIMEOUT_MS;
|
||||
const retryCount = params.retryCount || DEFAULT_FETCH_RETRY_COUNT;
|
||||
const candidateUrls = buildCanvasDownloadSourceCandidates(
|
||||
params.file.url,
|
||||
params.requestOrigin
|
||||
);
|
||||
|
||||
for (let attempt = 1; attempt <= retryCount; attempt += 1) {
|
||||
try {
|
||||
const response = await fetchWithTimeout(
|
||||
params.file.url,
|
||||
{
|
||||
method: "GET",
|
||||
cache: "no-store",
|
||||
redirect: "follow",
|
||||
headers: buildRemoteFetchHeaders(params.file.url, params.requestOrigin, params.cookie),
|
||||
},
|
||||
timeoutMs
|
||||
);
|
||||
for (const candidateUrl of candidateUrls) {
|
||||
for (let attempt = 1; attempt <= retryCount; attempt += 1) {
|
||||
try {
|
||||
const response = await fetchWithTimeout(
|
||||
candidateUrl,
|
||||
{
|
||||
method: "GET",
|
||||
cache: "no-store",
|
||||
redirect: "follow",
|
||||
headers: buildRemoteFetchHeaders(candidateUrl, params.requestOrigin, params.cookie),
|
||||
},
|
||||
timeoutMs
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
lastError = `HTTP ${response.status}`;
|
||||
if (attempt < retryCount && RETRYABLE_STATUS_CODES.has(response.status)) {
|
||||
await sleep(350 * attempt);
|
||||
continue;
|
||||
if (!response.ok) {
|
||||
lastError = `HTTP ${response.status}`;
|
||||
if (attempt < retryCount && RETRYABLE_STATUS_CODES.has(response.status)) {
|
||||
await sleep(350 * attempt);
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
const bytes = Buffer.from(await response.arrayBuffer());
|
||||
return await normalizeCanvasDownloadAsset({
|
||||
item: params.file,
|
||||
index: params.index,
|
||||
bytes,
|
||||
contentType: response.headers.get("content-type") || "",
|
||||
includeSequencePrefix: params.includeSequencePrefix !== false,
|
||||
});
|
||||
} catch (error) {
|
||||
lastError = normalizeDownloadError(error);
|
||||
if (attempt < retryCount) {
|
||||
await sleep(350 * attempt);
|
||||
const bytes = Buffer.from(await response.arrayBuffer());
|
||||
return await normalizeCanvasDownloadAsset({
|
||||
item: params.file,
|
||||
index: params.index,
|
||||
bytes,
|
||||
contentType: response.headers.get("content-type") || "",
|
||||
includeSequencePrefix: params.includeSequencePrefix !== false,
|
||||
});
|
||||
} catch (error) {
|
||||
lastError = normalizeDownloadError(error);
|
||||
if (attempt < retryCount) {
|
||||
await sleep(350 * attempt);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,6 +6,41 @@ function isHttpUrl(value: string): boolean {
|
||||
return /^https?:\/\//i.test(value);
|
||||
}
|
||||
|
||||
export function extractImageProxySourceUrl(url: string): string {
|
||||
const raw = String(url || "").trim();
|
||||
if (!raw) return "";
|
||||
|
||||
try {
|
||||
const parsed = new URL(raw, "https://image-proxy.local");
|
||||
if (parsed.pathname !== "/api/image-proxy") return "";
|
||||
const source = parsed.searchParams.get("url") || "";
|
||||
return isHttpUrl(source) ? source : "";
|
||||
} catch {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
function stripAliyunImageProcess(url: string): string {
|
||||
if (!isHttpUrl(url)) return url;
|
||||
|
||||
try {
|
||||
const parsed = new URL(url);
|
||||
const hostname = parsed.hostname.toLowerCase();
|
||||
if (
|
||||
hostname.endsWith(".aliyuncs.com") &&
|
||||
hostname.includes(".oss-") &&
|
||||
parsed.searchParams.has("x-oss-process")
|
||||
) {
|
||||
parsed.searchParams.delete("x-oss-process");
|
||||
return parsed.toString();
|
||||
}
|
||||
} catch {
|
||||
// keep the original URL
|
||||
}
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
export function isLikelyThumbnailUrl(url: string): boolean {
|
||||
const lowerUrl = url.toLowerCase();
|
||||
if (
|
||||
@ -28,6 +63,14 @@ export function isLikelyThumbnailUrl(url: string): boolean {
|
||||
export function inferOriginalImageUrl(url: string): string {
|
||||
if (!isHttpUrl(url)) return url;
|
||||
|
||||
const proxySourceUrl = extractImageProxySourceUrl(url);
|
||||
if (proxySourceUrl) {
|
||||
return inferOriginalImageUrl(proxySourceUrl);
|
||||
}
|
||||
|
||||
const unprocessedUrl = stripAliyunImageProcess(url);
|
||||
if (unprocessedUrl !== url) return unprocessedUrl;
|
||||
|
||||
if (url.includes("gemini-thumb-")) {
|
||||
return url.replace("gemini-thumb-", "gemini-orig-");
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user