/** * Live fixture capture for cardrush: one MTG list page (plain HTTP, set-code brackets), one One Piece * page (rarity brackets, sealed boxes) and — if a Scrapfly capture of the Pokémon store exists at * /tmp/cr_pokemon_list2.html (taken once during research, 25 credits) — the Pokémon page. * Usage: pnpm tsx connectors/api/cardrush/_capture.ts */ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'; import path from 'node:path'; import { fixtureDir, saveFixture } from '@rareindex/connectors/testing'; import meta from './meta.json' with { type: 'json' }; import { parseListPage, type CardrushPayload, type CardrushStore } from './index.js'; import { HTML_HEADERS } from '../_g1-cards-eu-jp-lib/index.js'; const wait = (ms: number) => new Promise((r) => setTimeout(r, ms)); mkdirSync(fixtureDir('cardrush'), { recursive: true }); const stores = meta.config.stores as unknown as CardrushStore[]; async function capture(host: string, listId: string, name: string, doc: string | null, engine: 'api' | 'scrapfly', keep: number, note: string) { const store = stores.find((s) => s.host === host)!; const url = `https://${host}/product-list/${listId}`; const page = doc ?? (await (await fetch(url, { headers: HTML_HEADERS })).text()); if (name === 'mtg-list-page1') writeFileSync(path.join(fixtureDir('cardrush'), 'list-page.html'), page); const { title, items, hasNext } = parseListPage(page, host); const soldOut = items.find((i) => i.soldOut); const kept = items.slice(0, keep); if (soldOut && !kept.includes(soldOut)) kept[kept.length - 1] = soldOut; const payload: CardrushPayload = { store: { host, categorySlug: store.categorySlug, franchise: store.franchise ?? null, brand: store.brand ?? null, bracket: store.bracket ?? 'rarity' }, listId, listName: title ?? store.lists[listId]!, page: 1, items: kept }; saveFixture('cardrush', name, { raw: { url, externalId: `${host}:${listId}:p1`, kind: 'listing', engine, fetchedAt: new Date(), payload }, expect: { minCount: 5, kinds: ['listing'], requiredFields: ['price', 'attributes.name'] }, note: `Live capture ${new Date().toISOString().slice(0, 10)} — ${url} via ${engine} (${note}; ${items.length} items, hasNext=${hasNext}, first ${kept.length} kept incl. a sold-out row when present)`, }); console.log(name, title, items.length, hasNext, kept[0]); } await capture('www.cardrush-mtg.jp', '100', 'mtg-list-page1', null, 'api', 16, 'Magic 4th Edition list: [COND]JP/EN name《language》【SET】'); await wait(4000); await capture('www.cardrush-op.jp', '4', 'one-piece-sealed-boxes', null, 'api', 8, 'One Piece sealed boxes (【未開封BOX】 → completeness sealed)'); await wait(4000); await capture('www.cardrush-op.jp', '17', 'one-piece-red-singles', null, 'api', 12, 'One Piece red singles with 【rarity】{number}'); const pk = '/tmp/cr_pokemon_list2.html'; if (existsSync(pk)) await capture('www.cardrush-pokemon.jp', '2', 'pokemon-scrapfly-page1', readFileSync(pk, 'utf8'), 'scrapfly', 12, 'Pokémon store behind Cloudflare, page captured through Scrapfly ASP (country jp, no JS); PSA-graded titles');