← back to Trending Dw
scripts/real-lib.js
52 lines
// real-lib.js — shared plumbing for the REAL-product-image pipeline.
// Real marketplace images are INTERNAL-ONLY intel (Steve override 2026-07-15): they live in
// new realImage* fields + public/assets/real/, and must never flow into `image`
// (which /api/put-on-shopify stages as a DW Shopify product image).
const fs = require('fs');
const path = require('path');
const crypto = require('crypto');
const ROOT = path.join(__dirname, '..');
const BEST = path.join(ROOT, 'data', 'bestsellers.json');
const RI_DIR = path.join(ROOT, 'data', 'real-images');
const LEDGER = path.join(RI_DIR, 'ledger.json');
const MANIFEST = path.join(RI_DIR, 'manifest.json');
const CAND_DIR = path.join(RI_DIR, 'candidates');
const ASSETS = path.join(ROOT, 'public', 'assets', 'real');
// TR-ids are regenerated by array index on every scout refresh (scout-to-bestsellers.js),
// so the ledger keys on a content fingerprint instead — it survives id churn.
function fingerprint(it){
return crypto.createHash('sha1').update([it.title, it.company, it.marketplace].join('|')).digest('hex').slice(0, 16);
}
const LISTICLE_HOSTS = new Set([
'realsimple.com', 'bhg.com', 'goodhousekeeping.com', 'thespruce.com',
'countryandtownhouse.com', 'wildfireinteriors.com', 'blog.wallpaperdirect.com'
]);
// host class drives the resolution strategy — the `marketplace` label lies
// (all 50 "Amazon / Big-box" items actually point at vendor sites + listicles).
function classifyUrl(url){
if (!url) return { host: null, klass: 'none' };
let host; try { host = new URL(url).hostname.replace(/^www\./, ''); } catch(e){ return { host: null, klass: 'none' }; }
if (host === 'etsy.com') return { host, klass: /\/listing\//.test(url) ? 'etsy-product' : 'etsy' };
if (host === 'spoonflower.com') return { host, klass: /\/(wallpaper|fabric)\/\d+/.test(url) ? 'spoonflower-product' : 'spoonflower' };
if (host === 'patternbank.com') return { host, klass: 'patternbank' };
if (LISTICLE_HOSTS.has(host)) return { host, klass: 'listicle' };
return { host, klass: 'vendor' };
}
function loadBest(){ return JSON.parse(fs.readFileSync(BEST, 'utf8')); }
function saveBest(data){ atomicWrite(BEST, JSON.stringify(data, null, 2)); }
function loadLedger(){ try { return JSON.parse(fs.readFileSync(LEDGER, 'utf8')); } catch(e){ return {}; } }
function saveLedger(l){ fs.mkdirSync(RI_DIR, { recursive: true }); atomicWrite(LEDGER, JSON.stringify(l, null, 2)); }
function atomicWrite(fp, body){ const tmp = fp + '.tmp'; fs.writeFileSync(tmp, body); fs.renameSync(tmp, fp); }
function sha256(buf){ return crypto.createHash('sha256').update(buf).digest('hex'); }
module.exports = { ROOT, BEST, RI_DIR, LEDGER, MANIFEST, CAND_DIR, ASSETS,
fingerprint, classifyUrl, loadBest, saveBest, loadLedger, saveLedger, atomicWrite, sha256 };