diff --git a/src/stores/coop.js b/src/stores/coop.js new file mode 100644 index 0000000..5bea951 --- /dev/null +++ b/src/stores/coop.js @@ -0,0 +1,355 @@ +"use strict"; + +const { normalizeCspc } = require("../utils/sku"); +const { humanBytes } = require("../utils/bytes"); +const { padLeft, padRight } = require("../utils/string"); + +const { mergeDiscoveredIntoDb } = require("../tracker/merge"); +const { buildDbObject, writeJsonAtomic } = require("../tracker/db"); +const { addCategoryResultToReport } = require("../tracker/report"); + +/* ---------------- formatting ---------------- */ + +function kbStr(bytes) { + return humanBytes(bytes).padStart(8, " "); +} +function secStr(ms) { + const s = Number.isFinite(ms) ? ms / 1000 : 0; + const t = Math.round(s * 10) / 10; + return (t < 10 ? `${t.toFixed(1)}s` : `${Math.round(s)}s`).padStart(7, " "); +} +function pageStr(i, total) { + const w = String(total).length; + return `${padLeft(i, w)}/${total}`; +} +function pctStr(done, total) { + const pct = total ? Math.floor((done / total) * 100) : 0; + return `${padLeft(pct, 3)}%`; +} + +/* ---------------- co-op specifics ---------------- */ + +const BASE = "https://shoponlinewhisky-wine.coopwinespiritsbeer.com"; +const REFERER = `${BASE}/worldofwhisky`; + +function coopHeaders(ctx, sourcepage) { + const coop = ctx.store.coop; + return { + Accept: "application/json, text/javascript, */*; q=0.01", + "Content-Type": "application/json", + Origin: BASE, + Referer: REFERER, + + // these 4 are required on their API calls (matches browser) + SessionKey: coop.sessionKey, + chainID: coop.chainId, + storeID: coop.storeId, + appVersion: coop.appVersion, + + AUTH_TOKEN: "null", + CONNECTION_ID: "null", + SESSION_ID: coop.sessionId || "null", + TIMESTAMP: String(Date.now()), + sourcepage, + }; +} + +async function fetchText(url, { headers, ua } = {}) { + const h = { ...(headers || {}) }; + if (ua) h["User-Agent"] = ua; + const res = await fetch(url, { method: "GET", headers: h }); + const text = await res.text(); + return { status: res.status, text }; + } + + function extractVar(html, re) { + const m = String(html || "").match(re); + return m ? String(m[1] || "").trim() : ""; + } + + async function ensureCoopBootstrap(ctx) { + const coop = ctx.store.coop; + if (coop.sessionKey && coop.chainId && coop.storeId && coop.appVersion) return; + + const r = await fetchText(REFERER, { + ua: ctx.store.ua, + headers: { + Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", + Referer: REFERER, + }, + }); + + if (r.status !== 200 || !r.text) { + throw new Error(`coop bootstrap failed: GET ${REFERER} => ${r.status}`); + } + + // Values are in