feat: music from last.fm
This commit is contained in:
parent
03bb2e1016
commit
7472eb6285
13 changed files with 125 additions and 188 deletions
32
src/_data/albums.js
Normal file
32
src/_data/albums.js
Normal file
|
@ -0,0 +1,32 @@
|
|||
const EleventyFetch = require('@11ty/eleventy-fetch')
|
||||
const ALBUM_DENYLIST = ['no-love-deep-web', 'unremittance']
|
||||
|
||||
module.exports = async function () {
|
||||
const MUSIC_KEY = process.env.API_KEY_LASTFM
|
||||
const url = `https://ws.audioscrobbler.com/2.0/?method=user.gettopalbums&user=coryd_&api_key=${MUSIC_KEY}&limit=8&format=json&period=7day`
|
||||
const res = EleventyFetch(url, {
|
||||
duration: '1h',
|
||||
type: 'json',
|
||||
}).catch()
|
||||
const data = await res
|
||||
return data['topalbums']['album'].map((album) => {
|
||||
return {
|
||||
title: album['name'],
|
||||
artist: album['artist']['name'],
|
||||
plays: album['playcount'],
|
||||
rank: album['@attr']['rank'],
|
||||
image: !ALBUM_DENYLIST.includes(album['name'].replace(/\s+/g, '-').toLowerCase())
|
||||
? album['image'][album['image'].length - 1]['#text'].replace(
|
||||
'https://lastfm.freetls.fastly.net',
|
||||
'https://cd-albums.b-cdn.net'
|
||||
)
|
||||
: `https://cdn.coryd.dev/albums/${album['name'].name
|
||||
.replace(/\s+/g, '-')
|
||||
.toLowerCase()}.jpg`,
|
||||
url: album['mbid']
|
||||
? `https://musicbrainz.org/album/${album['mbid']}`
|
||||
: `https://musicbrainz.org/search?query=${encodeURI(album['name'])}&type=release_group`,
|
||||
type: 'album',
|
||||
}
|
||||
})
|
||||
}
|
25
src/_data/artists.js
Normal file
25
src/_data/artists.js
Normal file
|
@ -0,0 +1,25 @@
|
|||
const EleventyFetch = require('@11ty/eleventy-fetch')
|
||||
|
||||
module.exports = async function () {
|
||||
const MUSIC_KEY = process.env.API_KEY_LASTFM
|
||||
const url = `https://ws.audioscrobbler.com/2.0/?method=user.gettopartists&user=coryd_&api_key=${MUSIC_KEY}&limit=8&format=json&period=7day`
|
||||
const res = EleventyFetch(url, {
|
||||
duration: '1h',
|
||||
type: 'json',
|
||||
}).catch()
|
||||
const data = await res
|
||||
return data['topartists']['artist'].map((artist) => {
|
||||
return {
|
||||
title: artist['name'],
|
||||
plays: artist['playcount'],
|
||||
rank: artist['@attr']['rank'],
|
||||
image:
|
||||
`https://cdn.coryd.dev/artists/${artist['name'].replace(/\s+/g, '-').toLowerCase()}.jpg` ||
|
||||
'https://cdn.coryd.dev/artists/missing-artist.jpg',
|
||||
url: artist['mbid']
|
||||
? `https://musicbrainz.org/artist/${artist['mbid']}`
|
||||
: `https://musicbrainz.org/search?query=${encodeURI(artist['name'])}&type=artist`,
|
||||
type: 'artist',
|
||||
}
|
||||
})
|
||||
}
|
|
@ -34,7 +34,7 @@ module.exports = async function () {
|
|||
data.push({
|
||||
image: images[i].src.replace(
|
||||
'https://cdn.thestorygraph.com',
|
||||
'https://books.coryd.dev'
|
||||
'https://cd-books.b-cdn.net'
|
||||
),
|
||||
})
|
||||
data.push({ url: `https://app.thestorygraph.com${urls[i].href}` })
|
||||
|
@ -52,7 +52,7 @@ module.exports = async function () {
|
|||
data[i]['author'] = authors[i].textContent
|
||||
data[i]['image'] = images[i].src.replace(
|
||||
'https://cdn.thestorygraph.com',
|
||||
'https://books.coryd.dev'
|
||||
'https://cd-books.b-cdn.net'
|
||||
)
|
||||
data[i]['url'] = `https://app.thestorygraph.com${urls[i].href}`
|
||||
data[i]['percentage'] = percentages[i].textContent
|
||||
|
|
|
@ -36,7 +36,7 @@ module.exports = async function () {
|
|||
})
|
||||
const tmdbData = await tmdbRes
|
||||
const posterPath = tmdbData['poster_path']
|
||||
movie.image = `https://movies.coryd.dev/t/p/w500${posterPath}`
|
||||
movie.image = `https://cd-movies.b-cdn.net/t/p/w500${posterPath}`
|
||||
}
|
||||
|
||||
return movies
|
||||
|
|
|
@ -1,91 +0,0 @@
|
|||
const { AssetCache } = require('@11ty/eleventy-fetch')
|
||||
const { aliasArtist, sanitizeMedia, sortByPlays } = require('../utils/media')
|
||||
|
||||
module.exports = async function () {
|
||||
const API_APPLE_MUSIC_DEVELOPER_TOKEN = process.env.API_APPLE_MUSIC_DEVELOPER_TOKEN
|
||||
const API_APPLE_MUSIC_USER_TOKEN = process.env.API_APPLE_MUSIC_USER_TOKEN
|
||||
const APPLE_RENEW_TOKEN_URL = process.env.APPLE_RENEW_TOKEN_URL
|
||||
const asset = new AssetCache('recent_tracks_data')
|
||||
const PAGE_SIZE = 30
|
||||
const PAGES = 8
|
||||
const response = {
|
||||
artists: {},
|
||||
albums: {},
|
||||
}
|
||||
|
||||
const RENEWED_MUSIC_TOKEN = await fetch(APPLE_RENEW_TOKEN_URL, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${API_APPLE_MUSIC_DEVELOPER_TOKEN}`,
|
||||
'X-Apple-Music-User-Token': `${API_APPLE_MUSIC_USER_TOKEN}`,
|
||||
},
|
||||
})
|
||||
.then((data) => data.json())
|
||||
.catch()
|
||||
|
||||
let CURRENT_PAGE = 0
|
||||
let res = []
|
||||
|
||||
if (asset.isCacheValid('1h')) return await asset.getCachedValue()
|
||||
|
||||
while (CURRENT_PAGE < PAGES) {
|
||||
const URL = `https://api.music.apple.com/v1/me/recent/played/tracks?limit=${PAGE_SIZE}&offset=${
|
||||
PAGE_SIZE * CURRENT_PAGE
|
||||
}&include[songs]=albums&extend=artistUrl`
|
||||
const tracks = await fetch(URL, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${API_APPLE_MUSIC_DEVELOPER_TOKEN}`,
|
||||
'music-user-token': `${RENEWED_MUSIC_TOKEN['music-token']}`,
|
||||
},
|
||||
})
|
||||
.then((data) => data.json())
|
||||
.catch()
|
||||
res = tracks['data']?.length ? [...res, ...tracks['data']] : [...res]
|
||||
CURRENT_PAGE++
|
||||
}
|
||||
|
||||
res.forEach((track) => {
|
||||
const artist = aliasArtist(track['attributes']['artistName'])
|
||||
const album = sanitizeMedia(track['attributes']['albumName'])
|
||||
if (!response['artists'][artist]) {
|
||||
response['artists'][artist] = {
|
||||
title: artist,
|
||||
image: `https://cdn.coryd.dev/artists/${artist.replace(/\s+/g, '-').toLowerCase()}.jpg`,
|
||||
url: track['attributes']['artistUrl']
|
||||
? track['attributes']['artistUrl']
|
||||
: `https://musicbrainz.org/search?query=${track['attributes']['artistName'].replace(
|
||||
/\s+/g,
|
||||
'+'
|
||||
)}&type=artist`,
|
||||
plays: 1,
|
||||
type: 'artist',
|
||||
}
|
||||
} else {
|
||||
response['artists'][artist].plays++
|
||||
}
|
||||
|
||||
// aggregate albums
|
||||
if (!response.albums[album]) {
|
||||
response.albums[album] = {
|
||||
title: album,
|
||||
artist: aliasArtist(track['attributes']['artistName']),
|
||||
image: track['attributes']['artwork']['url'].replace('{w}', '500').replace('{h}', '500'),
|
||||
url:
|
||||
track['relationships'] && track['relationships'].albums.data.length > 0
|
||||
? track['relationships'].albums.data.pop().attributes.url
|
||||
: `https://musicbrainz.org/taglookup/index?tag-lookup.artist=${track['attributes'][
|
||||
'artistName'
|
||||
].replace(/\s+/g, '+')}&tag-lookup.release=${album.replace(/\s+/g, '+')}`,
|
||||
plays: 1,
|
||||
type: 'album',
|
||||
}
|
||||
} else {
|
||||
response.albums[album].plays++
|
||||
}
|
||||
})
|
||||
response.artists = sortByPlays(response.artists)
|
||||
response.albums = sortByPlays(response.albums)
|
||||
await asset.save(response, 'json')
|
||||
return response
|
||||
}
|
|
@ -15,9 +15,9 @@ module.exports = async function () {
|
|||
{ name: 'GitHub', url: 'https://github.com/cdransf', icon: 'brand-github' },
|
||||
{ name: 'Mastodon', url: 'https://social.lol/@cory', icon: 'brand-mastodon' },
|
||||
{
|
||||
name: 'Apple Music',
|
||||
url: 'https://music.apple.com/profile/cdransf',
|
||||
icon: 'device-airpods',
|
||||
name: 'Last.fm',
|
||||
url: 'https://www.last.fm/user/coryd_',
|
||||
icon: 'headphones',
|
||||
},
|
||||
{ name: 'Trakt', url: 'https://trakt.tv/users/cdransf', icon: 'device-tv' },
|
||||
{ name: 'The StoryGraph', url: 'https://app.thestorygraph.com/profile/coryd', icon: 'books' },
|
||||
|
|
|
@ -74,7 +74,7 @@ module.exports = async function () {
|
|||
})
|
||||
const tmdbData = await tmdbRes
|
||||
const posterPath = tmdbData['poster_path']
|
||||
episode.image = `https://movies.coryd.dev/t/p/w500${posterPath}`
|
||||
episode.image = `https://cd-movies.b-cdn.net/t/p/w500${posterPath}`
|
||||
}
|
||||
|
||||
return episodes
|
||||
|
|
|
@ -3,8 +3,8 @@ layout: main
|
|||
---
|
||||
{% render "partials/header.liquid", site: site, page: page, nav: nav %}
|
||||
{{ content }}
|
||||
{% render "partials/now/media-grid.liquid", data:music.artists, icon: "microphone-2", title: "Artists", shape: "square", count: 8, loading: 'eager' %}
|
||||
{% render "partials/now/media-grid.liquid", data:music.albums, icon: "vinyl", title: "Albums", shape: "square", count: 8 %}
|
||||
{% render "partials/now/media-grid.liquid", data:artists, icon: "microphone-2", title: "Artists", shape: "square", count: 8, loading: 'eager' %}
|
||||
{% render "partials/now/media-grid.liquid", data:albums, icon: "vinyl", title: "Albums", shape: "square", count: 8 %}
|
||||
{% render "partials/now/albumReleases.liquid", albumReleases:albumReleases %}
|
||||
{% render "partials/now/media-grid.liquid", data:books, icon: "books", title: "Books", shape: "vertical", count: 6 %}
|
||||
{% render "partials/now/links.liquid", links:links %}
|
||||
|
|
|
@ -13,7 +13,7 @@ The function I've written works by making a pair of API calls: one to Last.fm wh
|
|||
export default async () => {
|
||||
// access our Last.fm API key and interpolate it into a call to their recent tracks endpoint
|
||||
const MUSIC_KEY = Netlify.env.get('API_KEY_LASTFM')
|
||||
const trackUrl = `https://ws.audioscrobbler.com/2.0/?method=user.getrecenttracks&user=cdrn_&api_key=${MUSIC_KEY}&limit=1&format=json`
|
||||
const trackUrl = `https://ws.audioscrobbler.com/2.0/?method=user.getrecenttracks&user=coryd_&api_key=${MUSIC_KEY}&limit=1&format=json`
|
||||
// fetch the track data
|
||||
const trackRes = await fetch(trackUrl, {
|
||||
type: 'json',
|
||||
|
@ -168,6 +168,6 @@ Finally, if the page this all lives on is loaded by a client without JavaScript
|
|||
|
||||
All of this, yields the single line at the bottom of this image — updated on each visit.
|
||||
|
||||
{% image 'https://cdn.coryd.dev/blog/now-playing.jpg', 'Now playing', 'border border-blue-600 dark:border-blue-400 rounded-lg overflow-hidden [&>*]:w-full' %}
|
||||
{% image '<https://cdn.coryd.dev/blog/now-playing.jpg>', 'Now playing', 'border border-blue-600 dark:border-blue-400 rounded-lg overflow-hidden [&>*]:w-full' %}
|
||||
|
||||
[^1]: Plus explicit conditions matching David Bowie and Minor Threat.
|
||||
|
|
|
@ -37,7 +37,7 @@ module.exports = async function () {
|
|||
if (data[index]) data[index]['author'] = author.textContent
|
||||
})
|
||||
doc.querySelectorAll('.md\\:block .book-cover img').forEach((image, index) => {
|
||||
const img = image.src.replace('https://cdn.thestorygraph.com', 'https://books.coryd.dev')
|
||||
const img = image.src.replace('https://cdn.thestorygraph.com', 'https://cd-books.b-cdn.net')
|
||||
if (!data[index]) data.push({ image: img })
|
||||
if (data[index]) data[index]['image'] = img
|
||||
})
|
||||
|
|
Reference in a new issue