import { extractCoverSignals } from './mindspace-thumbnails.mjs'; function stripHtml(value) { return String(value ?? '') .replace(/<[^>]+>/g, ' ') .replace(/\s+/g, ' ') .trim(); } function htmlWithoutEmbeddedCode(html) { return String(html ?? '') .replace(//gi, ' ') .replace(//gi, ' '); } function sanitizeFaqItem(item) { const q = stripHtml(item?.q ?? item?.question).slice(0, 120); const a = stripHtml(item?.a ?? item?.answer).slice(0, 500); if (!q || !a || q.length < 2 || a.length < 4) return null; if (/document\.|function\s*\(|createElement|innerHTML|querySelector/i.test(`${q} ${a}`)) return null; return { q, a }; } function decodeMetaAttribute(value) { return String(value ?? '').replaceAll('"', '"').replaceAll('&', '&').replaceAll('<', '<'); } function extractGeoMetaJsonRaw(html) { const source = String(html ?? ''); const marker = source.match(/]*name=["']mindspace-geo["'][^>]*content\s*=\s*/i); if (!marker) return null; let rest = source.slice(marker.index + marker[0].length); if (rest[0] === '"') { let raw = ''; for (let i = 1; i < rest.length; i += 1) { if (rest[i] === '&' && rest.slice(i, i + 6) === '"') { raw += '"'; i += 5; continue; } if (rest[i] === '"') return raw; raw += rest[i]; } return null; } const jsonStart = rest.indexOf('{'); if (jsonStart < 0) return null; let depth = 0; let inString = false; let escape = false; for (let i = jsonStart; i < rest.length; i += 1) { const ch = rest[i]; if (inString) { if (escape) { escape = false; continue; } if (ch === '\\') { escape = true; continue; } if (ch === '"') inString = false; continue; } if (ch === '"') { inString = true; continue; } if (ch === '{') depth += 1; else if (ch === '}') { depth -= 1; if (depth === 0) return rest.slice(jsonStart, i + 1); } } return null; } export function parseMindspaceGeoMeta(html) { const raw = extractGeoMetaJsonRaw(html); if (!raw) return {}; try { return JSON.parse(decodeMetaAttribute(raw)); } catch { return {}; } } export function extractFaqFromHeadings(html, { maxItems = 6 } = {}) { const source = htmlWithoutEmbeddedCode(html); const faq = []; const seen = new Set(); const pattern = /<(h[23])[^>]*>([\s\S]*?)<\/\1>\s*(?:]*>([\s\S]*?)<\/p>|]*>([\s\S]*?)<\/div>)/gi; for (const match of source.matchAll(pattern)) { const item = sanitizeFaqItem({ q: match[2], a: match[3] ?? match[4], }); if (!item) continue; const key = item.q.toLowerCase(); if (seen.has(key)) continue; seen.add(key); faq.push(item); if (faq.length >= maxItems) break; } return faq; } export function resolveGeoKeywords(html, authorMeta = null) { const meta = authorMeta ?? parseMindspaceGeoMeta(html); const fromAuthor = Array.isArray(meta.keywords) ? meta.keywords.map((item) => String(item ?? '').trim()).filter(Boolean) : []; if (fromAuthor.length > 0) return fromAuthor.slice(0, 12); const keywords = []; const seen = new Set(); const push = (value) => { const token = String(value ?? '').trim(); if (!token || token.length < 2 || seen.has(token)) return; seen.add(token); keywords.push(token); }; const cover = extractCoverSignals(String(html ?? '')); push(cover.tag); push(cover.subtitle); const title = String(html ?? '').match(/]*>([^<]+)<\/title>/i)?.[1]?.trim() ?? ''; if (title) push(title); for (const match of String(html ?? '').matchAll(/]*>([\s\S]*?)<\/h2>/gi)) { push(stripHtml(match[1])); if (keywords.length >= 12) break; } return keywords.slice(0, 12); } export function resolveGeoFaq(html, authorMeta = null) { const meta = authorMeta ?? parseMindspaceGeoMeta(html); const fromAuthor = Array.isArray(meta.faq) ? meta.faq.map((item) => sanitizeFaqItem(item)).filter(Boolean) : []; if (fromAuthor.length > 0) return fromAuthor.slice(0, 8); return extractFaqFromHeadings(html); }