import * as cheerio from 'cheerio';
/** Thin HTML helpers shared by connectors (cheerio). */
export function load(html: string) {
return cheerio.load(html);
}
export type CheerioRoot = ReturnType;
export function text($el: { text(): string } | null | undefined): string | null {
const t = $el?.text()?.replace(/\s+/g, ' ').trim();
return t ? t : null;
}
export function absUrl(base: string, href: string | null | undefined): string | null {
if (!href) return null;
try {
return new URL(href, base).toString();
} catch {
return null;
}
}
/** Extract JSON-LD blocks of a given @type (e.g. Product, Offer) from HTML. */
export function jsonLd(html: string, type?: string): Record[] {
const $ = cheerio.load(html);
const out: Record[] = [];
$('script[type="application/ld+json"]').each((_, el) => {
const raw = $(el).contents().text();
try {
const parsed = JSON.parse(raw) as unknown;
const items = Array.isArray(parsed) ? parsed : (parsed && typeof parsed === 'object' && '@graph' in (parsed as object) ? ((parsed as { '@graph': unknown[] })['@graph'] ?? []) : [parsed]);
for (const it of items) {
if (it && typeof it === 'object') {
const t = (it as { '@type'?: string | string[] })['@type'];
const types = Array.isArray(t) ? t : t ? [t] : [];
if (!type || types.includes(type)) out.push(it as Record);
}
}
} catch {
/* ignore malformed JSON-LD */
}
});
return out;
}
/** Extract Next.js __NEXT_DATA__ payload when present. */
export function nextData(html: string): unknown | null {
const m = html.match(/