#!/usr/bin/env node /** * Card Database Population Script * * This script fetches cards from external APIs and adds them to your local database. * Run this to populate your database with popular cards from various games. */ const fetch = require('node-fetch'); // Configuration const API_BASE_URL = process.env.REACT_APP_API_URL || 'http://localhost:8000'; const AUTH_TOKEN = process.env.TCG_VAULT_TOKEN; // Set this in your environment // Popular cards to fetch (examples) const POPULAR_CARDS = { MTG: [ 'Lightning Bolt', 'Black Lotus', 'Counterspell', 'Dark Ritual', 'Brainstorm', 'Force of Will', 'Wasteland', 'Tarmogoyf', 'Snapcaster Mage', 'Jace, the Mind Sculptor' ], POKEMON: [ 'Pikachu', 'Charizard', 'Blastoise', 'Venusaur', 'Mewtwo', 'Lugia', 'Ho-Oh', 'Rayquaza', 'Garchomp', 'Lucario' ], YUGIOH: [ 'Blue-Eyes White Dragon', 'Dark Magician', 'Red-Eyes Black Dragon', 'Exodia the Forbidden One', 'Slifer the Sky Dragon', 'Obelisk the Tormentor', 'The Winged Dragon of Ra', 'Elemental HERO Neos', 'Stardust Dragon', 'Number 39: Utopia' ] }; // Rate limiting utility class RateLimiter { constructor(maxRequests, timeWindow) { this.maxRequests = maxRequests; this.timeWindow = timeWindow; this.requests = []; } async waitForSlot() { const now = Date.now(); this.requests = this.requests.filter(time => now - time < this.timeWindow); if (this.requests.length >= this.maxRequests) { const oldestRequest = this.requests[0]; const waitTime = this.timeWindow - (now - oldestRequest); await new Promise(resolve => setTimeout(resolve, waitTime)); } this.requests.push(now); } } const scryfallLimiter = new RateLimiter(10, 1000); // 10 requests per second const pokemonLimiter = new RateLimiter(5, 1000); // 5 requests per second // API Services const mtgService = { async getCardByName(name) { await scryfallLimiter.waitForSlot(); try { const response = await fetch( `https://api.scryfall.com/cards/named?fuzzy=${encodeURIComponent(name)}` ); if (!response.ok) { console.log(`❌ MTG card not found: ${name}`); return null; } const card = await response.json(); return { name: card.name, game: 'MTG', set_name: card.set_name, set_code: card.set, card_number: card.collector_number, rarity: card.rarity, mana_cost: card.mana_cost, cmc: card.cmc, card_type: card.type_line, colors: card.colors, oracle_text: card.oracle_text, power: card.power, toughness: card.toughness, image_url: card.image_uris?.normal, stock_image_url: card.image_uris?.normal, current_price: card.prices?.usd ? parseFloat(card.prices.usd) : null, market_price: card.prices?.usd_foil ? parseFloat(card.prices.usd_foil) : null, }; } catch (error) { console.error(`❌ Error fetching MTG card ${name}:`, error.message); return null; } } }; const pokemonService = { async getCardByName(name) { await pokemonLimiter.waitForSlot(); try { const response = await fetch( `https://api.pokemontcg.io/v2/cards?q=name:${encodeURIComponent(name)}&pageSize=1` ); if (!response.ok) { console.log(`❌ Pokémon card not found: ${name}`); return null; } const data = await response.json(); if (data.data.length === 0) { console.log(`❌ Pokémon card not found: ${name}`); return null; } const card = data.data[0]; return { name: card.name, game: 'POKEMON', set_name: card.set.name, set_code: card.set.id, card_number: card.number, rarity: card.rarity, mana_cost: card.convertedRetreatCost?.toString(), cmc: card.convertedRetreatCost, card_type: card.supertype, colors: card.types || [], oracle_text: card.attacks?.map(attack => `${attack.name}: ${attack.text}`).join('\n'), power: card.hp, toughness: null, image_url: card.images.small, stock_image_url: card.images.large, current_price: card.cardmarket?.prices?.averageSellPrice || null, market_price: card.cardmarket?.prices?.lowPrice || null, }; } catch (error) { console.error(`❌ Error fetching Pokémon card ${name}:`, error.message); return null; } } }; const yugiohService = { async getCardByName(name) { try { const response = await fetch( `https://db.ygoprodeck.com/api/v7/cardinfo.php?fname=${encodeURIComponent(name)}` ); if (!response.ok) { console.log(`❌ Yu-Gi-Oh! card not found: ${name}`); return null; } const data = await response.json(); if (data.data.length === 0) { console.log(`❌ Yu-Gi-Oh! card not found: ${name}`); return null; } const card = data.data[0]; return { name: card.name, game: 'YUGIOH', set_name: card.set_name, set_code: card.set_code, card_number: card.num, rarity: card.rarity, mana_cost: card.level?.toString(), cmc: card.level, card_type: card.type, colors: [card.attribute], oracle_text: card.desc, power: card.atk?.toString(), toughness: card.def?.toString(), image_url: card.card_images?.[0]?.image_url, stock_image_url: card.card_images?.[0]?.image_url_small, current_price: card.card_prices?.[0]?.amazon_price ? parseFloat(card.card_prices[0].amazon_price) : null, market_price: card.card_prices?.[0]?.cardmarket_price ? parseFloat(card.card_prices[0].cardmarket_price) : null, }; } catch (error) { console.error(`❌ Error fetching Yu-Gi-Oh! card ${name}:`, error.message); return null; } } }; // Database API const databaseAPI = { async addCard(cardData) { try { const response = await fetch(`${API_BASE_URL}/api/cards/find-or-create`, { method: 'POST', headers: { 'Content-Type': 'application/json', ...(AUTH_TOKEN && { 'Authorization': `Bearer ${AUTH_TOKEN}` }), }, body: JSON.stringify({ name: cardData.name, game: cardData.game, setName: cardData.set_name, setCode: cardData.set_code, rarity: cardData.rarity, cardType: cardData.card_type, manaCost: cardData.mana_cost, imageUrl: cardData.stock_image_url, }), }); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } const result = await response.json(); return result; } catch (error) { console.error(`❌ Error adding card to database:`, error.message); return null; } } }; // Main execution async function populateDatabase() { console.log('🚀 Starting card database population...\n'); let totalProcessed = 0; let totalAdded = 0; let totalErrors = 0; for (const [game, cards] of Object.entries(POPULAR_CARDS)) { console.log(`📦 Processing ${game} cards...`); for (const cardName of cards) { totalProcessed++; console.log(` 🔍 Fetching: ${cardName}`); let cardData = null; // Fetch card data based on game switch (game) { case 'MTG': cardData = await mtgService.getCardByName(cardName); break; case 'POKEMON': cardData = await pokemonService.getCardByName(cardName); break; case 'YUGIOH': cardData = await yugiohService.getCardByName(cardName); break; } if (cardData) { console.log(` ✅ Found: ${cardData.name} (${cardData.set_name})`); // Add to database const result = await databaseAPI.addCard(cardData); if (result && result.success) { console.log(` 💾 Added to database: ${cardData.name}`); totalAdded++; } else { console.log(` ⚠️ Already exists or failed to add: ${cardData.name}`); totalErrors++; } } else { console.log(` ❌ Not found: ${cardName}`); totalErrors++; } // Small delay between requests await new Promise(resolve => setTimeout(resolve, 100)); } console.log(''); } console.log('📊 Population Summary:'); console.log(` Total processed: ${totalProcessed}`); console.log(` Successfully added: ${totalAdded}`); console.log(` Errors/not found: ${totalErrors}`); console.log('\n✨ Database population complete!'); } // Run the script if (require.main === module) { populateDatabase().catch(console.error); } module.exports = { populateDatabase, mtgService, pokemonService, yugiohService, databaseAPI };