Files
ravenscott-rocks/manual.js
snxraven b12b3882f3 Add pagination and WebSocket support for track listings
This commit introduces pagination and real-time updates to the music site, improving performance and user experience for browsing tracks across genres. Key changes include:

music_site.js:

Added Socket.IO for real-time communication between client and server.

Implemented pagination with TRACKS_PER_PAGE (4 tracks per page) in getTracks function, returning paginated tracks, current page, total pages, and total tracks.

Enhanced slug generation with usedSlugs Map to ensure uniqueness within genres.

Updated routes and WebSocket handlers to support pagination:

Modified getTracks to handle page parameters and return paginated data.

Added io.on('connection') to handle request_page events and emit page_data or error.

Updated / and /sitemap.xml to work with paginated track data.

Adjusted track retrieval in /:genre/track/:slug and /json/:genre to use allTracks for consistency.

index.ejs:

Added pagination controls (Previous/Next buttons) for each genre section, displaying current page and total pages.

Introduced page-container divs to manage paginated track displays, with active class for the current page.

Implemented client-side WebSocket logic with Socket.IO to request and render new pages dynamically.

Added page caching (pageCache) to store rendered pages and reduce server requests.

Enhanced lazy loading of iframes to apply to dynamically loaded pages.

Updated styling for page-container and iframes to ensure proper layout and responsiveness.

These changes enable users to navigate through tracks efficiently without loading all tracks at once, reduce server load, and provide a smoother browsing experience with real-time updates.
2025-06-22 02:54:41 -04:00

109 lines
4.1 KiB
JavaScript

const Soundcloud = require('soundcloud.ts').default;
const fs = require('fs');
const path = require('path');
const slugify = require('slugify');
// Configuration
const PLAYLIST_URL = 'https://soundcloud.com/snxraven/sets/raven-scott-rap'; // Metal playlist URL
const CACHE_FILE = path.join(__dirname, 'cache_rap.json'); // Cache file for metal playlist
// Helper function to create a slug from track title
function generateSlug(title) {
try {
const slug = slugify(title, {
lower: true,
strict: true,
remove: /[*+~.()'"!:@]/g
});
if (!slug || slug.trim() === '') {
console.warn(`Empty slug for title: "${title}". Using fallback.`);
return 'track-' + Date.now();
}
console.log(`Generated slug for "${title}": "${slug}"`);
return slug;
} catch (err) {
console.error(`Error generating slug for title: "${title}"`, err);
return 'track-' + Date.now();
}
}
// Helper function to sanitize track data
function sanitizeTrackData(track) {
return {
title: typeof track.title === 'string' ? track.title : 'Unknown Title',
description: typeof track.description === 'string' ? track.description : 'No description available',
url: typeof track.permalink_url === 'string' ? track.permalink_url : '',
playCount: Number.isFinite(track.playback_count) ? track.playback_count : 0,
publishedAt: typeof track.created_at === 'string' ? track.created_at : new Date().toISOString(),
slug: generateSlug(track.title || 'Unknown')
};
}
// Helper function to save cache
function saveCache(cacheFile, data) {
try {
// Filter out invalid tracks
const validTracks = data.tracks.filter(track => {
try {
JSON.stringify(track);
return true;
} catch (err) {
console.error(`Invalid track data for "${track.title}":`, err);
return false;
}
});
const cacheData = { tracks: validTracks, timestamp: data.timestamp };
console.log(`Saving cache with ${validTracks.length} tracks to ${cacheFile}`);
console.log('Cached track titles:', validTracks.map(t => t.title));
const jsonString = JSON.stringify(cacheData, null, 2);
fs.writeFileSync(cacheFile, jsonString, { encoding: 'utf8' });
console.log(`Cache saved to ${cacheFile}`);
} catch (err) {
console.error(`Error saving cache to ${cacheFile}:`, err);
console.error('Problematic cache data:', data);
}
}
// Fetch all tracks from a SoundCloud playlist
async function fetchPlaylist(playlistUrl) {
try {
const soundcloud = new Soundcloud(); // No client ID or OAuth token
const playlist = await soundcloud.playlists.getAlt(playlistUrl);
console.log(`Fetched playlist with ${playlist.tracks.length} tracks:`, playlist.tracks.map(t => t.title));
const tracks = [];
for (const track of playlist.tracks) {
try {
console.log(`Processing track: "${track.title || 'Unknown'}"`);
const trackData = sanitizeTrackData(track);
tracks.push(trackData);
console.log(`Successfully processed track: "${track.title || 'Unknown'}"`);
} catch (err) {
console.error(`Error processing track "${track.title || 'Unknown'}":`, err);
continue;
}
}
console.log(`Total fetched tracks: ${tracks.length}`);
if (tracks.length < 56) {
console.warn(`Expected ~56 tracks, but only ${tracks.length} were fetched. Verify playlist URL or check for private tracks.`);
}
return tracks;
} catch (err) {
console.error(`Error fetching playlist ${playlistUrl}:`, err);
return [];
}
}
// Main function to generate cache
async function generateCache() {
const tracks = await fetchPlaylist(PLAYLIST_URL);
saveCache(CACHE_FILE, { tracks, timestamp: Date.now() });
}
// Run the cache generation
generateCache().catch(err => {
console.error('Error generating cache:', err);
process.exit(1);
});