Add server support for last.fm history fetching
This lets us fetch all sorts of data from last.fm for display. We're doing some calculation to determine the last three albums listened to and sending that to the frontend.
This commit is contained in:
parent
238e34d97d
commit
00a9691363
2 changed files with 193 additions and 0 deletions
38
src/lib/types/lastfm.ts
Normal file
38
src/lib/types/lastfm.ts
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
export interface Artist {
|
||||
name: string
|
||||
mbid?: string
|
||||
}
|
||||
|
||||
export interface AlbumImages {
|
||||
small: string
|
||||
medium: string
|
||||
large: string
|
||||
extralarge: string
|
||||
mega: string
|
||||
default: string
|
||||
itunes?: string
|
||||
}
|
||||
|
||||
export interface Image {
|
||||
size: 'small' | 'medium' | 'large' | 'extralarge' | 'mega' | 'itunes'
|
||||
url: string
|
||||
}
|
||||
|
||||
export interface Album {
|
||||
name: string
|
||||
mbid?: string
|
||||
artist: Artist
|
||||
playCount: number
|
||||
url: string
|
||||
rank: number
|
||||
images: AlbumImages
|
||||
}
|
||||
|
||||
export interface WeeklyAlbumChart {
|
||||
albums: Album[]
|
||||
attr: {
|
||||
user: string
|
||||
from: string
|
||||
to: string
|
||||
}
|
||||
}
|
||||
155
src/routes/api/lastfm/+server.ts
Normal file
155
src/routes/api/lastfm/+server.ts
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
import { LastClient } from '@musicorum/lastfm'
|
||||
import {
|
||||
searchItunes,
|
||||
ItunesSearchOptions,
|
||||
ItunesMedia,
|
||||
ItunesEntityMusic
|
||||
} from 'node-itunes-search'
|
||||
import type { RequestHandler } from './$types'
|
||||
import type { Album, AlbumImages } from '$lib/types/lastfm'
|
||||
import type { LastfmImage } from '@musicorum/lastfm/dist/types/packages/common'
|
||||
|
||||
const LASTFM_API_KEY = process.env.LASTFM_API_KEY
|
||||
const USERNAME = 'jedmund'
|
||||
const ALBUM_LIMIT = 3
|
||||
|
||||
export const GET: RequestHandler = async ({ url }) => {
|
||||
const client = new LastClient(LASTFM_API_KEY)
|
||||
|
||||
try {
|
||||
// const albums = await getWeeklyAlbumChart(client, USERNAME)
|
||||
|
||||
const albums = await getRecentAlbums(client, USERNAME, ALBUM_LIMIT)
|
||||
const enrichedAlbums = await Promise.all(
|
||||
albums.slice(0, ALBUM_LIMIT).map((album) => enrichAlbumWithInfo(client, album))
|
||||
)
|
||||
const albumsWithItunesArt = await addItunesArtToAlbums(enrichedAlbums)
|
||||
|
||||
return new Response(JSON.stringify({ albums: albumsWithItunesArt }), {
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Error fetching album data:', error)
|
||||
return new Response(JSON.stringify({ error: 'Failed to fetch album data' }), {
|
||||
status: 500,
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async function getWeeklyAlbumChart(client: LastClient, username: string): Promise<Album[]> {
|
||||
const chart = await client.user.getWeeklyAlbumChart(username)
|
||||
return chart.albums.map((album) => ({
|
||||
...album,
|
||||
images: { small: '', medium: '', large: '', extralarge: '', mega: '', default: '' }
|
||||
}))
|
||||
}
|
||||
|
||||
async function getRecentAlbums(
|
||||
client: LastClient,
|
||||
username: string,
|
||||
limit: number
|
||||
): Promise<Album[]> {
|
||||
const recentTracks = await client.user.getRecentTracks(username, { limit: 50, extended: true })
|
||||
const uniqueAlbums = new Map<string, Album>()
|
||||
|
||||
for (const track of recentTracks.tracks) {
|
||||
if (uniqueAlbums.size >= limit) break
|
||||
|
||||
const albumKey = `${track.album.mbid || track.album.name}`
|
||||
if (!uniqueAlbums.has(albumKey)) {
|
||||
uniqueAlbums.set(albumKey, {
|
||||
name: track.album.name,
|
||||
artist: {
|
||||
name: track.artist.name,
|
||||
mbid: track.artist.mbid || ''
|
||||
},
|
||||
playCount: 1, // This is a placeholder, as we don't have actual play count for recent albums
|
||||
images: transformImages(track.images),
|
||||
mbid: track.album.mbid || '',
|
||||
url: track.url,
|
||||
rank: uniqueAlbums.size + 1
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return Array.from(uniqueAlbums.values())
|
||||
}
|
||||
|
||||
async function enrichAlbumWithInfo(client: LastClient, album: Album): Promise<Album> {
|
||||
const albumInfo = await client.album.getInfo(album.name, album.artist.name)
|
||||
return {
|
||||
...album,
|
||||
url: albumInfo?.url || '',
|
||||
images: transformImages(albumInfo?.images || [])
|
||||
}
|
||||
}
|
||||
|
||||
async function addItunesArtToAlbums(albums: Album[]): Promise<Album[]> {
|
||||
return Promise.all(albums.map(searchItunesForAlbum))
|
||||
}
|
||||
|
||||
async function searchItunesForAlbum(album: Album): Promise<Album> {
|
||||
const itunesResult = await searchItunesStores(album.name, album.artist.name)
|
||||
|
||||
if (itunesResult && itunesResult.results.length > 0) {
|
||||
const firstResult = itunesResult.results[0]
|
||||
console.log(firstResult)
|
||||
album.images.itunes = firstResult.artworkUrl100.replace('100x100', '600x600')
|
||||
}
|
||||
|
||||
return album
|
||||
}
|
||||
|
||||
async function searchItunesStores(albumName: string, artistName: string): Promise<any | null> {
|
||||
const stores = ['JP', 'US']
|
||||
for (const store of stores) {
|
||||
const encodedTerm = encodeURIComponent(`${albumName} ${artistName}`)
|
||||
const result = await searchItunes(
|
||||
new ItunesSearchOptions({
|
||||
term: encodedTerm,
|
||||
country: store,
|
||||
media: ItunesMedia.Music,
|
||||
entity: ItunesEntityMusic.Album,
|
||||
limit: 1
|
||||
})
|
||||
)
|
||||
|
||||
if (result.resultCount > 0) return result
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
function transformImages(images: LastfmImage[]): AlbumImages {
|
||||
const transformedImages: AlbumImages = {
|
||||
small: '',
|
||||
medium: '',
|
||||
large: '',
|
||||
extralarge: '',
|
||||
mega: '',
|
||||
default: ''
|
||||
}
|
||||
|
||||
images.forEach((img) => {
|
||||
switch (img.size) {
|
||||
case 'small':
|
||||
transformedImages.small = img.url
|
||||
break
|
||||
case 'medium':
|
||||
transformedImages.medium = img.url
|
||||
break
|
||||
case 'large':
|
||||
transformedImages.large = img.url
|
||||
break
|
||||
case 'extralarge':
|
||||
transformedImages.extralarge = img.url
|
||||
break
|
||||
case 'mega':
|
||||
transformedImages.mega = img.url
|
||||
break
|
||||
}
|
||||
})
|
||||
|
||||
return transformedImages
|
||||
}
|
||||
Loading…
Reference in a new issue