V1.1.1-dev (#7)

* Fix db connection handling

* Add new time to height calculator

* Versioning

* Finishing updates
This commit is contained in:
Skylar Grant 2023-01-25 04:53:49 -05:00 committed by GitHub
parent 83a05374cf
commit e64fa099c1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 170 additions and 78 deletions

View File

@ -1,50 +1,28 @@
# Grow A Tree Analyzer # Silvanus
This bot works with Grow A Tree Bot by Limbo Labs. This project is not affiliated with Limbo Labs in any way. 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 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.
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.
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 ## Commands
* `/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.
* `/setup` - Attempts automatic detection of your server's tree and leaderboard messages. * `/setupinfo` - Displays links to the current Tree and Tallest Trees messages configured in your server.
* `/setupinfo` - Displays your server's current configuration. * `/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]`)
* `/reset` - Resets your server's configuration, run `/setup` again if needed. * `/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.
## 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.

View File

@ -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}]}}

View File

@ -2,13 +2,6 @@ const dotenv = require('dotenv');
dotenv.config(); dotenv.config();
const debugMode = process.env.DEBUG || true; const debugMode = process.env.DEBUG || true;
const mysql = require('mysql'); 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 /* Table Structures
guild_info 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 = { module.exports = {
createGuildTables(guildId) { 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. // 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 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))"; 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) { if (err) {
reject("Error creating the guild_info table: " + err.message); reject("Error creating the guild_info table: " + err.message);
console.error("Offending query: " + createGuildInfoTableQuery); console.error("Offending query: " + createGuildInfoTableQuery);
db.end();
return; return;
} }
db.query(createLeaderboardTableQuery, (err) => { db.query(createLeaderboardTableQuery, (err) => {
if (err) { if (err) {
reject("Error creating the leaderboard table: " + err.message); reject("Error creating the leaderboard table: " + err.message);
console.error("Offending query: " + createLeaderboardTableQuery); console.error("Offending query: " + createLeaderboardTableQuery);
db.end();
return; return;
} }
resolve({ "status": "Successfully checked both tables.", "data": null }) resolve({ "status": "Successfully checked both tables.", "data": null });
db.end();
}); });
}); });
}); });
}, },
getGuildInfo(guildId) { 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 // 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)}`; 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 } // 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) { if (err) {
console.error(err); console.error(err);
reject("Error fetching guild information: " + err.message); reject("Error fetching guild information: " + err.message);
db.end();
return; return;
} }
/*const guildInfo = { "guildId": "123", /*const guildInfo = { "guildId": "123",
@ -102,6 +101,7 @@ module.exports = {
};*/ };*/
if (res.length == 0) { if (res.length == 0) {
reject("There is no database entry for your guild yet. Try running /setup"); reject("There is no database entry for your guild yet. Try running /setup");
db.end();
return; return;
} }
row = res[0]; row = res[0];
@ -113,11 +113,22 @@ module.exports = {
"leaderboardMessageId": row.leaderboard_message_id, "leaderboardMessageId": row.leaderboard_message_id,
"leaderboardChannelId": row.leaderboard_channel_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) { 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 }) // Returns a Promise, resolve({ "status": "", "data": null })
// guildInfo = { "guildId": "123", "treeName": "name", "treeHeight": 123, "treeMessageId": "123", "treeChannelId": "123", "leaderboardMessageId": "123", "leaderboardChannelId": "123"} // guildInfo = { "guildId": "123", "treeName": "name", "treeHeight": 123, "treeMessageId": "123", "treeChannelId": "123", "leaderboardMessageId": "123", "leaderboardChannelId": "123"}
// Set a server's tree information in the database // Set a server's tree information in the database
@ -128,13 +139,25 @@ module.exports = {
if (err) { if (err) {
console.error(err); console.error(err);
reject("Error setting the guild info: " + err.message); reject("Error setting the guild info: " + err.message);
db.end();
return; return;
} }
db.end();
resolve({ "status": "Successfully set the guild information", "data": null }); resolve({ "status": "Successfully set the guild information", "data": null });
}); });
}); });
}, },
setTreeInfo(guildInfo) { 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 }) // Returns a Promise, resolve({ "status": "", "data": null })
// guildInfo = { "guildId": "123", "treeName": "name", "treeHeight": 123, "treeMessageId": "123", "treeChannelId": "123", "leaderboardMessageId": "123", "leaderboardChannelId": "123"} // guildInfo = { "guildId": "123", "treeName": "name", "treeHeight": 123, "treeMessageId": "123", "treeChannelId": "123", "leaderboardMessageId": "123", "leaderboardChannelId": "123"}
// Set a server's tree information in the database) // Set a server's tree information in the database)
@ -144,14 +167,26 @@ module.exports = {
db.query(insertGuildInfoQuery, (err, res) => { db.query(insertGuildInfoQuery, (err, res) => {
if (err) { if (err) {
console.error(err); console.error(err);
db.end();
reject("Error setting the guild info: " + err.message); reject("Error setting the guild info: " + err.message);
return; return;
} }
db.end();
resolve({ "status": "Successfully set the guild information", "data": null }); resolve({ "status": "Successfully set the guild information", "data": null });
}); });
}); });
}, },
setLeaderboardInfo(guildInfo) { 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 }) // Returns a Promise, resolve({ "status": "", "data": null })
// guildInfo = { "guildId": "123", "treeName": "name", "treeHeight": 123, "treeMessageId": "123", "treeChannelId": "123", "leaderboardMessageId": "123", "leaderboardChannelId": "123"} // guildInfo = { "guildId": "123", "treeName": "name", "treeHeight": 123, "treeMessageId": "123", "treeChannelId": "123", "leaderboardMessageId": "123", "leaderboardChannelId": "123"}
// Set a server's tree information in the database // Set a server's tree information in the database
@ -161,14 +196,26 @@ module.exports = {
db.query(insertGuildInfoQuery, (err, res) => { db.query(insertGuildInfoQuery, (err, res) => {
if (err) { if (err) {
console.error(err); console.error(err);
db.end();
reject("Error setting the guild info: " + err.message); reject("Error setting the guild info: " + err.message);
return; return;
} }
db.end();
resolve({ "status": "Successfully set the guild information", "data": null }); resolve({ "status": "Successfully set the guild information", "data": null });
}); });
}); });
}, },
deleteGuildInfo(guildId) { 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 }) // Returns a Promise, resolve({ "status": "", "data": null })
// guildInfo = { "guildId": "123", "treeName": "name", "treeHeight": 123, "treeMessageId": "123", "treeChannelId": "123", "leaderboardMessageId": "123", "leaderboardChannelId": "123"} // guildInfo = { "guildId": "123", "treeName": "name", "treeHeight": 123, "treeMessageId": "123", "treeChannelId": "123", "leaderboardMessageId": "123", "leaderboardChannelId": "123"}
// Set a server's tree information in the database // Set a server's tree information in the database
@ -178,14 +225,26 @@ module.exports = {
db.query(deleteGuildInfoQuery, (err, res) => { db.query(deleteGuildInfoQuery, (err, res) => {
if (err) { if (err) {
console.error(err); console.error(err);
db.end();
reject("Error deleting the guild info: " + err.message); reject("Error deleting the guild info: " + err.message);
return; return;
} }
db.end();
resolve({ "status": "Successfully deleted the guild information", "data": null }); resolve({ "status": "Successfully deleted the guild information", "data": null });
}); });
}); });
}, },
getLeaderboard(guildId) { 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 }) // 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`; 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 } // 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) => { db.query(selectLeaderboardQuery, (err, res) => {
if (err) { if (err) {
console.error(err); console.error(err);
db.end();
reject("Error fetching the most recent leaderboard: " + err.message); reject("Error fetching the most recent leaderboard: " + err.message);
return; return;
} }
@ -205,11 +265,22 @@ module.exports = {
"hasPin": row.has_pin "hasPin": row.has_pin
}); });
}); });
db.end();
resolve({ "status": "Successfully fetched leaderboard.", "data": leaderboard }); resolve({ "status": "Successfully fetched leaderboard.", "data": leaderboard });
}); });
}); });
}, },
uploadLeaderboard(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 }) // Returns a Promise, resolve({ "status": "", "data": res })
// leaderboard = { "guildId": 1234, "entries": [ { "treeHeight": 12, "treeRank": 34, "treeName": "name", "hasPin": false }, {...}, {...} ] } // 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 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) => { db.query(insertLeaderboardQuery, [leaderboardValues], (err, res) => {
if (err) { if (err) {
reject("Error uploading the leaderboard: " + err.message); reject("Error uploading the leaderboard: " + err.message);
db.end();
console.error(err); console.error(err);
return; return;
} }
db.end();
resolve({ "status": "Successfully uploaded the leaderboard", "data": res }); resolve({ "status": "Successfully uploaded the leaderboard", "data": res });
}); });
}); });
}, }
db
}; };

View File

@ -304,7 +304,27 @@ const functions = {
}); });
}, },
getWaterTime(size) { 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;
})
});
} }
}; };

View File

@ -1,6 +1,6 @@
{ {
"name": "silvanus", "name": "silvanus",
"version": "1.1.0", "version": "1.1.1",
"description": "Grow A Tree Comparison Tool", "description": "Grow A Tree Comparison Tool",
"main": "main.js", "main": "main.js",
"scripts": { "scripts": {

View File

@ -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;
});
},
};