Updated script.js

This commit is contained in:
snfsx 2025-05-05 07:09:00 +00:00
parent bf08fbfc04
commit 739822883a
Signed by: snfsx
GPG key ID: B663DED13303E929

View file

@ -7,79 +7,15 @@ document.addEventListener('DOMContentLoaded', () => {
if ('scrollRestoration' in history) { if ('scrollRestoration' in history) {
history.scrollRestoration = 'manual'; history.scrollRestoration = 'manual';
} }
// Прокручиваем в самый верх
window.scrollTo(0, 0); window.scrollTo(0, 0);
// --- Copy TON Address --- // --- Copy TON Address ---
const copyTonButton = document.getElementById('copy-ton-button'); const copyTonButton = document.getElementById('copy-ton-button');
const tonAddressElement = document.getElementById('ton-address'); const tonAddressElement = document.getElementById('ton-address');
// --- Helper Function for execCommand Fallback ---
function copyUsingExecCommand(textToCopy) {
let success = false;
// Create a temporary textarea element
const textArea = document.createElement('textarea');
textArea.value = textToCopy;
// Make it non-editable to avoid focus issues
textArea.setAttribute('readonly', '');
// ** IMPORTANT ** Style to make it invisible but selectable
// Needs to be in the DOM and selectable to work
textArea.style.position = 'absolute';
textArea.style.left = '-9999px'; // Move off-screen
textArea.style.top = '0';
textArea.style.opacity = '0'; // Hide visually
textArea.style.pointerEvents = 'none'; // Prevent interaction
document.body.appendChild(textArea);
// Store original selection and focus
const currentFocus = document.activeElement;
const selectedRange = document.getSelection().rangeCount > 0
? document.getSelection().getRangeAt(0)
: false;
try {
// Select text inside the textarea
textArea.select();
// textArea.setSelectionRange(0, 99999); // For mobile potentially
// Attempt to copy
success = document.execCommand('copy');
if (!success) {
console.warn('document.execCommand("copy") returned false.');
}
} catch (err) {
console.error('Error executing document.execCommand("copy"):', err);
success = false;
} finally {
// Cleanup: remove the temporary element
document.body.removeChild(textArea);
// Restore original selection and focus
if (selectedRange) {
const selection = document.getSelection();
selection.removeAllRanges(); // Clear selection from temp textarea
selection.addRange(selectedRange); // Restore original selection
}
if (currentFocus && typeof currentFocus.focus === 'function') {
// Only focus if it's focusable
currentFocus.focus(); // Restore original focus
}
}
return success;
}
// --- End Helper Function ---
if (copyTonButton && tonAddressElement) { if (copyTonButton && tonAddressElement) {
copyTonButton.addEventListener('click', async () => { // Use async for await copyTonButton.addEventListener('click', async () => {
const address = (copyTonButton.dataset.address || tonAddressElement.textContent || '').trim(); const address = (copyTonButton.dataset.address || tonAddressElement.textContent || '').trim();
if (!address) { if (!address) {
console.error('Address to copy is empty.'); console.error('Address to copy is empty.');
alert('Cannot copy: Address is empty.'); alert('Cannot copy: Address is empty.');
@ -87,76 +23,60 @@ document.addEventListener('DOMContentLoaded', () => {
} }
let copied = false; let copied = false;
let copyMethod = ''; // To track which method worked
// --- METHOD 1: Try Modern Clipboard API (Requires HTTPS or localhost) --- // Use Modern Clipboard API (Requires HTTPS or localhost)
// Check if API exists AND if we are in a secure context
if (navigator.clipboard && window.isSecureContext) { if (navigator.clipboard && window.isSecureContext) {
try { try {
await navigator.clipboard.writeText(address); await navigator.clipboard.writeText(address);
copied = true; copied = true;
copyMethod = 'Clipboard API'; console.log('Address copied successfully using Clipboard API.');
} catch (err) { } catch (err) {
console.warn('navigator.clipboard.writeText failed (maybe permissions?):', err); console.error('navigator.clipboard.writeText failed:', err);
// If it failed, we'll try the fallback method below // Don't use fallback, just inform the user
} }
} else { } else {
console.warn('navigator.clipboard not available or context is not secure (HTTPS/localhost required). Trying fallback.'); console.warn('navigator.clipboard not available or context not secure (HTTPS/localhost required).');
} }
// --- METHOD 2: Fallback to execCommand (Legacy) --- // Provide Feedback
if (!copied) {
copied = copyUsingExecCommand(address);
if (copied) { if (copied) {
copyMethod = 'execCommand';
}
}
// --- Provide Feedback ---
if (copied) {
console.log(`Address copied successfully using: ${copyMethod}`);
const originalText = copyTonButton.textContent; const originalText = copyTonButton.textContent;
copyTonButton.textContent = 'Copied!'; copyTonButton.textContent = 'Copied!';
copyTonButton.disabled = true; // Disable button temporarily copyTonButton.disabled = true;
setTimeout(() => { setTimeout(() => {
copyTonButton.textContent = originalText; copyTonButton.textContent = originalText;
copyTonButton.disabled = false; copyTonButton.disabled = false;
}, 1500); }, 1500);
} else { } else {
// If both methods failed // Inform user if Clipboard API failed or wasn't available
console.error('Failed to copy address using both Clipboard API and execCommand.'); alert('Failed to copy address automatically. Please copy manually.\n(Requires HTTPS or localhost)');
alert('Failed to copy address. Please copy manually.');
} }
}); });
} else { } else {
if (!copyTonButton) console.warn("Element with ID 'copy-ton-button' not found."); if (!copyTonButton) console.warn("Element with ID 'copy-ton-button' not found.");
if (!tonAddressElement) console.warn("Element with ID 'ton-address' not found."); if (!tonAddressElement) console.warn("Element with ID 'ton-address' not found.");
} }
// --- End Copy TON Address ---
// --- Last.fm Now Playing --- // --- Last.fm Now Playing ---
const nowPlayingCover = document.getElementById('now-playing-cover'); const nowPlayingCover = document.getElementById('now-playing-cover');
const nowPlayingStatus = document.getElementById('now-playing-status'); const nowPlayingStatus = document.getElementById('now-playing-status');
const nowPlayingTitleLink = document.getElementById('now-playing-title-link'); const nowPlayingTitleLink = document.getElementById('now-playing-title-link');
const nowPlayingTitleText = document.getElementById('now-playing-title'); const nowPlayingTitleText = document.getElementById('now-playing-title');
const nowPlayingArtist = document.getElementById('now-playing-artist'); 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"; 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 previousTrackId = null;
let previousStatus = null; // can be 'playing', 'not_playing', 'error', 'rate_limited' let previousStatus = null; // 'playing', 'not_playing', 'error', 'rate_limited'
if (!nowPlayingCover || !nowPlayingStatus || !nowPlayingTitleLink || !nowPlayingTitleText || !nowPlayingArtist) { if (!nowPlayingCover || !nowPlayingStatus || !nowPlayingTitleLink || !nowPlayingTitleText || !nowPlayingArtist) {
console.error("One or more Last.fm elements are missing from the DOM."); console.error("One or more Last.fm elements are missing from the DOM.");
// Stop execution if elements are missing
// Consider throwing an error or returning, depending on context
// For this example, we'll just return to prevent further errors.
return; return;
} }
// Helper function to set the UI to an error/inactive state // Helper function to set the UI to an error/inactive state
function setErrorState(message, statusIdentifier = 'error') { function setErrorState(message, statusIdentifier = 'error') {
if (previousStatus !== statusIdentifier) { if (previousStatus !== statusIdentifier) {
nowPlayingStatus.textContent = message; nowPlayingStatus.textContent = message;
nowPlayingTitleText.textContent = ''; nowPlayingTitleText.textContent = '';
@ -165,89 +85,68 @@ function setErrorState(message, statusIdentifier = 'error') {
nowPlayingCover.src = placeholderSvg; nowPlayingCover.src = placeholderSvg;
} }
nowPlayingCover.alt = 'Error or inactive state'; nowPlayingCover.alt = 'Error or inactive state';
nowPlayingCover.classList.add('error'); // Add error class for visual feedback nowPlayingCover.classList.add('error');
nowPlayingTitleLink.removeAttribute('href'); nowPlayingTitleLink.removeAttribute('href');
nowPlayingTitleLink.classList.add('inactive-link'); nowPlayingTitleLink.classList.add('inactive-link');
nowPlayingTitleLink.setAttribute('aria-disabled', 'true'); nowPlayingTitleLink.setAttribute('aria-disabled', 'true');
previousStatus = statusIdentifier; previousStatus = statusIdentifier;
previousTrackId = null; previousTrackId = null;
} }
} }
function updateNowPlaying() {
function updateNowPlaying() {
fetch('/api/v1/now_playing') fetch('/api/v1/now_playing')
.then(response => { .then(response => {
// --- START 429 Handling ---
if (response.status === 429) { if (response.status === 429) {
// Attempt to parse the JSON body for the custom message
return response.json().then(errorData => { return response.json().then(errorData => {
// Use the message from the server if available
const message = errorData?.message || "Too many requests. Please wait."; const message = errorData?.message || "Too many requests. Please wait.";
setErrorState(message, 'rate_limited'); setErrorState(message, 'rate_limited');
// Throw an error to skip the rest of the .then chain and go to .catch throw new Error('Rate limit exceeded'); // Skip to .catch
// This signals that we handled this specific error type already
throw new Error('Rate limit exceeded');
}).catch(jsonError => { }).catch(jsonError => {
// If parsing the 429 JSON fails, use a default message
console.error("Could not parse 429 response body:", jsonError); console.error("Could not parse 429 response body:", jsonError);
setErrorState("Too many requests. Please wait.", 'rate_limited'); setErrorState("Too many requests. Please wait.", 'rate_limited');
throw new Error('Rate limit exceeded'); // Still go to .catch throw new Error('Rate limit exceeded');
}); });
} }
// --- END 429 Handling ---
if (!response.ok) { if (!response.ok) {
// Handle other HTTP errors (like 500, 404, etc.)
throw new Error(`HTTP error! status: ${response.status}`); throw new Error(`HTTP error! status: ${response.status}`);
} }
// If response is OK (2xx), parse the JSON
return response.json(); return response.json();
}) })
.then(data => { .then(data => {
// This block only runs if the response was OK (2xx) and not 429
let currentStatus; let currentStatus;
let currentTrackId = null; let currentTrackId = null;
nowPlayingCover.classList.remove('error');
nowPlayingCover.classList.remove('error'); // Remove error class if previous state was error if (data.error) {
// We already know data is not null/undefined if we reached here
if (data.error) { // Handle application-level errors returned in JSON
console.error('API Logic Error:', data.error); console.error('API Logic Error:', data.error);
currentStatus = 'error'; currentStatus = 'error';
setErrorState(data.error || 'Error fetching data.'); // Use helper setErrorState(data.error || 'Error fetching data.');
} else if (data.playing && data.artist && data.name) { } else if (data.playing && data.artist && data.name) {
currentStatus = 'playing'; currentStatus = 'playing';
currentTrackId = `${data.artist}-${data.name}`; currentTrackId = `${data.artist}-${data.name}`;
} else if (data.playing) { } else if (data.playing) {
console.warn('API returned playing:true, but missing artist or name.'); console.warn('API returned playing:true, but missing artist or name.');
currentStatus = 'error'; currentStatus = 'error';
setErrorState('Incomplete track data received.'); // Use helper setErrorState('Incomplete track data received.');
} else { } else {
currentStatus = 'not_playing'; currentStatus = 'not_playing';
} }
// Update UI only if state changed or track changed // Update UI only if state or track changed
if (currentStatus !== previousStatus || (currentStatus === 'playing' && currentTrackId !== previousTrackId)) { if (currentStatus !== previousStatus || (currentStatus === 'playing' && currentTrackId !== previousTrackId)) {
if (currentStatus === 'playing') { if (currentStatus === 'playing') {
nowPlayingStatus.textContent = ''; // Clear status text nowPlayingStatus.textContent = '';
nowPlayingTitleText.textContent = data.name; nowPlayingTitleText.textContent = data.name;
nowPlayingArtist.textContent = `by ${data.artist}`; nowPlayingArtist.textContent = `by ${data.artist}`;
nowPlayingTitleLink.href = `https://www.last.fm/music/${encodeURIComponent(data.artist)}/_/${encodeURIComponent(data.name)}`;
const lastfmUrl = `https://www.last.fm/music/${encodeURIComponent(data.artist)}/_/${encodeURIComponent(data.name)}`;
nowPlayingTitleLink.href = lastfmUrl;
nowPlayingTitleLink.classList.remove('inactive-link'); nowPlayingTitleLink.classList.remove('inactive-link');
nowPlayingTitleLink.removeAttribute('aria-disabled'); nowPlayingTitleLink.removeAttribute('aria-disabled');
const newCoverSrc = data.image_url || placeholderSvg; const newCoverSrc = data.image_url || placeholderSvg;
if (nowPlayingCover.src !== newCoverSrc) { if (nowPlayingCover.src !== newCoverSrc) {
nowPlayingCover.src = newCoverSrc; nowPlayingCover.src = newCoverSrc;
} }
nowPlayingCover.alt = data.image_url ? `Album cover for ${data.name} by ${data.artist}` : 'Album cover not available'; nowPlayingCover.alt = data.image_url ? `Album cover for ${data.name} by ${data.artist}` : 'Album cover not available';
} else if (currentStatus === 'not_playing') { } else if (currentStatus === 'not_playing') {
nowPlayingStatus.textContent = data.message || 'Not listening right now.'; nowPlayingStatus.textContent = data.message || 'Not listening right now.';
nowPlayingTitleText.textContent = ''; nowPlayingTitleText.textContent = '';
@ -256,33 +155,26 @@ function updateNowPlaying() {
nowPlayingCover.src = placeholderSvg; nowPlayingCover.src = placeholderSvg;
} }
nowPlayingCover.alt = 'Nothing playing'; nowPlayingCover.alt = 'Nothing playing';
nowPlayingTitleLink.removeAttribute('href'); nowPlayingTitleLink.removeAttribute('href');
nowPlayingTitleLink.classList.add('inactive-link'); nowPlayingTitleLink.classList.add('inactive-link');
nowPlayingTitleLink.setAttribute('aria-disabled', 'true'); nowPlayingTitleLink.setAttribute('aria-disabled', 'true');
} }
// Error states are handled above or in .catch using setErrorState // Error states handled via setErrorState
previousStatus = currentStatus; // Update previous status only on successful processing previousStatus = currentStatus;
previousTrackId = currentTrackId; previousTrackId = currentTrackId;
} }
}) })
.catch(error => { .catch(error => {
// Catch network errors, HTTP errors (thrown from !response.ok),
// and the specific 'Rate limit exceeded' error we threw.
console.error('Fetch Error:', error); console.error('Fetch Error:', error);
// Avoid setting generic error if it was the rate limit we already handled // Avoid setting generic error if it was the rate limit we already handled
if (error.message !== 'Rate limit exceeded') { if (error.message !== 'Rate limit exceeded') {
// Use the helper function for generic network/HTTP errors
setErrorState('Could not connect or error occurred.'); setErrorState('Could not connect or error occurred.');
} }
// If it *was* 'Rate limit exceeded', the UI is already set by the 429 handler.
}); });
} }
updateNowPlaying(); updateNowPlaying();
setInterval(updateNowPlaying, 30000); setInterval(updateNowPlaying, 30000);
// --- End Last.fm Now Playing ---
}); });