Skip to content
// Read the homepage HTML and pull out buying signals. No AI, no paid data provider.
const lead = $('Normalize Lead').first().json;
const fetched = $('Fetch Website').isExecuted ? $('Fetch Website').first().json : null;
const status = fetched?.statusCode ?? null;
const html = typeof fetched?.body === 'string' ? fetched.body : typeof fetched?.data === 'string' ? fetched.data : '';
const siteOk = Boolean(html) && status !== null && status < 400;

const grab = (re) => (html.match(re)?.[1] ?? '').replace(/\s+/g, ' ').trim();
const text = html.replace(/<script[\s\S]*?<\/script>|<style[\s\S]*?<\/style>/gi, ' ').replace(/<[^>]+>/g, ' ').replace(/&[a-z#0-9]+;/gi, ' ').replace(/\s+/g, ' ').trim();
const lower = (html + ' ' + text).toLowerCase();
const uniq = (a) => [...new Set(a)];

const TECH = {
  wordpress: /wp-content|wp-includes/, shopify: /cdn\.shopify\.com|myshopify\.com/, hubspot: /js\.hs-scripts\.com|hs-analytics/,
  google_analytics: /googletagmanager\.com|google-analytics\.com/, wix: /wix\.com|wixstatic/, squarespace: /squarespace/, webflow: /webflow/,
  intercom: /intercom/, stripe: /js\.stripe\.com/,
};
const BOOKING = /calendly\.com|acuityscheduling|cal\.com\/|booksy|squareup\.com\/appointments|zocdoc/;

const signals = siteOk ? {
  title: grab(/<title[^>]*>([\s\S]*?)<\/title>/i),
  description: grab(/<meta[^>]+name=["']description["'][^>]+content=["']([^"']*)["']/i),
  headline: grab(/<h1[^>]*>([\s\S]*?)<\/h1>/i).replace(/<[^>]+>/g, ''),
  emails: uniq((text.match(/[\w.+-]+@[\w-]+\.[\w.-]+/g) ?? []).map((e) => e.toLowerCase())).slice(0, 5),
  phones: uniq(text.match(/(?:\+?\d{1,2}[\s.-]?)?\(?\d{3}\)?[\s.-]\d{3}[\s.-]\d{4}/g) ?? []).slice(0, 3),
  socials: uniq((html.match(/https?:\/\/(?:www\.)?(?:linkedin\.com|facebook\.com|instagram\.com|x\.com|twitter\.com|youtube\.com)\/[^\s"'<>]+/gi) ?? [])),
  tech: Object.entries(TECH).filter(([, re]) => re.test(lower)).map(([k]) => k),
  has_booking: BOOKING.test(lower),
  hiring: /we['’]re hiring|careers|join our team|open positions/.test(lower),
  location: grab(/<address[^>]*>([\s\S]*?)<\/address>/i) || (text.match(/[A-Z][a-zA-Z]+,\s?[A-Z]{2}\s\d{5}/)?.[0] ?? ''),
} : null;

return [{
  json: {
    ...lead,
    site_ok: siteOk,
    site_status: status,
    site_error: siteOk ? null : fetched?.error?.message ?? fetched?.error ?? (lead.website ? `HTTP ${status ?? 'no response'}` : 'no website to check'),
    signals,
    page_text: siteOk ? text.slice(0, 4000) : '',
  },
}];