From e64fa099c11f6a71d55e666eb77076363a129a2b Mon Sep 17 00:00:00 2001 From: Skylar Grant Date: Wed, 25 Jan 2023 04:53:49 -0500 Subject: [PATCH] V1.1.1-dev (#7) * Fix db connection handling * Add new time to height calculator * Versioning * Finishing updates --- README.md | 70 ++++++------------ data/guildInfo.json | 1 - modules/dbfn.js | 130 +++++++++++++++++++++++++-------- modules/functions.js | 22 +++++- package.json | 2 +- slash-commands/timetoheight.js | 23 ++++++ 6 files changed, 170 insertions(+), 78 deletions(-) delete mode 100644 data/guildInfo.json create mode 100644 slash-commands/timetoheight.js diff --git a/README.md b/README.md index e2a7e1e..0abea42 100644 --- a/README.md +++ b/README.md @@ -1,50 +1,28 @@ -# Grow A Tree Analyzer -This bot works with Grow A Tree Bot by Limbo Labs. This project is not affiliated with Limbo Labs in any way. +# Silvanus +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 /help and /setup, then check out /compare. -This bot allows easy comparison between a server's tree and other trees displayed on the leaderboard. +Important 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. -## Usage -Add the bot to your server and make sure it has the proper permissions (`Send Messages` and `Send Messages in Threads` if applicable), then run `/setup` in the channel(s) that contain your server's tree and leaderboard messages. +For the best experience we recommend the use of a single /tree and /top trees message, otherwise make sure to run /setup each time you run /compare. + +Silvanus is not affiliated with Grow A Tree or Limbo Labs. + +## Add Silvanus to your server +[Invite Silvanus to your Discord Server](https://discord.com/api/oauth2/authorize?client_id=521624335119810561&permissions=274877908992&scope=bot%20applications.commands) + +## voidf1sh Development Support Server +[Join Discord Server](https://discord.gg/g5JRGn7PxU) + +## Setup +To begin analyzing your Tree, first you must set up the reference messages.\n\n1. Run `/setup` in the channel(s) that contain your server's tree and leaderboard messages.\n2. Now simply run `/compare` where you want your analysis to be visible. + +## Permissions +Silvanus requires permissions to `Send Messages` and `Send Messages in Threads` if applicable. ## Commands - -* `/setup` - Attempts automatic detection of your server's tree and leaderboard messages. -* `/setupinfo` - Displays your server's current configuration. -* `/reset` - Resets your server's configuration, run `/setup` again if needed. - - -## Database Structure - -### Table: guild_info -``` -+------------------------+-------------+------+-----+---------+----------------+ -| 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: leaderboard_info -``` -+-------------+--------------+------+-----+---------+----------------+ -| 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 - -* Move around some of the functions. -* Migrate storage to SQLite. \ No newline at end of file +* `/setup` - Automatically detects and saves the location of your server's Tree and Tallest Trees messages. Run this command in the channel(s) that contain these messages. +* `/setupinfo` - Displays links to the current Tree and Tallest Trees messages configured in your server. +* `/compare` - Sends a refreshable embed that calculcates the height difference between your tree and the trees currently displayed on your Tallest Trees message. There is also an Active Growth Indicator (`[+]`) and a water wait time calculation (`[20 mins]`) +* `/watertime height` - Calculates the wait time between waters for a tree of a given height. +* `/reset` - Removes your server's configuration from the database. +* `/help` - Displays the bot's help page and links to each command. \ No newline at end of file diff --git a/data/guildInfo.json b/data/guildInfo.json deleted file mode 100644 index a2d5d12..0000000 --- a/data/guildInfo.json +++ /dev/null @@ -1 +0,0 @@ -{"868542949737246730":{"treeMessageId":"1067246387361751132","treeChannelId":"1067246309448368148","rankMessageId":"1067264289578160229","rankChannelId":"1067246387361751132","treeName":"voidf1sh Development","treeHeight":"8.0","rankings":[{"rank":"21","name":"boop","height":"6403.0","myTree":false,"maybeMyTree":false},{"rank":"22","name":"Unfortunate","height":"6219.0","myTree":false,"maybeMyTree":false},{"rank":"23","name":"The Real Treebeard","height":"6124.6","myTree":false,"maybeMyTree":false},{"rank":"24","name":"Treevis Scott","height":"6124.0","myTree":false,"maybeMyTree":false},{"rank":"25","name":"Air Tank","height":"5967.0","myTree":false,"maybeMyTree":false},{"rank":"26","name":"Plant 2 Win","height":"5960.0","myTree":false,"maybeMyTree":false},{"rank":"27","name":"Cult's Wood","height":"5929.0","myTree":false,"maybeMyTree":false},{"rank":"28","name":"TreeIsOnline","height":"5807.0","myTree":false,"maybeMyTree":false},{"rank":"29","name":"Salad's Community Tree","height":"5752.0","myTree":false,"maybeMyTree":false},{"rank":"30","name":"SNS tree","height":"5730.6","myTree":false,"maybeMyTree":false}]}} \ No newline at end of file diff --git a/modules/dbfn.js b/modules/dbfn.js index 3106740..296fb3f 100644 --- a/modules/dbfn.js +++ b/modules/dbfn.js @@ -2,13 +2,6 @@ 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 @@ -39,26 +32,18 @@ leaderboard +-------------+--------------+------+-----+---------+----------------+ */ -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) { + const 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}`; + }); // 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))"; @@ -68,20 +53,33 @@ module.exports = { if (err) { reject("Error creating the guild_info table: " + err.message); console.error("Offending query: " + createGuildInfoTableQuery); + db.end(); return; } db.query(createLeaderboardTableQuery, (err) => { if (err) { reject("Error creating the leaderboard table: " + err.message); console.error("Offending query: " + createLeaderboardTableQuery); + db.end(); return; } - resolve({ "status": "Successfully checked both tables.", "data": null }) + resolve({ "status": "Successfully checked both tables.", "data": null }); + db.end(); }); }); }); }, getGuildInfo(guildId) { + const 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}`; + }); // 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 } @@ -90,6 +88,7 @@ module.exports = { if (err) { console.error(err); reject("Error fetching guild information: " + err.message); + db.end(); return; } /*const guildInfo = { "guildId": "123", @@ -102,6 +101,7 @@ module.exports = { };*/ if (res.length == 0) { reject("There is no database entry for your guild yet. Try running /setup"); + db.end(); return; } row = res[0]; @@ -113,11 +113,22 @@ module.exports = { "leaderboardMessageId": row.leaderboard_message_id, "leaderboardChannelId": row.leaderboard_channel_id }; - resolve({ "status": "Successfully fetched guild information", "data": guildInfo }) + db.end(); + resolve({ "status": "Successfully fetched guild information", "data": guildInfo }); }); }); }, setGuildInfo(guildInfo) { + const 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}`; + }); // 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 @@ -128,13 +139,25 @@ module.exports = { if (err) { console.error(err); reject("Error setting the guild info: " + err.message); + db.end(); return; } + db.end(); resolve({ "status": "Successfully set the guild information", "data": null }); }); }); }, setTreeInfo(guildInfo) { + const 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}`; + }); // 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) @@ -144,14 +167,26 @@ module.exports = { db.query(insertGuildInfoQuery, (err, res) => { if (err) { console.error(err); + db.end(); reject("Error setting the guild info: " + err.message); return; } + db.end(); resolve({ "status": "Successfully set the guild information", "data": null }); }); }); }, setLeaderboardInfo(guildInfo) { + const 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}`; + }); // 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 @@ -161,14 +196,26 @@ module.exports = { db.query(insertGuildInfoQuery, (err, res) => { if (err) { console.error(err); + db.end(); reject("Error setting the guild info: " + err.message); return; } + db.end(); resolve({ "status": "Successfully set the guild information", "data": null }); }); }); }, deleteGuildInfo(guildId) { + const 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}`; + }); // 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 @@ -178,14 +225,26 @@ module.exports = { db.query(deleteGuildInfoQuery, (err, res) => { if (err) { console.error(err); + db.end(); reject("Error deleting the guild info: " + err.message); return; } + db.end(); resolve({ "status": "Successfully deleted the guild information", "data": null }); }); }); }, getLeaderboard(guildId) { + const 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}`; + }); // 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 } @@ -193,6 +252,7 @@ module.exports = { db.query(selectLeaderboardQuery, (err, res) => { if (err) { console.error(err); + db.end(); reject("Error fetching the most recent leaderboard: " + err.message); return; } @@ -205,11 +265,22 @@ module.exports = { "hasPin": row.has_pin }); }); + db.end(); resolve({ "status": "Successfully fetched leaderboard.", "data": leaderboard }); }); }); }, uploadLeaderboard(leaderboard) { + const 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}`; + }); // 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 ?"; @@ -221,12 +292,13 @@ module.exports = { db.query(insertLeaderboardQuery, [leaderboardValues], (err, res) => { if (err) { reject("Error uploading the leaderboard: " + err.message); + db.end(); console.error(err); return; } + db.end(); 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 3819b5e..91e4e46 100644 --- a/modules/functions.js +++ b/modules/functions.js @@ -304,7 +304,27 @@ const functions = { }); }, getWaterTime(size) { - return Math.floor((Math.pow(size * 0.07 + 5, 1.1) / 60)); + const seconds = Math.floor(Math.pow(size * 0.07 + 5, 1.1)); + return (Math.floor((Math.pow(size * 0.07 + 5, 1.1))) / 60).toFixed(2); + }, + timeToHeight(interaction, destHeight) { + return new Promise((resolve, reject) => { + dbfn.getGuildInfo(interaction.guildId).then(res => { + let guildInfo = res.data; + let currentHeight = parseInt(guildInfo.treeHeight); + let time = 0; + for (let i = currentHeight; i < destHeight; i++) { + const waterTime = parseFloat(functions.getWaterTime(i)); + console.log("Height: " + i + "Time: " + waterTime); + time += waterTime; + } + resolve(time.toFixed(2)); + }).catch(err => { + console.error(err); + reject(err); + return; + }) + }); } }; diff --git a/package.json b/package.json index 6f36efc..9576e95 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "silvanus", - "version": "1.1.0", + "version": "1.1.1", "description": "Grow A Tree Comparison Tool", "main": "main.js", "scripts": { diff --git a/slash-commands/timetoheight.js b/slash-commands/timetoheight.js new file mode 100644 index 0000000..6b62a57 --- /dev/null +++ b/slash-commands/timetoheight.js @@ -0,0 +1,23 @@ +const { SlashCommandBuilder } = require('discord.js'); +const fn = require('../modules/functions.js'); + +module.exports = { + data: new SlashCommandBuilder() + .setName('timetoheight') + .setDescription('Calculate how long it would take to reach a given height') + .addStringOption(o => + o.setName('height') + .setDescription('Tree height in feet, numbers ONLY') + .setRequired(true)), + async execute(interaction) { + await interaction.deferReply(); + const destTreeHeight = interaction.options.getString('height'); + fn.timeToHeight(interaction, destTreeHeight).then(res => { + interaction.editReply(`It will take you ${res} minutes to reach ${destTreeHeight}ft.`); + }).catch(err => { + interaction.editReply("Error: " + err); + console.error(err); + return; + }); + }, +}; \ No newline at end of file