diff --git a/.env.template b/.env.template index 3c898fc..b60986e 100644 --- a/.env.template +++ b/.env.template @@ -4,4 +4,9 @@ discordToken= igdbClientId= igdbClientSecret= igdbAccessToken= -googleplacesapikey= \ No newline at end of file +googleplacesapikey= +spotifyClientId= +spotifyClientSecret= +spotifyAccessToken= +spotifyPlaylistTracking= +spotifyPlaylistChannel= \ No newline at end of file diff --git a/.gitignore b/.gitignore index 70f7095..f34cd37 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,6 @@ config.json *.sqlite backups *.idea -tempbeattimeline.png \ No newline at end of file +tempbeattimeline.png +playlistinfo.json +playlistContent.json \ No newline at end of file diff --git a/commands/fun/postnewplaylistupdates.js b/commands/fun/postnewplaylistupdates.js new file mode 100644 index 0000000..5b2793a --- /dev/null +++ b/commands/fun/postnewplaylistupdates.js @@ -0,0 +1,127 @@ +const { EmbedBuilder } = require('@discordjs/builders'); +const fs = require('fs'); + +async function getAllPlaylistTracks() { + let allTracks = []; + let offset = 0; + const limit = 100; + let total = 0; + + do { + await fetch( + `https://api.spotify.com/v1/playlists/${process.env.spotifyPlaylistTracking}/tracks?limit=${limit}&offset=${offset}`, + { + method: 'GET', + headers: { + 'Authorization': `Bearer ${process.env.spotifyAccessToken}`, + }, + }, + ) + .then(response => response.json()) + .then(response => { + allTracks = allTracks.concat(response.items); + total = response.total; + offset += limit; + }) + .catch(err => { + console.log(err); + }); + } while (allTracks.length < total); + + return allTracks; +} + + +let seenTrackIds = new Set(); + +async function PostNewPlaylistUpdates(client) { + if (!process.env.spotifyPlaylistTracking || !process.env.spotifyPlaylistChannel || !process.env.spotifyAccessToken) { + return; + } + + if (fs.existsSync('./playlistContent.json')) { + seenTrackIds = new Set(JSON.parse(fs.readFileSync('./playlistContent.json', 'utf-8'))); + } + else { + seenTrackIds = new Set(); + } + + + const tracks = await getAllPlaylistTracks(); + if (tracks.length <= 1) return; + + const currentIds = tracks.map(item => item.track.id); + + let newTracks; + + if (seenTrackIds.size > 0) + { + newTracks = currentIds.filter(id => !seenTrackIds.has(id)); + } else { + newTracks = currentIds; + } + + const channel = await client.channels.cache.get(process.env.spotifyPlaylistChannel); + + for (const trackID of newTracks) { + // Send discord embeds; + const track = tracks.find(item => item.track.id === trackID); + + console.log(track); + + const embed = new EmbedBuilder() + + .setColor(0x1db954) + .setTitle(`${track.track.name} added!`) + .setURL(track.track.external_urls.spotify) + .setFooter({ text: 'The Ochulus • 100 Games Challenge', iconURL: client.user.avatarURL() }) + .setTimestamp(); + + if (track.added_by) { + await fetch( + `https://api.spotify.com/v1/users/${track.added_by.id}`, + { + method: 'GET', + headers: { + 'Authorization': `Bearer ${process.env.spotifyAccessToken}`, + }, + }, + ) + .then(response => response.json()) + .then(response => { + if (response.images.length > 0) { + embed.setAuthor({ name: `${response.display_name} added a new song!`, iconURL: response.images[0].url }); + } + else { + embed.setAuthor({ name: `${response.display_name} added a new song!` }); + } + }) + .catch(err => { + console.log(err); + }); + } + + embed.addFields({ name: 'Title', value: `[${track.track.name}](${track.track.external_urls.spotify})`, inline: true }); + + const artists = track.track.artists.map(artist => `[${artist.name}](${artist.external_urls.spotify})`).join(', '); + embed.addFields({ name: 'Artists', value: artists, inline: true }); + + embed.addFields({ name: 'Album', value: `[${track.track.album.name}](${track.track.album.external_urls.spotify})`, inline: true }); + + if (track.track.album.images.length > 0) + { + embed.setThumbnail(track.track.album.images[0].url); + } + + await channel.send({ embeds: [embed] }); + } + + seenTrackIds = new Set(currentIds); + + fs.writeFileSync('./playlistContent.json', JSON.stringify([...seenTrackIds])); +} + +module.exports = { + getAllPlaylistTracks, + PostNewPlaylistUpdates, +}; \ No newline at end of file diff --git a/commands/fun/trackspotifyplaylist.js b/commands/fun/trackspotifyplaylist.js new file mode 100644 index 0000000..167bca5 --- /dev/null +++ b/commands/fun/trackspotifyplaylist.js @@ -0,0 +1,69 @@ +const { SlashCommandBuilder, EmbedBuilder } = require('discord.js'); +const axios = require('axios'); +const fs = require('fs'); +const { getAllPlaylistTracks } = require('./postnewplaylistupdates.js'); + +module.exports = { + data: new SlashCommandBuilder() + .setName('trackspotifyplaylist') + .setDescription('Tracks for changes in a spotify playlist and posts updates in the channel this message is sent') + .addStringOption(option => option.setName('playlisturl').setDescription('A link to the playlist to track.').setRequired(true)), + async execute(interaction) { + + await interaction.deferReply(); + + const playlistURL = interaction.options.getString('playlisturl'); + const lastIndexOf = playlistURL.lastIndexOf('/'); + const playlistID = playlistURL.substr(lastIndexOf + 1); + + const embed = new EmbedBuilder() + .setColor(0xFFD700) + .setFooter({ text: 'The Ochulus • 100 Games Challenge', iconURL: interaction.client.user.avatarURL() }) + .setTimestamp(); + + await fetch( + `https://api.spotify.com/v1/playlists/${playlistID}`, + { + method: 'GET', + headers: { + 'Authorization': `Bearer ${process.env.spotifyAccessToken}`, + }, + }, + ) + .then(response => response.json()) + .then(response => { + embed.setColor(0x1db954); + embed.setTitle(`Now tracking ${response.name}`); + embed.setURL(response.external_urls.spotify); + + if (response.images) { + embed.setThumbnail(`${response.images[0].url}`); + } + + embed.setDescription(`There are currently ${response.tracks.total} tracks in the playlist.`); + + console.log(response); + process.env.spotifyPlaylistTracking = response.id; + process.env.spotifyPlaylistChannel = interaction.channelId; + // Save to file + + const list = []; + list.push(process.env.spotifyPlaylistTracking); + list.push(process.env.spotifyPlaylistChannel); + fs.writeFileSync('./playlistinfo.json', JSON.stringify(list)); + }) + .catch(err => { + embed.setColor(0xFF0000); + embed.setTitle('Unable to track playlist'); + console.error(err); + }); + + if (process.env.spotifyPlaylistTracking) { + const allTracks = await getAllPlaylistTracks(); + const ids = allTracks.map(item => item.track.id); + fs.writeFileSync('./playlistContent.json', JSON.stringify([...ids])); + } + + await interaction.editReply({ embeds: [embed] }); + }, +}; \ No newline at end of file diff --git a/index.js b/index.js index dcea5e6..7ace54f 100644 --- a/index.js +++ b/index.js @@ -10,6 +10,10 @@ const { igdb } = require('./igdb.js'); const { backupDatabase } = require('./databaseHelperFunctions.js'); new igdb(); +const { Spotify } = require('./spotify.js'); +new Spotify(); +const { PostNewPlaylistUpdates } = require('./commands/fun/postnewplaylistupdates.js'); + // Create a new client instance const client = new Client({ intents: [GatewayIntentBits.Guilds] }); @@ -68,4 +72,17 @@ setInterval(() => { backupDatabase(); }, 86000000); -backupDatabase(); \ No newline at end of file +backupDatabase(); + + +if (fs.existsSync('./playlistinfo.json')) { + const info = JSON.parse(fs.readFileSync('./playlistinfo.json')); + process.env.spotifyPlaylistTracking = info[0]; + process.env.spotifyPlaylistChannel = info[1]; +} + +setInterval(() => { + PostNewPlaylistUpdates(client); +}, 60000); + +PostNewPlaylistUpdates(client); \ No newline at end of file diff --git a/spotify.js b/spotify.js new file mode 100644 index 0000000..a86e603 --- /dev/null +++ b/spotify.js @@ -0,0 +1,39 @@ +class Spotify { + constructor() { + // make a new token every hour + setInterval(() => { + this.makeClientCred(); + }, 3600000); + this.makeClientCred(); + } + + async makeClientCred() { + console.log('Making a spotify token'); + + const response = await fetch('https://accounts.spotify.com/api/token', + { + method: 'POST', + headers: { + 'Authorization': 'Basic ' + (new Buffer.from(process.env.spotifyClientId + ':' + process.env.spotifyClientSecret).toString('base64')), + 'Content-Type': 'application/x-www-form-urlencoded', + }, + body: new URLSearchParams({ + grant_type: 'client_credentials', + }), + }, + ); + + const data = await response.json(); + if (response.status != 200) { + console.log('Failed with ', data.status, data.body); + return; + } + + process.env.spotifyAccessToken = data.access_token; + } +} + +module.exports = { + Spotify, +}; +