/** * Live fixture capture for limitless-tcg: one English set list page (trimmed to 12 rows), one Japanese * set list page (no prices), and one card price-history payload. * Usage: pnpm tsx connectors/api/limitless-tcg/_capture.ts */ import { saveFixture } from '@rareindex/connectors/testing'; import { parseCardPage, parseSetIndex, parseSetList, type LimitlessPayload } from './index.js'; import { HTML_HEADERS, JSON_HEADERS } from '../_g1-cards-eu-jp-lib/index.js'; const SITE = 'https://limitlesstcg.com'; const text = async (u: string) => (await fetch(u, { headers: HTML_HEADERS })).text(); const wait = (ms: number) => new Promise((r) => setTimeout(r, ms)); const en = parseSetIndex(await text(`${SITE}/cards`), 'en'); const meg = en.find((s) => s.code === 'MEG') ?? en[0]!; await wait(1500); const enList = parseSetList(await text(`${SITE}/cards/${meg.code}?display=list`)); const enPayload: LimitlessPayload = { type: 'set', set: { ...meg, name: enList.setName ?? meg.name }, cards: enList.cards.slice(0, 12) }; saveFixture('limitless-tcg', 'en-set-list', { raw: { url: `${SITE}/cards/${meg.code}?display=list`, externalId: `set:en:${meg.code}`, kind: 'catalog_item', engine: 'api', fetchedAt: new Date(), payload: enPayload }, expect: { minCount: 12, kinds: ['catalog_item', 'price_observation'], requiredFields: ['attributes.set', 'attributes.number', 'attributes.identifiers.limitless_card'] }, note: `Live capture ${new Date().toISOString().slice(0, 10)} — ${SITE}/cards/${meg.code}?display=list, first 12 of ${enList.cards.length} rows (USD via TCGplayer, EUR via Cardmarket relayed)`, }); console.log('en', meg, enList.cards.length, enList.cards[0]); await wait(1500); const jp = parseSetIndex(await text(`${SITE}/cards/jp`), 'jp'); const jset = jp.find((s) => s.code === 'M6') ?? jp[0]!; await wait(1500); const jpList = parseSetList(await text(`${SITE}/cards/jp/${jset.code}?display=list`)); const jpPayload: LimitlessPayload = { type: 'set', set: { ...jset, name: jpList.setName ?? jset.name }, cards: jpList.cards.slice(0, 8) }; saveFixture('limitless-tcg', 'jp-set-list-no-prices', { raw: { url: `${SITE}/cards/jp/${jset.code}?display=list`, externalId: `set:jp:${jset.code}`, kind: 'catalog_item', engine: 'api', fetchedAt: new Date(), payload: jpPayload }, expect: { count: 8, kinds: ['catalog_item'], requiredFields: ['attributes.set', 'attributes.number'] }, note: `Live capture ${new Date().toISOString().slice(0, 10)} — Japanese set ${jset.code}: list rows have empty USD/EUR cells, so catalog items only`, }); console.log('jp', jset, jpList.cards.length, jpList.cards[0]); await wait(1500); const card = enList.cards[0]!; const cardHtml = await text(card.url); const cp = parseCardPage(cardHtml); await wait(1500); const hist = (await (await fetch(`${SITE}/api/cards/${cp.cardId}/prices`, { headers: { ...JSON_HEADERS, referer: card.url } })).json()) as { tcgplayer: [number, number][]; cardmarket: [number, number][] }; const histPayload: LimitlessPayload = { type: 'history', set: { ...meg, name: enList.setName ?? meg.name }, card: { number: card.number, name: cp.name ?? card.name, url: card.url, cardId: cp.cardId!, rarity: cp.rarity ?? card.rarity, image: cp.image ?? card.image, tcgplayerId: cp.tcgplayerId ?? card.usd?.tcgplayerId ?? null }, history: { tcgplayer: hist.tcgplayer.slice(-20), cardmarket: hist.cardmarket.slice(-20) }, }; saveFixture('limitless-tcg', 'card-price-history', { raw: { url: card.url, externalId: `history:en:${meg.code}/${card.number}`, kind: 'price_observation', engine: 'api', fetchedAt: new Date(), payload: histPayload }, expect: { minCount: 10, kinds: ['price_observation'], requiredFields: ['observationDate', 'attributes.number'] }, note: `Live capture ${new Date().toISOString().slice(0, 10)} — ${SITE}/api/cards/${cp.cardId}/prices (last 20 points per provider; values are cents, timestamps epoch ms)`, }); console.log('history', cp, hist.tcgplayer.length, hist.cardmarket.length);