const IMAGE_PROXY_PATH = "/api/image-proxy"; type ImageProxyFormat = "webp" | "avif" | "jpeg" | "png"; export type ImageProxyOptions = { width?: number; quality?: number; format?: ImageProxyFormat; delivery?: "display" | "proxy"; }; function clamp(value: number, min: number, max: number): number { return Math.max(min, Math.min(max, value)); } function normalizeWidth(width?: number): number | undefined { if (!Number.isFinite(width) || !width) return undefined; return clamp(Math.round(width), 64, 4096); } function normalizeQuality(quality?: number): number | undefined { if (!Number.isFinite(quality) || !quality) return undefined; return clamp(Math.round(quality), 40, 100); } function isAbsoluteHttpUrl(url: string): boolean { return /^https?:\/\//i.test(url); } function isBypassUrl(url: string): boolean { return ( !url || url.startsWith("data:") || url.startsWith("blob:") || url.startsWith(IMAGE_PROXY_PATH) || !isAbsoluteHttpUrl(url) ); } function isAliyunOssImageHost(hostname: string): boolean { const normalized = hostname.trim().toLowerCase(); return normalized.endsWith(".aliyuncs.com") && normalized.includes(".oss-"); } function isTrustedDirectStorageImageHost(hostname: string): boolean { const normalized = hostname.trim().toLowerCase(); return ( normalized === "yybb.top" || normalized.endsWith(".qiniucs.com") || normalized.endsWith(".qiniudn.com") || normalized.endsWith(".clouddn.com") || normalized.endsWith(".qbox.me") ); } function resolveTrustedDirectStorageDisplayUrl(url: string): string | null { try { const target = new URL(url); return isTrustedDirectStorageImageHost(target.hostname) ? target.toString() : null; } catch { return null; } } function resolveAliyunOssDisplayUrl( url: string, options: Pick ): string | null { let target: URL; try { target = new URL(url); } catch { return null; } if (!isAliyunOssImageHost(target.hostname)) { return null; } if (target.searchParams.has("x-oss-process")) { return target.toString(); } const width = normalizeWidth(options.width); const quality = normalizeQuality(options.quality); const format = options.format; if (!width && !quality && !format) { return target.toString(); } const processSteps = ["image"]; if (width) { processSteps.push(`resize,w_${width}`); } if (quality) { processSteps.push(`quality,q_${quality}`); } if (format) { const ossFormat = format === "jpeg" ? "jpg" : format; if (ossFormat !== "webp" && ossFormat !== "jpg" && ossFormat !== "png") { return null; } processSteps.push(`format,${ossFormat}`); } target.searchParams.set("x-oss-process", processSteps.join("/")); return target.toString(); } export function buildImageProxyUrl(url: string, options: ImageProxyOptions = {}): string { const source = String(url || "").trim(); if (isBypassUrl(source)) { return source; } const trustedDirectStorageUrl = resolveTrustedDirectStorageDisplayUrl(source); if (trustedDirectStorageUrl) { return trustedDirectStorageUrl; } if (options.delivery !== "proxy") { const directDisplayUrl = resolveAliyunOssDisplayUrl(source, options); if (directDisplayUrl) { return directDisplayUrl; } return source; } const width = normalizeWidth(options.width); const quality = normalizeQuality(options.quality); const format = options.format; const params = new URLSearchParams(); params.set("url", source); if (options.delivery === "proxy") params.set("proxy", "1"); if (width) params.set("w", String(width)); if (quality) params.set("q", String(quality)); if (format) params.set("f", format); return `${IMAGE_PROXY_PATH}?${params.toString()}`; }