130 lines
3.7 KiB
TypeScript
130 lines
3.7 KiB
TypeScript
const MARKDOWN_IMAGE_REGEX = /!\[([^\]]*)\]\((https?:\/\/[^\s)]+)\)/gim;
|
|
const PLAIN_IMAGE_URL_REGEX =
|
|
/https?:\/\/[^\s"'<>]+\.(?:jpg|jpeg|png|gif|webp|bmp|svg)(?:\?[^\s"'<>]*)?/gi;
|
|
|
|
function isHttpUrl(value: string): boolean {
|
|
return /^https?:\/\//i.test(value);
|
|
}
|
|
|
|
export function isLikelyThumbnailUrl(url: string): boolean {
|
|
const lowerUrl = url.toLowerCase();
|
|
if (
|
|
lowerUrl.includes("/thumb/") ||
|
|
lowerUrl.includes("thumbnail") ||
|
|
lowerUrl.includes("gemini-thumb-") ||
|
|
lowerUrl.includes("openai-thumb-") ||
|
|
lowerUrl.includes("volcano-thumb-") ||
|
|
lowerUrl.includes("alibaba-image-thumb-") ||
|
|
lowerUrl.includes("ruxa-image-thumb-") ||
|
|
lowerUrl.includes("fal-image-thumb-")
|
|
) {
|
|
return true;
|
|
}
|
|
|
|
return /(?:^|[-_/])thumb(?:[-_.\\/]|$)/i.test(url);
|
|
}
|
|
|
|
export function inferOriginalImageUrl(url: string): string {
|
|
if (!isHttpUrl(url)) return url;
|
|
|
|
if (url.includes("gemini-thumb-")) {
|
|
return url.replace("gemini-thumb-", "gemini-orig-");
|
|
}
|
|
if (url.includes("openai-thumb-")) {
|
|
return url.replace("openai-thumb-", "openai-orig-");
|
|
}
|
|
if (url.includes("volcano-thumb-")) {
|
|
return url.replace("volcano-thumb-", "volcano-orig-");
|
|
}
|
|
if (url.includes("alibaba-image-thumb-")) {
|
|
return url.replace("alibaba-image-thumb-", "alibaba-image-orig-");
|
|
}
|
|
if (url.includes("ruxa-image-thumb-")) {
|
|
return url.replace("ruxa-image-thumb-", "ruxa-image-orig-");
|
|
}
|
|
if (url.includes("fal-image-thumb-")) {
|
|
return url.replace("fal-image-thumb-", "fal-image-orig-");
|
|
}
|
|
|
|
if (url.includes("/thumb/")) {
|
|
return url.replace("/thumb/", "/");
|
|
}
|
|
|
|
if (url.includes("-thumb.")) {
|
|
return url.replace("-thumb.", ".");
|
|
}
|
|
|
|
return url;
|
|
}
|
|
|
|
export function extractOriginalImageUrlFromAlt(altText: string, fallbackUrl: string): string {
|
|
let originalUrl = fallbackUrl;
|
|
|
|
if (altText.includes("thumb|||")) {
|
|
const content = altText.substring(altText.indexOf("thumb|||") + 8);
|
|
const parts = content.split("|||");
|
|
if (parts[0] && isHttpUrl(parts[0])) {
|
|
originalUrl = parts[0];
|
|
}
|
|
} else if (altText.includes("thumb$$")) {
|
|
const content = altText.substring(altText.indexOf("thumb$$") + 7);
|
|
const parts = content.split("$$");
|
|
if (parts[0] && isHttpUrl(parts[0])) {
|
|
originalUrl = parts[0];
|
|
}
|
|
} else if (altText.includes("thumb|")) {
|
|
const content = altText.substring(altText.indexOf("thumb|") + 6);
|
|
const parts = content.split("|");
|
|
if (parts[0] && isHttpUrl(parts[0])) {
|
|
originalUrl = parts[0];
|
|
}
|
|
}
|
|
|
|
if (originalUrl === fallbackUrl || !isHttpUrl(originalUrl)) {
|
|
originalUrl = inferOriginalImageUrl(fallbackUrl);
|
|
}
|
|
|
|
return originalUrl;
|
|
}
|
|
|
|
export function extractImageUrlsFromText(text: string): string[] {
|
|
if (!text) return [];
|
|
|
|
const urls: string[] = [];
|
|
const added = new Set<string>();
|
|
|
|
const addUrl = (url: string) => {
|
|
if (!url || !isHttpUrl(url) || added.has(url)) return;
|
|
added.add(url);
|
|
urls.push(url);
|
|
};
|
|
|
|
let hasMarkdownImages = false;
|
|
let markdownMatch: RegExpExecArray | null;
|
|
MARKDOWN_IMAGE_REGEX.lastIndex = 0;
|
|
|
|
while ((markdownMatch = MARKDOWN_IMAGE_REGEX.exec(text)) !== null) {
|
|
hasMarkdownImages = true;
|
|
const altText = markdownMatch[1] || "";
|
|
const thumbnailUrl = markdownMatch[2] || "";
|
|
const originalUrl = extractOriginalImageUrlFromAlt(altText, thumbnailUrl);
|
|
addUrl(originalUrl || thumbnailUrl);
|
|
}
|
|
|
|
if (hasMarkdownImages) return urls;
|
|
|
|
const rawUrls = text.match(PLAIN_IMAGE_URL_REGEX) || [];
|
|
if (!rawUrls.length) return urls;
|
|
|
|
const nonThumbnailUrls = rawUrls.filter((url) => !isLikelyThumbnailUrl(url));
|
|
const candidates = nonThumbnailUrls.length
|
|
? nonThumbnailUrls
|
|
: rawUrls.map((url) => inferOriginalImageUrl(url));
|
|
|
|
for (const url of candidates) {
|
|
addUrl(url);
|
|
}
|
|
|
|
return urls;
|
|
}
|