← back to Fineartamerica Price Sync
Free HTTP FAA cost fetcher (stateful configurator) — pilot: 36/36 configs priced, all below cost
54dfec429a7d7edb2cf6c5fabf842e235a96a0fe · 2026-07-16 08:56:25 -0700 · Steve Abrams
Files touched
A bin/faa-fetch-http.jsA lib/faa-http.js
Diff
commit 54dfec429a7d7edb2cf6c5fabf842e235a96a0fe
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Thu Jul 16 08:56:25 2026 -0700
Free HTTP FAA cost fetcher (stateful configurator) — pilot: 36/36 configs priced, all below cost
---
bin/faa-fetch-http.js | 46 ++++++++++++++++++++++++++++++++++
lib/faa-http.js | 69 +++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 115 insertions(+)
diff --git a/bin/faa-fetch-http.js b/bin/faa-fetch-http.js
new file mode 100644
index 0000000..1cf9cc4
--- /dev/null
+++ b/bin/faa-fetch-http.js
@@ -0,0 +1,46 @@
+#!/usr/bin/env node
+// Populate data/cost-cache.json with real FAA costs via the free HTTP
+// configurator endpoint (lib/faa-http.js). No Browserbase, no login.
+//
+// node bin/faa-fetch-http.js --url "<faa product page>" --from-shopify <productId>
+//
+// For each distinct config on the Shopify product, replays FAA's setter sequence
+// and caches the returned cost keyed by configKey. Falls back to the page's
+// artworkId when the SKU's artworkId prices null (handles the SKU-vs-listing id
+// drift, e.g. Washington Top Hat SKU 65269183 vs live listing 65119339).
+
+const { openSession, priceFramedConfig } = require('../lib/faa-http');
+const { parseSku, configKey, configLabel } = require('../lib/parse-sku');
+const { loadCache, saveCache } = require('../lib/faa-cost');
+const { getProduct } = require('../lib/shopify');
+
+const args = process.argv.slice(2);
+const val = f => { const i = args.indexOf(f); return i >= 0 ? args[i + 1] : null; };
+const URL0 = val('--url');
+const PID = val('--from-shopify');
+
+(async () => {
+ if (!URL0 || !PID) { console.error('need --url and --from-shopify <productId>'); process.exit(1); }
+ const p = await getProduct(PID);
+ const seen = new Set(), configs = [];
+ for (const v of p.variants) {
+ const cfg = parseSku(v.sku); if (!cfg) continue;
+ const k = configKey(cfg); if (seen.has(k)) continue; seen.add(k); configs.push(cfg);
+ }
+ console.log(`${configs.length} distinct FAA configs on "${p.title}"`);
+
+ const session = await openSession(URL0);
+ console.log(`FAA session sid=${session.sessionId.slice(0,10)}… pageArtwork=${session.pageArtworkId}`);
+ const cache = loadCache();
+ let ok = 0, fail = 0;
+ for (const cfg of configs) {
+ let cost = await priceFramedConfig(session, cfg);
+ if (cost == null && cfg.artworkid !== session.pageArtworkId) {
+ cost = await priceFramedConfig(session, { ...cfg, artworkid: session.pageArtworkId }); // fallback
+ }
+ if (cost != null) { cache[configKey(cfg)] = cost; ok++; console.log(` ✓ ${configLabel(cfg)} → $${cost.toFixed(2)}`); }
+ else { fail++; console.log(` ✗ ${configLabel(cfg)} → null`); }
+ }
+ saveCache(cache);
+ console.log(`\ncosted ${ok}, failed ${fail}. cost-cache.json updated. ($0 — free HTTP)`);
+})().catch(e => { console.error(e); process.exit(1); });
diff --git a/lib/faa-http.js b/lib/faa-http.js
new file mode 100644
index 0000000..4ae6071
--- /dev/null
+++ b/lib/faa-http.js
@@ -0,0 +1,69 @@
+// FREE, login-free FAA per-config cost fetcher (cracked 2026-07-16).
+//
+// FAA's framed-print configurator is a STATEFUL session: each option selection
+// POSTs an `action=set*/select*` to
+// /queries/queryUpdateProductConfiguratorProductPrint.php
+// which mutates the session's config and returns {status, price}. So to price
+// one variant we replay its setter sequence in a single cookie session and read
+// the price off the final (setPrintSize) response. No browser, no login.
+//
+// Proven: 20x24 CRQ13/PM918 = $195.00, 7x8 = $74.00, 34x40 = $350.00.
+
+const UA = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36';
+const PRICE_URL = 'https://fineartamerica.com/queries/queryUpdateProductConfiguratorProductPrint.php';
+
+function parseCookies(setCookies) {
+ // node fetch exposes combined Set-Cookie via getSetCookie()
+ return (setCookies || []).map(c => c.split(';')[0]).join('; ');
+}
+
+// Open a configurator session for one FAA product page; capture cookies + ids.
+async function openSession(pageUrl) {
+ const res = await fetch(pageUrl + (pageUrl.includes('?') ? '&' : '?') + 'product=framed-print',
+ { headers: { 'User-Agent': UA } });
+ const cookie = parseCookies(res.headers.getSetCookie ? res.headers.getSetCookie() : []);
+ const html = await res.text();
+ const g = n => {
+ const m = html.match(new RegExp(`id='${n}'\\s+value='([^']*)'`)) ||
+ html.match(new RegExp(`name='${n}'\\s+value='([^']*)'`));
+ return m ? m[1] : '';
+ };
+ return { pageUrl, cookie, sessionId: g('sessionId'), uniqueId: g('uniqueId'), pageArtworkId: g('artworkId') };
+}
+
+async function post(session, params) {
+ const res = await fetch(PRICE_URL, {
+ method: 'POST',
+ headers: {
+ 'User-Agent': UA,
+ 'X-Requested-With': 'XMLHttpRequest',
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ 'Referer': session.pageUrl + '?product=framed-print',
+ ...(session.cookie ? { Cookie: session.cookie } : {}),
+ },
+ body: `sessionId=${session.sessionId}&uniqueId=${session.uniqueId}&${params}`,
+ });
+ const txt = await res.text();
+ try { return JSON.parse(txt); } catch { return { status: 'error', raw: txt.slice(0, 120) }; }
+}
+
+// Price one parsed framed-print config. Replays the setter sequence; returns the
+// FAA fulfillment cost (number) or null on failure.
+async function priceFramedConfig(session, cfg) {
+ const artworkId = cfg.artworkid || session.pageArtworkId;
+ const base = `productId=printframed&artworkId=${artworkId}`;
+ const steps = [
+ `${base}&action=selectPaper&paperId=${cfg.paperid}`,
+ `${base}&action=selectFrame&frameId=${cfg.frameid}`,
+ `${base}&action=selectMat1&mat1Id=${cfg.mat1id}`,
+ `${base}&action=selectMatWidth&matWidth=${cfg.mat1width || 2}`,
+ `${base}&action=selectFinish&finishId=${cfg.finishid}`,
+ `${base}&action=setPrintSize&width=${cfg.imagewidth}&height=${cfg.imageheight}`,
+ ];
+ let last = null;
+ for (const s of steps) last = await post(session, s);
+ if (!last || last.status !== 'success' || last.price == null) return null;
+ return parseFloat(last.price);
+}
+
+module.exports = { openSession, priceFramedConfig, PRICE_URL };
← ac5c24e FAA price-sync: SKU->FAA cost->cost/0.65/0.85 Shopify repric
·
back to Fineartamerica Price Sync
·
auto-save: 2026-07-16T09:12:20 (3 files) — bin/faa-fetch-all cc49883 →