Something went wrong. Try again.
This string has no description
Something went wrong. Try again.
8.1 kB · 221 lines · Created 2mo ago JavaScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221const VERSION = "2.1.4";const ADMIN_DID = "did:plc:l37td5yhxl2irrzrgvei4qay";const CONTACT_EMAIL = "danny@danielmorrisey.com";const SERVICE_NAME = "BlueAT Network Favicon Fetcher";const DEFAULT_FAVICON_URL = "https://cdn.madebydanny.uk/user-content/2026-04-20/cb70f504-07fa-4f02-942f-45f1ce80255a.svg";
const CACHE_TTL = 86400;
export default { async fetch(request, env, ctx) { const url = new URL(request.url);
if (url.pathname === "/xrpc/_health") { return Response.json({ status: "ok", version: VERSION, admin_did: ADMIN_DID, admin_contact_email: CONTACT_EMAIL, service_name: SERVICE_NAME, cache_ttl: CACHE_TTL, fallback_url: DEFAULT_FAVICON_URL, ts: Date.now(), }); }
let domain = url.pathname.slice(1);
if (!domain) return new Response("Usage: /{domain}", { status: 400 });
domain = domain.replace(/^https?:\/\//, "").replace(/\/.*$/, "").toLowerCase().trim();
if (!isValidDomain(domain)) return new Response("Invalid domain", { status: 400 });
const larger = url.searchParams.get("larger") === "true"; const defaultAvatar = url.searchParams.get("default-avatar"); const throwOn404 = url.searchParams.get("throw-error-on-404") === "true";
const cacheKey = new Request(`https://favicon-cache/${domain}?larger=${larger}`, request); const cache = caches.default;
// 1. Local PoP cache const cached = await cache.match(cacheKey); if (cached) return cached;
// 2. Global KV cache const kvKey = `favicon:${domain}:${larger}`; if (env.FAVICON_KV) { const kvEntry = await env.FAVICON_KV.getWithMetadata(kvKey, "arrayBuffer"); if (kvEntry.value) { const { contentType, source } = kvEntry.metadata ?? {}; const kvResponse = buildResponse(kvEntry.value, contentType, source, "kv"); ctx.waitUntil(cache.put(cacheKey, kvResponse.clone())); return kvResponse; } }
// 3. Full fetch pipeline const result = await fetchFavicon(domain, larger);
if (!result) { if (throwOn404) return new Response("No favicon found", { status: 404 }); if (defaultAvatar) return Response.redirect(defaultAvatar, 302); return Response.redirect(DEFAULT_FAVICON_URL, 302); }
const response = buildResponse(result.body, result.contentType, result.source, result.source);
ctx.waitUntil(Promise.all([ env.FAVICON_KV?.put(kvKey, result.body, { expirationTtl: CACHE_TTL, metadata: { contentType: result.contentType, source: result.source }, }), cache.put(cacheKey, response.clone()), ]));
return response; },};
function buildResponse(body, contentType, source, cacheLayer) { return new Response(body, { headers: { "Content-Type": contentType ?? "image/x-icon", "Cache-Control": `public, max-age=${CACHE_TTL}, s-maxage=${CACHE_TTL}`, "Access-Control-Allow-Origin": "*", "X-Favicon-Source": source ?? "unknown", "X-Cache-Layer": cacheLayer, "CDN-Cache-Control": `max-age=${CACHE_TTL}`, }, cf: { cacheTtl: CACHE_TTL, cacheEverything: true, }, });}
async function fetchFavicon(domain, larger) { if (larger) { const r = await tryFetch(`https://www.google.com/s2/favicons?domain=${domain}&sz=128`); if (r) return { ...r, source: "google-s2-128" }; }
const htmlResult = await fetchFromHTML(domain, larger); if (htmlResult) return htmlResult;
const ico = await tryFetch(`https://${domain}/favicon.ico`); if (ico) return { ...ico, source: "favicon.ico" };
const small = await tryFetch(`https://www.google.com/s2/favicons?domain=${domain}&sz=32`); if (small) return { ...small, source: "google-s2-32" };
return null;}
async function fetchFromHTML(domain, larger) { let html; try { const res = await fetchWithTimeout(`https://${domain}`, 5000); if (!res?.ok) { const www = await fetchWithTimeout(`https://www.${domain}`, 5000); if (!www?.ok) return null; html = await www.text(); } else { html = await res.text(); } } catch { return null; }
const icons = extractIcons(html, domain); const best = pickBestIcon(icons, larger); if (!best) return null;
const result = await tryFetch(best); return result ? { ...result, source: best } : null;}
function extractIcons(html, domain) { const icons = []; const patterns = [ /<link[^>]+rel=["'][^"']*icon[^"']*["'][^>]*href=["']([^"']+)["'][^>]*>/gi, /<link[^>]+href=["']([^"']+)["'][^>]*rel=["'][^"']*icon[^"']*["'][^>]*>/gi, /<link[^>]+rel=["'][^"']*apple-touch-icon[^"']*["'][^>]*href=["']([^"']+)["'][^>]*>/gi, ]; for (const re of patterns) { let m; while ((m = re.exec(html)) !== null) { const href = m[1].trim(); if (href && !href.startsWith("data:")) icons.push(resolveUrl(href, domain)); } } return [...new Set(icons)];}
function pickBestIcon(icons, larger) { if (!icons.length) return null; if (larger) { const svg = icons.find(u => u.match(/\.svg(\?|$)/i)); if (svg) return svg; const big = icons.find(u => u.match(/\b(512|256|192|180|152|144|128|96)\b/)); if (big) return big; } return icons.find(u => u.match(/\.ico(\?|$)/i)) || icons.find(u => u.match(/\.png(\?|$)/i)) || icons[0];}
function resolveUrl(href, domain) { if (href.startsWith("http://") || href.startsWith("https://")) return href; if (href.startsWith("//")) return `https:${href}`; if (href.startsWith("/")) return `https://${domain}${href}`; return `https://${domain}/${href}`;}
async function tryFetch(url) { try { const res = await fetchWithTimeout(url, 5000); if (!res?.ok) return null; const ct = res.headers.get("content-type") || ""; if (!ct.includes("image") && !ct.includes("octet-stream") && !ct.includes("svg") && !ct.includes("icon")) return null; const body = await res.arrayBuffer(); if (!body.byteLength) return null; return { body, contentType: sanitizeContentType(ct, url) }; } catch { return null; }}
async function fetchWithTimeout(url, ms) { const controller = new AbortController(); const id = setTimeout(() => controller.abort(), ms); try { return await fetch(url, { signal: controller.signal, headers: { "User-Agent": "Mozilla/5.0 (compatible; FaviconBot/1.0; +https://favicon.blueat.net)", Accept: "image/*,*/*;q=0.8", }, redirect: "follow", cf: { cacheTtl: CACHE_TTL, cacheEverything: true }, }); } finally { clearTimeout(id); }}
function sanitizeContentType(ct, url) { if (ct.includes("svg")) return "image/svg+xml"; if (ct.includes("png")) return "image/png"; if (ct.includes("jpeg") || ct.includes("jpg")) return "image/jpeg"; if (ct.includes("gif")) return "image/gif"; if (ct.includes("webp")) return "image/webp"; if (ct.includes("icon") || ct.includes("ico") || url?.endsWith(".ico")) return "image/x-icon"; return "image/x-icon";}
function isValidDomain(domain) { if (!domain.includes(".")) return false; const BLOCKED_EXT = [".php",".xml",".json",".yml",".yaml",".txt",".html",".htm",".asp",".aspx",".env",".ini",".cfg",".conf",".bak",".sql",".sh",".py",".rb",".pl",".cgi",".exe",".dll",".log"]; if (BLOCKED_EXT.some(ext => domain.endsWith(ext))) return false; const BLOCKED_NAMES = ["phpinfo","adminer","phpmyadmin","swagger","docker-compose","sitemap","robots","wp-login","xmlrpc","config","setup","install","readme","license","changelog"]; if (BLOCKED_NAMES.some(n => domain === n || domain.startsWith(n + "."))) return false; const tld = domain.split(".").pop(); if (!/^[a-zA-Z]{2,63}$/.test(tld)) return false; return /^[a-zA-Z0-9][a-zA-Z0-9\-\.]{0,251}[a-zA-Z0-9]$/.test(domain);}
Good one !