#!/usr/bin/env node import fs from 'node:fs/promises'; import path from 'node:path'; const ICON_BASE = (process.env.ICON_BASE || 'https://exil.es/icons/spells/').replace(/\/?$/, '/'); const dataDir = path.resolve('./talent-builder/public/data'); function toFilename(raw) { const s = (raw || '').trim().replace(/\\/g, '/'); const base = s.split('/').pop().replace(/\.(blp|png|jpg|jpeg)$/i, ''); return base.toLowerCase() + '.png'; } const files = await fs.readdir(dataDir); const jsons = files.filter(f => f.endsWith('.json') && !f.includes('.invalid.')); const icons = new Set(); for (const f of jsons) { try { const j = JSON.parse(await fs.readFile(path.join(dataDir, f), 'utf8')); for (const tab of Object.values(j)) { for (const t of tab.talents || []) icons.add(toFilename(t.iconTexture)); } } catch (e) { console.error(`Skipping ${f}: ${e.message}`); } } let missing = []; let ok = 0; let foundInItems = 0; for (const icon of icons) { const url = ICON_BASE + icon; try { const r = await fetch(url, { method: 'HEAD' }); if (!r.ok) { // Try alternative path for inv_ icons if (icon.startsWith('inv_')) { const altUrl = 'https://exil.es/icons/items/' + icon; const r2 = await fetch(altUrl, { method: 'HEAD' }); if (r2.ok) { foundInItems++; ok++; } else { missing.push({ icon, status: r.status }); } } else { missing.push({ icon, status: r.status }); } } else { ok++; } } catch (e) { missing.push({ icon, error: String(e) }); } } console.log('Icon base:', ICON_BASE); console.log('Alternative path for inv_* icons: https://exil.es/icons/items/'); console.log('Total unique icons:', icons.size); console.log('Available in primary path:', ok - foundInItems); console.log('Available in items path:', foundInItems); console.log('Total available:', ok); console.log('Missing:', missing.length); if (missing.length) { console.log('\nMissing list:'); for (const m of missing) console.log(m.icon, m.status || m.error || 'ERR'); }