melanie revised this gist 8 months ago. Go to revision
No changes
melanie revised this gist 8 months ago. Go to revision
No changes
melanie revised this gist 11 months ago. Go to revision
No changes
melanie revised this gist 11 months ago. Go to revision
1 file changed, 131 insertions
lastfm-weekly-report.ts(file created)
| @@ -0,0 +1,131 @@ | |||
| 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 | + | } | |
Newer
Older