SPB Git forge

spb/rareindex

Public
54commits 1branches 0releases
7.1 MBsize
maindefault branch
10 days agolast push
TypeScript 61.9% HTML 37.2% SQL 0.7%
3.3 KB · 69 lines typescript
Raw Blame History
1/**2 * Helpers shared by the TCG catalog/price connectors added in wave 3 (tcgcsv, digimoncard, swu-db,3 * grand-archive, sorcery-tcg, mtggoldfish, pokemonprice). Kept inside connectors/api (not the framework).4 */56export const UA_HEADERS = { 'user-agent': 'RareIndexBot/0.1 (+https://www.rareindex.io/about/data)', accept: 'application/json, text/html;q=0.9, */*;q=0.8' };78/** UTC midnight of a Date (observation dates are day-precise). */9export function dayOf(d: Date): Date {10  return new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate()));11}1213/** "2026-09-06T20:05:30+0000" | "2026-09-06" → UTC midnight Date, else null. */14export function isoDay(s: string | null | undefined): Date | null {15  if (!s) return null;16  const m = s.match(/^(\d{4})-(\d{2})-(\d{2})/);17  if (!m) return null;18  const d = new Date(Date.UTC(Number(m[1]), Number(m[2]) - 1, Number(m[3])));19  return Number.isNaN(d.getTime()) ? null : d;20}2122/**23 * TCGplayer "subTypeName" / finish label → RareIndex variant vocabulary shared with the API catalogs24 * (pokemontcg: 'Holo', 'Reverse Holo', '1st Edition', '1st Edition Holo'; scryfall: 'Foil', 'Etched Foil').25 * Returns null for the base printing.26 */27export function variantFromSubType(subType: string | null | undefined): string | null {28  if (!subType) return null;29  const s = subType.trim().toLowerCase();30  if (!s || s === 'normal' || s === 'regular' || s === 'standard' || s === 'non-foil' || s === 'nonfoil' || s === 'unlimited') return null;31  if (s === 'holofoil' || s === 'holo') return 'Holo';32  if (s === 'reverse holofoil' || s === 'reverse holo') return 'Reverse Holo';33  if (s === '1st edition holofoil' || s === '1st edition holo') return '1st Edition Holo';34  if (s === '1st edition normal' || s === '1st edition') return '1st Edition';35  if (s === 'unlimited holofoil') return 'Holo';36  if (s === 'foil') return 'Foil';37  if (s === 'etched foil' || s === 'foil etched') return 'Etched Foil';38  // Title-case anything else ("Hyperspace Foil", "Showcase", "Textured Foil")39  return subType.trim().replace(/\s+/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase());40}4142/** "PSA9" | "PSA 10" | "BGS9.5" | "CGC 9.8" | "Raw" → grader/grade. */43export function parseCompactGrade(label: string | null | undefined): { grader: string | null; grade: string | null } {44  if (!label) return { grader: null, grade: null };45  const s = label.trim();46  if (/^(raw|ungraded)$/i.test(s)) return { grader: 'raw', grade: null };47  const m = s.match(/^(PSA|BGS|CGC|SGC|TAG|ACE|CBCS)\s*(\d{1,2}(?:\.\d)?)$/i);48  if (!m) return { grader: null, grade: null };49  return { grader: m[1]!.toLowerCase(), grade: m[2]! };50}5152/** "101/102" → { number: "101", total: 102 }; "SV049" → { number: "SV049", total: null } */53export function splitNumber(s: string | null | undefined): { number: string | null; total: number | null } {54  if (!s) return { number: null, total: null };55  const t = s.trim();56  const m = t.match(/^([A-Za-z0-9-]+)\s*\/\s*([A-Za-z0-9-]+)$/);57  if (m) {58    const total = Number(m[2]);59    return { number: m[1]!, total: Number.isFinite(total) ? total : null };60  }61  return { number: t || null, total: null };62}6364/** Upscale a TCGplayer CDN thumbnail (…_200w.jpg → …_400w.jpg). */65export function tcgplayerImage(url: string | null | undefined): string[] {66  if (!url) return [];67  return [url.replace(/_200w\.jpg$/, '_400w.jpg')];68}69