← back to Fineartamerica Price Sync
lib/faa-http.js
70 lines
// 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 };