← back to Sanderson Onboard
scripts/harvest_trade.js
56 lines
// Daily trade-portal harvest (HEADLESS, reuse persistent auth profile). Best-effort: on auth loss -> NEEDS_REAUTH flag.
const puppeteer = require('/Users/macstudio3/Projects/Designer-Wallcoverings/DW-Programming/ImportNewSkufromURL/node_modules/puppeteer');
const fs = require('fs'), path = require('path');
const ROOT = path.resolve(__dirname, '..');
const PROFILE = ROOT + '/chrome-profile';
const DATA = ROOT + '/data';
const BASE = 'https://trade.sandersondesigngroup.com/us';
const sleep = ms => new Promise(r=>setTimeout(r,ms));
const log = s => { console.log(s); fs.appendFileSync(DATA+'/trade_harvest.log', new Date().toISOString()+' '+s+'\n'); };
async function loggedIn(p){
return await p.evaluate(()=>{
const links=Array.from(document.querySelectorAll('a')).map(a=>(a.getAttribute('href')||'')+' '+(a.textContent||''));
const t=document.body.innerText.toLowerCase();
return links.some(s=>/logout|sign ?out/i.test(s)) || t.includes('my account') || t.includes('quick order');
});
}
async function loadAll(p, cap){
let last=-1, stable=0;
for(let i=0;i<cap;i++){
const info=await p.evaluate(()=>{const m=document.body.innerText.match(/Viewing\s+(\d+)\s+of\s+(\d+)/i);return m?{shown:+m[1],total:+m[2]}:{shown:0,total:0};});
if(info.shown && info.shown>=info.total) return info;
if(info.shown===last){ if(++stable>=3) return info; } else { stable=0; last=info.shown; }
const clicked=await p.evaluate(()=>{const b=Array.from(document.querySelectorAll('button,a')).find(x=>/^load more/i.test((x.textContent||'').trim())&&x.offsetParent!==null); if(b){b.click();return true;} return false;});
if(!clicked) await p.evaluate(()=>window.scrollTo(0,document.body.scrollHeight));
await sleep(3200);
}
return await p.evaluate(()=>{const m=document.body.innerText.match(/Viewing\s+(\d+)\s+of\s+(\d+)/i);return m?{shown:+m[1],total:+m[2]}:{shown:0,total:0};});
}
(async()=>{
try{ fs.existsSync(DATA+'/NEEDS_REAUTH') && fs.unlinkSync(DATA+'/NEEDS_REAUTH'); }catch(e){}
const b=await puppeteer.launch({headless:true, userDataDir:PROFILE,
args:['--no-sandbox','--disable-dev-shm-usage','--window-size=1600,1000','--disable-blink-features=AutomationControlled']});
const p=(await b.pages())[0]||await b.newPage(); await p.setViewport({width:1600,height:1000}); p.setDefaultNavigationTimeout(60000);
await p.goto(BASE+'/',{waitUntil:'networkidle2'}); await sleep(3000);
try{ await p.evaluate(()=>{const t=Array.from(document.querySelectorAll('button,a')).find(x=>/^(accept|agree)/i.test((x.textContent||'').trim()));if(t)t.click();}); }catch(e){}
await sleep(1500);
if(!(await loggedIn(p))){
fs.writeFileSync(DATA+'/NEEDS_REAUTH', new Date().toISOString()+' trade portal session expired -> run scripts/reauth.sh (headed login)\n');
log('AUTH LOST -> NEEDS_REAUTH written; skipping trade harvest');
await b.close(); process.exit(3);
}
const summary={};
for(const cat of ['wallpaper','fabric']){
log('loading '+cat+'...');
await p.goto(BASE+'/products/'+cat,{waitUntil:'networkidle2'}); await sleep(4000);
const info=await loadAll(p, cat==='fabric'?260:120);
const txt=await p.evaluate(()=>document.body.innerText);
fs.writeFileSync(DATA+'/sanderson_'+cat+'_raw.txt', txt);
summary[cat]=info; log(cat+': loaded '+info.shown+'/'+info.total);
}
fs.writeFileSync(DATA+'/trade_summary.json', JSON.stringify(summary,null,2));
log('TRADE HARVEST OK '+JSON.stringify(summary));
await b.close();
})().catch(e=>{ log('ERROR '+e.message); process.exit(1); });