diff --git a/src/lib/types/lastfm.ts b/src/lib/types/lastfm.ts new file mode 100644 index 0000000..f647f6c --- /dev/null +++ b/src/lib/types/lastfm.ts @@ -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 + } +} diff --git a/src/routes/api/lastfm/+server.ts b/src/routes/api/lastfm/+server.ts new file mode 100644 index 0000000..34c984c --- /dev/null +++ b/src/routes/api/lastfm/+server.ts @@ -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 { + 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 { + const recentTracks = await client.user.getRecentTracks(username, { limit: 50, extended: true }) + const uniqueAlbums = new Map() + + 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 { + 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 { + return Promise.all(albums.map(searchItunesForAlbum)) +} + +async function searchItunesForAlbum(album: Album): Promise { + 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 { + 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 +}