document.addEventListener('DOMContentLoaded', init); const CONNECTION_TEST_INTERVAL = 2 * 60 * 1000; async function init() { try { const cfg = await browser.runtime.sendMessage({ type: 'GET_CONFIG' }); if (!cfg || !cfg.apiUrl || !cfg.apiKey) { showSetupRequired(); return; } document.getElementById('setup-required').classList.add('hidden'); document.getElementById('main-content').classList.remove('hidden'); setupEventListeners(); showPlatformsLoading(); testConnectionIfNeeded(); loadPlatformStatus().catch(e => showError(`Failed to load platforms: ${e.message}`)); } catch (e) { showSetupRequired(); const alert = document.querySelector('#setup-required .alert'); alert.textContent = ''; const s = document.createElement('strong'); s.textContent = 'Error'; const p = document.createElement('p'); p.textContent = e.message; alert.appendChild(s); alert.appendChild(p); alert.classList.add('alert-error'); } } function showSetupRequired() { document.getElementById('setup-required').classList.remove('hidden'); document.getElementById('main-content').classList.add('hidden'); document.getElementById('open-settings-btn').addEventListener('click', () => { browser.runtime.openOptionsPage(); }); } function showPlatformsLoading() { const c = document.getElementById('platforms-list'); c.textContent = ''; const d = document.createElement('div'); d.style.cssText = 'text-align:center;padding:18px;color:var(--on-surface-variant);'; d.textContent = 'Loading platforms…'; c.appendChild(d); } async function testConnectionIfNeeded() { const stored = await browser.storage.local.get(['lastConnectionTest', 'lastConnectionStatus']); const now = Date.now(); if (now - (stored.lastConnectionTest || 0) < CONNECTION_TEST_INTERVAL && stored.lastConnectionStatus !== undefined) { updateConnectionDot(stored.lastConnectionStatus); if (!stored.lastConnectionStatus) showError('Cannot connect to backend (cached).'); return; } const r = await browser.runtime.sendMessage({ type: 'TEST_CONNECTION' }); await browser.storage.local.set({ lastConnectionTest: now, lastConnectionStatus: r.connected }); updateConnectionDot(r.connected); if (!r.connected) showError(`Cannot connect to backend: ${r.error}`); } function updateConnectionDot(connected) { const d = document.getElementById('connection-status'); d.classList.toggle('connected', connected); d.title = connected ? 'Connected to FabledCurator' : 'Disconnected'; } async function loadPlatformStatus() { const status = await browser.runtime.sendMessage({ type: 'GET_PLATFORM_STATUS' }); const c = document.getElementById('platforms-list'); c.textContent = ''; for (const [key, platform] of Object.entries(PLATFORMS)) { c.appendChild(createPlatformCard(key, platform, status[key] || {})); } } function createPlatformCard(key, platform, status) { const card = document.createElement('div'); card.className = 'platform-card'; card.dataset.platform = key; const isTokenOnly = platform.authType === 'token' && !['discord', 'pixiv'].includes(key); const discordNeedsToken = key === 'discord' && !status.hasToken; if (isTokenOnly || discordNeedsToken) card.classList.add('disabled'); const icon = document.createElement('div'); icon.className = 'platform-icon'; icon.style.background = platform.color; icon.textContent = platform.name[0]; const info = document.createElement('div'); info.className = 'info'; const name = document.createElement('div'); name.className = 'name'; name.textContent = platform.name; const st = document.createElement('div'); st.className = `status ${statusClass(status, platform, key)}`; st.textContent = statusText(status, platform, key); info.appendChild(name); info.appendChild(st); const act = document.createElement('span'); act.className = 'action-icon'; act.textContent = '↥'; card.appendChild(icon); card.appendChild(info); card.appendChild(act); if (!isTokenOnly && !discordNeedsToken) { card.addEventListener('click', () => exportPlatformCookies(key, card)); } return card; } function statusText(s, platform, key) { if (key === 'discord') return s.hasToken ? 'Token captured — ready' : 'Open Discord to capture token'; if (key === 'pixiv') return s.hasToken ? 'Token captured — ready' : 'Click to authenticate via OAuth'; if (platform.authType === 'token') return 'Manual token entry required'; if (s.error) return 'Error checking cookies'; if (!s.hasCookies || !s.cookieCount) return 'No cookies — log in first'; return `${s.cookieCount} cookies ready`; } function statusClass(s, platform, key) { if (key === 'discord') return s.hasToken ? 'ready' : 'no-cookies'; if (key === 'pixiv') return s.hasToken ? 'ready' : 'no-cookies'; if (platform.authType === 'token') return 'no-cookies'; if (s.error) return 'error'; if (!s.hasCookies || !s.cookieCount) return 'no-cookies'; return 'ready'; } async function exportPlatformCookies(key, card) { card.classList.add('loading'); hideStatusMessage(); try { const r = await browser.runtime.sendMessage({ type: 'EXPORT_COOKIES', platform: key }); if (r.error) showError(r.error); else { const n = r.cookieCount ?? null; const verifiedSuffix = r.verified ? ' (verified ✓)' : ''; const msg = n !== null ? `${PLATFORMS[key].name}: ${n} cookies exported${verifiedSuffix}` : `${PLATFORMS[key].name}: token exported`; showSuccess(msg); await loadPlatformStatus(); } } catch (e) { showError(e.message); } finally { card.classList.remove('loading'); } } async function exportAllCookies() { const btn = document.getElementById('export-all-btn'); btn.disabled = true; btn.textContent = 'Exporting…'; hideStatusMessage(); try { const r = await browser.runtime.sendMessage({ type: 'EXPORT_ALL_COOKIES' }); const wins = Object.values(r).filter(x => x.success).length; const fails = Object.values(r).filter(x => !x.success && !x.skipped).length; if (wins && !fails) showSuccess(`Exported ${wins} platforms`); else if (wins) showWarning(`${wins} succeeded, ${fails} failed`); else if (fails) showError('All exports failed. Are you logged in?'); else showWarning('Nothing to export'); await loadPlatformStatus(); } catch (e) { showError(e.message); } finally { btn.disabled = false; btn.textContent = 'Export all platforms'; } } async function loadSources() { const c = document.getElementById('sources-list'); c.textContent = ''; const d = document.createElement('div'); d.style.cssText = 'text-align:center;padding:18px;color:var(--on-surface-variant);'; d.textContent = 'Loading sources…'; c.appendChild(d); const r = await browser.runtime.sendMessage({ type: 'LIST_SOURCES' }); c.textContent = ''; if (r.error) { const e = document.createElement('div'); e.style.cssText = 'padding:12px;color:var(--error);'; e.textContent = r.error; c.appendChild(e); return; } if (!r.sources || r.sources.length === 0) { const empty = document.createElement('div'); empty.style.cssText = 'text-align:center;padding:18px;color:var(--on-surface-variant);'; empty.textContent = 'No sources yet.'; c.appendChild(empty); return; } for (const src of r.sources) c.appendChild(createSourceRow(src)); } function createSourceRow(src) { const row = document.createElement('div'); row.className = 'source-row'; const info = document.createElement('div'); info.className = 'info'; const name = document.createElement('div'); name.className = 'name'; name.textContent = `${src.platform} · #${src.id}`; const url = document.createElement('div'); url.className = 'url'; url.textContent = src.url; info.appendChild(name); info.appendChild(url); const play = document.createElement('button'); play.className = 'play'; play.textContent = '▶'; play.title = 'Check now'; play.addEventListener('click', async () => { play.disabled = true; const r = await browser.runtime.sendMessage({ type: 'CHECK_SOURCE', sourceId: src.id }); play.disabled = false; if (r.error) showError(r.error); else showSuccess(`Triggered check for source #${src.id}`); }); row.appendChild(info); row.appendChild(play); return row; } function setupEventListeners() { document.getElementById('export-all-btn').addEventListener('click', exportAllCookies); document.getElementById('settings-btn').addEventListener('click', () => browser.runtime.openOptionsPage()); for (const tab of document.querySelectorAll('.tab')) { tab.addEventListener('click', () => { for (const t of document.querySelectorAll('.tab')) t.classList.remove('active'); tab.classList.add('active'); for (const p of document.querySelectorAll('.tab-panel')) p.classList.add('hidden'); document.getElementById(`tab-${tab.dataset.tab}`).classList.remove('hidden'); if (tab.dataset.tab === 'sources') loadSources(); }); } } function showSuccess(m) { showStatusMessage(m, 'success'); } function showError(m) { showStatusMessage(m, 'error'); } function showWarning(m) { showStatusMessage(m, 'warning'); } function showStatusMessage(text, kind) { const el = document.getElementById('status-message'); el.textContent = text; el.className = `status-message ${kind}`; el.classList.remove('hidden'); } function hideStatusMessage() { document.getElementById('status-message').classList.add('hidden'); }