← back to Dw Fleet Registry

harvest-niche.mjs

60 lines

#!/usr/bin/env node
// Targeted per-niche high-res harvest for the niche-starved product sites.
// Uses the live store's predictive search (/search/suggest.json) to find niche
// photography the broad products.json harvest missed, probes true resolution,
// and APPENDS high-res (>=1500px) hits to catalog-hires.json so fanout can match.
// Read-only against the store.  Usage: node harvest-niche.mjs <slug...>

import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { execSync } from 'node:child_process';

const PROJECTS = path.join(os.homedir(), 'Projects');
const SELF = path.dirname(new URL(import.meta.url).pathname);
const STORE = 'https://designerwallcoverings.com';
const catPath = path.join(SELF, 'catalog-hires.json');
const cat = JSON.parse(fs.readFileSync(catPath, 'utf8'));
const seen = new Set(cat.pool.map(p => p.src));

const slugs = process.argv.slice(2);
function niche(slug) {
  try { const sv = fs.readFileSync(path.join(PROJECTS, slug, 'server.js'), 'utf8');
    const m = sv.match(/NICHE_POS\s*=\s*\[([^\]]*)\]/);
    if (m) { const ks = m[1].split(',').map(x => x.replace(/["'\s]/g, '')).filter(Boolean); if (ks.length) return ks; } } catch {}
  return slug.toLowerCase().replace(/(wallcoverings?|wallpapers?|walls?|covering)/g, ' ').trim().split(/\s+/).filter(w => w.length > 2);
}
function dims(f) { try { const o = execSync(`sips -g pixelWidth -g pixelHeight "${f}" 2>/dev/null`).toString();
  return { w:+(o.match(/pixelWidth: (\d+)/)||[])[1]||0, h:+(o.match(/pixelHeight: (\d+)/)||[])[1]||0 }; } catch { return {w:0,h:0}; } }

let added = 0;
for (const slug of slugs) {
  const kws = niche(slug).slice(0, 3);
  let siteAdded = 0;
  for (const kw of kws) {
    let products = [];
    try {
      const r = await fetch(`${STORE}/search/suggest.json?q=${encodeURIComponent(kw)}&resources%5Btype%5D=product&resources%5Blimit%5D=10`, { headers: { 'User-Agent': 'dw-niche-harvest/1.0' } });
      const j = await r.json();
      products = j.resources?.results?.products || [];
    } catch {}
    for (const p of products) {
      const src = (p.image || p.featured_image || '').split('?')[0];
      if (!src || seen.has(src)) continue;
      try {
        execSync(`curl -s --max-time 12 "${src}?width=2400" -o /tmp/nh.jpg`, { stdio:'ignore' });
        const d = dims('/tmp/nh.jpg');
        if (d.w >= 1500 && d.h >= 1500) {
          seen.add(src);
          cat.pool.push({ src, w: d.w, h: d.h, title: p.title || '', handle: p.handle || '', meta: ((p.title||'') + ' ' + kw + ' ' + kws.join(' ')).toLowerCase() });
          added++; siteAdded++;
        }
      } catch {}
    }
  }
  console.log(`  ${slug.padEnd(24)} niche=[${kws.join(',')}] +${siteAdded} high-res`);
}
cat.count = cat.pool.length;
fs.writeFileSync(catPath, JSON.stringify(cat) + '\n');
console.log(`\nadded ${added} high-res images · pool now ${cat.pool.length}`);