diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index 1c73922..272d3e9 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -2,9 +2,9 @@ name: Docker Image CI on: push: - branches: [ "master" ] + branches: [ "main" ] pull_request: - branches: [ "master" ] + branches: [ "main" ] env: DHUB_UNAME: ${{ secrets.DHUB_UNAME }} diff --git a/README.md b/README.md index 12ad72f..e2a7e1e 100644 --- a/README.md +++ b/README.md @@ -15,30 +15,33 @@ Add the bot to your server and make sure it has the proper permissions (`Send Me ## Database Structure -### Table: guildinfo +### Table: guild_info ``` -+-----------------+-------------+------+-----+---------+----------------+ -| Field | Type | Null | Key | Default | Extra | -+-----------------+-------------+------+-----+---------+----------------+ -| id | int(10) | NO | PRI | NULL | auto_increment | -| guild_id | varchar(50) | NO | | NULL | | -| tree_message_id | varchar(50) | NO | | NULL | | -| tree_channel_id | varchar(50) | NO | | NULL | | -| rank_message_id | varchar(50) | NO | | NULL | | -| rank_channel_id | varchar(50) | NO | | NULL | | -| tree_height | varchar(10) | NO | | NULL | | -+-----------------+-------------+------+-----+---------+----------------+ ++------------------------+-------------+------+-----+---------+----------------+ +| Field | Type | Null | Key | Default | Extra | ++------------------------+-------------+------+-----+---------+----------------+ +| guild_id | varchar(50) | NO | PRI | NULL | auto_increment | +| tree_message_id | varchar(50) | NO | | | | +| tree_channel_id | varchar(50) | NO | | | | +| leaderboard_message_id | varchar(50) | NO | | | | +| leaderboard_channel_id | varchar(50) | NO | | | | +| tree_height | varchar(10) | NO | | 0 | | ++------------------------+-------------+------+-----+---------+----------------+ + ``` -### Table: treeinfo +### Table: leaderboard_info ``` -+-----------+--------------+------+-----+---------+----------------+ -| Field | Type | Null | Key | Default | Extra | -+-----------+--------------+------+-----+---------+----------------+ -| id | int(10) | NO | PRI | NULL | auto_increment | -| treename | varchar(100) | NO | | NULL | | -| treerank | int(10) | NO | | NULL | | -| timestamp | varchar(50) | NO | | NULL | | -+-----------+--------------+------+-----+---------+----------------+ ++-------------+--------------+------+-----+---------+----------------+ +| Field | Type | Null | Key | Default | Extra | ++-------------+--------------+------+-----+---------+----------------+ +| id | int(10) | NO | PRI | NULL | auto_increment | +| guild_id | varchar(50) | NO | | | | +| tree_name | varchar(100) | NO | | | | +| tree_rank | int(10) | NO | | | | +| tree_height | int(10) | NO | | 0 | | +| has_pin | tinyint(1) | NO | | 0 | | +| timestamp | varchar(50) | NO | | | | ++-------------+--------------+------+-----+---------+----------------+ ``` ## Changes to Implement diff --git a/data/strings.json b/data/strings.json index 66b9f05..1d2c13b 100644 --- a/data/strings.json +++ b/data/strings.json @@ -4,9 +4,18 @@ }, "help": { "title": "Silvanus Help", - "info": "This bot will analyze your tree (from the Grow A Tree Bot by Limbo Labs) and compare its growth to other trees displayed on the leaderboard.\n\nThis bot assumes that there is a single message and a single message that everyone uses. If everyone creates their own and , the reference messages will have to be updated every time, or old data will be displayed.\n\nSeeing old data? Try running again, sometimes configurations get lost.", + "info": "Silvanus is the ultimate Grow A Tree companion bot! Quickly compare your server's tree to others on the leaderboard with automatic calculation of tree height differences, active growth detection, watering time calculations, and more! Get started with and , then check out .\n\nImportant Note: Silvanus is only as up-to-date as your server's Tree and Tallest Trees messages. Make sure to refresh them before refreshing Silvanus' Compare message.\n\nFor the best experience we recommend the use of a single and message, otherwise make sure to run each time you run .", "setup": "To begin analyzing your Tree, first you must set up the reference messages.\n\n1. Run in the channel(s) that contain your server's tree and leaderboard messages.\n2. Now simply run where you want your analysis to be visible.", - "permissions": "At a minimum, Silvanus requires permissions to `Send Messages` and `Send Messages in Threads` if applicable. If Analyzer is given permission to `Manage Messages`, the bot will delete the `.settree` and `.setranks` messages to reduce spam." + "permissions": "At a minimum, Silvanus requires permissions to `Send Messages` and `Send Messages in Threads` if applicable. If Analyzer is given permission to `Manage Messages`, the bot will delete the `.settree` and `.setranks` messages to reduce spam.", + "allCommands": " | | | | | " + }, + "commands": { + "compare": "", + "setup": "", + "watertime": "", + "setupinfo": "", + "reset": "", + "help": "" }, "embeds": { "footer": "Silvanus is not affiliated with Grow A Tree or Limbo Labs", @@ -20,7 +29,8 @@ "cancel": "❌" }, "urls": { - "avatar": "https://cdn.discordapp.com/avatars/513184762073055252/12227aa23a06d5178853e59b72c7487b.webp?size=128" + "avatar": "https://cdn.discordapp.com/avatars/513184762073055252/12227aa23a06d5178853e59b72c7487b.webp?size=128", + "supportServer": "https://discord.gg/g5JRGn7PxU" }, "error": { "noGuild": "Setup has not been completed yet. Try running or " diff --git a/main.js b/main.js index fcfaee8..ea56db2 100644 --- a/main.js +++ b/main.js @@ -56,4 +56,8 @@ client.on('interactionCreate', async interaction => { } }); +process.on('unhandledRejection', error => { + console.error('Unhandled promise rejection:', error); +}); + client.login(token); \ No newline at end of file diff --git a/modules/dbfn.js b/modules/dbfn.js new file mode 100644 index 0000000..3106740 --- /dev/null +++ b/modules/dbfn.js @@ -0,0 +1,232 @@ +const dotenv = require('dotenv'); +dotenv.config(); +const debugMode = process.env.DEBUG || true; +const mysql = require('mysql'); +let db = mysql.createConnection({ + host : process.env.DBHOST, + user : process.env.DBUSER, + password : process.env.DBPASS, + database : process.env.DBNAME, + port : process.env.DBPORT +}); + +/* Table Structures +guild_info ++------------------------+-------------+------+-----+---------+----------------+ +| Field | Type | Null | Key | Default | Extra | ++------------------------+-------------+------+-----+---------+----------------+ +| guild_id | varchar(50) | NO | PRI | NULL | auto_increment | +| tree_name | varchar(100)| NO | | | | +| tree_height | varchar(10) | NO | | 0 | | +| tree_message_id | varchar(50) | NO | | | | +| tree_channel_id | varchar(50) | NO | | | | +| leaderboard_message_id | varchar(50) | NO | | | | +| leaderboard_channel_id | varchar(50) | NO | | | | ++------------------------+-------------+------+-----+---------+----------------+ +*/ +/* +leaderboard ++-------------+--------------+------+-----+---------+----------------+ +| Field | Type | Null | Key | Default | Extra | ++-------------+--------------+------+-----+---------+----------------+ +| id | int(10) | NO | PRI | NULL | auto_increment | +| guild_id | varchar(50) | NO | | | | +| tree_name | varchar(100) | NO | | | | +| tree_rank | int(10) | NO | | | | +| tree_height | int(10) | NO | | 1 | | +| has_pin | tinyint(1) | NO | | 0 | | +| timestamp | varchar(50) | NO | | | | ++-------------+--------------+------+-----+---------+----------------+ +*/ + +db.connect((err) => { + if (err) throw `Error connecting to the database: ${err.message}`; +}); + +db.on('error', function(err) { + db = mysql.createConnection({ + host : process.env.DBHOST, + user : process.env.DBUSER, + password : process.env.DBPASS, + database : process.env.DBNAME, + port : process.env.DBPORT + }); + db.connect((err) => { + if (err) throw `Error connecting to the database: ${err.message}`; + }); + }); + + +module.exports = { + createGuildTables(guildId) { + // Create the guild-information and rank-information tables to be used. + const createGuildInfoTableQuery = "CREATE TABLE IF NOT EXISTS guild_info(guild_id VARCHAR(50) NOT NULL, tree_name VARCHAR(100) NOT NULL DEFAULT 'Run /setup where your tree is.', tree_height INT(10) NOT NULL DEFAULT 0, tree_message_id VARCHAR(50) NOT NULL DEFAULT 'Run /setup where your tree is.', tree_channel_id VARCHAR(50) NOT NULL DEFAULT 'Run /setup where your tree is.', leaderboard_message_id VARCHAR(50) NOT NULL DEFAULT 'Run /setup where your leaderboard is.', leaderboard_channel_id VARCHAR(50) NOT NULL DEFAULT 'Run /setup where your leaderboard is.', CONSTRAINT guild_pk PRIMARY KEY (guild_id))"; + const createLeaderboardTableQuery = "CREATE TABLE IF NOT EXISTS leaderboard(id INT(10) NOT NULL AUTO_INCREMENT,guild_id VARCHAR(50) NOT NULL,tree_name VARCHAR(100) NOT NULL,tree_rank INT(10) NOT NULL,tree_height INT(10) NOT NULL DEFAULT 1,has_pin TINYINT(1) NOT NULL DEFAULT 0,timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, CONSTRAINT id_pk PRIMARY KEY(id))"; + // TODO run the queries, then add a call to this function at the beginning of main.js or functions.js + return new Promise((resolve, reject) => { + db.query(createGuildInfoTableQuery, (err) => { + if (err) { + reject("Error creating the guild_info table: " + err.message); + console.error("Offending query: " + createGuildInfoTableQuery); + return; + } + db.query(createLeaderboardTableQuery, (err) => { + if (err) { + reject("Error creating the leaderboard table: " + err.message); + console.error("Offending query: " + createLeaderboardTableQuery); + return; + } + resolve({ "status": "Successfully checked both tables.", "data": null }) + }); + }); + }); + }, + getGuildInfo(guildId) { + // Get a server's tree information from the database + const selectGuildInfoQuery = `SELECT tree_name, tree_height, tree_message_id, tree_channel_id, leaderboard_message_id, leaderboard_channel_id FROM guild_info WHERE guild_id = ${db.escape(guildId)}`; + // TODO run this query and return a promise then structure the output into a GuildInfo object. resolve with { "status": , "data": guildInfo } + return new Promise((resolve, reject) => { + db.query(selectGuildInfoQuery, (err, res) => { + if (err) { + console.error(err); + reject("Error fetching guild information: " + err.message); + return; + } + /*const guildInfo = { "guildId": "123", + "treeName": "name", + "treeHeight": 123, + "treeMessageId": "123", + "treeChannelId": "123", + "leaderboardMessageId": "123", + "leaderboardChannelId": "123" + };*/ + if (res.length == 0) { + reject("There is no database entry for your guild yet. Try running /setup"); + return; + } + row = res[0]; + const guildInfo = { "guildId": row.guild_id, + "treeName": row.tree_name, + "treeHeight": row.tree_height, + "treeMessageId": row.tree_message_id, + "treeChannelId": row.tree_channel_id, + "leaderboardMessageId": row.leaderboard_message_id, + "leaderboardChannelId": row.leaderboard_channel_id + }; + resolve({ "status": "Successfully fetched guild information", "data": guildInfo }) + }); + }); + }, + setGuildInfo(guildInfo) { + // Returns a Promise, resolve({ "status": "", "data": null }) + // guildInfo = { "guildId": "123", "treeName": "name", "treeHeight": 123, "treeMessageId": "123", "treeChannelId": "123", "leaderboardMessageId": "123", "leaderboardChannelId": "123"} + // Set a server's tree information in the database + const insertGuildInfoQuery = `INSERT INTO guild_info (guild_id, tree_name, tree_height, tree_message_id, tree_channel_id, leaderboard_message_id, leaderboard_channel_id) VALUES (${db.escape(guildInfo.guildId)}, ${db.escape(guildInfo.treeName)}, ${db.escape(guildInfo.treeHeight)},${db.escape(guildInfo.treeMessageId)}, ${db.escape(guildInfo.treeChannelId)}, ${db.escape(guildInfo.leaderboardMessageId)}, ${db.escape(guildInfo.leaderboardChannelId)}) ON DUPLICATE KEY UPDATE tree_name = ${db.escape(guildInfo.treeName)},tree_height = ${db.escape(guildInfo.treeHeight)},tree_message_id = ${db.escape(guildInfo.treeMessageId)},tree_channel_id = ${db.escape(guildInfo.treeChannelId)},leaderboard_message_id = ${db.escape(guildInfo.leaderboardMessageId)},leaderboard_channel_id = ${db.escape(guildInfo.leaderboardChannelId)}`; + // TODO run this query and return a promise, then resolve with { "status": , "data": null } + return new Promise((resolve, reject) => { + db.query(insertGuildInfoQuery, (err, res) => { + if (err) { + console.error(err); + reject("Error setting the guild info: " + err.message); + return; + } + resolve({ "status": "Successfully set the guild information", "data": null }); + }); + }); + }, + setTreeInfo(guildInfo) { + // Returns a Promise, resolve({ "status": "", "data": null }) + // guildInfo = { "guildId": "123", "treeName": "name", "treeHeight": 123, "treeMessageId": "123", "treeChannelId": "123", "leaderboardMessageId": "123", "leaderboardChannelId": "123"} + // Set a server's tree information in the database) + const insertGuildInfoQuery = `INSERT INTO guild_info (guild_id, tree_name, tree_height, tree_message_id, tree_channel_id) VALUES (${db.escape(guildInfo.guildId)}, ${db.escape(guildInfo.treeName)}, ${db.escape(guildInfo.treeHeight)},${db.escape(guildInfo.treeMessageId)}, ${db.escape(guildInfo.treeChannelId)}) ON DUPLICATE KEY UPDATE tree_name = ${db.escape(guildInfo.treeName)},tree_height = ${db.escape(guildInfo.treeHeight)},tree_message_id = ${db.escape(guildInfo.treeMessageId)},tree_channel_id = ${db.escape(guildInfo.treeChannelId)}`; + // TODO run this query and return a promise, then resolve with { "status": , "data": null } + return new Promise((resolve, reject) => { + db.query(insertGuildInfoQuery, (err, res) => { + if (err) { + console.error(err); + reject("Error setting the guild info: " + err.message); + return; + } + resolve({ "status": "Successfully set the guild information", "data": null }); + }); + }); + }, + setLeaderboardInfo(guildInfo) { + // Returns a Promise, resolve({ "status": "", "data": null }) + // guildInfo = { "guildId": "123", "treeName": "name", "treeHeight": 123, "treeMessageId": "123", "treeChannelId": "123", "leaderboardMessageId": "123", "leaderboardChannelId": "123"} + // Set a server's tree information in the database + const insertGuildInfoQuery = `INSERT INTO guild_info (guild_id, leaderboard_message_id, leaderboard_channel_id) VALUES (${db.escape(guildInfo.guildId)}, ${db.escape(guildInfo.leaderboardMessageId)}, ${db.escape(guildInfo.leaderboardChannelId)}) ON DUPLICATE KEY UPDATE leaderboard_message_id = ${db.escape(guildInfo.leaderboardMessageId)},leaderboard_channel_id = ${db.escape(guildInfo.leaderboardChannelId)}`; + // TODO run this query and return a promise, then resolve with { "status": , "data": null } + return new Promise((resolve, reject) => { + db.query(insertGuildInfoQuery, (err, res) => { + if (err) { + console.error(err); + reject("Error setting the guild info: " + err.message); + return; + } + resolve({ "status": "Successfully set the guild information", "data": null }); + }); + }); + }, + deleteGuildInfo(guildId) { + // Returns a Promise, resolve({ "status": "", "data": null }) + // guildInfo = { "guildId": "123", "treeName": "name", "treeHeight": 123, "treeMessageId": "123", "treeChannelId": "123", "leaderboardMessageId": "123", "leaderboardChannelId": "123"} + // Set a server's tree information in the database + const deleteGuildInfoQuery = `DELETE FROM guild_info WHERE guild_id = ${db.escape(guildId)}`; + // TODO run this query and return a promise, then resolve with { "status": , "data": null } + return new Promise((resolve, reject) => { + db.query(deleteGuildInfoQuery, (err, res) => { + if (err) { + console.error(err); + reject("Error deleting the guild info: " + err.message); + return; + } + resolve({ "status": "Successfully deleted the guild information", "data": null }); + }); + }); + }, + getLeaderboard(guildId) { + // Returns a Promise, resolve({ "status": "", "data": leaderboard }) + const selectLeaderboardQuery = `SELECT id, tree_name, tree_rank, tree_height, has_pin FROM leaderboard WHERE guild_id = ${db.escape(guildId)} ORDER BY id DESC LIMIT 10`; + // TODO run the query and return a promise then process the results. resolve with { "status": , "data": leaderboard } + return new Promise((resolve, reject) => { + db.query(selectLeaderboardQuery, (err, res) => { + if (err) { + console.error(err); + reject("Error fetching the most recent leaderboard: " + err.message); + return; + } + let leaderboard = []; + res.forEach(row => { + leaderboard.push({ + "treeName": row.tree_name, + "treeRank": row.tree_rank, + "treeHeight": row.tree_height, + "hasPin": row.has_pin + }); + }); + resolve({ "status": "Successfully fetched leaderboard.", "data": leaderboard }); + }); + }); + }, + uploadLeaderboard(leaderboard) { + // Returns a Promise, resolve({ "status": "", "data": res }) + // leaderboard = { "guildId": 1234, "entries": [ { "treeHeight": 12, "treeRank": 34, "treeName": "name", "hasPin": false }, {...}, {...} ] } + const insertLeaderboardQuery = "INSERT INTO `leaderboard` (guild_id, tree_name, tree_rank, tree_height, has_pin) VALUES ?"; + const leaderboardValues = []; + leaderboard.entries.forEach(ranking => { + leaderboardValues.push([leaderboard.guildId, ranking.treeName, ranking.treeRank, ranking.treeHeight, ranking.hasPin]); + }); + return new Promise((resolve, reject) => { + db.query(insertLeaderboardQuery, [leaderboardValues], (err, res) => { + if (err) { + reject("Error uploading the leaderboard: " + err.message); + console.error(err); + return; + } + resolve({ "status": "Successfully uploaded the leaderboard", "data": res }); + }); + }); + }, + db +}; \ No newline at end of file diff --git a/modules/functions.js b/modules/functions.js index 013eccc..3819b5e 100644 --- a/modules/functions.js +++ b/modules/functions.js @@ -3,6 +3,7 @@ const dotenv = require('dotenv'); dotenv.config(); const isDev = process.env.isDev; +const package = require('../package.json'); // filesystem const fs = require('fs'); @@ -13,9 +14,15 @@ const { EmbedBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle } = Discord; // Various imports from other files const config = require('../data/config.json'); -let guildInfo = require('../data/guildInfo.json'); const strings = require('../data/strings.json'); const slashCommandFiles = fs.readdirSync('./slash-commands/').filter(file => file.endsWith('.js')); +const dbfn = require('./dbfn.js'); + +dbfn.createGuildTables().then(res => { + console.log(res.status); +}).catch(err => { + console.error(err); +}); const functions = { // Functions for managing and creating Collections @@ -53,7 +60,7 @@ const functions = { .setColor(strings.embeds.color) .setTitle('Tree Growth Comparison') .setDescription(content) - .setFooter({text: strings.embeds.footer}); + .setFooter({text: `v${package.version} - ${strings.embeds.footer}`}); const messageContents = { embeds: [embed], components: [refreshActionRow] }; return messageContents; }, @@ -62,7 +69,7 @@ const functions = { .setColor(strings.embeds.color) .setTitle(strings.help.title) .setDescription(content) - .setFooter({ text: strings.embeds.footer }); + .setFooter({ text: `v${package.version} - ${strings.embeds.footer}` }); const privateBool = private == 'true'; const messageContents = { embeds: [embed], ephemeral: privateBool }; return messageContents; @@ -72,7 +79,7 @@ const functions = { .setColor(0xFF0000) .setTitle('Error!') .setDescription("Error: " + content) - .setFooter({ text: strings.embeds.footer }); + .setFooter({ text: `v${package.version} - ${strings.embeds.footer}` }); const messageContents = { embeds: [embed], ephemeral: true }; return messageContents; }, @@ -81,7 +88,7 @@ const functions = { .setColor(0x8888FF) .setTitle('Information') .setDescription(content) - .setFooter({ text: strings.embeds.footer }); + .setFooter({ text: `v${package.version} - ${strings.embeds.footer}` }); const messageContents = { embeds: [embed], ephemeral: true }; return messageContents; } @@ -89,168 +96,212 @@ const functions = { rankings: { parse(interaction) { return new Promise ((resolve, reject) => { - if (guildInfo[interaction.guildId] == undefined) { - reject(strings.error.noGuild); - return; - } - if (guildInfo[interaction.guildId].rankMessageId != undefined) { - interaction.guild.channels.fetch(guildInfo[interaction.guildId].rankChannelId).then(c => { - c.messages.fetch(guildInfo[interaction.guildId].rankMessageId).then(rankMessage => { - if ((rankMessage.embeds.length == 0) || (rankMessage.embeds[0].data.title != 'Tallest Trees' )) { - reject("This doesn't appear to be a valid ``/top trees`` message."); - return; - } - let lines = rankMessage.embeds[0].data.description.split('\n'); - let rankings = []; - for (let i = 0; i < 10; i++) { - let breakdown = lines[i].split(' - '); - if (breakdown[0].includes('🥇')) { - breakdown[0] = '``#1 ``' - } else if (breakdown[0].includes('🥈')) { - breakdown[0] = '``#2 ``' - } else if (breakdown[0].includes('🥉')) { - breakdown[0] = '``#3 ``' + dbfn.getGuildInfo(interaction.guildId).then(res => { + const guildInfo = res.data; + if (guildInfo.guildId == "") { + reject(strings.error.noGuild); + return; + } + if (guildInfo.leaderboardMessageId != undefined) { + interaction.guild.channels.fetch(guildInfo.leaderboardChannelId).then(c => { + c.messages.fetch(guildInfo.leaderboardMessageId).then(leaderboardMessage => { + if ((leaderboardMessage.embeds.length == 0) || (leaderboardMessage.embeds[0].data.title != 'Tallest Trees' )) { + reject("This doesn't appear to be a valid ``/top trees`` message."); + return; } - - let trimmedRank = breakdown[0].slice(breakdown[0].indexOf('#') + 1, breakdown[0].lastIndexOf('``')); - - let trimmedName = breakdown[1].slice(breakdown[1].indexOf('``') + 2); - trimmedName = trimmedName.slice(0, trimmedName.indexOf('``')); - - let trimmedHeight = parseFloat(breakdown[2].slice(0, breakdown[2].indexOf('ft'))).toFixed(1); - let isMyTree = false; - let isMaybeMyTree = false; - if (breakdown[2].includes('📍')) isMyTree = true; - if (breakdown[1].includes(guildInfo[interaction.guildId].treeName)) maybeMyTree = true; - - rankings.push({ - rank: trimmedRank, - name: trimmedName, - height: trimmedHeight, - myTree: isMyTree, - maybeMyTree: isMaybeMyTree + let lines = leaderboardMessage.embeds[0].data.description.split('\n'); + let leaderboard = { + "guildId": interaction.guildId, + "entries": [] + }; + for (let i = 0; i < 10; i++) { + // Breakdown each line separating it on each - + let breakdown = lines[i].split(' - '); + + // Check if the first part, the ranking, has these emojis to detect first second and third place + if (breakdown[0].includes('🥇')) { + breakdown[0] = '``#1 ``' + } else if (breakdown[0].includes('🥈')) { + breakdown[0] = '``#2 ``' + } else if (breakdown[0].includes('🥉')) { + breakdown[0] = '``#3 ``' + } + + // Clean off the excess and get just the number from the rank, make sure it's an int not string + let trimmedRank = parseInt(breakdown[0].slice(breakdown[0].indexOf('#') + 1, breakdown[0].lastIndexOf('``'))); + + // Clean off the excess and get just the tree name + let trimmedName = breakdown[1].slice(breakdown[1].indexOf('``') + 2); + trimmedName = trimmedName.slice(0, trimmedName.indexOf('``')); + + // Clean off the excess and get just the tree height, make sure it's a 1 decimal float + let trimmedHeight = parseFloat(breakdown[2].slice(0, breakdown[2].indexOf('ft'))).toFixed(1); + let isMyTree = false; + let isMaybeMyTree = false; + if (breakdown[2].includes('📍')) isMyTree = true; + if (breakdown[1].includes(guildInfo.treeName)) maybeMyTree = true; + + // "entries": [ { "treeHeight": 12, "treeRank": 34, "treeName": "name" }, ] } + + + leaderboard.entries.push({ + treeRank: trimmedRank, + treeName: trimmedName, + treeHeight: trimmedHeight, + hasPin: isMyTree + }); + } + + dbfn.uploadLeaderboard(leaderboard).then(res => { + console.log(res.status); + resolve(res.status); + }).catch(err => { + console.error(err); + reject(err); + return; }); - } - - guildInfo[interaction.guildId].rankings = rankings; - fs.writeFileSync('./data/guildInfo.json', JSON.stringify(guildInfo)); - guildInfo = require('../data/guildInfo.json'); - resolve(rankings); + }); }); - }); - } else { - reject("The rankMessageId is undefined somehow"); + } else { + reject("The leaderboardMessageId is undefined somehow"); + return; + } + }).catch(err => { + reject(err); return; - } + }); }); }, compare(interaction) { - if (guildInfo[interaction.guildId] == undefined) { - return strings.error.noGuild; - } - let treeHeight = parseFloat(guildInfo[interaction.guildId].treeHeight).toFixed(1); - if ((guildInfo[interaction.guildId].rankings.length > 0) && (treeHeight > 0)) { - let replyString = 'Current Tree Height: ' + treeHeight + 'ft\n\n'; - guildInfo[interaction.guildId].rankings.forEach(treeRanking => { - let difference = parseFloat(treeRanking.height).toFixed(1) - treeHeight; - let decimal = (treeRanking.height % 1).toFixed(1); - let growthIndicator = ""; - if (decimal > 0) { - growthIndicator += "[+]"; - } - const absDifference = parseFloat(Math.abs(difference)).toFixed(1); - if (treeRanking.myTree) { - replyString += "This is your tree. "; - } else if (treeRanking.maybeMyTree) { - replyString += "This might be your tree. Same height, same name. "; - } else { - if (difference > 0) { - replyString += `#${treeRanking.rank} - ${absDifference}ft${growthIndicator} shorter `; - } else if (difference < 0) { - replyString += `#${treeRanking.rank} - ${absDifference}ft${growthIndicator} taller `; - } else if (difference == 0) { - replyString += `#${treeRanking.rank} - Same Height${growthIndicator} `; - } - } - replyString += `[${functions.getWaterTime(treeRanking.height)} mins]\n`; + return new Promise((resolve, reject) => { + dbfn.getGuildInfo(interaction.guildId).then(res => { + const guildInfo = res.data; + guildInfo.guildId = interaction.guildId; + + let treeHeight = parseFloat(guildInfo.treeHeight).toFixed(1); + dbfn.getLeaderboard(interaction.guildId).then(res => { + const leaderboard = res.data; + + let replyString = 'Current Tree Height: ' + treeHeight + 'ft\n\n'; + leaderboard.reverse().forEach(treeRanking => { + let difference = parseFloat(treeRanking.treeHeight).toFixed(1) - treeHeight; + let decimal = (treeRanking.treeHeight % 1).toFixed(1); + let growthIndicator = ""; + if (decimal > 0) { + growthIndicator += "[+]"; + } + const absDifference = parseFloat(Math.abs(difference)).toFixed(1); + if (treeRanking.hasPin) { + replyString += "This is your tree. "; + } else if ((treeRanking.treeHeight == treeHeight) && (treeRanking.treeName == guildInfo.treeName)) { + replyString += "This might be your tree. Same height, same name. "; + } else { + if (difference > 0) { + replyString += `#${treeRanking.treeRank} - ${absDifference}ft${growthIndicator} shorter `; + } else if (difference < 0) { + replyString += `#${treeRanking.treeRank} - ${absDifference}ft${growthIndicator} taller `; + } else if (difference == 0) { + replyString += `#${treeRanking.treeRank} - Same Height${growthIndicator} `; + } + } + replyString += `[${functions.getWaterTime(treeRanking.treeHeight)} mins]\n`; + }); + resolve('Here\'s how your tree compares: \n' + replyString); + }).catch(err => { + console.error(err); + }); + }).catch(err => { + reject(err); + return; }); - return 'Here\'s how your tree compares: \n' + replyString; - } else { - console.error('Not configured correctly\n' + 'Guild ID: ' + interaction.guildId + '\nGuild Info: ' + JSON.stringify(guildInfo[interaction.guildId])); - return 'Not configured correctly'; - } + }); } }, tree: { parse(interaction) { let input; return new Promise((resolve, reject) => { - if (guildInfo[interaction.guildId] == undefined) { - reject(`The guild entry hasn't been created yet. [${interaction.guildId || interaction.commandGuildId}]`); + dbfn.getGuildInfo(interaction.guildId).then(res => { + const guildInfo = res.data; + guildInfo.guildId = interaction.guildId; + if (guildInfo == undefined) { + reject(`The guild entry hasn't been created yet. [${interaction.guildId || interaction.commandGuildId}]`); + return; + } + if (guildInfo.treeMessageId != "Run /setup where your tree is.") { + interaction.guild.channels.fetch(guildInfo.treeChannelId).then(c => { + c.messages.fetch(guildInfo.treeMessageId).then(m => { + if ( (m.embeds.length == 0) || !(m.embeds[0].data.description.includes('Your tree is')) ) { + reject("This doesn't appear to be a valid ``/tree`` message."); + return; + } + input = m.embeds[0].data.description; + let treeName = m.embeds[0].data.title; + let lines = input.split('\n'); + guildInfo.treeHeight = parseFloat(lines[0].slice(lines[0].indexOf('is') + 3, lines[0].indexOf('ft'))).toFixed(1); + guildInfo.treeName = treeName; + dbfn.setTreeInfo(guildInfo).then(res => { + resolve("The reference tree message has been saved/updated."); + }); + }); + }) + } else { + console.error('treeMessageId undefined'); + reject("There was an unknown error while setting the tree message."); + return; + } + }).catch(err => { + reject(err); + console.error(err); return; - } - if (guildInfo[interaction.guildId].treeMessageId != "") { - interaction.guild.channels.fetch(guildInfo[interaction.guildId].treeChannelId).then(c => { - c.messages.fetch(guildInfo[interaction.guildId].treeMessageId).then(m => { - if ( (m.embeds.length == 0) || !(m.embeds[0].data.description.includes('Your tree is')) ) { - reject("This doesn't appear to be a valid ``/tree`` message."); - return; - } - input = m.embeds[0].data.description; - let treeName = m.embeds[0].data.title; - let lines = input.split('\n'); - guildInfo[interaction.guildId].treeHeight = parseFloat(lines[0].slice(lines[0].indexOf('is') + 3, lines[0].indexOf('ft'))).toFixed(1); - guildInfo[interaction.guildId].treeName = treeName; - fs.writeFileSync('./data/guildInfo.json', JSON.stringify(guildInfo)); - guildInfo = require('../data/guildInfo.json'); - resolve("The reference tree message has been saved/updated."); - }); - }) - } else { - console.error('treeMessageId undefined'); - reject("There was an unknown error while setting the tree message."); - return; - } + }); }); } }, refresh(interaction) { functions.rankings.parse(interaction).then(r1 => { functions.tree.parse(interaction).then(r2 => { - const embed = functions.builders.comparisonEmbed(functions.rankings.compare(interaction), functions.builders.refreshAction()) - interaction.update(embed); + functions.rankings.compare(interaction).then(res => { + const embed = functions.builders.comparisonEmbed(res, functions.builders.refreshAction()) + interaction.update(embed); + }).catch(err => { + console.error(err); + }); }).catch(e => { + console.error(e); interaction.reply(functions.builders.errorEmbed(e)); }); }).catch(e => { + console.error(e); interaction.reply(functions.builders.errorEmbed(e)); }); }, reset(guildId) { - delete guildInfo[guildId]; - fs.writeFileSync('./data/guildInfo.json', JSON.stringify(guildInfo)); - guildInfo = require('../data/guildInfo.json'); - return; + return new Promise((resolve, reject) => { + dbfn.deleteGuildInfo(guildId).then(res => { + resolve(res); + }).catch(err => { + console.error(err); + reject(err); + return; + }); + }); }, getInfo(guildId) { - const thisGuildInfo = guildInfo[guildId]; - if (thisGuildInfo != undefined) { - let thisGuildInfoString = ""; - if (thisGuildInfo.treeMessageId != "") { - thisGuildInfoString += `Tree Message: https://discord.com/channels/${guildId}/${thisGuildInfo.treeChannelId}/${thisGuildInfo.treeMessageId}\n`; - - } - if (thisGuildInfo.rankMessageId != "") { - thisGuildInfoString += `Rank Message: https://discord.com/channels/${guildId}/${thisGuildInfo.rankChannelId}/${thisGuildInfo.rankMessageId}\n`; - } - if (thisGuildInfo.treeHeight != "") { - thisGuildInfoString += `Tree Height: ${thisGuildInfo.treeHeight}\n`; - } - return `Here is your servers setup info:\n${thisGuildInfoString}`; - } else { - return "Your guild hasn't been set up yet."; - } + return new Promise((resolve, reject) => { + dbfn.getGuildInfo(guildId).then(res => { + let guildInfo = res.data; + let guildInfoString = ""; + guildInfoString += `Tree Message: https://discord.com/channels/${guildId}/${guildInfo.treeChannelId}/${guildInfo.treeMessageId}\n`; + guildInfoString += `Rank Message: https://discord.com/channels/${guildId}/${guildInfo.leaderboardChannelId}/${guildInfo.leaderboardMessageId}\n`; + resolve(`Here is your servers setup info:\n${guildInfoString}`); + }).catch(err => { + console.error(err); + reject(err); + return; + }) + }); }, getWaterTime(size) { return Math.floor((Math.pow(size * 0.07 + 5, 1.1) / 60)); diff --git a/package.json b/package.json index 5f9ecbb..6f36efc 100644 --- a/package.json +++ b/package.json @@ -1,23 +1,24 @@ { - "name": "treeanalyzer", - "version": "1.0.5", - "description": "Analyze Grow A Tree", + "name": "silvanus", + "version": "1.1.0", + "description": "Grow A Tree Comparison Tool", "main": "main.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "repository": { "type": "git", - "url": "git+https://github.com/voidf1sh/treeanalyzer.git" + "url": "git+https://github.com/voidf1sh/silvanus.git" }, "author": "Skylar Grant", "license": "ISC", "bugs": { - "url": "https://github.com/voidf1sh/treeanalyzer/issues" + "url": "https://github.com/voidf1sh/silvanus/issues" }, - "homepage": "https://github.com/voidf1sh/treeanalyzer#readme", + "homepage": "https://github.com/voidf1sh/silvanus#readme", "dependencies": { "discord.js": "^14.7.1", - "dotenv": "^16.0.3" + "dotenv": "^16.0.3", + "mysql": "^2.18.1" } } diff --git a/slash-commands/compare.js b/slash-commands/compare.js index cf0ba38..37864f8 100644 --- a/slash-commands/compare.js +++ b/slash-commands/compare.js @@ -6,7 +6,18 @@ module.exports = { .setName('compare') .setDescription('See how your tree compares to other trees!'), async execute(interaction) { - const embed = fn.builders.comparisonEmbed(fn.rankings.compare(interaction), fn.builders.refreshAction()); - interaction.reply(embed); + interaction.deferReply().then(() => { + fn.rankings.compare(interaction).then(res => { + const embed = fn.builders.comparisonEmbed(res, fn.builders.refreshAction()); + interaction.editReply(embed).catch(err => { + console.error(err); + }); + }).catch(err => { + interaction.editReply(fn.builders.errorEmbed(err)).catch(err => { + console.error(err); + }); + console.error(err); + }); + }) }, }; \ No newline at end of file diff --git a/slash-commands/help.js b/slash-commands/help.js index 07a1e40..6319426 100644 --- a/slash-commands/help.js +++ b/slash-commands/help.js @@ -14,7 +14,7 @@ module.exports = { { name: "True", value: "true" }, { name: "False", value: "false" })), execute(interaction) { - const helpEmbed = fn.builders.helpEmbed(`${strings.help.info}\n\n**Setup**\n${strings.help.setup}`, interaction.options.getString('private')); + const helpEmbed = fn.builders.helpEmbed(`${strings.help.info}\n\n**Setup**\n${strings.help.setup}\n\n**All Commands**\n${strings.help.allCommands}\n\n**Support Server**\n${strings.urls.supportServer}`, interaction.options.getString('private')); interaction.reply(helpEmbed); }, }; \ No newline at end of file diff --git a/slash-commands/reset.js b/slash-commands/reset.js index cecb3cd..7cd7985 100644 --- a/slash-commands/reset.js +++ b/slash-commands/reset.js @@ -6,7 +6,19 @@ module.exports = { .setName('reset') .setDescription('Reset all message assignments in your server'), execute(interaction) { - fn.reset(interaction.guildId); - interaction.reply(fn.builders.embed("Assignments Reset")); + interaction.deferReply({ ephemeral: true }).then(() => { + fn.reset(interaction.guildId).then(res => { + interaction.editReply(fn.builders.embed("Assignments Reset")).catch(err => { + console.error(err); + }); + }).catch(err => { + console.error(err); + interaction.editReply("There was a problem deleting your guild information, contact @voidf1sh#0420 for help.").catch(err => { + console.error(err); + }); + }); + }).catch(err => { + console.error(err); + }); }, }; \ No newline at end of file diff --git a/slash-commands/setup.js b/slash-commands/setup.js index 646fc79..1a95bed 100644 --- a/slash-commands/setup.js +++ b/slash-commands/setup.js @@ -1,7 +1,7 @@ const { SlashCommandBuilder } = require('discord.js'); const fn = require('../modules/functions.js'); -const guildInfo = require('../data/guildInfo.json'); const strings = require('../data/strings.json'); +const dbfn = require('../modules/dbfn.js'); module.exports = { data: new SlashCommandBuilder() @@ -9,41 +9,57 @@ module.exports = { .setDescription('Attempt automatic configuration of the bot.'), execute(interaction) { interaction.deferReply({ ephemeral: true }).then(function () { - if (guildInfo[interaction.guildId] == undefined) { - guildInfo[interaction.guildId] = { - "treeMessageId": "", - "treeChannelId": "", - "rankMessageId": "", - "rankChannelId": "", - "treeName": "", - "treeHeight": 0, - "rankings": [] - }; - } + /*const guildInfo = { "guildId": "123", + "treeName": "name", + "treeHeight": 123, + "treeMessageId": "123", + "treeChannelId": "123", + "leaderboardMessageId": "123", + "leaderboardChannelId": "123" + };*/ + const guildInfo = { "guildId": interaction.guildId, + "treeName": "name", + "treeHeight": 123, + "treeMessageId": "123", + "treeChannelId": "123", + "leaderboardMessageId": "123", + "leaderboardChannelId": "123" + }; interaction.channel.messages.fetch({ limit: 20 }).then(function (msgs) { let treeFound = false; - let rankFound = false; + let leaderboardFound = false; msgs.reverse().forEach(msg => { if (msg.embeds.length > 0) { if (msg.embeds[0].data.description.includes("Your tree is")) { treeFound = true; - guildInfo[interaction.guildId].treeChannelId = msg.channelId; - guildInfo[interaction.guildId].treeMessageId = msg.id; - fn.tree.parse(msg); + guildInfo.treeName = msg.embeds[0].title; + guildInfo.treeChannelId = msg.channelId; + guildInfo.treeMessageId = msg.id; } else if (msg.embeds[0].data.title == "Tallest Trees") { - rankFound = true; - guildInfo[interaction.guildId].rankChannelId = msg.channelId; - guildInfo[interaction.guildId].rankMessageId = msg.id; - fn.rankings.parse(msg); + leaderboardFound = true; + guildInfo.leaderboardChannelId = msg.channelId; + guildInfo.leaderboardMessageId = msg.id; } } }); - if (treeFound && !(rankFound)) { - interaction.editReply(fn.builders.embed(strings.status.treeNoLeaderboard)); - } else if (!(treeFound) && rankFound) { - interaction.editReply(fn.builders.embed(strings.status.leaderboardNoTree)); - } else if (treeFound && rankFound) { - interaction.editReply(fn.builders.embed(strings.status.treeAndLeaderboard)); + if (treeFound && !(leaderboardFound)) { + dbfn.setTreeInfo(guildInfo).then(res => { + interaction.editReply(fn.builders.embed(strings.status.treeNoLeaderboard)); + }).catch(err => { + console.error(err); + }); + } else if (!(treeFound) && leaderboardFound) { + dbfn.setLeaderboardInfo(guildInfo).then(res => { + interaction.editReply(fn.builders.embed(strings.status.leaderboardNoTree)); + }).catch(err => { + console.error(err); + }); + } else if (treeFound && leaderboardFound) { + dbfn.setGuildInfo(guildInfo).then(res => { + interaction.editReply(fn.builders.embed(strings.status.treeAndLeaderboard)); + }).catch(err => { + console.error(err); + }); } }); }); diff --git a/slash-commands/setupinfo.js b/slash-commands/setupinfo.js index b30871a..8597c94 100644 --- a/slash-commands/setupinfo.js +++ b/slash-commands/setupinfo.js @@ -6,7 +6,16 @@ module.exports = { .setName('setupinfo') .setDescription('View information about how the bot is set up in your server'), execute(interaction) { - const embed = fn.builders.embed(fn.getInfo(interaction.guildId)); - interaction.reply(embed); + interaction.deferReply({ephemeral: true}).then(() => { + fn.getInfo(interaction.guildId).then(res => { + const embed = fn.builders.embed(res); + interaction.editReply(embed); + }).catch(err => { + interaction.editReply(err); + console.error(err); + }); + }).catch(err => { + console.error(err); + }); }, }; \ No newline at end of file