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; // Define genre playlists const playlists = { metal: { url: 'https://soundcloud.com/snxraven/sets/raven-scott-metal', cacheFile: path.join(__dirname, 'cache_metal.json'), tracks: [] }, altrock: { url: 'https://soundcloud.com/snxraven/sets/raven-scott-alt-rock', cacheFile: path.join(__dirname, 'cache_altrock.json'), tracks: [] }, rap: { url: 'https://soundcloud.com/snxraven/sets/raven-scott-rap', cacheFile: path.join(__dirname, 'cache_rap.json'), tracks: [] } }; // Helper function to create a slug from track title function generateSlug(title) { return title .toLowerCase() .replace(/[^a-z0-9]+/g, '-') .replace(/(^-|-$)/g, ''); } // Helper function to read the cache function readCache(cacheFile) { if (fs.existsSync(cacheFile)) { const data = fs.readFileSync(cacheFile, 'utf8'); return JSON.parse(data); } return null; } // Helper function to save cache function saveCache(cacheFile, data) { fs.writeFileSync(cacheFile, JSON.stringify(data), 'utf8'); } // Fetch playlist tracks from SoundCloud async function fetchPlaylist(playlistUrl) { const playlist = await client.getPlaylist(playlistUrl); return playlist.tracks.map(track => ({ title: track.title, description: track.description || 'No description available', url: track.url, playCount: track.playCount || 0, publishedAt: track.publishedAt || new Date().toISOString(), slug: generateSlug(track.title) })); } // Get tracks for a specific genre async function getTracks(genre, fetch = false) { const playlist = playlists[genre]; const cache = readCache(playlist.cacheFile); const oneWeekInMs = 7 * 24 * 60 * 60 * 1000; const now = Date.now(); if (fetch || !cache || (now - cache.timestamp) > oneWeekInMs) { playlist.tracks = await fetchPlaylist(playlist.url); saveCache(playlist.cacheFile, { tracks: playlist.tracks, timestamp: now }); } else { playlist.tracks = cache.tracks.map(track => ({ ...track, slug: track.slug || generateSlug(track.title) })); } // Sort by playCount first, then by publishedAt playlist.tracks.sort((a, b) => { if (b.playCount !== a.playCount) return b.playCount - a.playCount; return new Date(b.publishedAt) - new Date(a.publishedAt); }); return playlist.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 genreTracks = {}; for (const genre in playlists) { genreTracks[genre] = await getTracks(genre); } res.render('index', { genreTracks }); }); // Sitemap endpoint app.get('/sitemap.xml', async (req, res) => { let sitemap = `\n`; sitemap += `\n`; // Home page sitemap += `\n https://raven-scott.rocks/\n 1.0\n\n`; // Track pages for each genre for (const genre in playlists) { const tracks = await getTracks(genre); tracks.forEach(track => { sitemap += `\n https://raven-scott.rocks/${genre}/track/${track.slug}\n 0.8\n\n`; }); } sitemap += ``; res.header('Content-Type', 'application/xml'); res.send(sitemap); }); // Redirect /genre to /#genre app.get('/:genre', async (req, res) => { const { genre } = req.params; if (playlists[genre]) { res.redirect(`/#${genre}`); } else { res.status(404).send('Genre not found'); } }); // Individual track page route app.get('/:genre/track/:slug', async (req, res) => { const { genre, slug } = req.params; if (!playlists[genre]) { return res.status(404).send('Genre not found'); } const tracks = await getTracks(genre); const track = tracks.find(t => t.slug === slug); if (!track) { return res.status(404).send('Track not found'); } res.render('track', { track, genre }); }); // JSON endpoint for specific genre app.get('/json/:genre', async (req, res) => { const { genre } = req.params; if (!playlists[genre]) { return res.status(404).json({ error: 'Genre not found' }); } const tracks = await getTracks(genre); res.json(tracks); }); // Listen on the specified port app.listen(PORT, () => { console.log(`Server is running on port ${PORT}`); });