const IMAGE_META_KEYS = new Set([ "og:image", "og:image:url", "twitter:image", "twitter:image:src", ]); const FETCH_TIMEOUT_MS = 5_000; function getAttribute(tag: string, name: string): string | null { const pattern = new RegExp(`${name}\\s*=\\s*["']([^"']+)["']`, "i"); return tag.match(pattern)?.[1] ?? null; } function toHttpUrl(value: string, baseUrl: string): string | null { try { const url = new URL(value, baseUrl); return url.protocol === "http:" || url.protocol === "https:" ? url.href : null; } catch { return null; } } function isRuncRunUrl(value: string): boolean { try { const hostname = new URL(value).hostname.toLowerCase(); return hostname === "runc.run" || hostname.endsWith(".runc.run"); } catch { return false; } } function findRuncIntroImage(html: string, baseUrl: string): string | null { const introMatch = html.match(/]*class=["'][^"']*\brun-intro__image\b[^"']*["'][^>]*>[\s\S]*?]*>/i); if (!introMatch) { return null; } const src = getAttribute(introMatch[0], "src"); return src ? toHttpUrl(src, baseUrl) : null; } function findMetaImage(html: string, baseUrl: string): string | null { const tags = html.match(/]*>/gi) ?? []; for (const tag of tags) { const key = (getAttribute(tag, "property") || getAttribute(tag, "name") || "").toLowerCase(); if (!IMAGE_META_KEYS.has(key)) { continue; } const content = getAttribute(tag, "content"); if (!content) { continue; } const imageUrl = toHttpUrl(content, baseUrl); if (imageUrl) { return imageUrl; } } return null; } export function extractRaceCoverImageFromHtml(html: string, pageUrl: string): string | null { if (isRuncRunUrl(pageUrl)) { const runcImage = findRuncIntroImage(html, pageUrl); if (runcImage) { return runcImage; } } return findMetaImage(html, pageUrl); } export async function extractRaceCoverImage(officialUrl: string): Promise { const normalizedUrl = toHttpUrl(officialUrl, officialUrl); if (!normalizedUrl) { return null; } const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS); try { const response = await fetch(normalizedUrl, { redirect: "follow", signal: controller.signal, }); if (!response.ok) { return null; } const html = await response.text(); return extractRaceCoverImageFromHtml(html, response.url || normalizedUrl); } catch { return null; } finally { clearTimeout(timeout); } }