/** * Live capture + smoke for the wave-4 connectors (mtgjson, cardkingdom, myslabs, tag-pop, comc, alt-xyz). * Usage: pnpm tsx connectors/api/_lib/capture-s.ts [ids…] [--no-save] */ import { createCrawlContext, createRouter, type RareIndexConnector, type RawRecordInput } from '@rareindex/connectors'; import { localMeta } from './local-meta.js'; import { saveFixture } from '@rareindex/connectors/testing'; import { childLogger } from '@rareindex/shared'; interface Plan { dir: string; limit: number; seeds?: string[]; pick: (raw: RawRecordInput, i: number) => string | null; kinds: string[]; requiredFields: string[]; } const picked = new Set(); const PLANS: Record = { mtgjson: { dir: 'api', limit: 320, seeds: ['LEA'], pick: (raw, i) => { const p = raw.payload as { card: { name: string }; prices: unknown }; if (p.card.name === 'Black Lotus') return 'lea-black-lotus'; if (i === 0) return 'lea-first'; return null; }, kinds: ['catalog_item', 'price_observation'], requiredFields: ['attributes.identifiers.scryfall_id', 'attributes.setCode'], }, cardkingdom: { dir: 'api', limit: 6, pick: (raw, i) => (i === 0 ? 'single-first' : i === 3 ? 'single-fourth' : null), kinds: ['catalog_item', 'price_observation'], requiredFields: ['attributes.identifiers.cardkingdom_id', 'attributes.set'] }, myslabs: { dir: 'api', limit: 6, pick: (raw, i) => (i === 0 ? 'slab-first' : i === 2 ? 'slab-third' : i === 5 ? 'slab-sixth' : null), kinds: ['listing'], requiredFields: ['attributes.identifiers.myslabs_id', 'price'] }, 'tag-pop': { dir: 'firecrawl', limit: 1, seeds: ['https://my.taggrading.com/pop-report/Pokemon/1999/WOTC%20Pok%C3%A9mon?setName=Base%20Set'], pick: () => 'pokemon-1999-base-set', kinds: ['population_report'], requiredFields: ['attributes.set', 'attributes.number'] }, comc: { dir: 'firecrawl', limit: 1, seeds: ['Cards/Basketball/1986/Fleer'], pick: () => 'basketball-1986-fleer-p1', kinds: ['listing'], requiredFields: ['attributes.set', 'price'] }, 'alt-xyz': { dir: 'firecrawl', limit: 8, pick: (raw) => { const p = raw.payload as { transactions: unknown[]; altValue: number | null; listPrice: number | null }; if (p.transactions.length && !picked.has('item-with-transactions')) return 'item-with-transactions'; if ((p.altValue || p.listPrice) && !picked.has('item-priced')) return 'item-priced'; return null; }, kinds: ['sale', 'price_observation', 'listing'], requiredFields: ['attributes.identifiers.alt_item_id'], }, }; const args = process.argv.slice(2); const noSave = args.includes('--no-save'); const ids = args.filter((a) => !a.startsWith('--')); const targets = ids.length ? ids : Object.keys(PLANS); const router = createRouter({ firecrawlApiKey: process.env.FIRECRAWL_API_KEY, scrapflyApiKey: process.env.SCRAPFLY_API_KEY }); for (const id of targets) { const plan = PLANS[id]; if (!plan) { console.log(`[${id}] no plan`); continue; } const started = Date.now(); const mod = (await import(`../../${plan.dir}/${id}/index.ts`)) as { default: (m: ReturnType) => RareIndexConnector }; const metaJson = (await import(`../../${plan.dir}/${id}/meta.json`, { with: { type: 'json' } })) as { default: unknown }; const connector = mod.default(localMeta(metaJson.default)); const ctx = createCrawlContext({ router, meta: connector.meta, options: { mode: 'probe', limit: plan.limit, ...(plan.seeds ? { seeds: plan.seeds } : {}) }, log: childLogger({ connector: id, level: 'warn' }) }); let raws = 0; let normalized = 0; let printed = 0; let saved = 0; const kinds: Record = {}; try { for await (const raw of connector.crawl(ctx)) { const rawLike = { ...raw, externalId: raw.externalId ?? null, fetchedAt: raw.fetchedAt ?? new Date() }; const out = await connector.normalize(rawLike); normalized += out.length; for (const r of out) kinds[r.kind] = (kinds[r.kind] ?? 0) + 1; const name = plan.pick(raw, raws); raws++; if (name && !noSave) { picked.add(name); saveFixture(id, name, { raw: rawLike, expect: { minCount: 1, kinds: plan.kinds, requiredFields: plan.requiredFields }, note: `Live capture ${new Date().toISOString().slice(0, 10)} from ${raw.url}` }); saved++; } if (printed < 3 && out.length) { printed++; const r = out.find((x) => x.kind === 'sale') ?? out.find((x) => x.kind === 'price_observation') ?? out.find((x) => x.kind === 'population_report') ?? out[0]!; const s = r as unknown as Record; const attrs = (s.attributes ?? {}) as Record; console.log(`[${id}] ${r.kind} · ${String(s.rawTitle ?? attrs.name ?? '')} · ${String(attrs.categorySlug)} · set=${String(attrs.set)} #${String(attrs.number)} · grade=${JSON.stringify(s.grade ?? null)} · price=${String(s.price ?? s.total ?? '')} ${String(s.currency ?? '')} · date=${String(s.saleDate ?? s.observationDate ?? s.reportDate ?? '')}`); } } } catch (err) { console.log(`[${id}] crawl error: ${err instanceof Error ? err.message : String(err)}`); } console.log(`[${id}] raw=${raws} normalized=${normalized} kinds=${JSON.stringify(kinds)} saved=${saved} anomalies=${ctx.anomalies.length} engines=${JSON.stringify(ctx.engineStats)} ms=${Date.now() - started}`); if (ctx.anomalies.length) console.log(`[${id}] anomalies: ${ctx.anomalies.slice(0, 5).join(' ; ')}`); }