From 3a5d51c915d9757099fd88773094ca8657a4d3a5 Mon Sep 17 00:00:00 2001 From: Raven Scott Date: Thu, 17 Oct 2024 04:39:30 -0400 Subject: [PATCH] first commit --- .gitignore | 3 + README.md | 148 +++++++++++++++++++++++++++++++++++++++++++++++ app.js | 111 +++++++++++++++++++++++++++++++++++ package.json | 19 ++++++ views/index.ejs | 50 ++++++++++++++++ views/layout.ejs | 20 +++++++ views/track.ejs | 37 ++++++++++++ 7 files changed, 388 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 app.js create mode 100644 package.json create mode 100644 views/index.ejs create mode 100644 views/layout.ejs create mode 100644 views/track.ejs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c9e55e8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +node_modules +cache.json +package-lock.json \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..2b75674 --- /dev/null +++ b/README.md @@ -0,0 +1,148 @@ +# Raven Scott Metal Site + +Welcome to the **Raven Scott Metal Site**, a dynamic and metal-inspired web application that lists and showcases SoundCloud tracks from the playlist "Raven Scott Metal." The site is designed using **Bootstrap 4** and features a dark, edgy design tailored to a metal aesthetic. Each track is displayed with a SoundCloud player, with links to detailed pages for each individual track. + +## Table of Contents +- [Features](#features) +- [Installation](#installation) +- [Usage](#usage) +- [SoundCloud Playlist](#soundcloud-playlist) +- [Caching](#caching) +- [Routes](#routes) +- [Technologies Used](#technologies-used) +- [Known Issues](#known-issues) +- [Contributing](#contributing) +- [License](#license) + +## Features +- Displays all tracks from the "Raven Scott Metal" SoundCloud playlist. +- Dynamic slug-based routes for each track. +- Sorts tracks by the number of plays, followed by publication date. +- Embeds SoundCloud players for each track. +- Caching system to prevent fetching the playlist too frequently. +- Bootstrap 4 responsive design with a dark metal theme. +- Fallback message if a track doesn't have a description. +- Easy-to-use interface with individual pages for each track. + +## Installation + +To get started with the Raven Scott Metal site locally, follow these steps: + +1. Clone the repository: + ```bash + git clone https://github.com/your-username/raven-scott-metal-site.git + ``` + +2. Navigate to the project directory: + ```bash + cd raven-scott-metal-site + ``` + +3. Install the dependencies: + ```bash + npm install + ``` + +4. Create a cache file for storing track information: + ```bash + touch cache.json + ``` + +5. Run the application: + ```bash + npm start + ``` + +6. Open your browser and navigate to `http://localhost:3000`. + +## Usage + +### Fetching Playlist Data +The site uses the **SoundCloud Scraper** to fetch playlist data. By default, the playlist is only fetched once a week and cached in `cache.json`. You can force a fresh fetch using the `--fetch` flag: + +```bash +npm start -- --fetch +``` + +### Viewing Tracks +The homepage lists all tracks from the "Raven Scott Metal" playlist. Each track has its own card displaying the following information: +- Title of the track +- Short description (or a fallback message if no description is available) +- Embedded SoundCloud player + +### Track Pages +Each track has a dedicated page with its own URL based on a slug generated from the track title. The URL format is: +``` +http://localhost:3000/tracks/ +``` + +On each track page, you'll find: +- The track title +- The full description of the track (if available), rendered with line breaks +- An embedded SoundCloud player to listen to the track + +## SoundCloud Playlist + +The site fetches tracks from the following SoundCloud playlist: +- **Playlist URL:** [Raven Scott Metal](https://soundcloud.com/snxraven/sets/raven-scott-metal) + +Each track is fetched and displayed in order of play count (most played first), and then by the publication date. + +## Caching + +The site uses a caching mechanism to reduce the number of requests to the SoundCloud API. The cache stores: +- A list of tracks from the SoundCloud playlist +- The timestamp of the last time the playlist was fetched + +By default, the cache is valid for 7 days. After that, the playlist will be fetched again. If you want to force a refresh of the playlist before the cache expires, run the app with the `--fetch` flag. + +### Cache Structure +The cache is stored in `cache.json` and looks like this: +```json +{ + "tracks": [ + { + "title": "Ebon Shroud", + "description": "A chilling, shadowy realm...", + "url": "https://soundcloud.com/snxraven/ebon-shroud", + "playCount": 323, + "publishedAt": "2024-07-31T07:26:51.000Z", + "slug": "ebon-shroud" + }, + ... + ], + "timestamp": 1729150557891 +} +``` + +## Routes + +### Home Page +- **URL:** `/` +- **Description:** Lists all tracks from the "Raven Scott Metal" playlist. Tracks are sorted by play count and then by publication date. Each track is displayed in a card with a link to the track page. + +### Track Pages +- **URL Format:** `/tracks/` +- **Description:** Displays detailed information about a specific track, including its full description and an embedded SoundCloud player. + +### Example Track URL +``` +http://localhost:3000/tracks/ebon-shroud +``` + +## Technologies Used + +### Backend +- **Node.js**: JavaScript runtime for the server. +- **Express.js**: Web framework for building the server-side application. +- **SoundCloud Scraper**: Library for fetching SoundCloud playlist and track data. +- **EJS**: Templating engine for rendering HTML views. +- **Bootstrap 4**: Frontend CSS framework for responsive design. + +### Frontend +- **Bootstrap 4**: Used for styling the site with a responsive, mobile-first design. +- **Custom Styles**: Added custom styles for a dark metal theme. + +## Known Issues +- SoundCloud API rate limits may affect data fetching if too many requests are made in a short period. +- If a track doesn't have a description, the fallback message is displayed. However, some descriptions may not render properly if they contain unusual formatting. diff --git a/app.js b/app.js new file mode 100644 index 0000000..e3d6985 --- /dev/null +++ b/app.js @@ -0,0 +1,111 @@ +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 }); +}); + +// Listen on the specified port +app.listen(PORT, () => { + console.log(`Server is running on port ${PORT}`); +}); diff --git a/package.json b/package.json new file mode 100644 index 0000000..b718d05 --- /dev/null +++ b/package.json @@ -0,0 +1,19 @@ +{ + "name": "raven-scott-metal-website", + "version": "1.0.0", + "description": "Metal themed website for Raven Scott's music.", + "main": "app.js", + "scripts": { + "start": "node app.js" + }, + "dependencies": { + "axios": "^0.27.2", + "cheerio": "^1.0.0-rc.10", + "ejs": "^3.1.8", + "express": "^4.18.1", + "puppeteer": "^23.6.0", + "rss-to-json": "^2.1.1", + "sitemap": "^8.0.0", + "soundcloud-scraper": "^5.0.3" + } +} diff --git a/views/index.ejs b/views/index.ejs new file mode 100644 index 0000000..3612191 --- /dev/null +++ b/views/index.ejs @@ -0,0 +1,50 @@ + + + + + + Raven Scott Metal Playlist + + + + +
+

Raven Scott Metal Tracks

+
+ <% tracks.forEach(track => { %> +
+
+
+
<%= track.title %>
+

+ + + More Details +
+
+
+ <% }) %> +
+
+ + + diff --git a/views/layout.ejs b/views/layout.ejs new file mode 100644 index 0000000..467e617 --- /dev/null +++ b/views/layout.ejs @@ -0,0 +1,20 @@ + + + + + + <%= title || 'Raven Scott Metal' %> + + + +
+

<%= title || 'Raven Scott Metal' %>

+
+
+ <%- include('content') %> +
+
+

© 2024 Raven Scott

+
+ + diff --git a/views/track.ejs b/views/track.ejs new file mode 100644 index 0000000..10b9e14 --- /dev/null +++ b/views/track.ejs @@ -0,0 +1,37 @@ + + + + + + <%= track.title %> + + + + +
+
+
+

<%= track.title %>

+ +

+ <%- (track.description && track.description.trim()) + ? track.description.replace(/\n/g, '
') + : 'No description available for this track.' %> +

+
+
+
+ +