Replace pokemontcg.io API discovery and import with raw JSON from PokemonTCG/pokemon-tcg-data; format collector numbers as number/printedTotal and drop the API key dependency for catalog sync. Co-authored-by: Cursor <cursoragent@cursor.com>
111 lines
3.3 KiB
JavaScript
111 lines
3.3 KiB
JavaScript
const DEFAULT_POKEMON_TCG_DATA_BASE =
|
|
'https://raw.githubusercontent.com/PokemonTCG/pokemon-tcg-data/master';
|
|
|
|
export function getPokemonTcgDataBaseUrl() {
|
|
return process.env.POKEMON_TCG_DATA_BASE_URL || DEFAULT_POKEMON_TCG_DATA_BASE;
|
|
}
|
|
|
|
export function pokemonSetsUrl() {
|
|
return `${getPokemonTcgDataBaseUrl()}/sets/en.json`;
|
|
}
|
|
|
|
export function pokemonSetCardsUrl(setCode) {
|
|
return `${getPokemonTcgDataBaseUrl()}/cards/en/${encodeURIComponent(setCode)}.json`;
|
|
}
|
|
|
|
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
|
|
function githubHeaders() {
|
|
return {
|
|
'User-Agent': 'Deck-Hearth/1.0',
|
|
Accept: 'application/json',
|
|
};
|
|
}
|
|
|
|
export async function fetchWithRetry(url, maxRetries = 3, delayMs = 2000) {
|
|
for (let attempt = 1; attempt <= maxRetries; attempt += 1) {
|
|
try {
|
|
const response = await fetch(url, { headers: githubHeaders() });
|
|
|
|
if (response.ok) {
|
|
return response;
|
|
}
|
|
|
|
if (response.status === 404) {
|
|
throw new Error(`Resource not found: ${response.status}`);
|
|
}
|
|
|
|
if (response.status === 504 || response.status === 503 || response.status === 429) {
|
|
console.log(
|
|
`[fetchWithRetry] Attempt ${attempt}: ${response.status}, retrying in ${delayMs * attempt}ms`
|
|
);
|
|
await delay(delayMs * attempt);
|
|
continue;
|
|
}
|
|
|
|
throw new Error(`Pokemon TCG data fetch error: ${response.status}`);
|
|
} catch (error) {
|
|
if (attempt === maxRetries) {
|
|
throw error;
|
|
}
|
|
console.log(`[fetchWithRetry] Attempt ${attempt} failed:`, error.message);
|
|
await delay(delayMs * attempt);
|
|
}
|
|
}
|
|
|
|
throw new Error('fetchWithRetry exhausted retries');
|
|
}
|
|
|
|
export async function fetchPokemonSets() {
|
|
const response = await fetchWithRetry(pokemonSetsUrl());
|
|
const sets = await response.json();
|
|
return Array.isArray(sets) ? sets : [];
|
|
}
|
|
|
|
export async function fetchPokemonSetCards(setCode) {
|
|
try {
|
|
const response = await fetchWithRetry(pokemonSetCardsUrl(setCode));
|
|
const cards = await response.json();
|
|
return Array.isArray(cards) ? cards : [];
|
|
} catch (error) {
|
|
if (error.message.includes('404')) {
|
|
console.warn(`[fetchPokemonSetCards] No card file for set ${setCode} in pokemon-tcg-data`);
|
|
return [];
|
|
}
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
export function normalizePokemonRarity(rarity) {
|
|
const value = rarity || 'Unknown';
|
|
if (value.includes('Holo')) return 'Holographic';
|
|
if (value.includes('Secret')) return 'Secret Rare';
|
|
if (value.includes('Ultra')) return 'Ultra Rare';
|
|
return value;
|
|
}
|
|
|
|
/**
|
|
* Map a pokemon-tcg-data card JSON object into DB insert fields.
|
|
*/
|
|
export function mapGithubCardForInsert(card, setMeta) {
|
|
const setCode = setMeta?.id || null;
|
|
const setName = setMeta?.name || setCode;
|
|
const printedTotal = setMeta?.printedTotal || setMeta?.total || null;
|
|
const cardNumber =
|
|
card.number && printedTotal ? `${card.number}/${printedTotal}` : card.number || null;
|
|
|
|
return {
|
|
externalId: card.id,
|
|
name: card.name,
|
|
setName,
|
|
setCode,
|
|
cardNumber,
|
|
rarity: normalizePokemonRarity(card.rarity),
|
|
cardType: card.supertype || 'Pokemon',
|
|
types: card.types || [],
|
|
flavorText: card.flavorText || null,
|
|
hp: card.hp || card.attacks?.[0]?.damage || null,
|
|
imageSmall: card.images?.small || null,
|
|
imageLarge: card.images?.large || null,
|
|
};
|
|
}
|