const express = require("express"); const bodyParser = require("body-parser"); const cors = require("cors"); const https = require("https"); const fs = require("fs/promises"); const agent = new https.Agent({ rejectUnauthorized: false, }); const app = express(); app.use(cors()); app.use(bodyParser.json()); const apiCache = { _: {}, get(key) { if ( this._[key] && this._[key].timestamp + (this._[key].expire ?? 180000) > Date.now() ) { return this._[key].value; } else { return undefined; } }, set(key, value, expire) { this._[key] = { timestamp: Date.now(), expire: expire, value: value, }; }, }; app.get("/getMostRecentSpotifyTrack", async function (req, res) { if (apiCache.get("getMostRecentSpotifyTrack")) { res.send(JSON.stringify(apiCache.get("getMostRecentSpotifyTrack"))); } else { const lastfmResponse = await fetch( `http://ws.audioscrobbler.com/2.0/?method=user.getrecenttracks&user=jdf221&api_key=${process.env.LASTFM_API_KEY}&format=json` ).then((res) => res.json()); const response = { track: undefined, artist: undefined, }; const track = lastfmResponse?.["recenttracks"]?.["track"]?.[0]; response.track = track?.name; response.artist = track?.artist?.["#text"]; apiCache.set("getMostRecentSpotifyTrack", response); res.send(JSON.stringify(response)); } }); app.listen(3000);`2` async function getSpotifyApi() { return { token: await fs .readFile("/opt/prod/storage/spotifySeasons/tokenData.json") .then((b) => JSON.parse(b.toString())), application: { id: process.env.SPOTIFY_API_ID, secret: process.env.SPOTIFY_API_SECRET, }, async sendRequest(url, method = "get", data, asJson = true) { await new Promise((r) => setTimeout(r, 500)); if ( url !== "https://accounts.spotify.com/api/token" && Date.now() > this.token.expiresAfterTimestamp ) { await this.refreshToken(); } return await fetch(url, { method: method, body: data ? asJson ? JSON.stringify(data) : Object.keys(data) .map( (key) => encodeURIComponent(key) + "=" + encodeURIComponent(data[key]) ) .join("&") : undefined, headers: { "Content-Type": asJson ? "application/json" : "application/x-www-form-urlencoded;charset=UTF-8", Authorization: url == "https://accounts.spotify.com/api/token" ? "Basic " + new Buffer.from( this.application.id + ":" + this.application.secret ).toString("base64") : "Bearer " + this.token.access_token, }, }).then((b) => b.json()); }, async refreshToken() { const response = await this.sendRequest( "https://accounts.spotify.com/api/token", "post", { grant_type: "refresh_token", refresh_token: this.token.refresh_token, }, false ); this.token = response; this.token.expiresAfterTimestamp = Date.now() + this.token.expires_in * 1000; this.token.refresh_token = process.env.SPOTIFY_API_REFRESH_TOKEN await fs.writeFile( "/opt/prod/storage/spotifySeasons/tokenData.json", JSON.stringify(this.token) ); }, async createPlaylist(name) { return ( await this.sendRequest( "https://api.spotify.com/v1/users/jdf221/playlists", "post", { name: name, public: false, } ) ).id; }, async search(query) { const queryObject = { q: query, type: ["track"] }; const queryString = Object.keys(queryObject) .map( (key) => encodeURIComponent(key) + "=" + encodeURIComponent(queryObject[key]) ) .join("&"); return this.sendRequest( "https://api.spotify.com/v1/search?" + queryString ); }, async addTracksToPlaylist(playlistId, tracksArray) { return this.sendRequest( `https://api.spotify.com/v1/playlists/${playlistId}/tracks`, "post", { uris: tracksArray }, true ); }, }; } async function weeklySpotifyHandler() { const spotifyApi = await getSpotifyApi(); const seasons = [ "winter", // 0 Jan "winter", // 1 February "spring", // 2 March "spring", // 3 April "spring", // 4 May "summer", // 5 June "summer", // 6 July "summer", // 7 August "fall", // 8 September "fall", // 9 October "fall", // 10 November "winter", // 11 December ]; const currentDate = new Date(); currentDate.setDate(currentDate.getDate() - 7); const currentSeason = seasons[currentDate.getUTCMonth()]; const currentPlaylistInternalId = `${currentSeason}-${currentDate.getUTCFullYear()}`; const createdPlaylists = await fs .readFile("/opt/prod/storage/spotifySeasons/playlists.json") .then((b) => JSON.parse(b.toString())); if (!createdPlaylists[currentPlaylistInternalId]) { const newPlaylistId = await spotifyApi.createPlaylist( currentDate.getUTCFullYear() + " " + (currentSeason[0].toUpperCase() + currentSeason.slice(1)) ); createdPlaylists[currentPlaylistInternalId] = { spotifyPlaylistId: newPlaylistId, tracks: [], }; } const playlistId = createdPlaylists[currentPlaylistInternalId].spotifyPlaylistId; const topTracks = await fetch( `https://ws.audioscrobbler.com/2.0/?method=user.getTopTracks&user=jdf221&period=7day&api_key=${process.env.LASTFM_API_KEY}&format=json` ).then((b) => b.json()); const filteredTopTracks = topTracks.toptracks.track.filter( (t) => parseInt(t.playcount) > 2 ); const newTracksToAdd = []; for (const lastfmTrack of filteredTopTracks) { const spotifySearch = await spotifyApi.search( `track:${lastfmTrack.name} artist:${lastfmTrack.artist.name}` ); const tracksButExplicitFirst = spotifySearch.tracks.items.sort((a, b) => { if (a.explicit && b.explicit) return 0; if (a.explicit) return -1; if (b.explicit) return 1; }); for (const track of tracksButExplicitFirst) { if ( lastfmTrack.artist.name === track.artists[0].name && lastfmTrack.name === track.name ) { if ( !createdPlaylists[currentPlaylistInternalId].tracks.includes( track.uri ) ) { createdPlaylists[currentPlaylistInternalId].tracks.push(track.uri); newTracksToAdd.push(track.uri); } break; } } } spotifyApi.addTracksToPlaylist(playlistId, newTracksToAdd); await fs.writeFile( "/opt/prod/storage/spotifySeasons/playlists.json", JSON.stringify(createdPlaylists) ); } setInterval(() => { const currentDate = new Date(); if ( currentDate.getDay() === 0 && currentDate.getUTCHours() === 0 && currentDate.getUTCMinutes() < 29 ) { weeklySpotifyHandler(); } }, 1000 * 60 * 30); // Every 30 minutes //weeklySpotifyHandler();