mirror of
https://github.com/M4X809/list-of-lp.git
synced 2025-12-25 19:12:48 +00:00
- Changed track IDs in fetched-songs.json and song-links.json from hyphenated to underscore format for consistency. - Added FontAwesome dependencies to package.json for icon support. - Introduced a new function in fetch-song-links.ts to retrieve song links by ID. - Refactored TrackCard component to utilize the new hash functionality for track modals. - Updated layout.tsx to include FontAwesome styles and configuration.
247 lines
6.3 KiB
TypeScript
247 lines
6.3 KiB
TypeScript
#!/usr/bin/env bun
|
|
|
|
import { join } from "path";
|
|
import { $ } from "bun";
|
|
import { albums } from "../src/lib/list";
|
|
|
|
// Configuration
|
|
const API_BASE = "https://api.song.link/v1-alpha.1/links";
|
|
const RATE_LIMIT_DELAY = 6000; // 6 seconds (10 requests per minute)
|
|
const USER_COUNTRY = "DE"; // Germany
|
|
const DATA_DIR = join(import.meta.dir, "../data");
|
|
const CACHE_FILE = join(DATA_DIR, "fetched-songs.json");
|
|
const OUTPUT_TS_FILE = join(import.meta.dir, "../src/lib/songLinks.ts");
|
|
const OUTPUT_JSON_FILE = join(DATA_DIR, "song-links.json");
|
|
|
|
interface SongLinks {
|
|
spotify?: string;
|
|
youtube?: string;
|
|
youtubeMusic?: string;
|
|
appleMusic?: string;
|
|
}
|
|
|
|
interface SongLinksData {
|
|
[songId: string]: SongLinks;
|
|
}
|
|
|
|
interface CachedSongs {
|
|
[songId: string]: boolean;
|
|
}
|
|
|
|
// Delay function for rate limiting
|
|
const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
|
|
// Load cached songs
|
|
async function loadCache(): Promise<CachedSongs> {
|
|
const file = Bun.file(CACHE_FILE);
|
|
const exists = await file.exists();
|
|
|
|
if (!exists) {
|
|
console.log("No cache file found, starting fresh...");
|
|
return {};
|
|
}
|
|
|
|
try {
|
|
return await file.json();
|
|
} catch {
|
|
console.log("Invalid cache file, starting fresh...");
|
|
return {};
|
|
}
|
|
}
|
|
|
|
// Load existing song links
|
|
async function loadExistingSongLinks(): Promise<SongLinksData> {
|
|
const file = Bun.file(OUTPUT_JSON_FILE);
|
|
const exists = await file.exists();
|
|
|
|
if (!exists) {
|
|
console.log("No existing song links found, starting fresh...");
|
|
return {};
|
|
}
|
|
|
|
try {
|
|
return await file.json();
|
|
} catch {
|
|
console.log("Invalid song links file, starting fresh...");
|
|
return {};
|
|
}
|
|
}
|
|
|
|
// Ensure data directory exists
|
|
async function ensureDataDir(): Promise<void> {
|
|
await Bun.write(join(DATA_DIR, ".gitkeep"), "");
|
|
}
|
|
|
|
// Save cache
|
|
async function saveCache(cache: CachedSongs): Promise<void> {
|
|
await ensureDataDir();
|
|
await Bun.write(CACHE_FILE, JSON.stringify(cache, null, 2));
|
|
}
|
|
|
|
// Save song links to JSON
|
|
async function saveSongLinksJSON(songLinks: SongLinksData): Promise<void> {
|
|
await ensureDataDir();
|
|
await Bun.write(OUTPUT_JSON_FILE, JSON.stringify(songLinks, null, 2));
|
|
}
|
|
|
|
// Save song links to TypeScript file
|
|
async function saveSongLinksTS(songLinks: SongLinksData): Promise<void> {
|
|
const tsContent = `// This file is auto-generated by scripts/fetch-song-links.ts
|
|
// Do not edit manually
|
|
// Last updated: ${new Date().toISOString()}
|
|
// Run: bun run fetch-links
|
|
|
|
export interface SongLinks {
|
|
spotify?: string;
|
|
youtube?: string;
|
|
youtubeMusic?: string;
|
|
appleMusic?: string;
|
|
}
|
|
|
|
export interface SongLinksData {
|
|
[songId: string]: SongLinks;
|
|
}
|
|
|
|
export function getSongLink(songId: string): SongLinks {
|
|
return songLinks[songId];
|
|
}
|
|
|
|
export const songLinks: SongLinksData = ${JSON.stringify(songLinks, null, 2)};
|
|
`;
|
|
|
|
await Bun.write(OUTPUT_TS_FILE, tsContent);
|
|
}
|
|
|
|
// Fetch song links from API
|
|
async function fetchSongLinks(spotifyUrl: string, songName: string): Promise<SongLinks | null> {
|
|
try {
|
|
// Encode the Spotify URL
|
|
const encodedUrl = encodeURIComponent(spotifyUrl);
|
|
|
|
// Use the url parameter with songIfSingle for better matching
|
|
const url = `${API_BASE}?url=${encodedUrl}&userCountry=${USER_COUNTRY}&songIfSingle=true`;
|
|
|
|
console.log(` Fetching: ${songName}`);
|
|
console.log(` Spotify URL: ${spotifyUrl}`);
|
|
|
|
const response = await fetch(url);
|
|
|
|
if (!response.ok) {
|
|
const errorText = await response.text();
|
|
console.error(` ❌ Failed to fetch (${response.status}): ${errorText}`);
|
|
return null;
|
|
}
|
|
|
|
const data = await response.json();
|
|
|
|
// Extract links from the response
|
|
const links: SongLinks = {
|
|
spotify: data.linksByPlatform?.spotify?.url || undefined,
|
|
youtube: data.linksByPlatform?.youtube?.url || undefined,
|
|
youtubeMusic: data.linksByPlatform?.youtubeMusic?.url || undefined,
|
|
appleMusic: data.linksByPlatform?.appleMusic?.url || undefined,
|
|
};
|
|
|
|
console.log(` ✓ Success!`);
|
|
return links;
|
|
} catch (error) {
|
|
console.error(` ❌ Error:`, error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// Main function
|
|
async function main() {
|
|
console.log("🎵 Starting song link fetcher...\n");
|
|
|
|
// Load existing data
|
|
const cache = await loadCache();
|
|
const songLinks = await loadExistingSongLinks();
|
|
|
|
let requestCount = 0;
|
|
let successCount = 0;
|
|
let skipCount = 0;
|
|
let failCount = 0;
|
|
|
|
// Process all albums and tracks
|
|
for (const album of albums) {
|
|
console.log(`\n📀 Album: ${album.label}`);
|
|
|
|
for (const track of album.tracks) {
|
|
const songId = track.id;
|
|
const songName = track.label;
|
|
const spotifyUrl = track.__SPOTIFY_URL__;
|
|
|
|
// Skip if already fetched
|
|
if (cache[songId]) {
|
|
console.log(` ⏭️ Skipping: ${songName} (already fetched)`);
|
|
skipCount++;
|
|
continue;
|
|
}
|
|
|
|
// Skip if no Spotify URL
|
|
if (!spotifyUrl) {
|
|
console.log(` ⏭️ Skipping: ${songName} (no Spotify URL)`);
|
|
skipCount++;
|
|
continue;
|
|
}
|
|
|
|
// Add delay before request (except for first request)
|
|
if (requestCount > 0) {
|
|
console.log(` ⏳ Waiting ${RATE_LIMIT_DELAY / 1000}s (rate limit)...`);
|
|
await delay(RATE_LIMIT_DELAY);
|
|
}
|
|
|
|
// Fetch links
|
|
const links = await fetchSongLinks(spotifyUrl, songName);
|
|
|
|
if (links) {
|
|
songLinks[songId] = links;
|
|
cache[songId] = true;
|
|
successCount++;
|
|
|
|
// Save progress after each successful fetch
|
|
await saveSongLinksJSON(songLinks);
|
|
await saveCache(cache);
|
|
} else {
|
|
failCount++;
|
|
}
|
|
|
|
requestCount++;
|
|
}
|
|
}
|
|
|
|
// Save final TypeScript file
|
|
console.log("\n💾 Generating TypeScript file...");
|
|
await saveSongLinksTS(songLinks);
|
|
|
|
// Summary
|
|
console.log("\n" + "=".repeat(50));
|
|
console.log("📊 Summary:");
|
|
console.log(` Total requests: ${requestCount}`);
|
|
console.log(` Successful: ${successCount}`);
|
|
console.log(` Skipped: ${skipCount}`);
|
|
console.log(` Failed: ${failCount}`);
|
|
console.log("=".repeat(50));
|
|
|
|
// Format files with prettier
|
|
console.log("\n🎨 Formatting files with Prettier...");
|
|
try {
|
|
await $`bunx prettier --write ${OUTPUT_TS_FILE} ${OUTPUT_JSON_FILE} ${CACHE_FILE}`;
|
|
console.log(` ✓ Formatted all files`);
|
|
} catch (error) {
|
|
console.error(" ⚠️ Prettier formatting failed:", error);
|
|
}
|
|
|
|
console.log("\n✨ Done!");
|
|
console.log(`\n📁 Output files:`);
|
|
console.log(` - ${OUTPUT_TS_FILE}`);
|
|
console.log(` - ${OUTPUT_JSON_FILE}`);
|
|
console.log(` - ${CACHE_FILE}`);
|
|
}
|
|
|
|
// Run the script
|
|
main().catch((error) => {
|
|
console.error("❌ Fatal error:", error);
|
|
process.exit(1);
|
|
});
|