require("dotenv").config(); const fs = require("fs"); const path = require("path"); const { MongoClient } = require("mongodb"); const DB_FILE = path.join(__dirname, "db.json"); const CSV_FILE = path.join(__dirname, "characters.csv"); // Helper to parse a CSV line, respecting double quotes function parseCSVLine(line) { const result = []; let current = ""; let inQuotes = false; for (let i = 0; i < line.length; i++) { const char = line[i]; if (char === '"') { inQuotes = !inQuotes; } else if (char === "," && !inQuotes) { result.push(current.trim()); current = ""; } else { current += char; } } result.push(current.trim()); return result; } // Generate template if CSV does not exist if (!fs.existsSync(CSV_FILE)) { const templateContent = `Name,Base price,features,Image URL 1,Caption 1,Image URL 2,Caption 2,Image URL 3,CAption 3,Category Goku,100,"Series: Dragon Ball; Rarity: SSR",AgACAg...,Goku Super Saiyan,AgACAg...,Goku Base Form,,,Anime Naruto,80,"Series: Naruto; Rarity: SR",AgACAg...,Naruto Sage Mode,,,,,,Anime Luffy,90,"Series: One Piece; Rarity: UR",AgACAg...,Luffy Gear 5,,,,,,Anime `; fs.writeFileSync(CSV_FILE, templateContent, "utf8"); console.log("šŸ“ No 'characters.csv' file was found."); console.log("āœ… Created a sample 'characters.csv' with the requested columns!"); console.log("ā„¹ļø Open 'characters.csv' in Excel/Google Sheets, add your characters, and run this script again."); process.exit(0); } const fileContent = fs.readFileSync(CSV_FILE, "utf8"); const lines = fileContent.split(/\r?\n/).filter((line) => line.trim() !== ""); if (lines.length <= 1) { console.log("āš ļø The 'characters.csv' file is empty or only contains the header row."); process.exit(0); } // Skip header const header = lines[0]; const dataLines = lines.slice(1); let successCount = 0; let failCount = 0; const errors = []; const importedChars = []; dataLines.forEach((line, index) => { const lineNum = index + 2; // 1-indexed plus header line offset const parts = parseCSVLine(line); // We expect at least the first 3 columns if (parts.length < 3) { failCount++; errors.push(`Line ${lineNum}: Missing core columns (Expected Name, Base price, features)`); return; } const name = parts[0]; const basePriceVal = parts[1]; const featuresRaw = parts[2]; const img1 = parts[3]; const cap1 = parts[4]; const img2 = parts[5]; const cap2 = parts[6]; const img3 = parts[7]; const cap3 = parts[8]; const category = parts[9]; if (!name) { failCount++; errors.push(`Line ${lineNum}: Character Name is required.`); return; } const base_price = parseInt(basePriceVal, 10); if (isNaN(base_price) || base_price < 0) { failCount++; errors.push(`Line ${lineNum}: Invalid Base Price '${basePriceVal}'. Must be a non-negative number.`); return; } // Parse features (separated by ;) const features = featuresRaw ? featuresRaw .split(";") .map((f) => f.trim()) .filter((f) => f.length > 0) : []; // Build image_url list const image_url = []; if (img1) { if (cap1) { image_url.push({ file_id: img1, caption: cap1 }); } else { image_url.push(img1); } } if (img2) { if (cap2) { image_url.push({ file_id: img2, caption: cap2 }); } else { image_url.push(img2); } } if (img3) { if (cap3) { image_url.push({ file_id: img3, caption: cap3 }); } else { image_url.push(img3); } } // Generate unique character ID const id = `custom_${Date.now()}_${Math.floor(1000 + Math.random() * 9000)}`; importedChars.push({ id, name, image_url, base_price, features, category: category || "", addedBy: "bulk_import" }); successCount++; }); async function saveImportedData() { if (successCount === 0) { console.log(`\nāŒ Bulk Import Failed.`); return; } const mongoUri = process.env.MONGODB_URI; if (mongoUri) { console.log("\nšŸ”Œ Connecting to MongoDB for import..."); let client; try { client = new MongoClient(mongoUri); await client.connect(); const db = client.db(); const collection = db.collection("bot_state"); const record = await collection.findOne({ _id: "state" }); let currentDB = record ? record.data : { customCharacters: [] }; currentDB.customCharacters = currentDB.customCharacters || []; currentDB.customCharacters.push(...importedChars); await collection.updateOne( { _id: "state" }, { $set: { data: currentDB } }, { upsert: true } ); console.log(`\nšŸŽ‰ Bulk Import Complete (MongoDB Atlas)!`); console.log(`āœ… Successfully imported *${successCount}* characters.`); } catch (err) { console.error("āŒ Failed to save changes to MongoDB:", err.message || err); process.exit(1); } finally { if (client) await client.close(); } } else { // Fallback to local db.json console.log("\nšŸ’¾ Saving to local db.json..."); let localDB = { customCharacters: [] }; if (fs.existsSync(DB_FILE)) { try { localDB = JSON.parse(fs.readFileSync(DB_FILE, "utf8")); } catch (err) { console.error("āŒ Failed to parse local db.json:", err); process.exit(1); } } localDB.customCharacters = localDB.customCharacters || []; localDB.customCharacters.push(...importedChars); try { fs.writeFileSync(DB_FILE, JSON.stringify(localDB, null, 2), "utf8"); console.log(`\nšŸŽ‰ Bulk Import Complete (local file)!`); console.log(`āœ… Successfully imported *${successCount}* characters.`); } catch (err) { console.error("āŒ Failed to save changes to db.json:", err); process.exit(1); } } if (failCount > 0) { console.log(`āš ļø Failed to import *${failCount}* rows due to formatting/validation errors:`); errors.forEach((err) => console.log(` - ${err}`)); } } saveImportedData();