first
This commit is contained in:
168
music_site.js
Normal file
168
music_site.js
Normal file
@ -0,0 +1,168 @@
|
||||
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 });
|
||||
});
|
||||
|
||||
// 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);
|
||||
});
|
||||
|
||||
// Sitemap endpoint
|
||||
app.get('/sitemap.xml', async (req, res) => {
|
||||
let sitemap = `<?xml version="1.0" encoding="UTF-8"?>\n`;
|
||||
sitemap += `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n`;
|
||||
|
||||
// Home page
|
||||
sitemap += `<url>\n <loc>https://raven-scott.rocks/</loc>\n <priority>1.0</priority>\n</url>\n`;
|
||||
|
||||
// Track pages for each genre
|
||||
for (const genre in playlists) {
|
||||
const tracks = await getTracks(genre);
|
||||
tracks.forEach(track => {
|
||||
sitemap += `<url>\n <loc>https://raven-scott.rocks/${genre}/track/${track.slug}</loc>\n <priority>0.8</priority>\n</url>\n`;
|
||||
});
|
||||
}
|
||||
|
||||
sitemap += `</urlset>`;
|
||||
|
||||
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}`);
|
||||
});
|
Reference in New Issue
Block a user