// Text utilities kept from forthena's lib/llm.js (this site has NO LLM layer — // the ranking is deterministic, see lib/feed.js). The file keeps the llm.js // module name so the crawl/article imports don't churn. export function sanitize(s) { return String(s || "") .replace(/<[^>]*>/g, " ") .replace(/\s+/g, " ") .trim(); } // First image an RSS item exposes, in descending order of reliability. export function extractImage(item, feed) { const cands = []; const enc = item.enclosure; if (enc && enc.url && (/image/i.test(enc.type || "") || /\.(jpe?g|png|webp|gif|avif)/i.test(enc.url))) cands.push(enc.url); for (const key of ["mediaContent", "mediaThumbnail", "media:content", "media:thumbnail"]) { const v = item[key]; const list = Array.isArray(v) ? v : v ? [v] : []; for (const m of list) { const u = (m && (m["$"]?.url || m.url || m.$?.url)) || null; if (u) cands.push(u); } } const itn = item.itunesImage || item.image; if (itn) cands.push((itn["$"] && itn["$"].href) || itn.href || (typeof itn === "string" ? itn : null)); const html = [item["content:encoded"], item.content, item.contentSnippet, item.summary] .filter(Boolean) .join(" "); const m = /]+src=["']([^"']+)["']/i.exec(html); if (m) cands.push(m[1]); const fi = feed && feed.image && (feed.image.url || feed.image.$?.url); if (fi) cands.push(fi); for (const u of cands) { const s = String(u || "").trim(); if (!/^https?:\/\//i.test(s)) continue; if (/1x1|spacer|pixel\.gif|blank\.|logo-|sprite/i.test(s)) continue; return s.slice(0, 500); } return null; }