#!/usr/bin/env node /** * Uses Playwright (headless Chromium) to: * 1. Navigate to each store's discount page * 2. Intercept all XHR/fetch JSON responses * 3. Identify the real API endpoints serving discount data * 4. Save discovered endpoints + raw data * * Usage: node discover_discount_apis.js */ 'use strict'; const { chromium } = require('/home/ubuntu/.openclaw/workspace/node_modules/playwright'); const fs = require('fs/promises'); const path = require('path'); const WORKSPACE = '/home/ubuntu/.openclaw/workspace'; const STORES = [ { name: 'jumbo', urls: [ 'https://www.jumbo.com.ar/descuentos-del-dia?type=por-dia&day=4', 'https://www.jumbo.com.ar/descuentos-del-dia?type=por-dia&day=5', ], }, { name: 'disco', urls: [ 'https://www.disco.com.ar/descuentos-del-dia?type=por-dia&day=4', ], }, { name: 'changomas', urls: [ 'https://www.masonline.com.ar/promociones-bancarias?dia=jueves', 'https://www.masonline.com.ar/promociones-bancarias?dia=viernes', ], }, { name: 'carrefour', urls: [ 'https://www.carrefour.com.ar/descuentos-bancarios?filtro=por-dia&dia=jueves&formato=ecommerce', 'https://www.carrefour.com.ar/descuentos-bancarios?filtro=por-dia&dia=viernes&formato=ecommerce', ], }, ]; // Words that indicate the response contains discount/promo data const DISCOUNT_KEYWORDS = ['descuento', 'discount', 'benefit', 'promo', 'banco', 'bank', 'tarjeta', 'reintegro', 'cuota', 'financ', 'medio', 'pago', 'vigencia', 'legal']; function looksLikeDiscountData(text) { const lower = text.toLowerCase(); return DISCOUNT_KEYWORDS.filter(w => lower.includes(w)).length >= 2; } async function interceptStore(browser, store) { const page = await browser.newPage(); const captured = []; // Intercept all responses page.on('response', async (response) => { const url = response.url(); const ct = response.headers()['content-type'] || ''; // Skip assets, tracking, etc. const skip = ['vtexassets', 'analytics', 'gtm', 'facebook', 'tiktok', 'hotjar', 'clarity', '.js', '.css', '.png', '.jpg', '.svg', '.woff', 'segment.io', 'amplitude', 'mixpanel']; if (skip.some(s => url.includes(s))) return; if (!ct.includes('json') && !url.includes('graphql')) return; try { const text = await response.text(); if (!text || text.length < 30) return; const isDiscount = looksLikeDiscountData(text); const entry = { url, status: response.status(), size: text.length, isDiscount, preview: text.slice(0, 300), }; if (isDiscount) { try { entry.data = JSON.parse(text); } catch {} console.log(` [${store.name}] DISCOUNT API: ${url.slice(0, 100)} (${text.length}b)`); } captured.push(entry); } catch {} }); for (const url of store.urls) { process.stdout.write(`[${store.name}] Loading ${url.slice(0, 80)}... `); try { await page.goto(url, { waitUntil: 'networkidle', timeout: 30000 }); // Extra wait for lazy-loaded components await page.waitForTimeout(3000); console.log('done'); } catch (e) { console.log(`timeout/error: ${e.message.slice(0, 50)}`); } } await page.close(); const discountCalls = captured.filter(c => c.isDiscount); return { store: store.name, total: captured.length, discountCalls }; } async function main() { console.log('Launching Playwright Chromium...'); const browser = await chromium.launch({ headless: true, args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage'], }); const allResults = {}; for (const store of STORES) { console.log(`\n── ${store.name.toUpperCase()} ──`); try { const result = await interceptStore(browser, store); allResults[store.name] = result; console.log(` Total API calls: ${result.total}, discount APIs: ${result.discountCalls.length}`); if (result.discountCalls.length > 0) { console.log(' Discount API endpoints found:'); const seen = new Set(); for (const c of result.discountCalls) { // Normalize URL to extract pattern (remove specific day values) const normalized = c.url.replace(/day=\d+/g, 'day=N') .replace(/dia=[a-z]+/ig, 'dia=DAY') .replace(/viernes|jueves|lunes|martes|miercoles|sabado|domingo/ig, 'DAY'); if (!seen.has(normalized)) { seen.add(normalized); console.log(` ${normalized.slice(0, 110)}`); if (c.data) { const keys = Array.isArray(c.data) ? `[array len=${c.data.length}]` : Object.keys(c.data).slice(0, 8).join(', '); console.log(` keys: ${keys}`); } } } } } catch (e) { console.error(` FAILED: ${e.message}`); allResults[store.name] = { error: e.message }; } } await browser.close(); const outFile = path.join(WORKSPACE, 'data', 'discounts', 'api-discovery.json'); await fs.mkdir(path.dirname(outFile), { recursive: true }); await fs.writeFile(outFile, JSON.stringify({ discoveredAt: new Date().toISOString(), stores: allResults, }, null, 2)); console.log(`\nSaved: ${outFile}`); } main().catch(e => { console.error(e); process.exit(1); });