2026-05-27 15:59:59 -04:00
|
|
|
import { sql } from '@vercel/postgres';
|
|
|
|
|
|
|
|
|
|
const EXCLUDED_MTG_SET_TYPES = new Set(['token', 'memorabilia', 'funny', 'treasure_chest']);
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Pure filter: Scryfall set objects not yet present in knownCodes (lowercase set codes).
|
|
|
|
|
*/
|
|
|
|
|
export function filterMissingMtgSets(scryfallSets, knownCodes, now = new Date()) {
|
|
|
|
|
const missing = [];
|
|
|
|
|
|
|
|
|
|
for (const set of scryfallSets) {
|
|
|
|
|
if (!set?.code || set.digital) continue;
|
|
|
|
|
if (!set.released_at) continue;
|
|
|
|
|
if (new Date(set.released_at) > now) continue;
|
|
|
|
|
if (EXCLUDED_MTG_SET_TYPES.has(set.set_type)) continue;
|
|
|
|
|
|
|
|
|
|
const code = set.code.toLowerCase();
|
|
|
|
|
if (knownCodes.has(code)) continue;
|
|
|
|
|
|
|
|
|
|
missing.push({
|
|
|
|
|
code: set.code,
|
|
|
|
|
name: set.name,
|
|
|
|
|
releasedAt: set.released_at,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-27 22:49:58 -04:00
|
|
|
missing.sort((a, b) => b.releasedAt.localeCompare(a.releasedAt));
|
2026-05-27 15:59:59 -04:00
|
|
|
return missing;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Pure filter: Pokémon TCG set objects not yet present in knownCodes (lowercase set ids).
|
|
|
|
|
*/
|
|
|
|
|
export function filterMissingPokemonSets(pokemonSets, knownCodes) {
|
|
|
|
|
const missing = [];
|
|
|
|
|
|
|
|
|
|
for (const set of pokemonSets) {
|
|
|
|
|
if (!set?.id) continue;
|
|
|
|
|
const code = set.id.toLowerCase();
|
|
|
|
|
if (knownCodes.has(code)) continue;
|
|
|
|
|
|
|
|
|
|
missing.push({
|
|
|
|
|
id: set.id,
|
|
|
|
|
name: set.name,
|
|
|
|
|
releasedAt: set.releaseDate || null,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-27 22:49:58 -04:00
|
|
|
missing.sort((a, b) => {
|
|
|
|
|
const dateA = a.releasedAt || '';
|
|
|
|
|
const dateB = b.releasedAt || '';
|
|
|
|
|
if (!dateA && !dateB) return 0;
|
|
|
|
|
if (!dateA) return 1;
|
|
|
|
|
if (!dateB) return -1;
|
|
|
|
|
return dateB.localeCompare(dateA);
|
|
|
|
|
});
|
2026-05-27 15:59:59 -04:00
|
|
|
return missing;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function getKnownMtgSetCodes() {
|
|
|
|
|
const { rows } = await sql`
|
|
|
|
|
SELECT DISTINCT LOWER(set_code) AS set_code
|
|
|
|
|
FROM cards
|
|
|
|
|
WHERE game = 'MTG' AND set_code IS NOT NULL
|
|
|
|
|
`;
|
|
|
|
|
return new Set(rows.map((row) => row.set_code));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function getKnownPokemonSetCodes() {
|
|
|
|
|
const { rows } = await sql`
|
|
|
|
|
SELECT DISTINCT LOWER(set_code) AS set_code
|
|
|
|
|
FROM cards
|
|
|
|
|
WHERE game = 'Pokemon' AND set_code IS NOT NULL
|
|
|
|
|
`;
|
|
|
|
|
return new Set(rows.map((row) => row.set_code));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function discoverMissingMtgSets() {
|
|
|
|
|
const response = await fetch('https://api.scryfall.com/sets');
|
|
|
|
|
if (!response.ok) {
|
|
|
|
|
throw new Error(`Scryfall sets API error: ${response.status}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const data = await response.json();
|
|
|
|
|
const knownCodes = await getKnownMtgSetCodes();
|
|
|
|
|
return filterMissingMtgSets(data.data || [], knownCodes);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function discoverMissingPokemonSets() {
|
|
|
|
|
const { fetchWithRetry, pokemonHeaders } = await import('./pokemon.js');
|
|
|
|
|
const response = await fetchWithRetry('https://api.pokemontcg.io/v2/sets');
|
|
|
|
|
const data = await response.json();
|
|
|
|
|
const knownCodes = await getKnownPokemonSetCodes();
|
|
|
|
|
return filterMissingPokemonSets(data.data || [], knownCodes);
|
|
|
|
|
}
|