| 1 | const LASTFM_API_KEY = process.env.FEED_LASTFM_API_KEY || ""; |
| 2 | const LASTFM_USERNAME = process.env.FEED_LASTFM_USERNAME || ""; |
| 3 | const LASTFM_API = "https://ws.audioscrobbler.com/2.0/"; |
| 4 | |
| 5 | function formatItem({ |
| 6 | artist, |
| 7 | image, |
| 8 | name, |
| 9 | playcount, |
| 10 | url, |
| 11 | }: { |
| 12 | artist: { "#text": string }; |
| 13 | image?: { "#text": string }[]; |
| 14 | name: string; |
| 15 | playcount: string; |
| 16 | url: string; |
| 17 | }) { |
| 18 | return { |
| 19 | name: artist ? `${name} by ${artist?.["#text"]}` : name, |
| 20 | image: image ? image?.at(-1)?.["#text"] : undefined, |
| 21 | playcount, |
| 22 | url, |
| 23 | }; |
| 24 | } |
| 25 | |
| 26 | function epochToIso(epoch: string) { |
| 27 | const date = new Date(Number.parseInt(epoch) * 1000); |
| 28 | return date?.toISOString(); |
| 29 | } |
| 30 | |
| 31 | function isoToYearAndWeek(iso: string) { |
| 32 | const date = new Date(iso); |
| 33 | const year = date.getFullYear(); |
| 34 | const firstOfYear = new Date(year, 0, 1); |
| 35 | const daysSinceFirstOfYear = Math.floor( |
| 36 | (+date - +firstOfYear) / (24 * 60 * 60 * 1000), |
| 37 | ); |
| 38 | const week = Math.ceil((date.getDay() + 1 + daysSinceFirstOfYear) / 7); |
| 39 | return { |
| 40 | week, |
| 41 | year, |
| 42 | }; |
| 43 | } |
| 44 | |
| 45 | function listToHTML( |
| 46 | arr: { |
| 47 | name: string; |
| 48 | playcount: string; |
| 49 | url: string; |
| 50 | }[], |
| 51 | ) { |
| 52 | return arr |
| 53 | .map( |
| 54 | ({ name, playcount, url }) => |
| 55 | `<li><a href="${url}">${name}</a> (${playcount} plays)</li>`, |
| 56 | ) |
| 57 | .join(""); |
| 58 | } |
| 59 | |
| 60 | async function lastFm(method = "") { |
| 61 | const data = await fetch( |
| 62 | `${LASTFM_API}?method=${method}&api_key=${LASTFM_API_KEY}&format=json&user=${LASTFM_USERNAME}`, |
| 63 | ); |
| 64 | return await data.json(); |
| 65 | } |
| 66 | |
| 67 | async function getWeeklyChartList(limit = 5) { |
| 68 | const data = await lastFm("user.getWeeklyChartList"); |
| 69 | return data?.weeklychartlist?.chart?.slice(limit * -1); |
| 70 | } |
| 71 | |
| 72 | async function getWeeklyTrackChart(to: string, from: string, limit = 5) { |
| 73 | const data = await lastFm(`user.getWeeklyTrackChart&from=${from}&to=${to}`); |
| 74 | return data?.weeklytrackchart?.track?.slice(0, limit)?.map(formatItem); |
| 75 | } |
| 76 | |
| 77 | async function getWeeklyAlbumChart(to: string, from: string, limit = 5) { |
| 78 | const data = await lastFm(`user.getWeeklyAlbumChart&from=${from}&to=${to}`); |
| 79 | return data?.weeklyalbumchart?.album?.slice(0, limit)?.map(formatItem); |
| 80 | } |
| 81 | |
| 82 | async function getWeeklyArtistChart(to: string, from: string, limit = 5) { |
| 83 | const data = await lastFm(`user.getWeeklyArtistChart&from=${from}&to=${to}`); |
| 84 | return data?.weeklyartistchart?.artist?.slice(0, limit)?.map(formatItem); |
| 85 | } |
| 86 | |
| 87 | async function buildReport() { |
| 88 | const output = []; |
| 89 | const weeks = await getWeeklyChartList(); |
| 90 | for (const week of weeks) { |
| 91 | output.push({ |
| 92 | to: week.to, |
| 93 | from: week.from, |
| 94 | tracks: await getWeeklyTrackChart(week.to, week.from), |
| 95 | albums: await getWeeklyAlbumChart(week.to, week.from), |
| 96 | artists: await getWeeklyArtistChart(week.to, week.from), |
| 97 | }); |
| 98 | } |
| 99 | return output; |
| 100 | } |
| 101 | |
| 102 | export default async function buildFeedItems() { |
| 103 | const data = await buildReport(); |
| 104 | return data |
| 105 | .map(({ to, from, tracks, albums, artists }) => { |
| 106 | const trackList = listToHTML(tracks); |
| 107 | const albumList = listToHTML(albums); |
| 108 | const artistList = listToHTML(artists); |
| 109 | const toIso = epochToIso(to); |
| 110 | const fromIso = epochToIso(from); |
| 111 | const { week, year } = isoToYearAndWeek(fromIso); |
| 112 | |
| 113 | if (!trackList && !albumList && !artistList) { |
| 114 | return; |
| 115 | } |
| 116 | |
| 117 | return { |
| 118 | content_html: [ |
| 119 | artistList ? `<h2>Top Artists</h2><ol>${artistList}</ol>` : "", |
| 120 | albumList ? `<h2>Top Albums</h2><ol>${albumList}</ol>` : "", |
| 121 | trackList ? `<h2>Top Tracks</h2><ol>${trackList}</ol>` : "", |
| 122 | ].join(""), |
| 123 | date_published: toIso, |
| 124 | id: `last.fm-${from}-${to}`, |
| 125 | title: "weekly last.fm report", |
| 126 | url: `https://www.last.fm/user/ZicklePop/listening-report/year/${year}/week/${week}`, |
| 127 | tags: ["last.fm"], |
| 128 | }; |
| 129 | }) |
| 130 | .filter((o) => !!o); |
| 131 | } |
| 132 |
melanie / lastfm-weekly-report.ts
Last active 3 months ago