From cb3e6df0bd8e1fc70609c047438d270c2285cce2 Mon Sep 17 00:00:00 2001 From: Minz Date: Wed, 3 Apr 2024 18:21:52 +0200 Subject: [PATCH] Add LFM integration to link_track --- commands/utility/linkTrack.js | 83 ++++++++++++++++++++++++++++++----- config.json.example | 3 +- core/db.js | 39 ++++++++++++++++ main.js | 3 ++ package-lock.json | 48 ++++++++++++++++++++ package.json | 1 + 6 files changed, 166 insertions(+), 11 deletions(-) create mode 100644 core/db.js diff --git a/commands/utility/linkTrack.js b/commands/utility/linkTrack.js index bb00ff0..b9fb4ee 100644 --- a/commands/utility/linkTrack.js +++ b/commands/utility/linkTrack.js @@ -1,6 +1,7 @@ const { SlashCommandBuilder, EmbedBuilder } = require('discord.js'); -const { spotify } = require('../../config.json'); +const { spotify, lfmKey } = require('../../config.json'); const SpotifyWebApi = require('spotify-web-api-node'); +const axios = require('axios').default; module.exports = { category: 'utility', @@ -29,12 +30,16 @@ module.exports = { { name: '10', value: '<:10:1221744572326215680>' }, )) .addIntegerOption(option => - option.setName('scrobbles') - .setDescription('Your scrobble count at the time of submission') + option.setName('scrobbles_override') + .setDescription('Override your scrobble count. Otherwise fetches from LFM (use /lfm link to add account)') .setRequired(false)) .addStringOption(option => - option.setName('title_override') - .setDescription('Custom title, otherwise uses title from Spotify') + option.setName('artist') + .setDescription('Override artist, otherwise uses artist from Spotify') + .setRequired(false)) + .addStringOption(option => + option.setName('songname') + .setDescription('Override song name, otherwise uses name from Spotify') .setRequired(false)) .addStringOption(option => option.setName('footer_override') @@ -58,8 +63,11 @@ module.exports = { let urls = interaction.options.getString('links', true).split(" "); let title = ' '; - let description = ''; + let description = ' '; let spotifyID = null; + let artists = ''; + let songName = ''; + let mainArtist = ''; const trackEmbed = new EmbedBuilder() .setColor(Math.floor(Math.random() * 16777214) + 1) @@ -96,7 +104,9 @@ module.exports = { if(spotifyID) { let spotifyTrack = await this.spotifyAPI.getTrack(spotifyID); trackEmbed.setImage(spotifyTrack['body'].album.images[0].url); - title = `${spotifyTrack['body'].name} - ${spotifyTrack['body'].artists.map(a => a.name).join(', ')}`; + songName = spotifyTrack['body'].name; + mainArtist = spotifyTrack['body'].artists[0].name; + artists = spotifyTrack['body'].artists.map(a => a.name).join(', '); let releaseDate = new Date(spotifyTrack['body'].album.release_date).toDateString(); description = `From the album **${spotifyTrack['body'].album.name}**\nReleased **${releaseDate}**\n\n` + description; } @@ -104,8 +114,12 @@ module.exports = { if(interaction.options.getAttachment('cover_override')) { trackEmbed.setImage(interaction.options.getAttachment('cover_override').url); } + songName = interaction.options.getString('songname', false) ?? songName; + artists = interaction.options.getString('artist', false) ?? artists; + mainArtist = interaction.options.getString('artist', false) ?? mainArtist; + + title = `${songName} - ${artists}`; - title = interaction.options.getString('title_override', false) ?? title; trackEmbed.setTitle(`${title}`); let submitterName = interaction.member.displayName; @@ -118,10 +132,42 @@ module.exports = { ) } - if(interaction.options.getInteger('scrobbles', false)) { + let db = await interaction.client.localDB; + let dbResult = await db.get(`SELECT * FROM lastfm WHERE discord_id = ?`,[interaction.member.id]); + let lfmUsername = dbResult['lastfm_name']; + let lfmData = await axios.get(`https://ws.audioscrobbler.com/2.0/?method=track.getInfo&api_key=${lfmKey}&artist=${mainArtist}&track=${songName}&format=json&user=${lfmUsername}`); + + if(interaction.options.getInteger('scrobbles', false) || lfmData.data['track']?.['userplaycount'] > 0) { trackEmbed.addFields( - { name: `${submitterName} scrobbled this`, value: `${interaction.options.getInteger('scrobbles', false)} times so far`, inline: true }, + { name: `${submitterName} scrobbled this`, value: `${interaction.options.getInteger('scrobbles', false) ?? lfmData.data['track']['userplaycount']} times so far`, inline: true }, ) + } + + if(lfmData.data['track']) { + description += `<:lfm:1225099203039203338> [View on LFM](${lfmData.data['track']['url']})`; + let tags = lfmData.data['track']['toptags']['tag'].map(a => a.name); + console.log(lfmData.data['track']); + let listeners = this.numToHumanReadable(lfmData.data['track']['listeners']); + let globalScrobbles = this.numToHumanReadable(lfmData.data['track']['playcount']); + + trackEmbed.addFields( + { name: `LFM Global`, value: `Listeners: ${listeners}\nScrobbles: ${globalScrobbles}\n`, inline: false }, + ) + + let tagHeader = 'Tags'; + if(!tags) { + //artist tag fallback + let lfmArtistTags = await axios.get(`https://ws.audioscrobbler.com/2.0/?method=artist.getTopTags&api_key=${lfmKey}&artist=${mainArtist}&format=json`); + console.log(lfmArtistTags); + tags = lfmArtistTags.data['toptags']['tag'].map(a => a.name); + tagHeader = `Artist ${tagHeader}`; + } + if(tags) { + tags = this.joinLineBreak(tags, ', ', 3); + trackEmbed.addFields( + { name: `${tagHeader}`, value: `${tags.substr(0,60)}${tags.length > 60 ? '...' : ''}`, inline: true }, + ) + } } if(!description) { @@ -136,4 +182,21 @@ module.exports = { let response = await interaction.reply({content: 'done', ephemeral: true}); await response.delete(); }, + numToHumanReadable: function(num) { + if (num > 1000000 ) { + return `${(num/1000000).toFixed(1)}M`; + } + if (num > 1000 ) { + return `${(num/1000).toFixed(1)}K`; + } + return num; + }, + joinLineBreak: function(arr, joinChar, n) { + let groupedArray = []; + for (let index = 0; index < arr.length; index += n) { + let joined = index + n < arr.length ? arr.slice(index,index+n).join(joinChar) : arr.slice(index).join(joinChar); + groupedArray.push(joined); + } + return groupedArray.join('\n'); + } }; \ No newline at end of file diff --git a/config.json.example b/config.json.example index ee7c7ce..ef5725d 100644 --- a/config.json.example +++ b/config.json.example @@ -5,5 +5,6 @@ "spotify": { "clientID": "", "clientSecret": "" - } + }, + "lfmKey": "" } \ No newline at end of file diff --git a/core/db.js b/core/db.js new file mode 100644 index 0000000..3f0921f --- /dev/null +++ b/core/db.js @@ -0,0 +1,39 @@ +const fs = require("fs"); +const sqlite3 = require("sqlite3").verbose(); +const { open } = require('sqlite'); +const filepath = "./data/minzbot.db"; + +async function createDbConnection() { + if (fs.existsSync(filepath)) { + //new sqlite3.Database(filepath); + const db = await open({filename: filepath, driver: sqlite3.Database}); + await initDB(db); + return db; + } else { + const db = await open({filename: filepath, driver: sqlite3.Database}); + await initDB(db); + console.log("[DATABASE] Connection with SQLite has been established"); + return db; + } +} + +async function initDB(db) { + await db.exec(` + CREATE TABLE IF NOT EXISTS anniversaries ( + ID INTEGER PRIMARY KEY AUTOINCREMENT, + name VARCHAR(50) NOT NULL, + guild_id TEXT NOT NULL, + discord_id VARCHAR(50) NOT NULL, + last_anniversary_notification TEXT + ); + `); + await db.exec(` + CREATE TABLE IF NOT EXISTS lastfm ( + discord_id TEXT PRIMARY KEY NOT NULL, + lastfm_name TEXT + ); + `); + console.log('[DATABASE] Created new DB table'); +} + +module.exports = createDbConnection(); \ No newline at end of file diff --git a/main.js b/main.js index 6d5dbef..00efc94 100644 --- a/main.js +++ b/main.js @@ -3,6 +3,8 @@ const path = require('node:path'); const { Client, Collection, Events, GatewayIntentBits, Options, Partials } = require('discord.js'); const { token } = require('./config.json'); +const localDB = require('./core/db'); + const client = new Client({ intents: [GatewayIntentBits.Guilds, GatewayIntentBits.MessageContent, GatewayIntentBits.GuildMessages, GatewayIntentBits.GuildMembers, GatewayIntentBits.GuildPresences, GatewayIntentBits.GuildMessageReactions], partials: [Partials.Message, Partials.Channel, Partials.Reaction], @@ -12,6 +14,7 @@ const client = new Client({ } }); +client.localDB = localDB; client.commands = new Collection(); const foldersPath = path.join(__dirname, 'commands'); const commandFolders = fs.readdirSync(foldersPath); diff --git a/package-lock.json b/package-lock.json index 447cf0e..03639b1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "1.0.0", "license": "ISC", "dependencies": { + "axios": "^1.6.8", "discord.js": "^14.13.0", "dotenv": "^16.3.1", "spotify-web-api-node": "^5.0.2", @@ -269,6 +270,29 @@ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, + "node_modules/axios": { + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz", + "integrity": "sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/axios/node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -661,6 +685,25 @@ "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" }, + "node_modules/follow-redirects": { + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/form-data": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", @@ -1401,6 +1444,11 @@ "node": ">=10" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "node_modules/pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", diff --git a/package.json b/package.json index d068877..0d8bad5 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,6 @@ { "dependencies": { + "axios": "^1.6.8", "discord.js": "^14.13.0", "dotenv": "^16.3.1", "spotify-web-api-node": "^5.0.2",