moving to soundcloud.ts
This commit is contained in:
165
music_site.js
165
music_site.js
@ -1,9 +1,9 @@
|
||||
const express = require('express');
|
||||
const app = express();
|
||||
const path = require('path');
|
||||
const SoundCloud = require('soundcloud-scraper');
|
||||
const client = new SoundCloud.Client();
|
||||
const Soundcloud = require('soundcloud.ts').default;
|
||||
const fs = require('fs');
|
||||
const slugify = require('slugify');
|
||||
const PORT = process.env.PORT || 6767;
|
||||
|
||||
// Define genre playlists
|
||||
@ -27,43 +27,137 @@ const playlists = {
|
||||
url: 'https://soundcloud.com/snxraven/sets/raven-scott-lofi',
|
||||
cacheFile: path.join(__dirname, 'cache_lofi.json'),
|
||||
tracks: []
|
||||
},
|
||||
edm: {
|
||||
url: 'https://soundcloud.com/snxraven/sets/raven-scott-edm',
|
||||
cacheFile: path.join(__dirname, 'cache_edm.json'),
|
||||
tracks: []
|
||||
},
|
||||
cuts: {
|
||||
url: 'https://soundcloud.com/snxraven/sets/lets-cut-it',
|
||||
cacheFile: path.join(__dirname, 'cache_cuts.json'),
|
||||
tracks: []
|
||||
}
|
||||
};
|
||||
|
||||
// Helper function to create a slug from track title
|
||||
function generateSlug(title) {
|
||||
return title
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9]+/g, '-')
|
||||
.replace(/(^-|-$)/g, '');
|
||||
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) {
|
||||
// Log raw track data to debug URL fields
|
||||
console.log(`Raw track data for "${track.title || 'Unknown'}":`, JSON.stringify(track, null, 2));
|
||||
|
||||
// Use permalink_url or url (from cache), with fallback
|
||||
const permalinkUrl = typeof track.permalink_url === 'string' ? track.permalink_url :
|
||||
typeof track.url === 'string' ? track.url :
|
||||
typeof track.permalink === 'string' ? track.permalink : '';
|
||||
|
||||
// Construct embed URL for SoundCloud player
|
||||
const embedUrl = permalinkUrl ?
|
||||
`https://w.soundcloud.com/player/?url=${encodeURIComponent(permalinkUrl)}&color=%23ff5500&auto_play=false&hide_related=false&show_comments=true&show_user=true&show_reposts=false&show_teaser=true` :
|
||||
'';
|
||||
|
||||
if (!embedUrl) {
|
||||
console.warn(`No embed URL generated for track "${track.title || 'Unknown'}"`);
|
||||
}
|
||||
|
||||
return {
|
||||
title: typeof track.title === 'string' ? track.title : 'Unknown Title',
|
||||
description: typeof track.description === 'string' ? track.description : 'No description available',
|
||||
url: permalinkUrl, // Permalink URL for links
|
||||
embedUrl: embedUrl, // For SoundCloud player iframe or widget
|
||||
playCount: Number.isFinite(track.playback_count) ? track.playback_count :
|
||||
Number.isFinite(track.playCount) ? track.playCount : 0,
|
||||
publishedAt: typeof track.created_at === 'string' ? track.created_at :
|
||||
typeof track.publishedAt === 'string' ? track.publishedAt : new Date().toISOString(),
|
||||
slug: typeof track.slug === 'string' ? track.slug : generateSlug(track.title || 'Unknown')
|
||||
};
|
||||
}
|
||||
|
||||
// Helper function to read the cache
|
||||
function readCache(cacheFile) {
|
||||
if (fs.existsSync(cacheFile)) {
|
||||
const data = fs.readFileSync(cacheFile, 'utf8');
|
||||
return JSON.parse(data);
|
||||
try {
|
||||
const data = fs.readFileSync(cacheFile, { encoding: 'utf8' });
|
||||
return JSON.parse(data);
|
||||
} catch (err) {
|
||||
console.error(`Error reading cache from ${cacheFile}:`, err);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Helper function to save cache
|
||||
function saveCache(cacheFile, data) {
|
||||
fs.writeFileSync(cacheFile, JSON.stringify(data), 'utf8');
|
||||
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 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)
|
||||
}));
|
||||
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);
|
||||
console.log(`Generated embed URL: "${trackData.embedUrl}"`);
|
||||
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}`);
|
||||
return tracks;
|
||||
} catch (err) {
|
||||
console.error(`Error fetching playlist ${playlistUrl}:`, err);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// Get tracks for a specific genre
|
||||
@ -77,17 +171,32 @@ async function getTracks(genre, fetch = false) {
|
||||
playlist.tracks = await fetchPlaylist(playlist.url);
|
||||
saveCache(playlist.cacheFile, { tracks: playlist.tracks, timestamp: now });
|
||||
} else {
|
||||
playlist.tracks = cache.tracks.map(track => ({
|
||||
playlist.tracks = cache.tracks.map(track => sanitizeTrackData({
|
||||
...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);
|
||||
});
|
||||
// Sort tracks
|
||||
if (genre === 'cuts') {
|
||||
// Sort "cuts" playlist by EP number extracted from title
|
||||
playlist.tracks.sort((a, b) => {
|
||||
// Extract EP number from title (e.g., "Lets Cut It EP 1" -> 1)
|
||||
const getEpNumber = (title) => {
|
||||
const match = title.match(/EP\s*(\d+)/i);
|
||||
return match ? parseInt(match[1], 10) : Infinity; // Fallback for non-matching titles
|
||||
};
|
||||
const epA = getEpNumber(a.title);
|
||||
const epB = getEpNumber(b.title);
|
||||
return epA - epB; // Ascending order
|
||||
});
|
||||
} else {
|
||||
// Existing sorting for other genres (by playCount, then 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;
|
||||
}
|
||||
@ -125,12 +234,11 @@ app.get('/sitemap.xml', async (req, res) => {
|
||||
}
|
||||
|
||||
sitemap += `</urlset>`;
|
||||
|
||||
|
||||
res.header('Content-Type', 'application/xml');
|
||||
res.send(sitemap);
|
||||
});
|
||||
|
||||
|
||||
// Redirect /genre to /#genre
|
||||
app.get('/:genre', async (req, res) => {
|
||||
const { genre } = req.params;
|
||||
@ -147,7 +255,7 @@ app.get('/:genre/track/:slug', async (req, res) => {
|
||||
if (!playlists[genre]) {
|
||||
return res.status(404).send('Genre not found');
|
||||
}
|
||||
|
||||
|
||||
const tracks = await getTracks(genre);
|
||||
const track = tracks.find(t => t.slug === slug);
|
||||
|
||||
@ -168,7 +276,6 @@ app.get('/json/:genre', async (req, res) => {
|
||||
res.json(tracks);
|
||||
});
|
||||
|
||||
|
||||
// Listen on the specified port
|
||||
app.listen(PORT, () => {
|
||||
console.log(`Server is running on port ${PORT}`);
|
||||
|
Reference in New Issue
Block a user