const express = require('express'); const app = express(); const path = require('path'); const SoundCloud = require('soundcloud-scraper'); const client = new SoundCloud.Client(); const fs = require('fs'); const PORT = process.env.PORT || 6767; let tracks = []; // Store the tracks globally const CACHE_FILE = path.join(__dirname, 'cache.json'); // Helper function to create a slug from track title function generateSlug(title) { return title .toLowerCase() // Ensure lowercase .replace(/[^a-z0-9]+/g, '-') // Replace non-alphanumeric characters with hyphens .replace(/(^-|-$)/g, ''); // Remove leading and trailing hyphens } // Helper function to read the cache function readCache() { if (fs.existsSync(CACHE_FILE)) { const data = fs.readFileSync(CACHE_FILE, 'utf8'); return JSON.parse(data); } return null; } // Helper function to save cache function saveCache(data) { fs.writeFileSync(CACHE_FILE, JSON.stringify(data), 'utf8'); } // Fetch playlist tracks from SoundCloud async function fetchPlaylist() { const playlist = await client.getPlaylist('https://soundcloud.com/snxraven/sets/raven-scott-metal'); return playlist.tracks.map(track => { const slug = generateSlug(track.title); // Generate slug here return { title: track.title, description: track.description || 'No description available', url: track.url, playCount: track.playCount || 0, publishedAt: track.publishedAt || new Date().toISOString(), slug // Save the slug in the track object }; }); } // Get tracks from cache or SoundCloud async function getTracks(fetch = false) { const cache = readCache(); const oneWeekInMs = 7 * 24 * 60 * 60 * 1000; // One week in milliseconds const now = Date.now(); if (fetch || !cache || (now - cache.timestamp) > oneWeekInMs) { // Fetch fresh tracks from SoundCloud tracks = await fetchPlaylist(); saveCache({ tracks, timestamp: now }); } else { // Load from cache tracks = cache.tracks; // Ensure slug generation in case it is missing from the cached data tracks = tracks.map(track => { if (!track.slug) { track.slug = generateSlug(track.title); } return track; }); } // Sort by playCount first, then by publishedAt tracks.sort((a, b) => { if (b.playCount !== a.playCount) return b.playCount - a.playCount; return new Date(b.publishedAt) - new Date(a.publishedAt); }); return tracks; } // Serve static files from public directory app.use(express.static(path.join(__dirname, 'public'))); // Set EJS as templating engine app.set('view engine', 'ejs'); app.set('views', path.join(__dirname, 'views')); // Home page route app.get('/', async (req, res) => { const allTracks = await getTracks(); res.render('index', { tracks: allTracks }); }); // Individual track page route app.get('/track/:slug', async (req, res) => { const allTracks = await getTracks(); const track = allTracks.find(t => t.slug === req.params.slug); if (!track) { return res.status(404).send('Track not found'); } res.render('track', { track }); }); // JSON endpoint to return cached tracks app.get('/json', async (req, res) => { const allTracks = await getTracks(); res.json(allTracks); }); // Sitemap endpoint app.get('/sitemap.xml', async (req, res) => { const allTracks = await getTracks(); let sitemap = `\n`; sitemap += `\n`; // Home page sitemap += `\n https://raven-scott.rocks/\n 1.0\n\n`; // Track pages allTracks.forEach(track => { sitemap += `\n https://raven-scott.rocks/track/${track.slug}\n 0.8\n\n`; }); sitemap += ``; res.header('Content-Type', 'application/xml'); res.send(sitemap); }); // Listen on the specified port app.listen(PORT, () => { console.log(`Server is running on port ${PORT}`); });