mirror of
https://github.com/M4X809/list-of-lp.git
synced 2025-12-25 19:12:48 +00:00
Add new scripts and update album data
- Added a new script command "fetch-links" to package.json for fetching song links. - Updated tsconfig.json to include "bun-types" in the types array. - Removed the VIEW_TRANSITIONS_GUIDE.md file. - Modified album track IDs to be more descriptive and added Spotify URLs for each track in list.ts. - Updated ListTypes.ts to include a new field for Spotify URLs in the Track type. - Cleaned up the AlbumDetail component by removing commented-out code for the album header.
This commit is contained in:
parent
6633b7858f
commit
a8097d84fc
12 changed files with 879 additions and 297 deletions
|
|
@ -1,100 +0,0 @@
|
|||
# View Transitions Implementation Guide
|
||||
|
||||
## Overview
|
||||
|
||||
This project now uses the native **View Transitions API** to create smooth animations between pages, including browser back/forward navigation. The implementation is based on best practices from the [nmn.sh](https://github.com/nmn/nmn.sh) repository.
|
||||
|
||||
## What Changed
|
||||
|
||||
### 1. CSS Configuration (`src/index.css`)
|
||||
|
||||
Added comprehensive view transition CSS rules:
|
||||
|
||||
```css
|
||||
/* Enable smooth cross-document view transitions (browser navigation) */
|
||||
@view-transition {
|
||||
navigation: auto;
|
||||
}
|
||||
|
||||
/* Default transition for all elements */
|
||||
::view-transition-old(root),
|
||||
::view-transition-new(root) {
|
||||
animation-duration: 0.3s;
|
||||
animation-timing-function: ease-in-out;
|
||||
}
|
||||
```
|
||||
|
||||
**Key Feature**: `@view-transition { navigation: auto; }` enables transitions to work with native browser navigation (back/forward buttons, URL changes), not just client-side routing.
|
||||
|
||||
### 2. Component Updates
|
||||
|
||||
**AlbumCard.tsx** and **album/[albumId]/page.tsx**:
|
||||
|
||||
Changed from React `ViewTransition` component wrapper:
|
||||
```tsx
|
||||
// ❌ Old approach (only works with client-side routing)
|
||||
<ViewTransition name={`album-card-image-${album.id}`}>
|
||||
<Image src={image} alt={label} />
|
||||
</ViewTransition>
|
||||
```
|
||||
|
||||
To native CSS `viewTransitionName` property:
|
||||
```tsx
|
||||
// ✅ New approach (works with all navigation)
|
||||
<Image
|
||||
src={image}
|
||||
alt={label}
|
||||
style={{ viewTransitionName: `album-card-image-${album.id}` }}
|
||||
/>
|
||||
```
|
||||
|
||||
### 3. TypeScript Support
|
||||
|
||||
Added type definitions in `src/types/view-transitions.d.ts` to extend React's `CSSProperties` interface with `viewTransitionName` support.
|
||||
|
||||
## How It Works
|
||||
|
||||
### Client-Side Routing
|
||||
- Uses `next-view-transitions` package
|
||||
- `<Link>` component from the package automatically triggers view transitions
|
||||
- Smooth animations between pages when clicking links
|
||||
|
||||
### Browser Navigation (Back/Forward)
|
||||
- Native `@view-transition { navigation: auto; }` CSS rule
|
||||
- Browser automatically captures before/after states
|
||||
- Smooth transitions even with browser buttons or URL changes
|
||||
- **No JavaScript required** for basic transitions
|
||||
|
||||
### Named Transitions
|
||||
Elements with matching `viewTransitionName` values will morph smoothly between pages:
|
||||
- Album images: `album-card-image-{id}`
|
||||
- Album titles: `album-card-title-{id}`
|
||||
- Release dates: `album-card-release-date-{id}`
|
||||
|
||||
## Browser Support
|
||||
|
||||
- **Chrome/Edge**: Full support (v111+)
|
||||
- **Safari**: Support in v18+ (macOS Sonoma, iOS 17)
|
||||
- **Firefox**: In development
|
||||
- **Fallback**: Graceful degradation to instant navigation
|
||||
|
||||
## Testing
|
||||
|
||||
1. **Client-side navigation**: Click any album card → smooth transition
|
||||
2. **Browser back button**: Press back → smooth transition
|
||||
3. **Direct URL change**: Type URL directly → smooth transition on supported browsers
|
||||
|
||||
## Benefits Over Previous Implementation
|
||||
|
||||
1. ✅ **Works with browser navigation** (back/forward buttons)
|
||||
2. ✅ **Works with direct URL changes**
|
||||
3. ✅ **Better performance** (native browser API)
|
||||
4. ✅ **More reliable** (less JavaScript, more standards-based)
|
||||
5. ✅ **Progressive enhancement** (works without JS)
|
||||
|
||||
## References
|
||||
|
||||
- [View Transitions API (MDN)](https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API)
|
||||
- [nmn.sh repository](https://github.com/nmn/nmn.sh)
|
||||
- [Chrome View Transitions Guide](https://developer.chrome.com/docs/web-platform/view-transitions/)
|
||||
|
||||
0
data/.gitkeep
Normal file
0
data/.gitkeep
Normal file
27
data/fetched-songs.json
Normal file
27
data/fetched-songs.json
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"hybrid-theory-1": true,
|
||||
"hybrid-theory-2": true,
|
||||
"hybrid-theory-3": true,
|
||||
"hybrid-theory-4": true,
|
||||
"hybrid-theory-5": true,
|
||||
"hybrid-theory-6": true,
|
||||
"hybrid-theory-7": true,
|
||||
"hybrid-theory-8": true,
|
||||
"hybrid-theory-9": true,
|
||||
"hybrid-theory-10": true,
|
||||
"hybrid-theory-11": true,
|
||||
"hybrid-theory-12": true,
|
||||
"meteora-1": true,
|
||||
"meteora-2": true,
|
||||
"meteora-3": true,
|
||||
"meteora-4": true,
|
||||
"meteora-5": true,
|
||||
"meteora-6": true,
|
||||
"meteora-7": true,
|
||||
"meteora-8": true,
|
||||
"meteora-9": true,
|
||||
"meteora-10": true,
|
||||
"meteora-11": true,
|
||||
"meteora-12": true,
|
||||
"meteora-13": true
|
||||
}
|
||||
152
data/song-links.json
Normal file
152
data/song-links.json
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
{
|
||||
"hybrid-theory-1": {
|
||||
"spotify": "https://open.spotify.com/track/4HvcbAR8LisalXKt7cpqE1",
|
||||
"youtube": "https://www.youtube.com/watch?v=vjVkXlxsO8Q",
|
||||
"youtubeMusic": "https://music.youtube.com/watch?v=vjVkXlxsO8Q",
|
||||
"appleMusic": "https://geo.music.apple.com/de/album/_/528436018?i=528436568&mt=1&app=music&ls=1&at=1000lHKX&ct=api_http&itscg=30200&itsct=odsl_m"
|
||||
},
|
||||
"hybrid-theory-2": {
|
||||
"spotify": "https://open.spotify.com/track/4bYLTrlcqctyHck3fjhMgW",
|
||||
"youtube": "https://www.youtube.com/watch?v=4qlCC1GOwFw",
|
||||
"youtubeMusic": "https://music.youtube.com/watch?v=4qlCC1GOwFw",
|
||||
"appleMusic": "https://geo.music.apple.com/de/album/_/590431776?i=590431778&mt=1&app=music&ls=1&at=1000lHKX&ct=api_http&itscg=30200&itsct=odsl_m"
|
||||
},
|
||||
"hybrid-theory-3": {
|
||||
"spotify": "https://open.spotify.com/track/1Q9QN6dHEDeUegsySY012f",
|
||||
"youtube": "https://www.youtube.com/watch?v=M8UTS2iFXOo",
|
||||
"youtubeMusic": "https://music.youtube.com/watch?v=M8UTS2iFXOo",
|
||||
"appleMusic": "https://geo.music.apple.com/de/album/_/528436018?i=528437421&mt=1&app=music&ls=1&at=1000lHKX&ct=api_http&itscg=30200&itsct=odsl_m"
|
||||
},
|
||||
"hybrid-theory-4": {
|
||||
"spotify": "https://open.spotify.com/track/214LJVTqrkUOGemWDGHJpr",
|
||||
"youtube": "https://www.youtube.com/watch?v=jZSPAp8kCl4",
|
||||
"youtubeMusic": "https://music.youtube.com/watch?v=jZSPAp8kCl4",
|
||||
"appleMusic": "https://geo.music.apple.com/de/album/_/590431776?i=590431781&mt=1&app=music&ls=1&at=1000lHKX&ct=api_http&itscg=30200&itsct=odsl_m"
|
||||
},
|
||||
"hybrid-theory-5": {
|
||||
"spotify": "https://open.spotify.com/track/0X576XiCW9uEtko6f6VkcU",
|
||||
"youtube": "https://www.youtube.com/watch?v=Gd9OhYroLN0",
|
||||
"youtubeMusic": "https://music.youtube.com/watch?v=Gd9OhYroLN0",
|
||||
"appleMusic": "https://geo.music.apple.com/de/album/_/591534774?i=591534780&mt=1&app=music&ls=1&at=1000lHKX&ct=api_http&itscg=30200&itsct=odsl_m"
|
||||
},
|
||||
"hybrid-theory-6": {
|
||||
"spotify": "https://open.spotify.com/track/5tkHje8FGSF258gi869GcV",
|
||||
"youtube": "https://www.youtube.com/watch?v=ig-fyQqf510",
|
||||
"youtubeMusic": "https://music.youtube.com/watch?v=ig-fyQqf510",
|
||||
"appleMusic": "https://geo.music.apple.com/de/album/_/528436018?i=528437609&mt=1&app=music&ls=1&at=1000lHKX&ct=api_http&itscg=30200&itsct=odsl_m"
|
||||
},
|
||||
"hybrid-theory-7": {
|
||||
"spotify": "https://open.spotify.com/track/4SJoEhpI6C61wtUfxSHMGc",
|
||||
"youtube": "https://www.youtube.com/watch?v=wWBp-nlGX1o",
|
||||
"youtubeMusic": "https://music.youtube.com/watch?v=wWBp-nlGX1o",
|
||||
"appleMusic": "https://geo.music.apple.com/de/album/_/528436018?i=528437611&mt=1&app=music&ls=1&at=1000lHKX&ct=api_http&itscg=30200&itsct=odsl_m"
|
||||
},
|
||||
"hybrid-theory-8": {
|
||||
"spotify": "https://open.spotify.com/track/7q115ia4fQn9zonjpexWsY",
|
||||
"youtube": "https://www.youtube.com/watch?v=eVTXPUF4Oz4",
|
||||
"youtubeMusic": "https://music.youtube.com/watch?v=eVTXPUF4Oz4",
|
||||
"appleMusic": "https://geo.music.apple.com/de/album/_/528436018?i=528437613&mt=1&app=music&ls=1&at=1000lHKX&ct=api_http&itscg=30200&itsct=odsl_m"
|
||||
},
|
||||
"hybrid-theory-9": {
|
||||
"spotify": "https://open.spotify.com/track/1B8WdDScvobpFsZLfdmIE1",
|
||||
"youtube": "https://www.youtube.com/watch?v=3t2WkCudwfY",
|
||||
"youtubeMusic": "https://music.youtube.com/watch?v=3t2WkCudwfY",
|
||||
"appleMusic": "https://geo.music.apple.com/de/album/_/528436018?i=528437708&mt=1&app=music&ls=1&at=1000lHKX&ct=api_http&itscg=30200&itsct=odsl_m"
|
||||
},
|
||||
"hybrid-theory-10": {
|
||||
"spotify": "https://open.spotify.com/track/3JAu2BH8sSI46rJGzQerb6",
|
||||
"youtube": "https://www.youtube.com/watch?v=HNCgBuI2eJc",
|
||||
"youtubeMusic": "https://music.youtube.com/watch?v=HNCgBuI2eJc",
|
||||
"appleMusic": "https://geo.music.apple.com/de/album/_/528436018?i=528437709&mt=1&app=music&ls=1&at=1000lHKX&ct=api_http&itscg=30200&itsct=odsl_m"
|
||||
},
|
||||
"hybrid-theory-11": {
|
||||
"spotify": "https://open.spotify.com/track/15voDM9pGkwEvHBUecpcmo",
|
||||
"youtube": "https://www.youtube.com/watch?v=qqC5sdsHLq8",
|
||||
"youtubeMusic": "https://music.youtube.com/watch?v=qqC5sdsHLq8",
|
||||
"appleMusic": "https://geo.music.apple.com/de/album/_/528436018?i=528437710&mt=1&app=music&ls=1&at=1000lHKX&ct=api_http&itscg=30200&itsct=odsl_m"
|
||||
},
|
||||
"hybrid-theory-12": {
|
||||
"spotify": "https://open.spotify.com/track/4HlMOXxwF9SYG1RoJxEjio",
|
||||
"youtube": "https://www.youtube.com/watch?v=Ve1LNJEIKUE",
|
||||
"youtubeMusic": "https://music.youtube.com/watch?v=Ve1LNJEIKUE",
|
||||
"appleMusic": "https://geo.music.apple.com/de/album/_/590431776?i=590431790&mt=1&app=music&ls=1&at=1000lHKX&ct=api_http&itscg=30200&itsct=odsl_m"
|
||||
},
|
||||
"meteora-1": {
|
||||
"spotify": "https://open.spotify.com/track/4hbpxoMCp62bd1sdy7XehS",
|
||||
"youtube": "https://www.youtube.com/watch?v=U6R-twDkrcI",
|
||||
"youtubeMusic": "https://music.youtube.com/watch?v=U6R-twDkrcI",
|
||||
"appleMusic": "https://geo.music.apple.com/de/album/_/590423275?i=590423280&mt=1&app=music&ls=1&at=1000lHKX&ct=api_http&itscg=30200&itsct=odsl_m"
|
||||
},
|
||||
"meteora-2": {
|
||||
"spotify": "https://open.spotify.com/track/6HZdbb05lEXLvcmee3ZXO2",
|
||||
"youtube": "https://www.youtube.com/watch?v=oWfGOVWrueo",
|
||||
"youtubeMusic": "https://music.youtube.com/watch?v=oWfGOVWrueo",
|
||||
"appleMusic": "https://geo.music.apple.com/de/album/_/1668484895?i=1668485277&mt=1&app=music&ls=1&at=1000lHKX&ct=api_http&itscg=30200&itsct=odsl_m"
|
||||
},
|
||||
"meteora-3": {
|
||||
"spotify": "https://open.spotify.com/track/3agtg0x11wPvLIWkYR39nZ",
|
||||
"youtube": "https://www.youtube.com/watch?v=zsCD5XCu6CM",
|
||||
"youtubeMusic": "https://music.youtube.com/watch?v=zsCD5XCu6CM",
|
||||
"appleMusic": "https://geo.music.apple.com/de/album/_/528435845?i=528437020&mt=1&app=music&ls=1&at=1000lHKX&ct=api_http&itscg=30200&itsct=odsl_m"
|
||||
},
|
||||
"meteora-4": {
|
||||
"spotify": "https://open.spotify.com/track/4zP2e2aIzOZGEFTq1MDJmm",
|
||||
"youtube": "https://www.youtube.com/watch?v=NjdgcHdzvac",
|
||||
"youtubeMusic": "https://music.youtube.com/watch?v=NjdgcHdzvac",
|
||||
"appleMusic": "https://geo.music.apple.com/de/album/_/590423275?i=590423283&mt=1&app=music&ls=1&at=1000lHKX&ct=api_http&itscg=30200&itsct=odsl_m"
|
||||
},
|
||||
"meteora-5": {
|
||||
"spotify": "https://open.spotify.com/track/3R9NKZ4jmxNjOFihSF5s7f",
|
||||
"youtube": "https://www.youtube.com/watch?v=oMals9XXQY8",
|
||||
"youtubeMusic": "https://music.youtube.com/watch?v=oMals9XXQY8",
|
||||
"appleMusic": "https://geo.music.apple.com/de/album/_/590423275?i=590423284&mt=1&app=music&ls=1&at=1000lHKX&ct=api_http&itscg=30200&itsct=odsl_m"
|
||||
},
|
||||
"meteora-6": {
|
||||
"spotify": "https://open.spotify.com/track/17bgialGAwoiGj1STY4cnR",
|
||||
"youtube": "https://www.youtube.com/watch?v=U5zdmjVeQzE",
|
||||
"youtubeMusic": "https://music.youtube.com/watch?v=U5zdmjVeQzE",
|
||||
"appleMusic": "https://geo.music.apple.com/de/album/_/590423275?i=590423285&mt=1&app=music&ls=1&at=1000lHKX&ct=api_http&itscg=30200&itsct=odsl_m"
|
||||
},
|
||||
"meteora-7": {
|
||||
"spotify": "https://open.spotify.com/track/4Yf5bqU3NK4kNOypcrLYwU",
|
||||
"youtube": "https://www.youtube.com/watch?v=LYU-8IFcDPw",
|
||||
"youtubeMusic": "https://music.youtube.com/watch?v=LYU-8IFcDPw",
|
||||
"appleMusic": "https://geo.music.apple.com/de/album/_/528435845?i=528437024&mt=1&app=music&ls=1&at=1000lHKX&ct=api_http&itscg=30200&itsct=odsl_m"
|
||||
},
|
||||
"meteora-8": {
|
||||
"spotify": "https://open.spotify.com/track/2DrMcGNKEY0FZqjMovGaCm",
|
||||
"youtube": "https://www.youtube.com/watch?v=6dEAeCHQrBs",
|
||||
"youtubeMusic": "https://music.youtube.com/watch?v=6dEAeCHQrBs",
|
||||
"appleMusic": "https://geo.music.apple.com/de/album/_/528435845?i=528437025&mt=1&app=music&ls=1&at=1000lHKX&ct=api_http&itscg=30200&itsct=odsl_m"
|
||||
},
|
||||
"meteora-9": {
|
||||
"spotify": "https://open.spotify.com/track/6n8TMVyFKoUmDc4apxceRD",
|
||||
"youtube": "https://www.youtube.com/watch?v=v2H4l9RpkwM",
|
||||
"youtubeMusic": "https://music.youtube.com/watch?v=v2H4l9RpkwM",
|
||||
"appleMusic": "https://geo.music.apple.com/de/album/_/528435845?i=528437026&mt=1&app=music&ls=1&at=1000lHKX&ct=api_http&itscg=30200&itsct=odsl_m"
|
||||
},
|
||||
"meteora-10": {
|
||||
"spotify": "https://open.spotify.com/track/60IkVf7UfQXmt5CwkpcX8a",
|
||||
"youtube": "https://www.youtube.com/watch?v=YLHpvjrFpe0",
|
||||
"youtubeMusic": "https://music.youtube.com/watch?v=YLHpvjrFpe0",
|
||||
"appleMusic": "https://geo.music.apple.com/de/album/_/528435845?i=528437027&mt=1&app=music&ls=1&at=1000lHKX&ct=api_http&itscg=30200&itsct=odsl_m"
|
||||
},
|
||||
"meteora-11": {
|
||||
"spotify": "https://open.spotify.com/track/4m1P1BSg9nLxaWbvPhNdhw",
|
||||
"youtube": "https://www.youtube.com/watch?v=QJ87793QXes",
|
||||
"youtubeMusic": "https://music.youtube.com/watch?v=QJ87793QXes",
|
||||
"appleMusic": "https://geo.music.apple.com/de/album/_/590423275?i=590423550&mt=1&app=music&ls=1&at=1000lHKX&ct=api_http&itscg=30200&itsct=odsl_m"
|
||||
},
|
||||
"meteora-12": {
|
||||
"spotify": "https://open.spotify.com/track/3sbezh9MMCQrr4KY7zw13j",
|
||||
"youtube": "https://www.youtube.com/watch?v=J1KqQYsUYIk",
|
||||
"youtubeMusic": "https://music.youtube.com/watch?v=J1KqQYsUYIk",
|
||||
"appleMusic": "https://geo.music.apple.com/de/album/_/528435845?i=528437513&mt=1&app=music&ls=1&at=1000lHKX&ct=api_http&itscg=30200&itsct=odsl_m"
|
||||
},
|
||||
"meteora-13": {
|
||||
"spotify": "https://open.spotify.com/track/2nLtzopw4rPReszdYBJU6h",
|
||||
"youtube": "https://www.youtube.com/watch?v=kXYiU_JCYtU",
|
||||
"youtubeMusic": "https://music.youtube.com/watch?v=kXYiU_JCYtU",
|
||||
"appleMusic": "https://geo.music.apple.com/de/album/_/528435845?i=528437514&mt=1&app=music&ls=1&at=1000lHKX&ct=api_http&itscg=30200&itsct=odsl_m"
|
||||
}
|
||||
}
|
||||
|
|
@ -7,7 +7,8 @@
|
|||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "eslint ."
|
||||
"lint": "eslint .",
|
||||
"fetch-links": "bun run scripts/fetch-song-links.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@mantine/carousel": "^8.3.5",
|
||||
|
|
@ -23,6 +24,7 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.36.0",
|
||||
"@types/bun": "^1.3.1",
|
||||
"@types/node": "^24.6.0",
|
||||
"@types/react": "^19.2.2",
|
||||
"@types/react-dom": "^19.2.2",
|
||||
|
|
|
|||
74
scripts/README.md
Normal file
74
scripts/README.md
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
# Song Links Fetcher
|
||||
|
||||
This script fetches streaming links (Spotify, YouTube, YouTube Music, Apple Music) for all Linkin Park songs in the project using the SongLink API.
|
||||
|
||||
## Features
|
||||
|
||||
- 🎵 Fetches links for all songs from the `list.ts` file using their Spotify URLs
|
||||
- ⏱️ Respects API rate limit (10 requests per minute)
|
||||
- 💾 Caches already-fetched songs to avoid redundant API calls
|
||||
- 📁 Outputs both TypeScript and JSON files
|
||||
- 📊 Shows progress and summary statistics
|
||||
- ⏭️ Automatically skips songs without Spotify URLs
|
||||
- 🎨 Automatically formats all generated files with Prettier
|
||||
|
||||
## Usage
|
||||
|
||||
Run the script with:
|
||||
|
||||
```bash
|
||||
bun run fetch-links
|
||||
```
|
||||
|
||||
## Output Files
|
||||
|
||||
1. **`src/lib/songLinks.ts`** - TypeScript file with exported `songLinks` object
|
||||
2. **`data/song-links.json`** - JSON file with all song links
|
||||
3. **`data/fetched-songs.json`** - Cache file tracking which songs have been fetched
|
||||
|
||||
## Data Structure
|
||||
|
||||
The generated TypeScript file exports a `songLinks` object with this structure:
|
||||
|
||||
```typescript
|
||||
{
|
||||
"hybrid-theory-1": {
|
||||
"spotify": "https://open.spotify.com/track/...",
|
||||
"youtube": "https://www.youtube.com/watch?v=...",
|
||||
"youtubeMusic": "https://music.youtube.com/watch?v=...",
|
||||
"appleMusic": "https://music.apple.com/..."
|
||||
},
|
||||
// ... more songs
|
||||
}
|
||||
```
|
||||
|
||||
## API Information
|
||||
|
||||
- **API**: [SongLink/Odesli API](https://linktree.notion.site/API-d0ebe08a5e304a55928405eb682f6741)
|
||||
- **Endpoint**: `https://api.song.link/v1-alpha.1/links`
|
||||
- **Parameters**:
|
||||
- `url`: Spotify track URL (encoded)
|
||||
- `userCountry`: DE (Germany)
|
||||
- `songIfSingle`: true (for better matching)
|
||||
- **Rate Limit**: 10 requests per minute
|
||||
- **Delay between requests**: 6 seconds
|
||||
|
||||
## Requirements
|
||||
|
||||
Each track in `list.ts` must have a `__SPOTIFY_URL__` field with a valid Spotify track URL. Tracks without this field will be automatically skipped.
|
||||
|
||||
## Resumable
|
||||
|
||||
If the script is interrupted or fails for any song, you can simply run it again. It will:
|
||||
- Skip already-fetched songs (based on the cache file)
|
||||
- Continue fetching remaining songs
|
||||
- Preserve all previously fetched data
|
||||
|
||||
## Clearing Cache
|
||||
|
||||
To re-fetch all songs from scratch, delete the cache file:
|
||||
|
||||
```bash
|
||||
rm data/fetched-songs.json
|
||||
```
|
||||
|
||||
243
scripts/fetch-song-links.ts
Normal file
243
scripts/fetch-song-links.ts
Normal file
|
|
@ -0,0 +1,243 @@
|
|||
#!/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 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);
|
||||
});
|
||||
|
|
@ -39,7 +39,7 @@ export default async function AlbumDetail({ params }: { params: Promise<{ albumI
|
|||
const theme = getThemeColors(albumId);
|
||||
|
||||
return (
|
||||
<Box className="min-h-screen bg-gray-900">
|
||||
<Box className="min-h-screen">
|
||||
<Container size="xl" className="py-12">
|
||||
{/* Back Button */}
|
||||
<BackButton />
|
||||
|
|
@ -76,69 +76,6 @@ export default async function AlbumDetail({ params }: { params: Promise<{ albumI
|
|||
</GridCol>
|
||||
</Grid>
|
||||
|
||||
{/* Album Header */}
|
||||
{/* <Box className="mb-12 grid grid-cols-1 gap-12 lg:grid-cols-3">
|
||||
{/* Album Cover */}
|
||||
{/* <Box className="lg:col-span-1">
|
||||
<Card className="overflow-hidden border border-gray-700 bg-gray-800/50 backdrop-blur-sm">
|
||||
<Image
|
||||
src={album.image}
|
||||
alt={album.label}
|
||||
width={250}
|
||||
height={250}
|
||||
className="transition-all duration-700 ease-out"
|
||||
/>
|
||||
</Card>
|
||||
</Box> */}
|
||||
|
||||
{/* Album Info */}
|
||||
{/* <Box className="space-y-6 lg:col-span-2">
|
||||
<Stack gap="md">
|
||||
<Title order={1} className="text-6xl font-bold text-white">
|
||||
{album.label}
|
||||
</Title>
|
||||
|
||||
<Text size="xl" className="text-gray-400">
|
||||
Released:{" "}
|
||||
{new Date(album.releaseDate).toLocaleDateString("en-US", {
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
})}
|
||||
</Text>
|
||||
|
||||
<Group gap="md">
|
||||
<Badge
|
||||
size="lg"
|
||||
style={{
|
||||
backgroundColor: theme.primary,
|
||||
color: "white",
|
||||
}}
|
||||
>
|
||||
{album.tracks.length} Songs
|
||||
</Badge>
|
||||
<Badge
|
||||
size="lg"
|
||||
variant="light"
|
||||
style={{
|
||||
backgroundColor: `${theme.secondary}20`,
|
||||
color: theme.secondary,
|
||||
borderColor: theme.secondary,
|
||||
}}
|
||||
>
|
||||
Linkin Park
|
||||
</Badge>
|
||||
</Group>
|
||||
</Stack>
|
||||
|
||||
<Divider color={theme.primary} />
|
||||
|
||||
<Text size="lg" className="leading-relaxed text-gray-300">
|
||||
{album.description}
|
||||
</Text>
|
||||
</Box> */}
|
||||
{/* </Box> */}
|
||||
|
||||
{/* Track List */}
|
||||
{album.tracks.length > 0 && (
|
||||
<Box>
|
||||
|
|
|
|||
|
|
@ -5,6 +5,9 @@ export type Track = {
|
|||
studioUrl: string | null;
|
||||
emilyLiveUrl: string | null;
|
||||
lpLiveUrl: string | null;
|
||||
|
||||
// this is the spotify url for the album
|
||||
__SPOTIFY_URL__: string;
|
||||
};
|
||||
|
||||
export type Album = {
|
||||
|
|
|
|||
314
src/lib/list.ts
314
src/lib/list.ts
|
|
@ -11,100 +11,124 @@ export const albums: Album[] = [
|
|||
"Hybrid Theory is the debut studio album by American rock band Linkin Park, released on October 24, 2000, by Warner Bros. Records. It was recorded at The Plant Studios in Sausalito, California, and produced by Don Gilmore. The album was a commercial success, reaching number one on the Billboard 200 chart and selling over 10 million copies in the United States alone.",
|
||||
tracks: [
|
||||
{
|
||||
id: "1",
|
||||
id: "hybrid-theory-1",
|
||||
label: "Papercut",
|
||||
duration: "03:04",
|
||||
studioUrl: null,
|
||||
emilyLiveUrl: null,
|
||||
lpLiveUrl: null,
|
||||
|
||||
__SPOTIFY_URL__: "https://open.spotify.com/track/4HvcbAR8LisalXKt7cpqE1?si=a736ed41b9e84ff9",
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
id: "hybrid-theory-2",
|
||||
label: "One Step Closer",
|
||||
duration: "02:35",
|
||||
studioUrl: null,
|
||||
emilyLiveUrl: null,
|
||||
lpLiveUrl: null,
|
||||
|
||||
__SPOTIFY_URL__: "https://open.spotify.com/track/4bYLTrlcqctyHck3fjhMgW?si=dd2c47138fed4c4c",
|
||||
},
|
||||
{
|
||||
id: "3",
|
||||
id: "hybrid-theory-3",
|
||||
label: "With You",
|
||||
duration: "03:23",
|
||||
studioUrl: null,
|
||||
emilyLiveUrl: null,
|
||||
lpLiveUrl: null,
|
||||
|
||||
__SPOTIFY_URL__: "https://open.spotify.com/track/1Q9QN6dHEDeUegsySY012f?si=d4a1aca393954caa",
|
||||
},
|
||||
{
|
||||
id: "4",
|
||||
id: "hybrid-theory-4",
|
||||
label: "Points of Authority",
|
||||
duration: "03:20",
|
||||
studioUrl: null,
|
||||
emilyLiveUrl: null,
|
||||
lpLiveUrl: null,
|
||||
|
||||
__SPOTIFY_URL__: "https://open.spotify.com/track/214LJVTqrkUOGemWDGHJpr?si=749f11d7cd9a4f56",
|
||||
},
|
||||
{
|
||||
id: "5",
|
||||
id: "hybrid-theory-5",
|
||||
label: "Crawling",
|
||||
duration: "03:29",
|
||||
studioUrl: null,
|
||||
emilyLiveUrl: null,
|
||||
lpLiveUrl: null,
|
||||
|
||||
__SPOTIFY_URL__: "https://open.spotify.com/track/0X576XiCW9uEtko6f6VkcU?si=0a47dc1d660b4c71",
|
||||
},
|
||||
{
|
||||
id: "6",
|
||||
id: "hybrid-theory-6",
|
||||
label: "Runaway",
|
||||
duration: "03:03",
|
||||
studioUrl: null,
|
||||
emilyLiveUrl: null,
|
||||
lpLiveUrl: null,
|
||||
|
||||
__SPOTIFY_URL__: "https://open.spotify.com/track/5tkHje8FGSF258gi869GcV?si=f69a7e1ebf0f4f31",
|
||||
},
|
||||
{
|
||||
id: "7",
|
||||
id: "hybrid-theory-7",
|
||||
label: "By Myself",
|
||||
duration: "03:09",
|
||||
studioUrl: null,
|
||||
emilyLiveUrl: null,
|
||||
lpLiveUrl: null,
|
||||
|
||||
__SPOTIFY_URL__: "https://open.spotify.com/track/4SJoEhpI6C61wtUfxSHMGc?si=4ca78fd74ae74d6f",
|
||||
},
|
||||
{
|
||||
id: "8",
|
||||
id: "hybrid-theory-8",
|
||||
label: "In the End",
|
||||
duration: "03:36",
|
||||
studioUrl: null,
|
||||
emilyLiveUrl: null,
|
||||
lpLiveUrl: null,
|
||||
|
||||
__SPOTIFY_URL__: "https://open.spotify.com/track/7q115ia4fQn9zonjpexWsY?si=1e4cb0fe76244b11",
|
||||
},
|
||||
{
|
||||
id: "9",
|
||||
id: "hybrid-theory-9",
|
||||
label: "A Place for My Head",
|
||||
duration: "03:04",
|
||||
studioUrl: null,
|
||||
emilyLiveUrl: null,
|
||||
lpLiveUrl: null,
|
||||
|
||||
__SPOTIFY_URL__: "https://open.spotify.com/track/1B8WdDScvobpFsZLfdmIE1?si=f1b5b6fe0abb4e23",
|
||||
},
|
||||
{
|
||||
id: "10",
|
||||
id: "hybrid-theory-10",
|
||||
label: "Forgotten",
|
||||
duration: "03:04",
|
||||
studioUrl: null,
|
||||
emilyLiveUrl: null,
|
||||
lpLiveUrl: null,
|
||||
|
||||
__SPOTIFY_URL__: "https://open.spotify.com/track/3JAu2BH8sSI46rJGzQerb6?si=92186e546f0049b9",
|
||||
},
|
||||
{
|
||||
id: "11",
|
||||
id: "hybrid-theory-11",
|
||||
label: "Cure for the Itch",
|
||||
duration: "02:37",
|
||||
studioUrl: null,
|
||||
emilyLiveUrl: null,
|
||||
lpLiveUrl: null,
|
||||
|
||||
__SPOTIFY_URL__: "https://open.spotify.com/track/15voDM9pGkwEvHBUecpcmo?si=0b50fb655a2e4649",
|
||||
},
|
||||
{
|
||||
id: "12",
|
||||
id: "hybrid-theory-12",
|
||||
label: "Pushing Me Away",
|
||||
duration: "03:11",
|
||||
studioUrl: null,
|
||||
emilyLiveUrl: null,
|
||||
lpLiveUrl: null,
|
||||
|
||||
__SPOTIFY_URL__: "https://open.spotify.com/track/4HlMOXxwF9SYG1RoJxEjio?si=1b9286e4d2c947d3",
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
@ -118,216 +142,242 @@ export const albums: Album[] = [
|
|||
"Meteora is the second studio album by American rock band Linkin Park, released on March 24, 2003, by Warner Bros. Records. It was recorded at The Plant Studios in Sausalito, California, and produced by Don Gilmore. The album was a commercial success, reaching number one on the Billboard 200 chart and selling over 10 million copies in the United States alone.",
|
||||
tracks: [
|
||||
{
|
||||
id: "1",
|
||||
id: "meteora-1",
|
||||
label: "Foreword",
|
||||
duration: "00:13",
|
||||
studioUrl: null,
|
||||
emilyLiveUrl: null,
|
||||
lpLiveUrl: null,
|
||||
|
||||
__SPOTIFY_URL__: "https://open.spotify.com/track/4hbpxoMCp62bd1sdy7XehS?si=0c4b2f93b36e43e7",
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
id: "meteora-2",
|
||||
label: "Don't Stay",
|
||||
duration: "03:07",
|
||||
studioUrl: null,
|
||||
emilyLiveUrl: null,
|
||||
lpLiveUrl: null,
|
||||
|
||||
__SPOTIFY_URL__: "https://open.spotify.com/track/6HZdbb05lEXLvcmee3ZXO2?si=b1f8362c72694a1b",
|
||||
},
|
||||
{
|
||||
id: "3",
|
||||
id: "meteora-3",
|
||||
label: "Somewhere I Belong",
|
||||
duration: "03:33",
|
||||
studioUrl: null,
|
||||
emilyLiveUrl: null,
|
||||
lpLiveUrl: null,
|
||||
|
||||
__SPOTIFY_URL__: "https://open.spotify.com/track/3agtg0x11wPvLIWkYR39nZ?si=e79dfe0143374cd9",
|
||||
},
|
||||
{
|
||||
id: "4",
|
||||
id: "meteora-4",
|
||||
label: "Lying from You",
|
||||
duration: "02:55",
|
||||
studioUrl: null,
|
||||
emilyLiveUrl: null,
|
||||
lpLiveUrl: null,
|
||||
|
||||
__SPOTIFY_URL__: "https://open.spotify.com/track/4zP2e2aIzOZGEFTq1MDJmm?si=a7951a3a01e740ac",
|
||||
},
|
||||
{
|
||||
id: "5",
|
||||
id: "meteora-5",
|
||||
label: "Hit the Floor",
|
||||
duration: "02:44",
|
||||
studioUrl: null,
|
||||
emilyLiveUrl: null,
|
||||
lpLiveUrl: null,
|
||||
|
||||
__SPOTIFY_URL__: "https://open.spotify.com/track/3R9NKZ4jmxNjOFihSF5s7f?si=3f07863add1e4e0f",
|
||||
},
|
||||
{
|
||||
id: "6",
|
||||
id: "meteora-6",
|
||||
label: "Easier to Run",
|
||||
duration: "03:24",
|
||||
studioUrl: null,
|
||||
emilyLiveUrl: null,
|
||||
lpLiveUrl: null,
|
||||
|
||||
__SPOTIFY_URL__: "https://open.spotify.com/track/17bgialGAwoiGj1STY4cnR?si=679a5b245d1d4610",
|
||||
},
|
||||
{
|
||||
id: "7",
|
||||
id: "meteora-7",
|
||||
label: "Faint",
|
||||
duration: "02:42",
|
||||
studioUrl: null,
|
||||
emilyLiveUrl: null,
|
||||
lpLiveUrl: null,
|
||||
|
||||
__SPOTIFY_URL__: "https://open.spotify.com/track/4Yf5bqU3NK4kNOypcrLYwU?si=cf2f7362cff04704",
|
||||
},
|
||||
{
|
||||
id: "8",
|
||||
id: "meteora-8",
|
||||
label: "Figure.09",
|
||||
duration: "03:17",
|
||||
studioUrl: null,
|
||||
emilyLiveUrl: null,
|
||||
lpLiveUrl: null,
|
||||
|
||||
__SPOTIFY_URL__: "https://open.spotify.com/track/2DrMcGNKEY0FZqjMovGaCm?si=583eb32931e84c48",
|
||||
},
|
||||
{
|
||||
id: "9",
|
||||
id: "meteora-9",
|
||||
label: "Breaking the Habit",
|
||||
duration: "03:16",
|
||||
studioUrl: null,
|
||||
emilyLiveUrl: null,
|
||||
lpLiveUrl: null,
|
||||
|
||||
__SPOTIFY_URL__: "https://open.spotify.com/track/6n8TMVyFKoUmDc4apxceRD?si=2b17f6b1906f46ec",
|
||||
},
|
||||
{
|
||||
id: "10",
|
||||
id: "meteora-10",
|
||||
label: "From the Inside",
|
||||
duration: "02:55",
|
||||
studioUrl: null,
|
||||
emilyLiveUrl: null,
|
||||
lpLiveUrl: null,
|
||||
|
||||
__SPOTIFY_URL__: "https://open.spotify.com/track/60IkVf7UfQXmt5CwkpcX8a?si=56ad75e64c174188",
|
||||
},
|
||||
{
|
||||
id: "11",
|
||||
id: "meteora-11",
|
||||
label: "Nobody's Listening",
|
||||
duration: "02:58",
|
||||
studioUrl: null,
|
||||
emilyLiveUrl: null,
|
||||
lpLiveUrl: null,
|
||||
|
||||
__SPOTIFY_URL__: "https://open.spotify.com/track/4m1P1BSg9nLxaWbvPhNdhw?si=9671f8d0299f429f",
|
||||
},
|
||||
{
|
||||
id: "12",
|
||||
id: "meteora-12",
|
||||
label: "Session",
|
||||
duration: "02:24",
|
||||
studioUrl: null,
|
||||
emilyLiveUrl: null,
|
||||
lpLiveUrl: null,
|
||||
|
||||
__SPOTIFY_URL__: "https://open.spotify.com/track/3sbezh9MMCQrr4KY7zw13j?si=62626db46f734d63",
|
||||
},
|
||||
{
|
||||
id: "13",
|
||||
id: "meteora-13",
|
||||
label: "Numb",
|
||||
duration: "03:05",
|
||||
studioUrl: null,
|
||||
emilyLiveUrl: null,
|
||||
lpLiveUrl: null,
|
||||
|
||||
__SPOTIFY_URL__: "https://open.spotify.com/track/2nLtzopw4rPReszdYBJU6h?si=8023141252ac4fec",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "minutes-to-midnight",
|
||||
label: "Minutes to Midnight",
|
||||
releaseDate: "2007-05-14",
|
||||
image: "/minutes_to_midnight.jpg",
|
||||
url: "/minutes-to-midnight",
|
||||
description:
|
||||
"Minutes to Midnight is the third studio album by American rock band Linkin Park, released on May 14, 2007, by Warner Bros. Records. The album marked a departure from the band's previous nu-metal sound, incorporating more alternative rock and experimental elements.",
|
||||
tracks: [
|
||||
{
|
||||
id: "1",
|
||||
label: "Wake",
|
||||
duration: "01:40",
|
||||
studioUrl: null,
|
||||
emilyLiveUrl: null,
|
||||
lpLiveUrl: null,
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
label: "Given Up",
|
||||
duration: "03:09",
|
||||
studioUrl: null,
|
||||
emilyLiveUrl: null,
|
||||
lpLiveUrl: null,
|
||||
},
|
||||
{
|
||||
id: "3",
|
||||
label: "Leave Out All the Rest",
|
||||
duration: "03:29",
|
||||
studioUrl: null,
|
||||
emilyLiveUrl: null,
|
||||
lpLiveUrl: null,
|
||||
},
|
||||
{
|
||||
id: "4",
|
||||
label: "Bleed It Out",
|
||||
duration: "02:44",
|
||||
studioUrl: null,
|
||||
emilyLiveUrl: null,
|
||||
lpLiveUrl: null,
|
||||
},
|
||||
{
|
||||
id: "5",
|
||||
label: "Shadow of the Day",
|
||||
duration: "04:49",
|
||||
studioUrl: null,
|
||||
emilyLiveUrl: null,
|
||||
lpLiveUrl: null,
|
||||
},
|
||||
{
|
||||
id: "6",
|
||||
label: "What I've Done",
|
||||
duration: "03:25",
|
||||
studioUrl: null,
|
||||
emilyLiveUrl: null,
|
||||
lpLiveUrl: null,
|
||||
},
|
||||
{
|
||||
id: "7",
|
||||
label: "Hands Held High",
|
||||
duration: "03:53",
|
||||
studioUrl: null,
|
||||
emilyLiveUrl: null,
|
||||
lpLiveUrl: null,
|
||||
},
|
||||
{
|
||||
id: "8",
|
||||
label: "No More Sorrow",
|
||||
duration: "03:41",
|
||||
studioUrl: null,
|
||||
emilyLiveUrl: null,
|
||||
lpLiveUrl: null,
|
||||
},
|
||||
{
|
||||
id: "9",
|
||||
label: "Valentine's Day",
|
||||
duration: "03:16",
|
||||
studioUrl: null,
|
||||
emilyLiveUrl: null,
|
||||
lpLiveUrl: null,
|
||||
},
|
||||
{
|
||||
id: "10",
|
||||
label: "In Between",
|
||||
duration: "03:16",
|
||||
studioUrl: null,
|
||||
emilyLiveUrl: null,
|
||||
lpLiveUrl: null,
|
||||
},
|
||||
{
|
||||
id: "11",
|
||||
label: "In Pieces",
|
||||
duration: "03:38",
|
||||
studioUrl: null,
|
||||
emilyLiveUrl: null,
|
||||
lpLiveUrl: null,
|
||||
},
|
||||
{
|
||||
id: "12",
|
||||
label: "The Little Things Give You Away",
|
||||
duration: "06:23",
|
||||
studioUrl: null,
|
||||
emilyLiveUrl: null,
|
||||
lpLiveUrl: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
// {
|
||||
// id: "minutes-to-midnight",
|
||||
// label: "Minutes to Midnight",
|
||||
// releaseDate: "2007-05-14",
|
||||
// image: "/minutes_to_midnight.jpg",
|
||||
// url: "/minutes-to-midnight",
|
||||
// description:
|
||||
// "Minutes to Midnight is the third studio album by American rock band Linkin Park, released on May 14, 2007, by Warner Bros. Records. The album marked a departure from the band's previous nu-metal sound, incorporating more alternative rock and experimental elements.",
|
||||
// tracks: [
|
||||
// {
|
||||
// id: "minutes-to-midnight-1",
|
||||
// label: "Wake",
|
||||
// duration: "01:40",
|
||||
// studioUrl: null,
|
||||
// emilyLiveUrl: null,
|
||||
// lpLiveUrl: null,
|
||||
// },
|
||||
// {
|
||||
// id: "minutes-to-midnight-2",
|
||||
// label: "Given Up",
|
||||
// duration: "03:09",
|
||||
// studioUrl: null,
|
||||
// emilyLiveUrl: null,
|
||||
// lpLiveUrl: null,
|
||||
// },
|
||||
// {
|
||||
// id: "minutes-to-midnight-3",
|
||||
// label: "Leave Out All the Rest",
|
||||
// duration: "03:29",
|
||||
// studioUrl: null,
|
||||
// emilyLiveUrl: null,
|
||||
// lpLiveUrl: null,
|
||||
// },
|
||||
// {
|
||||
// id: "minutes-to-midnight-4",
|
||||
// label: "Bleed It Out",
|
||||
// duration: "02:44",
|
||||
// studioUrl: null,
|
||||
// emilyLiveUrl: null,
|
||||
// lpLiveUrl: null,
|
||||
// },
|
||||
// {
|
||||
// id: "minutes-to-midnight-5",
|
||||
// label: "Shadow of the Day",
|
||||
// duration: "04:49",
|
||||
// studioUrl: null,
|
||||
// emilyLiveUrl: null,
|
||||
// lpLiveUrl: null,
|
||||
// },
|
||||
// {
|
||||
// id: "minutes-to-midnight-6",
|
||||
// label: "What I've Done",
|
||||
// duration: "03:25",
|
||||
// studioUrl: null,
|
||||
// emilyLiveUrl: null,
|
||||
// lpLiveUrl: null,
|
||||
// },
|
||||
// {
|
||||
// id: "minutes-to-midnight-7",
|
||||
// label: "Hands Held High",
|
||||
// duration: "03:53",
|
||||
// studioUrl: null,
|
||||
// emilyLiveUrl: null,
|
||||
// lpLiveUrl: null,
|
||||
// },
|
||||
// {
|
||||
// id: "minutes-to-midnight-8",
|
||||
// label: "No More Sorrow",
|
||||
// duration: "03:41",
|
||||
// studioUrl: null,
|
||||
// emilyLiveUrl: null,
|
||||
// lpLiveUrl: null,
|
||||
// },
|
||||
// {
|
||||
// id: "minutes-to-midnight-9",
|
||||
// label: "Valentine's Day",
|
||||
// duration: "03:16",
|
||||
// studioUrl: null,
|
||||
// emilyLiveUrl: null,
|
||||
// lpLiveUrl: null,
|
||||
// },
|
||||
// {
|
||||
// id: "minutes-to-midnight-10",
|
||||
// label: "In Between",
|
||||
// duration: "03:16",
|
||||
// studioUrl: null,
|
||||
// emilyLiveUrl: null,
|
||||
// lpLiveUrl: null,
|
||||
// },
|
||||
// {
|
||||
// id: "minutes-to-midnight-11",
|
||||
// label: "In Pieces",
|
||||
// duration: "03:38",
|
||||
// studioUrl: null,
|
||||
// emilyLiveUrl: null,
|
||||
// lpLiveUrl: null,
|
||||
// },
|
||||
// {
|
||||
// id: "minutes-to-midnight-12",
|
||||
// label: "The Little Things Give You Away",
|
||||
// duration: "06:23",
|
||||
// studioUrl: null,
|
||||
// emilyLiveUrl: null,
|
||||
// lpLiveUrl: null,
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
];
|
||||
|
|
|
|||
193
src/lib/songLinks.ts
Normal file
193
src/lib/songLinks.ts
Normal file
|
|
@ -0,0 +1,193 @@
|
|||
// This file is auto-generated by scripts/fetch-song-links.ts
|
||||
// Do not edit manually
|
||||
// Last updated: 2025-10-24T22:59:54.578Z
|
||||
// Run: bun run fetch-links
|
||||
|
||||
export interface SongLinks {
|
||||
spotify?: string;
|
||||
youtube?: string;
|
||||
youtubeMusic?: string;
|
||||
appleMusic?: string;
|
||||
}
|
||||
|
||||
export interface SongLinksData {
|
||||
[songId: string]: SongLinks;
|
||||
}
|
||||
|
||||
export const songLinks: SongLinksData = {
|
||||
"hybrid-theory-1": {
|
||||
spotify: "https://open.spotify.com/track/4HvcbAR8LisalXKt7cpqE1",
|
||||
youtube: "https://www.youtube.com/watch?v=vjVkXlxsO8Q",
|
||||
youtubeMusic: "https://music.youtube.com/watch?v=vjVkXlxsO8Q",
|
||||
appleMusic:
|
||||
"https://geo.music.apple.com/de/album/_/528436018?i=528436568&mt=1&app=music&ls=1&at=1000lHKX&ct=api_http&itscg=30200&itsct=odsl_m",
|
||||
},
|
||||
"hybrid-theory-2": {
|
||||
spotify: "https://open.spotify.com/track/4bYLTrlcqctyHck3fjhMgW",
|
||||
youtube: "https://www.youtube.com/watch?v=4qlCC1GOwFw",
|
||||
youtubeMusic: "https://music.youtube.com/watch?v=4qlCC1GOwFw",
|
||||
appleMusic:
|
||||
"https://geo.music.apple.com/de/album/_/590431776?i=590431778&mt=1&app=music&ls=1&at=1000lHKX&ct=api_http&itscg=30200&itsct=odsl_m",
|
||||
},
|
||||
"hybrid-theory-3": {
|
||||
spotify: "https://open.spotify.com/track/1Q9QN6dHEDeUegsySY012f",
|
||||
youtube: "https://www.youtube.com/watch?v=M8UTS2iFXOo",
|
||||
youtubeMusic: "https://music.youtube.com/watch?v=M8UTS2iFXOo",
|
||||
appleMusic:
|
||||
"https://geo.music.apple.com/de/album/_/528436018?i=528437421&mt=1&app=music&ls=1&at=1000lHKX&ct=api_http&itscg=30200&itsct=odsl_m",
|
||||
},
|
||||
"hybrid-theory-4": {
|
||||
spotify: "https://open.spotify.com/track/214LJVTqrkUOGemWDGHJpr",
|
||||
youtube: "https://www.youtube.com/watch?v=jZSPAp8kCl4",
|
||||
youtubeMusic: "https://music.youtube.com/watch?v=jZSPAp8kCl4",
|
||||
appleMusic:
|
||||
"https://geo.music.apple.com/de/album/_/590431776?i=590431781&mt=1&app=music&ls=1&at=1000lHKX&ct=api_http&itscg=30200&itsct=odsl_m",
|
||||
},
|
||||
"hybrid-theory-5": {
|
||||
spotify: "https://open.spotify.com/track/0X576XiCW9uEtko6f6VkcU",
|
||||
youtube: "https://www.youtube.com/watch?v=Gd9OhYroLN0",
|
||||
youtubeMusic: "https://music.youtube.com/watch?v=Gd9OhYroLN0",
|
||||
appleMusic:
|
||||
"https://geo.music.apple.com/de/album/_/591534774?i=591534780&mt=1&app=music&ls=1&at=1000lHKX&ct=api_http&itscg=30200&itsct=odsl_m",
|
||||
},
|
||||
"hybrid-theory-6": {
|
||||
spotify: "https://open.spotify.com/track/5tkHje8FGSF258gi869GcV",
|
||||
youtube: "https://www.youtube.com/watch?v=ig-fyQqf510",
|
||||
youtubeMusic: "https://music.youtube.com/watch?v=ig-fyQqf510",
|
||||
appleMusic:
|
||||
"https://geo.music.apple.com/de/album/_/528436018?i=528437609&mt=1&app=music&ls=1&at=1000lHKX&ct=api_http&itscg=30200&itsct=odsl_m",
|
||||
},
|
||||
"hybrid-theory-7": {
|
||||
spotify: "https://open.spotify.com/track/4SJoEhpI6C61wtUfxSHMGc",
|
||||
youtube: "https://www.youtube.com/watch?v=wWBp-nlGX1o",
|
||||
youtubeMusic: "https://music.youtube.com/watch?v=wWBp-nlGX1o",
|
||||
appleMusic:
|
||||
"https://geo.music.apple.com/de/album/_/528436018?i=528437611&mt=1&app=music&ls=1&at=1000lHKX&ct=api_http&itscg=30200&itsct=odsl_m",
|
||||
},
|
||||
"hybrid-theory-8": {
|
||||
spotify: "https://open.spotify.com/track/7q115ia4fQn9zonjpexWsY",
|
||||
youtube: "https://www.youtube.com/watch?v=eVTXPUF4Oz4",
|
||||
youtubeMusic: "https://music.youtube.com/watch?v=eVTXPUF4Oz4",
|
||||
appleMusic:
|
||||
"https://geo.music.apple.com/de/album/_/528436018?i=528437613&mt=1&app=music&ls=1&at=1000lHKX&ct=api_http&itscg=30200&itsct=odsl_m",
|
||||
},
|
||||
"hybrid-theory-9": {
|
||||
spotify: "https://open.spotify.com/track/1B8WdDScvobpFsZLfdmIE1",
|
||||
youtube: "https://www.youtube.com/watch?v=3t2WkCudwfY",
|
||||
youtubeMusic: "https://music.youtube.com/watch?v=3t2WkCudwfY",
|
||||
appleMusic:
|
||||
"https://geo.music.apple.com/de/album/_/528436018?i=528437708&mt=1&app=music&ls=1&at=1000lHKX&ct=api_http&itscg=30200&itsct=odsl_m",
|
||||
},
|
||||
"hybrid-theory-10": {
|
||||
spotify: "https://open.spotify.com/track/3JAu2BH8sSI46rJGzQerb6",
|
||||
youtube: "https://www.youtube.com/watch?v=HNCgBuI2eJc",
|
||||
youtubeMusic: "https://music.youtube.com/watch?v=HNCgBuI2eJc",
|
||||
appleMusic:
|
||||
"https://geo.music.apple.com/de/album/_/528436018?i=528437709&mt=1&app=music&ls=1&at=1000lHKX&ct=api_http&itscg=30200&itsct=odsl_m",
|
||||
},
|
||||
"hybrid-theory-11": {
|
||||
spotify: "https://open.spotify.com/track/15voDM9pGkwEvHBUecpcmo",
|
||||
youtube: "https://www.youtube.com/watch?v=qqC5sdsHLq8",
|
||||
youtubeMusic: "https://music.youtube.com/watch?v=qqC5sdsHLq8",
|
||||
appleMusic:
|
||||
"https://geo.music.apple.com/de/album/_/528436018?i=528437710&mt=1&app=music&ls=1&at=1000lHKX&ct=api_http&itscg=30200&itsct=odsl_m",
|
||||
},
|
||||
"hybrid-theory-12": {
|
||||
spotify: "https://open.spotify.com/track/4HlMOXxwF9SYG1RoJxEjio",
|
||||
youtube: "https://www.youtube.com/watch?v=Ve1LNJEIKUE",
|
||||
youtubeMusic: "https://music.youtube.com/watch?v=Ve1LNJEIKUE",
|
||||
appleMusic:
|
||||
"https://geo.music.apple.com/de/album/_/590431776?i=590431790&mt=1&app=music&ls=1&at=1000lHKX&ct=api_http&itscg=30200&itsct=odsl_m",
|
||||
},
|
||||
"meteora-1": {
|
||||
spotify: "https://open.spotify.com/track/4hbpxoMCp62bd1sdy7XehS",
|
||||
youtube: "https://www.youtube.com/watch?v=U6R-twDkrcI",
|
||||
youtubeMusic: "https://music.youtube.com/watch?v=U6R-twDkrcI",
|
||||
appleMusic:
|
||||
"https://geo.music.apple.com/de/album/_/590423275?i=590423280&mt=1&app=music&ls=1&at=1000lHKX&ct=api_http&itscg=30200&itsct=odsl_m",
|
||||
},
|
||||
"meteora-2": {
|
||||
spotify: "https://open.spotify.com/track/6HZdbb05lEXLvcmee3ZXO2",
|
||||
youtube: "https://www.youtube.com/watch?v=oWfGOVWrueo",
|
||||
youtubeMusic: "https://music.youtube.com/watch?v=oWfGOVWrueo",
|
||||
appleMusic:
|
||||
"https://geo.music.apple.com/de/album/_/1668484895?i=1668485277&mt=1&app=music&ls=1&at=1000lHKX&ct=api_http&itscg=30200&itsct=odsl_m",
|
||||
},
|
||||
"meteora-3": {
|
||||
spotify: "https://open.spotify.com/track/3agtg0x11wPvLIWkYR39nZ",
|
||||
youtube: "https://www.youtube.com/watch?v=zsCD5XCu6CM",
|
||||
youtubeMusic: "https://music.youtube.com/watch?v=zsCD5XCu6CM",
|
||||
appleMusic:
|
||||
"https://geo.music.apple.com/de/album/_/528435845?i=528437020&mt=1&app=music&ls=1&at=1000lHKX&ct=api_http&itscg=30200&itsct=odsl_m",
|
||||
},
|
||||
"meteora-4": {
|
||||
spotify: "https://open.spotify.com/track/4zP2e2aIzOZGEFTq1MDJmm",
|
||||
youtube: "https://www.youtube.com/watch?v=NjdgcHdzvac",
|
||||
youtubeMusic: "https://music.youtube.com/watch?v=NjdgcHdzvac",
|
||||
appleMusic:
|
||||
"https://geo.music.apple.com/de/album/_/590423275?i=590423283&mt=1&app=music&ls=1&at=1000lHKX&ct=api_http&itscg=30200&itsct=odsl_m",
|
||||
},
|
||||
"meteora-5": {
|
||||
spotify: "https://open.spotify.com/track/3R9NKZ4jmxNjOFihSF5s7f",
|
||||
youtube: "https://www.youtube.com/watch?v=oMals9XXQY8",
|
||||
youtubeMusic: "https://music.youtube.com/watch?v=oMals9XXQY8",
|
||||
appleMusic:
|
||||
"https://geo.music.apple.com/de/album/_/590423275?i=590423284&mt=1&app=music&ls=1&at=1000lHKX&ct=api_http&itscg=30200&itsct=odsl_m",
|
||||
},
|
||||
"meteora-6": {
|
||||
spotify: "https://open.spotify.com/track/17bgialGAwoiGj1STY4cnR",
|
||||
youtube: "https://www.youtube.com/watch?v=U5zdmjVeQzE",
|
||||
youtubeMusic: "https://music.youtube.com/watch?v=U5zdmjVeQzE",
|
||||
appleMusic:
|
||||
"https://geo.music.apple.com/de/album/_/590423275?i=590423285&mt=1&app=music&ls=1&at=1000lHKX&ct=api_http&itscg=30200&itsct=odsl_m",
|
||||
},
|
||||
"meteora-7": {
|
||||
spotify: "https://open.spotify.com/track/4Yf5bqU3NK4kNOypcrLYwU",
|
||||
youtube: "https://www.youtube.com/watch?v=LYU-8IFcDPw",
|
||||
youtubeMusic: "https://music.youtube.com/watch?v=LYU-8IFcDPw",
|
||||
appleMusic:
|
||||
"https://geo.music.apple.com/de/album/_/528435845?i=528437024&mt=1&app=music&ls=1&at=1000lHKX&ct=api_http&itscg=30200&itsct=odsl_m",
|
||||
},
|
||||
"meteora-8": {
|
||||
spotify: "https://open.spotify.com/track/2DrMcGNKEY0FZqjMovGaCm",
|
||||
youtube: "https://www.youtube.com/watch?v=6dEAeCHQrBs",
|
||||
youtubeMusic: "https://music.youtube.com/watch?v=6dEAeCHQrBs",
|
||||
appleMusic:
|
||||
"https://geo.music.apple.com/de/album/_/528435845?i=528437025&mt=1&app=music&ls=1&at=1000lHKX&ct=api_http&itscg=30200&itsct=odsl_m",
|
||||
},
|
||||
"meteora-9": {
|
||||
spotify: "https://open.spotify.com/track/6n8TMVyFKoUmDc4apxceRD",
|
||||
youtube: "https://www.youtube.com/watch?v=v2H4l9RpkwM",
|
||||
youtubeMusic: "https://music.youtube.com/watch?v=v2H4l9RpkwM",
|
||||
appleMusic:
|
||||
"https://geo.music.apple.com/de/album/_/528435845?i=528437026&mt=1&app=music&ls=1&at=1000lHKX&ct=api_http&itscg=30200&itsct=odsl_m",
|
||||
},
|
||||
"meteora-10": {
|
||||
spotify: "https://open.spotify.com/track/60IkVf7UfQXmt5CwkpcX8a",
|
||||
youtube: "https://www.youtube.com/watch?v=YLHpvjrFpe0",
|
||||
youtubeMusic: "https://music.youtube.com/watch?v=YLHpvjrFpe0",
|
||||
appleMusic:
|
||||
"https://geo.music.apple.com/de/album/_/528435845?i=528437027&mt=1&app=music&ls=1&at=1000lHKX&ct=api_http&itscg=30200&itsct=odsl_m",
|
||||
},
|
||||
"meteora-11": {
|
||||
spotify: "https://open.spotify.com/track/4m1P1BSg9nLxaWbvPhNdhw",
|
||||
youtube: "https://www.youtube.com/watch?v=QJ87793QXes",
|
||||
youtubeMusic: "https://music.youtube.com/watch?v=QJ87793QXes",
|
||||
appleMusic:
|
||||
"https://geo.music.apple.com/de/album/_/590423275?i=590423550&mt=1&app=music&ls=1&at=1000lHKX&ct=api_http&itscg=30200&itsct=odsl_m",
|
||||
},
|
||||
"meteora-12": {
|
||||
spotify: "https://open.spotify.com/track/3sbezh9MMCQrr4KY7zw13j",
|
||||
youtube: "https://www.youtube.com/watch?v=J1KqQYsUYIk",
|
||||
youtubeMusic: "https://music.youtube.com/watch?v=J1KqQYsUYIk",
|
||||
appleMusic:
|
||||
"https://geo.music.apple.com/de/album/_/528435845?i=528437513&mt=1&app=music&ls=1&at=1000lHKX&ct=api_http&itscg=30200&itsct=odsl_m",
|
||||
},
|
||||
"meteora-13": {
|
||||
spotify: "https://open.spotify.com/track/2nLtzopw4rPReszdYBJU6h",
|
||||
youtube: "https://www.youtube.com/watch?v=kXYiU_JCYtU",
|
||||
youtubeMusic: "https://music.youtube.com/watch?v=kXYiU_JCYtU",
|
||||
appleMusic:
|
||||
"https://geo.music.apple.com/de/album/_/528435845?i=528437514&mt=1&app=music&ls=1&at=1000lHKX&ct=api_http&itscg=30200&itsct=odsl_m",
|
||||
},
|
||||
};
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||
"types": ["bun-types"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
"moduleResolution": "bundler",
|
||||
|
|
|
|||
Loading…
Reference in a new issue