← back to Hollywood Import
search-pass.mjs
56 lines
// Exhaustive live-site search pass: for each Group-2 pattern not in the grid crawl, drive the
// site's own "Search the site..." box (page fires Meilisearch) and READ THE RESPONSE the page
// receives — page-initiated, in-bounds. Record whether the pattern is live + its price.
import { chromium } from 'playwright';
import fs from 'node:fs';
const slug = s => (s || '').toLowerCase().replace(/[^a-z0-9]+/g, '_').replace(/^_+|_+$/g, '');
const terms = JSON.parse(fs.readFileSync(new URL('search-candidates.json', import.meta.url)));
const b = await chromium.launch({ channel: 'chrome', headless: true });
const ctx = await b.newContext({ storageState: new URL('momentum-auth.json', import.meta.url).pathname, viewport: { width: 1500, height: 1200 } });
const p = await ctx.newPage();
let lastHits = null;
p.on('response', async (res) => {
if (!/meilisearch.*\/search/i.test(res.url())) return;
try { const j = await res.json(); if (Array.isArray(j.hits)) lastHits = j.hits; } catch {}
});
await p.goto('https://momentumco.com/products?forceSync=1', { waitUntil: 'domcontentloaded', timeout: 40000 }).catch(()=>{});
await p.waitForTimeout(4000);
const box = p.locator('input[placeholder="Search the site..."]').first();
const out = [];
let dumpedKeys = false;
for (const { term, rows } of terms) {
lastHits = null;
await box.click().catch(()=>{});
await box.fill('').catch(()=>{});
await box.type(term, { delay: 30 }).catch(()=>{});
// wait for a meilisearch response
for (let i = 0; i < 20 && !lastHits; i++) await p.waitForTimeout(250);
await p.waitForTimeout(400);
let found = null;
if (lastHits && lastHits.length) {
if (!dumpedKeys) { console.log('HIT FIELDS:', Object.keys(lastHits[0]).join(',')); dumpedKeys = true; }
const tk = slug(term);
// a hit whose pattern/name slug equals the term = a real live match
found = lastHits.find(h => {
const nm = h.pattern_name || h.pattern || h.name || h.title || '';
return slug(nm).startsWith(tk) || tk.startsWith(slug(nm));
});
}
const rec = { term, rows, live: !!found };
if (found) {
rec.matchedPattern = found.pattern_name || found.pattern || found.name || found.title;
rec.price = found.price ?? found.list_price ?? found.unit_price ?? found.amount ?? null;
rec.priceFields = Object.fromEntries(Object.entries(found).filter(([k]) => /price|amount|net|cost/i.test(k)));
}
out.push(rec);
console.log(`${rec.live ? '✓ LIVE ' : '· absent'} "${term}" (${rows} rows)` + (found ? ` → ${rec.matchedPattern} ${JSON.stringify(rec.priceFields)}` : ''));
}
fs.writeFileSync(new URL('search-pass-result.json', import.meta.url), JSON.stringify(out, null, 2));
const live = out.filter(o => o.live);
console.log(`\n=== ${live.length}/${out.length} terms LIVE (rows recoverable: ${live.reduce((a,o)=>a+o.rows,0)}) ===`);
await b.close();