// script.js // Copyright (c) 2025 snfsx.xyz // Licensed under the MIT License. See https://opensource.org/licenses/MIT document.addEventListener('DOMContentLoaded', () => { if ('scrollRestoration' in history) { history.scrollRestoration = 'manual'; } window.scrollTo(0, 0); // --- Copy TON Address --- const copyTonButton = document.getElementById('copy-ton-button'); const tonAddressElement = document.getElementById('ton-address'); if (copyTonButton && tonAddressElement) { copyTonButton.addEventListener('click', async () => { const address = (copyTonButton.dataset.address || tonAddressElement.textContent || '').trim(); if (!address) { console.error('Address to copy is empty.'); alert('Cannot copy: Address is empty.'); return; } let copied = false; // Use Modern Clipboard API (Requires HTTPS or localhost) if (navigator.clipboard && window.isSecureContext) { try { await navigator.clipboard.writeText(address); copied = true; console.log('Address copied successfully using Clipboard API.'); } catch (err) { console.error('navigator.clipboard.writeText failed:', err); // Don't use fallback, just inform the user } } else { console.warn('navigator.clipboard not available or context not secure (HTTPS/localhost required).'); } // Provide Feedback if (copied) { const originalText = copyTonButton.textContent; copyTonButton.textContent = 'Copied!'; copyTonButton.disabled = true; setTimeout(() => { copyTonButton.textContent = originalText; copyTonButton.disabled = false; }, 1500); } else { // Inform user if Clipboard API failed or wasn't available alert('Failed to copy address automatically. Please copy manually.\n(Requires HTTPS or localhost)'); } }); } else { if (!copyTonButton) console.warn("Element with ID 'copy-ton-button' not found."); if (!tonAddressElement) console.warn("Element with ID 'ton-address' not found."); } // --- Last.fm Now Playing --- const nowPlayingCover = document.getElementById('now-playing-cover'); const nowPlayingStatus = document.getElementById('now-playing-status'); const nowPlayingTitleLink = document.getElementById('now-playing-title-link'); const nowPlayingTitleText = document.getElementById('now-playing-title'); const nowPlayingArtist = document.getElementById('now-playing-artist'); const placeholderSvg = "data:image/svg+xml,%3Csvg width='80' height='80' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 80 80'%3E%3Crect width='100%25' height='100%25' fill='%231f3a3a'/%3E%3C/svg%3E"; let previousTrackId = null; let previousStatus = null; // 'playing', 'not_playing', 'error', 'rate_limited' if (!nowPlayingCover || !nowPlayingStatus || !nowPlayingTitleLink || !nowPlayingTitleText || !nowPlayingArtist) { console.error("One or more Last.fm elements are missing from the DOM."); return; } // Helper function to set the UI to an error/inactive state function setErrorState(message, statusIdentifier = 'error') { if (previousStatus !== statusIdentifier) { nowPlayingStatus.textContent = message; nowPlayingTitleText.textContent = ''; nowPlayingArtist.textContent = ''; if (nowPlayingCover.src !== placeholderSvg) { nowPlayingCover.src = placeholderSvg; } nowPlayingCover.alt = 'Error or inactive state'; nowPlayingCover.classList.add('error'); nowPlayingTitleLink.removeAttribute('href'); nowPlayingTitleLink.classList.add('inactive-link'); nowPlayingTitleLink.setAttribute('aria-disabled', 'true'); previousStatus = statusIdentifier; previousTrackId = null; } } function updateNowPlaying() { fetch('/api/v1/now_playing') .then(response => { if (response.status === 429) { return response.json().then(errorData => { const message = errorData?.message || "Too many requests. Please wait."; setErrorState(message, 'rate_limited'); throw new Error('Rate limit exceeded'); // Skip to .catch }).catch(jsonError => { console.error("Could not parse 429 response body:", jsonError); setErrorState("Too many requests. Please wait.", 'rate_limited'); throw new Error('Rate limit exceeded'); }); } if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return response.json(); }) .then(data => { let currentStatus; let currentTrackId = null; nowPlayingCover.classList.remove('error'); if (data.error) { console.error('API Logic Error:', data.error); currentStatus = 'error'; setErrorState(data.error || 'Error fetching data.'); } else if (data.playing && data.artist && data.name) { currentStatus = 'playing'; currentTrackId = `${data.artist}-${data.name}`; } else if (data.playing) { console.warn('API returned playing:true, but missing artist or name.'); currentStatus = 'error'; setErrorState('Incomplete track data received.'); } else { currentStatus = 'not_playing'; } // Update UI only if state or track changed if (currentStatus !== previousStatus || (currentStatus === 'playing' && currentTrackId !== previousTrackId)) { if (currentStatus === 'playing') { nowPlayingStatus.textContent = ''; nowPlayingTitleText.textContent = data.name; nowPlayingArtist.textContent = `by ${data.artist}`; nowPlayingTitleLink.href = `https://www.last.fm/music/${encodeURIComponent(data.artist)}/_/${encodeURIComponent(data.name)}`; nowPlayingTitleLink.classList.remove('inactive-link'); nowPlayingTitleLink.removeAttribute('aria-disabled'); const newCoverSrc = data.image_url || placeholderSvg; if (nowPlayingCover.src !== newCoverSrc) { nowPlayingCover.src = newCoverSrc; } nowPlayingCover.alt = data.image_url ? `Album cover for ${data.name} by ${data.artist}` : 'Album cover not available'; } else if (currentStatus === 'not_playing') { nowPlayingStatus.textContent = data.message || 'Not listening right now.'; nowPlayingTitleText.textContent = ''; nowPlayingArtist.textContent = ''; if (nowPlayingCover.src !== placeholderSvg) { nowPlayingCover.src = placeholderSvg; } nowPlayingCover.alt = 'Nothing playing'; nowPlayingTitleLink.removeAttribute('href'); nowPlayingTitleLink.classList.add('inactive-link'); nowPlayingTitleLink.setAttribute('aria-disabled', 'true'); } // Error states handled via setErrorState previousStatus = currentStatus; previousTrackId = currentTrackId; } }) .catch(error => { console.error('Fetch Error:', error); // Avoid setting generic error if it was the rate limit we already handled if (error.message !== 'Rate limit exceeded') { setErrorState('Could not connect or error occurred.'); } }); } updateNowPlaying(); setInterval(updateNowPlaying, 30000); });