← back to Dw Marketing Reels

fetch-ol-images.cjs

94 lines

'use strict';
// Final pass: through Browserbase (cloud IP can reach the blocked O&L host), for each PDP
// (a) scroll to trigger lazy-load, (b) read the magento gallery JSON (full colorway swatches),
// (c) fetch the chosen image bytes in-browser and return base64 so we can save locally.
const os = require('os'), fs = require('fs'), path = require('path');
require('dotenv').config({ path: os.homedir() + '/.claude/skills/browserbase/.env' });
const Browserbase = require('@browserbasehq/sdk').default;
const { chromium } = require('playwright-core');
const sleep = ms => new Promise(r => setTimeout(r, ms));
const OUT = '/Users/macstudio3/Projects/dw-marketing-reels/sourced-images/imageless-14';
fs.mkdirSync(OUT, { recursive: true });

// pages to open; each returns full gallery so we can pick colorway-correct swatch
const pages = [
  { key:'W7814-01', url:'https://www.osborneandlittle.com/colonnato-w7814.html' },
  { key:'W7682-01', url:'https://www.osborneandlittle.com/coralline-w7682.html' },
  { key:'W7024-03', url:'https://www.osborneandlittle.com/japanese-garden-w7024-w7024.html' },
  { key:'W6541-02', url:'https://www.osborneandlittle.com/peacock-w6541.html' },
  { key:'W5223-03', url:'https://www.osborneandlittle.com/tamba-w5223.html' },
  { key:'W7334-1',  url:'https://www.osborneandlittle.com/trailing-orchid-w7334.html' },
  { key:'EUR-70328',url:'https://www.osborneandlittle.com/audley_W6297-W6297.html' },
  { key:'EUR-70402',url:'https://www.osborneandlittle.com/canestra_W6341-W6341.html' },
  { key:'EUR-70351',url:'https://www.osborneandlittle.com/chameleon_W6305-W6305.html' },
  { key:'NCW4270',  url:'https://www.osborneandlittle.com/coromandel_NCW4270-NCW4270.html' },
];

async function galleryOf(page){
  // scroll to load lazy imgs
  await page.evaluate(async()=>{ for(let y=0;y<3000;y+=400){window.scrollTo(0,y);await new Promise(r=>setTimeout(r,150));} window.scrollTo(0,0); });
  await sleep(1500);
  return await page.evaluate(() => {
    const out = { imgs:[], magento:[] };
    document.querySelectorAll('img').forEach(im => {
      const s = im.currentSrc || im.src || im.getAttribute('data-src') || '';
      if (/media\/catalog\/product/i.test(s) && !/AVATAR|white_logo|brand\//i.test(s)) out.imgs.push(s);
    });
    // magento fotorama/gallery json
    const html = document.documentElement.innerHTML;
    const re = /"(?:img|full|thumb)":"(https?:[^"]*media\/catalog\/product[^"]+)"/g; let m;
    while((m=re.exec(html))) out.magento.push(m[1].replace(/\\\//g,'/'));
    return { imgs:[...new Set(out.imgs)], magento:[...new Set(out.magento)] };
  });
}

async function fetchBytes(page, url){
  return await page.evaluate(async (u) => {
    const r = await fetch(u); if(!r.ok) return null;
    const b = await r.arrayBuffer(); const bytes = new Uint8Array(b);
    let bin=''; for(let i=0;i<bytes.length;i++) bin+=String.fromCharCode(bytes[i]);
    return { b64: btoa(bin), type: r.headers.get('content-type')||'', len: bytes.length };
  }, url);
}

(async () => {
  const bb = new Browserbase({ apiKey: process.env.BROWSERBASE_API_KEY });
  const session = await bb.sessions.create({ projectId: process.env.BROWSERBASE_PROJECT_ID, browserSettings: { solveCaptchas: true } });
  console.error('session', session.id);
  const browser = await chromium.connectOverCDP(session.connectUrl);
  const page = browser.contexts()[0].pages()[0] || await browser.contexts()[0].newPage();
  page.setDefaultTimeout(45000);
  const manifest = [];
  for (const p of pages){
    const rec = { key:p.key, url:p.url };
    try {
      await page.goto(p.url, { waitUntil:'domcontentloaded' }).catch(()=>{});
      await sleep(2500);
      rec.gallery = await galleryOf(page);
      // choose candidate imgs to download: SKU-named first, else all product imgs (non-hero)
      const all = [...new Set([...rec.gallery.magento, ...rec.gallery.imgs])];
      rec.candidates = all;
      // download every candidate that isn't the collection hero pattern (mpiowebpcache/bff0.. = og hero)
      const dl = all.filter(u=>/catalog\/product/i.test(u));
      rec.saved = [];
      for (let i=0;i<dl.length && i<8;i++){
        const u = dl[i];
        const r = await fetchBytes(page, u);
        if (r && r.len>2000){
          const ext = /webp/i.test(r.type)?'webp':(/png/i.test(r.type)?'png':'jpg');
          const base = (u.split('/').pop()||('img'+i)).replace(/\.(webp|jpg|jpeg|png).*$/i,'');
          const fn = `${p.key}__${base}.${ext}`;
          fs.writeFileSync(path.join(OUT, fn), Buffer.from(r.b64,'base64'));
          rec.saved.push({ file:fn, from:u, bytes:r.len, type:r.type });
        }
      }
    } catch(e){ rec.error = e.message; }
    console.error(`[${p.key}] mag=${rec.gallery?rec.gallery.magento.length:0} img=${rec.gallery?rec.gallery.imgs.length:0} saved=${rec.saved?rec.saved.length:0} ${rec.error||''}`);
    manifest.push(rec);
  }
  await browser.close().catch(()=>{});
  const fp = '/Users/macstudio3/Projects/dw-marketing-reels/fetch-ol-manifest.json';
  fs.writeFileSync(fp, JSON.stringify(manifest,null,2));
  console.log(fp);
})().catch(e=>{ console.error('FATAL', e.message); process.exit(1); });