180 lines
No EOL
8.5 KiB
JavaScript
180 lines
No EOL
8.5 KiB
JavaScript
// 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);
|
|
|
|
}); |