let currentUser = null; let currentChallenge = null; let authMethod = null; function escapeHtml(str) { const div = document.createElement('div'); div.textContent = str; return div.innerHTML; } // --- Matrix auth --- function goToMatrixLogin() { const slug = window.location.pathname.split('/').filter(Boolean)[0]; window.location.href = '/' + slug + '/matrix-login'; } function signOut() { sessionStorage.removeItem('matrix_session_token'); currentUser = null; authMethod = null; window.location.reload(); } function showNostrAuth() { document.getElementById('auth_method_select').style.display = 'none'; document.getElementById('nostr_auth_section').style.display = 'block'; } async function checkMatrixSession() { const sessionToken = sessionStorage.getItem('matrix_session_token'); if (!sessionToken) return false; try { const slug = window.location.pathname.split('/').filter(Boolean)[0]; const resp = await fetch('/' + slug + '/matrixVerify', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ session_token: sessionToken }) }); if (resp.ok) { const data = await resp.json(); currentUser = { displayName: data.display_name, sessionToken: sessionToken }; authMethod = 'matrix'; document.getElementById('auth_section').style.display = 'none'; document.getElementById('vote_section').style.display = 'block'; document.getElementById('logged_in_as').textContent = 'Signed in as: ' + data.display_name; loadSubmissions(); return true; } else { sessionStorage.removeItem('matrix_session_token'); } } catch (e) { sessionStorage.removeItem('matrix_session_token'); } return false; } // --- Nostr auth --- function hasNip07() { return typeof window.nostr !== 'undefined' && window.nostr !== null; } async function connectNip07() { if (!hasNip07()) { setAuthStatus('No Nostr browser extension detected. Enter your npub manually.', true); return; } try { const hexKey = await window.nostr.getPublicKey(); const resp = await fetch('encode_npub', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ hex: hexKey }) }); if (resp.ok) { const data = await resp.json(); document.getElementById('npub_input').value = data.npub; document.getElementById('nip07_badge').style.display = 'block'; setAuthStatus('Connected! Click "Sign In to Vote" to continue.'); } else { document.getElementById('npub_input').value = hexKey; setAuthStatus('Connected (using hex key). Click "Sign In to Vote".'); } } catch (err) { setAuthStatus('Error: ' + err.message, true); } } async function authenticate() { const npub = document.getElementById('npub_input').value.trim(); if (!npub) { setAuthStatus('Enter your npub or connect AlbyHub first.', true); return; } setAuthStatus('Requesting challenge...'); try { const resp = await fetch('challenge', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ npub: npub }) }); if (resp.status === 404) { setAuthStatus('Npub not linked. Please link your identity first.', true); return; } if (!resp.ok) { const text = await resp.text(); setAuthStatus('Error: ' + text, true); return; } const data = await resp.json(); currentChallenge = data.challenge; if (!hasNip07()) { setAuthStatus('No Nostr extension. Please install AlbyHub to sign.', true); return; } setAuthStatus('Please sign the challenge in your extension...'); const signedEvent = await window.nostr.signEvent({ created_at: Math.floor(Date.now() / 1000), kind: 1, tags: [], content: currentChallenge }); currentUser = { npub: npub, displayName: data.display_name, signedEvent: signedEvent }; authMethod = 'nostr'; document.getElementById('auth_section').style.display = 'none'; document.getElementById('vote_section').style.display = 'block'; document.getElementById('logged_in_as').textContent = 'Signed in as: ' + data.display_name; loadSubmissions(); } catch (err) { if (err.message && err.message.includes('denied')) { setAuthStatus('Signature request denied.', true); } else { setAuthStatus('Error: ' + err.message, true); } } } function setAuthStatus(msg, isError) { const el = document.getElementById('auth_status'); el.innerHTML = msg; el.className = isError ? 'error' : 'success'; } // --- Submissions --- async function loadSubmissions() { try { const response = await fetch('submissions'); const data = await response.json(); const submissionList = document.getElementById('suggestion_list'); submissionList.innerHTML = ''; if (data.Submissions && data.Submissions.length > 0) { data.Submissions.forEach((sub) => { const li = document.createElement('li'); li.innerHTML = ` ${escapeHtml(sub.submitter)} SUGGESTED : ${escapeHtml(sub.submission)} `; submissionList.appendChild(li); }); } else { submissionList.innerHTML = '
  • No submissions available
  • '; } } catch (error) { console.error('Error loading submissions:', error); document.getElementById('suggestion_list').innerHTML = '
  • Error loading submissions
  • '; } } // --- Voting --- async function submitVote() { if (!currentUser) { alert('Please sign in first'); return; } const selectedRadio = document.querySelector('input[name="vote"]:checked'); if (!selectedRadio) { alert('Please select a title to vote for'); return; } const voteData = { selected_submission: selectedRadio.value, submitter: selectedRadio.dataset.submitter, submission_time: parseInt(selectedRadio.dataset.time) }; try { if (authMethod === 'matrix') { const resp = await fetch('matrixVote', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ session_token: currentUser.sessionToken, ...voteData }) }); if (resp.ok) { alert('Vote submitted successfully!'); selectedRadio.checked = false; } else { const text = await resp.text(); alert('Error: ' + text); } } else if (authMethod === 'nostr') { const challengeResp = await fetch('challenge', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ npub: currentUser.npub }) }); if (!challengeResp.ok) { const text = await challengeResp.text(); alert('Error getting challenge: ' + text); return; } const challengeData = await challengeResp.json(); const challengeMsg = challengeData.challenge; const signedEvent = await window.nostr.signEvent({ created_at: Math.floor(Date.now() / 1000), kind: 1, tags: [], content: challengeMsg }); const voteResp = await fetch('verifyVote', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ npub: currentUser.npub, challenge: challengeMsg, ...voteData, signed_event: signedEvent }) }); if (voteResp.ok) { alert('Vote submitted successfully!'); selectedRadio.checked = false; } else { const text = await voteResp.text(); alert('Error: ' + text); } } } catch (error) { console.error('Error submitting vote:', error); alert('Error submitting vote: ' + error.message); } } // --- Init --- document.addEventListener('DOMContentLoaded', async () => { setInterval(() => { if (currentUser) loadSubmissions(); }, 30000); const matrixAuthed = await checkMatrixSession(); if (matrixAuthed) return; if (hasNip07()) { document.getElementById('nip07_badge').style.display = 'block'; } });