lastfm-weekly-report.ts
· 3.5 KiB · TypeScript
Surowy
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 }) =>
`<li><a href="${url}">${name}</a> (${playcount} plays)</li>`,
)
.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 ? `<h2>Top Artists</h2><ol>${artistList}</ol>` : "",
albumList ? `<h2>Top Albums</h2><ol>${albumList}</ol>` : "",
trackList ? `<h2>Top Tracks</h2><ol>${trackList}</ol>` : "",
].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);
}
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 |