const LASTFM_API_KEY = process.env.FEED_LASTFM_API_KEY || ""; const LASTFM_USERNAME = process.env.FEED_LASTFM_USERNAME || ""; const LASTFM_API = "https://ws.audioscrobbler.com/2.0/"; function formatItem({ artist, image, name, playcount, url, }: { artist: { "#text": string }; image?: { "#text": string }[]; name: string; playcount: string; url: string; }) { return { name: artist ? `${name} by ${artist?.["#text"]}` : name, image: image ? image?.at(-1)?.["#text"] : undefined, playcount, url, }; } function epochToIso(epoch: string) { const date = new Date(Number.parseInt(epoch) * 1000); return date?.toISOString(); } function isoToYearAndWeek(iso: string) { const date = new Date(iso); const year = date.getFullYear(); const firstOfYear = new Date(year, 0, 1); const daysSinceFirstOfYear = Math.floor( (+date - +firstOfYear) / (24 * 60 * 60 * 1000), ); const week = Math.ceil((date.getDay() + 1 + daysSinceFirstOfYear) / 7); return { week, year, }; } function listToHTML( arr: { name: string; playcount: string; url: string; }[], ) { return arr .map( ({ name, playcount, url }) => `
  • ${name} (${playcount} plays)
  • `, ) .join(""); } async function lastFm(method = "") { const data = await fetch( `${LASTFM_API}?method=${method}&api_key=${LASTFM_API_KEY}&format=json&user=${LASTFM_USERNAME}`, ); return await data.json(); } async function getWeeklyChartList(limit = 5) { const data = await lastFm("user.getWeeklyChartList"); return data?.weeklychartlist?.chart?.slice(limit * -1); } async function getWeeklyTrackChart(to: string, from: string, limit = 5) { const data = await lastFm(`user.getWeeklyTrackChart&from=${from}&to=${to}`); return data?.weeklytrackchart?.track?.slice(0, limit)?.map(formatItem); } async function getWeeklyAlbumChart(to: string, from: string, limit = 5) { const data = await lastFm(`user.getWeeklyAlbumChart&from=${from}&to=${to}`); return data?.weeklyalbumchart?.album?.slice(0, limit)?.map(formatItem); } async function getWeeklyArtistChart(to: string, from: string, limit = 5) { const data = await lastFm(`user.getWeeklyArtistChart&from=${from}&to=${to}`); return data?.weeklyartistchart?.artist?.slice(0, limit)?.map(formatItem); } async function buildReport() { const output = []; const weeks = await getWeeklyChartList(); for (const week of weeks) { output.push({ to: week.to, from: week.from, tracks: await getWeeklyTrackChart(week.to, week.from), albums: await getWeeklyAlbumChart(week.to, week.from), artists: await getWeeklyArtistChart(week.to, week.from), }); } return output; } export default async function buildFeedItems() { const data = await buildReport(); return data .map(({ to, from, tracks, albums, artists }) => { const trackList = listToHTML(tracks); const albumList = listToHTML(albums); const artistList = listToHTML(artists); const toIso = epochToIso(to); const fromIso = epochToIso(from); const { week, year } = isoToYearAndWeek(fromIso); if (!trackList && !albumList && !artistList) { return; } return { content_html: [ artistList ? `

    Top Artists

      ${artistList}
    ` : "", albumList ? `

    Top Albums

      ${albumList}
    ` : "", trackList ? `

    Top Tracks

      ${trackList}
    ` : "", ].join(""), date_published: toIso, id: `last.fm-${from}-${to}`, title: "weekly last.fm report", url: `https://www.last.fm/user/ZicklePop/listening-report/year/${year}/week/${week}`, tags: ["last.fm"], }; }) .filter((o) => !!o); }