← back to Selfadhesivewallpaper

scripts/pull-from-shopify.js

124 lines

// Self-adhesive wallpaper pull from live DW Shopify catalog.
// Strict scope: peel-and-stick / removable / self-adhesive / easy-up wallcoverings.
// Filter logic: (a) keyword match on title/tags  OR
//               (b) known peel-and-stick vendor + niche keyword.
// Cap output at 500 most recent (sort by published_at desc).
const https = require('https');
const fs = require('fs');
const path = require('path');
const { classifyAesthetic } = require('../../_shared/microsite-aesthetic');
const SITE_CFG_RAILS = (() => { try { return require('../site.config.json').rails || []; } catch { return []; } })();

const NICHE_TAG = /\b(removable|peel[\s-]?and[\s-]?stick|peel[\s-]?stick|self[\s-]?adhesive|easy[\s-]?up|repositionable)\b/i;
const NICHE_TITLE = /\b(removable|peel[\s-]?and[\s-]?stick|peel[\s-]?stick|self[\s-]?adhesive|easy[\s-]?up|repositionable)\b/i;
const REJECT_TITLE = /(visual.{0,3}merchandiser|bh.?90210|\bimage[ _-]?4\b|lamp|rug|pillow|throw|tripod|frame|mirror|vase|candle|sculpture|figurine)/i;

const VENDOR_ALLOWLIST = [
  'ps removable wallpaper',
  'surface stick',
  'peel and stick',
  'peel-and-stick',
  'apartment wallpaper',
  'drop it modern',
  'rebel walls',
  'calico wallpaper',
];

function vendorMatches(vendor) {
  const v = (vendor || '').toLowerCase();
  return VENDOR_ALLOWLIST.some(name => v.includes(name));
}

function fetchPage(page) {
  return new Promise((resolve, reject) => {
    https.get(`https://designerwallcoverings.com/products.json?limit=250&page=${page}`, {
      headers: { 'User-Agent': 'Mozilla/5.0 selfadhesivewallpaper-builder' }
    }, (res) => {
      let data = '';
      res.on('data', c => data += c);
      res.on('end', () => { try { resolve(JSON.parse(data).products || []); } catch(e) { reject(e); } });
    }).on('error', reject);
  });
}

function aestheticOf(tags, title) {
  const blob = ((tags || []).join(' ') + ' ' + (title || '')).toLowerCase();
  if (/easy[\s-]?up/.test(blob)) return 'easy-up';
  if (/peel[\s-]?and[\s-]?stick|peel[\s-]?stick/.test(blob)) return 'peel-and-stick';
  if (/self[\s-]?adhesive/.test(blob)) return 'self-adhesive';
  if (/repositionable/.test(blob)) return 'repositionable';
  return 'removable';
}

function classifyForSite(tags, title) {
  const railsMatch = classifyAesthetic(tags, SITE_CFG_RAILS);
  if (railsMatch && railsMatch !== 'all') return railsMatch;
  return aestheticOf(tags, title);
}

(async () => {
  const all = [];
  for (let page = 1; page <= 30; page++) {
    const products = await fetchPage(page);
    if (!products.length) break;
    all.push(...products);
    if (products.length < 250) break;
  }
  console.log(`fetched: ${all.length}`);

  const niche = all
    .filter(p => p.product_type && /wallcovering/i.test(p.product_type))
    .filter(p => !REJECT_TITLE.test(p.title || ''))
    .filter(p => p.images && p.images.length > 0 && p.images[0].src)
    .filter(p => {
      const tagsBlob = (p.tags || []).join(' ');
      const title = p.title || '';
      const hasKeyword = NICHE_TAG.test(tagsBlob) || NICHE_TITLE.test(title);
      // Plain keyword path
      if (hasKeyword) return true;
      // Vendor + keyword path (still strict — vendor alone is not enough,
      //   except for vendors whose entire catalog IS peel-and-stick).
      const v = (p.vendor || '').toLowerCase();
      const isPureNicheVendor =
        v.includes('ps removable wallpaper') ||
        v.includes('surface stick') ||
        v.includes('peel and stick') ||
        v.includes('peel-and-stick');
      if (isPureNicheVendor) return true;
      if (vendorMatches(p.vendor) && hasKeyword) return true;
      return false;
    })
    .map(p => ({
      sku: p.handle,
      handle: p.handle,
      title: p.title,
      vendor: (p.vendor || '').trim(),
      product_type: p.product_type,
      published_at: p.published_at || p.created_at || '',
      image_url: p.images[0].src,
      tags: p.tags || [],
      aesthetic: classifyForSite(p.tags, p.title),
      product_url: `https://designerwallcoverings.com/products/${p.handle}`,
    }));

  console.log(`niche-filtered (pre-cap): ${niche.length}`);

  // Cap at 500 most recent.
  niche.sort((a, b) => (b.published_at || '').localeCompare(a.published_at || ''));
  const capped = niche.slice(0, 500);
  console.log(`capped: ${capped.length}`);

  const byA = {};
  for (const p of capped) byA[p.aesthetic] = (byA[p.aesthetic] || 0) + 1;
  console.log('aesthetics:', byA);
  const byV = {};
  for (const p of capped) byV[p.vendor] = (byV[p.vendor] || 0) + 1;
  console.log('top vendors:', Object.entries(byV).sort((a,b)=>b[1]-a[1]).slice(0,10));

  const tmp = path.join(__dirname, '..', 'data', 'products.json.tmp');
  const final = path.join(__dirname, '..', 'data', 'products.json');
  fs.writeFileSync(tmp, JSON.stringify(capped, null, 2));
  fs.renameSync(tmp, final);
  console.log('wrote data/products.json');
})();