Revert "Pull from treeanalyzer"

This reverts commit d3ac2fc869.
This commit is contained in:
Skylar Grant 2023-01-21 09:58:32 -05:00
parent d3ac2fc869
commit 52f2206b55
13 changed files with 370 additions and 11 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

View File

@ -1,2 +1,47 @@
# Discord Bot Template # Grow A Tree Analyzer
This is a very basic Discord.js v14 bot template. This is meant to be an easy jumping-off point for quick bot creation without having to set up the basics every time. This bot works with Grow A Tree Bot by Limbo Labs. This project is not affiliated with Limbo Labs in any way.
This bot allows easy comparison between a server's tree and other trees displayed on the leaderboard.
## 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.
## 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: guildinfo
```
+-----------------+-------------+------+-----+---------+----------------+
| 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 | |
+-----------------+-------------+------+-----+---------+----------------+
```
### Table: treeinfo
```
+-----------+--------------+------+-----+---------+----------------+
| 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 | |
+-----------+--------------+------+-----+---------+----------------+
```
## Changes to Implement
* Move around some of the functions.
* Migrate storage to SQLite.

View File

@ -1,4 +1,9 @@
{ {
"guildId": "", "guildId": "868542949737246730",
"validCommands": [] "messageId": "",
"treeMessageId": "",
"treeHeight": 0,
"validCommands": [],
"rankMessageId": "",
"rankings": []
} }

1
data/guildInfo.json Normal file
View File

@ -0,0 +1 @@
{"269250657809072139":{"treeMessageId":"1042987280954032158","treeChannelId":"1042987243670872154","rankMessageId":"1065390277767987210","rankChannelId":"1042987923928256553","rankings":[{"rank":"111","name":"Taboo Tree","height":"3030.0"},{"rank":"112","name":"booster tree","height":"3029.0"},{"rank":"113","name":"Pod Birch","height":"3017.0"},{"rank":"114","name":"Wise Mystical Tree","height":"3013.0"},{"rank":"115","name":"Shimmer Doge DAO Tree","height":"2978.9"},{"rank":"116","name":"Simon","height":"2969.1"},{"rank":"117","name":"Cloud Tree","height":"2959.6"},{"rank":"118","name":"The Monke Tree","height":"2947.3"},{"rank":"119","name":"Woody","height":"2925.6"},{"rank":"120","name":"Azure Woodku","height":"2908.0"}],"treeHeight":"179.1"},"803049831839956992":{"treeMessageId":"1052343193980645376","treeChannelId":"1022324601864331335","rankMessageId":"1065121016604524544","rankChannelId":"1022324665374474341","rankings":[{"rank":"1 ","name":"WLR's Ultimate Sudowoodo","height":14203.5},{"rank":"2 ","name":"Our Bonfire Tree","height":13572.9},{"rank":"3 ","name":"The Gremlin Tree","height":12968},{"rank":"4 ","name":"World Tree","height":10600.6},{"rank":"5 ","name":"Emory's Baby","height":10163.9},{"rank":"6 ","name":"Charles the Tree","height":9920},{"rank":"7 ","name":"SMMO-Babel","height":9397},{"rank":"8 ","name":"Spaghetti","height":9341},{"rank":"9 ","name":"the fortnite tree","height":8883},{"rank":"10","name":"TreeVana","height":8002.6}],"treeHeight":261},"1006579138909458513":{"treeMessageId":"1065399196682821713","treeChannelId":"1065398553528250369","rankMessageId":"1065399316979662928","rankChannelId":"1065398553528250369","rankings":[{"rank":"1 ","name":"WLR's Ultimate Sudowoodo","height":"14232.9"},{"rank":"2 ","name":"Our Bonfire Tree","height":"13612.1"},{"rank":"3 ","name":"The Gremlin Tree","height":"12994.8"},{"rank":"4 ","name":"World Tree","height":"10623.0"},{"rank":"5 ","name":"Emory's Baby","height":"10187.8"},{"rank":"6 ","name":"Charles the Tree","height":"9951.0"},{"rank":"7 ","name":"SMMO-Babel","height":"9438.0"},{"rank":"8 ","name":"Spaghetti","height":"9365.0"},{"rank":"9 ","name":"the fortnite tree","height":"8895.0"},{"rank":"10","name":"TreeVana","height":"8019.2"}],"treeHeight":"7941.9"},"760701839427108874":{"treeMessageId":"1065405516400042114","treeChannelId":"1050272057839067178","rankMessageId":"1051889574365904907","rankChannelId":"1050272057839067178","rankings":[{"rank":"251","name":"Bom","height":"1621.0"},{"rank":"252","name":"Tree Of Happiness","height":"1621.0"},{"rank":"253","name":"Treehouse","height":"1604.0"},{"rank":"254","name":"Viktoria II","height":"1595.0"},{"rank":"255","name":"tree of virginity","height":"1588.0"},{"rank":"256","name":"nick's pride","height":"1588.0"},{"rank":"257","name":"Vitraya Ramunong","height":"1583.0"},{"rank":"258","name":"oven tree","height":"1538.7"},{"rank":"259","name":"Tr3e","height":"1527.1"},{"rank":"260","name":"Dicecraft","height":"1523.0"}],"treeHeight":"1527.1"},"868542949737246730":{"treeMessageId":"1065417587502092328","treeChannelId":"1065417555445043300","rankMessageId":"1065417630581801100","rankChannelId":"1065417587502092328","rankings":[{"rank":"1 ","name":"WLR's Ultimate Sudowoodo","height":"14233.4"},{"rank":"2 ","name":"Our Bonfire Tree","height":"13612.7"},{"rank":"3 ","name":"The Gremlin Tree","height":"12995.3"},{"rank":"4 ","name":"World Tree","height":"10623.8"},{"rank":"5 ","name":"Emory's Baby","height":"10188.1"},{"rank":"6 ","name":"Charles the Tree","height":"9951.0"},{"rank":"7 ","name":"SMMO-Babel","height":"9438.4"},{"rank":"8 ","name":"Spaghetti","height":"9365.5"},{"rank":"9 ","name":"the fortnite tree","height":"8895.0"},{"rank":"10","name":"TreeVana","height":"8020.0"}],"treeHeight":"1.0"}}

View File

@ -1,21 +1,22 @@
{ {
"help": { "help": {
"info": "", "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 </tree:972648557796524032> message and a single </top trees:1051840665362894950> message that everyone uses. If everyone creates their own </tree:972648557796524032> and </top trees:1051840665362894950>, the reference messages will have to be updated every time, or old data will be displayed.",
"setup": "", "setup": "To begin analyzing your Tree, first you must set up the reference messages.\n\n1. Run </setup:1065407649363005561> in the channel(s) that contain your server's tree and leaderboard messages.\n2. Now simply run </compare:1065346941166297128> where you want your analysis to be visible.",
"permissions": "" "permissions": "At a minimum, Grow A Tree Analyzer 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."
}, },
"embeds": { "embeds": {
"footer": "", "footer": "Grow A Tree Analyzer is not affiliated with Grow A Tree or Limbo Labs",
"color": "0x55FF55" "color": "0x55FF55"
}, },
"emoji": { "emoji": {
"joint": "<:joint:862082955902976000>",
"next": "⏭️", "next": "⏭️",
"previous": "⏮️", "previous": "⏮️",
"confirm": "☑️", "confirm": "☑️",
"cancel": "❌" "cancel": "❌"
}, },
"urls": { "urls": {
"avatar": "" "avatar": "https://cdn.discordapp.com/avatars/513184762073055252/12227aa23a06d5178853e59b72c7487b.webp?size=128"
}, },
"temp": {} "temp": {}
} }

View File

@ -30,20 +30,23 @@ client.once('ready', () => {
fn.collections.slashCommands(client); fn.collections.slashCommands(client);
console.log('Ready!'); console.log('Ready!');
client.channels.fetch(statusChannelId).then(channel => { client.channels.fetch(statusChannelId).then(channel => {
channel.send(`${new Date().toISOString()} -- Ready`); channel.send(`${new Date().toISOString()} -- \nStartup Sequence Complete`);
}); });
}); });
// slash-commands // slash-commands
client.on('interactionCreate', async interaction => { client.on('interactionCreate', async interaction => {
if (interaction.isCommand()) { if (interaction.isCommand()) {
// if (isDev) {
// console.log(interaction);
// }
const { commandName } = interaction; const { commandName } = interaction;
if (client.slashCommands.has(commandName)) { if (client.slashCommands.has(commandName)) {
client.slashCommands.get(commandName).execute(interaction); client.slashCommands.get(commandName).execute(interaction);
} else { } else {
interaction.reply('Sorry, I don\'t have access to that command.'); interaction.reply('Sorry, I don\'t have access to that command.');
console.error('Slash command attempted to run but not found: /' + commandName); console.error('Slash command attempted to run but not found: ' + commandName);
} }
} }

View File

@ -47,6 +47,16 @@ const functions = {
); );
return refreshActionRow; return refreshActionRow;
}, },
comparisonEmbed(content, refreshActionRow) {
// Create the embed using the content passed to this function
const embed = new EmbedBuilder()
.setColor(strings.embeds.color)
.setTitle('Tree Growth Comparison')
.setDescription(content)
.setFooter({text: strings.embeds.footer});
const messageContents = { embeds: [embed], components: [refreshActionRow] };
return messageContents;
},
helpEmbed(content, private) { helpEmbed(content, private) {
const embed = new EmbedBuilder() const embed = new EmbedBuilder()
.setColor(strings.embeds.color) .setColor(strings.embeds.color)
@ -76,6 +86,115 @@ const functions = {
return messageContents; return messageContents;
} }
}, },
rankings: {
parse(interaction) {
return new Promise ((resolve, reject) => {
if (guildInfo[interaction.guildId] == undefined) {
reject("The guild entry hasn't been created yet.");
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 ``'
}
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);
rankings.push({
rank: trimmedRank,
name: trimmedName,
height: trimmedHeight
});
}
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");
return;
}
});
},
compare(interaction) {
if (guildInfo[interaction.guildId] == undefined) {
return `Please reset the reference messages! (${interaction.guildId})`;
}
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(e => {
let difference = parseFloat(e.height).toFixed(1) - treeHeight;
const absDifference = parseFloat(Math.abs(difference)).toFixed(1);
if (difference > 0) {
replyString += `${absDifference}ft shorter than rank #${e.rank}\n`;
} else if (difference < 0) {
replyString += `${absDifference}ft taller than rank #${e.rank}\n`;
} else if (difference == 0) {
replyString += `Same height as rank #${e.rank}\n`;
}
});
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}]`);
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 lines = input.split('\n');
guildInfo[interaction.guildId].treeHeight = parseFloat(lines[0].slice(lines[0].indexOf('is') + 3, lines[0].indexOf('ft'))).toFixed(1);
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) { refresh(interaction) {
functions.rankings.parse(interaction).then(r1 => { functions.rankings.parse(interaction).then(r1 => {
functions.tree.parse(interaction).then(r2 => { functions.tree.parse(interaction).then(r2 => {
@ -87,6 +206,36 @@ const functions = {
}).catch(e => { }).catch(e => {
interaction.reply(functions.builders.errorEmbed(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;
},
getInfo(guildId) {
const guildInfo = guildInfo[guildId];
if (guildInfo != undefined) {
let guildInfoString = "";
if (guildInfo.treeMessageId != "") {
guildInfoString += `Tree Message ID: ${guildInfo.treeMessageId}\n`;
}
if (guildInfo.treeChannelId != "") {
guildInfoString += `Tree Channel ID: ${guildInfo.treeChannelId}\n`;
}
if (guildInfo.rankMessageId != "") {
guildInfoString += `Rank Message ID: ${guildInfo.rankMessageId}\n`;
}
if (guildInfo.rankChannelId != "") {
guildInfoString += `Rank Channel ID: ${guildInfo.rankChannelId}\n`;
}
if (guildInfo.treeHeight != "") {
guildInfoString += `Tree Height: ${guildInfo.treeHeight}\n`;
}
return `Here if your servers setup info:\n${guildInfoString}`;
} else {
return "Your guild hasn't been set up yet.";
}
} }
}; };

23
package.json Normal file
View File

@ -0,0 +1,23 @@
{
"name": "treeanalyzer",
"version": "1.0.0",
"description": "Analyze Grow A Tree",
"main": "main.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git+https://github.com/voidf1sh/treeanalyzer.git"
},
"author": "Skylar Grant",
"license": "ISC",
"bugs": {
"url": "https://github.com/voidf1sh/treeanalyzer/issues"
},
"homepage": "https://github.com/voidf1sh/treeanalyzer#readme",
"dependencies": {
"discord.js": "^14.7.1",
"dotenv": "^16.0.3"
}
}

12
slash-commands/compare.js Normal file
View File

@ -0,0 +1,12 @@
const { SlashCommandBuilder } = require('discord.js');
const fn = require('../modules/functions.js');
module.exports = {
data: new SlashCommandBuilder()
.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);
},
};

49
slash-commands/help.js Normal file
View File

@ -0,0 +1,49 @@
const { SlashCommandBuilder, messageLink } = require('discord.js');
const fn = require('../modules/functions.js');
const strings = require('../data/strings.json');
module.exports = {
data: new SlashCommandBuilder()
.setName('help')
.setDescription('Get help using the bot')
.addSubcommand(subcommand =>
subcommand
.setName('info')
.setDescription('Learn about the bot')
.addStringOption(o =>
o.setName('private')
.setDescription('Should the response be sent privately?')
.setRequired(true)
.addChoices(
{ name: "True", value: "true" },
{ name: "False", value: "false" }
)))
.addSubcommand(subcommand =>
subcommand
.setName('setup')
.setDescription('Learn how to setup the bot')
.addStringOption(o =>
o.setName('private')
.setDescription('Should the response be sent privately?')
.setRequired(true)
.addChoices(
{ name: "True", value: "true" },
{ name: "False", value: "false" }
)))
.addSubcommand(subcommand =>
subcommand
.setName('permissions')
.setDescription('Learn about the bot\'s permissions')
.addStringOption(o =>
o.setName('private')
.setDescription('Should the response be sent privately?')
.setRequired(true)
.addChoices(
{ name: "True", value: "true" },
{ name: "False", value: "false" }
))),
execute(interaction) {
const helpEmbed = fn.builders.helpEmbed(strings.help[interaction.options.getSubcommand()], interaction.options.getString('private'));
interaction.reply(helpEmbed);
},
};

12
slash-commands/reset.js Normal file
View File

@ -0,0 +1,12 @@
const { SlashCommandBuilder } = require('discord.js');
const fn = require('../modules/functions.js');
module.exports = {
data: new SlashCommandBuilder()
.setName('reset')
.setDescription('Reset all message assignments in your server'),
execute(interaction) {
fn.reset(interaction.guildId);
interaction.reply(fn.builders.embed("Assignments Reset"));
},
};

47
slash-commands/setup.js Normal file
View File

@ -0,0 +1,47 @@
const { SlashCommandBuilder } = require('discord.js');
const fn = require('../modules/functions.js');
const guildInfo = require('../data/guildInfo.json');
module.exports = {
data: new SlashCommandBuilder()
.setName('setup')
.setDescription('Attempt automatic configuration of the bot.'),
execute(interaction) {
if (guildInfo[interaction.guildId] == undefined) {
guildInfo[interaction.guildId] = {
"treeMessageId": "",
"treeChannelId": "",
"rankMessageId": "",
"rankChannelId": "",
"rankings": [],
"treeHeight": 0
};
}
interaction.channel.messages.fetch({ limit: 20 }).then(msgs => {
let treeFound = false;
let rankFound = false;
msgs.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);
} 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);
}
}
});
if (treeFound && !(rankFound)) {
interaction.reply(fn.builders.embed("A tree message was found, but a leaderboard message was not. Please run this command again in the channel containing the leaderboard if you haven't done so already. Run ``/setupinfo`` to see if the message is set."));
} else if (!(treeFound) && rankFound) {
interaction.reply(fn.builders.embed("A leaderboard message was found, but a tree message was not. Please run this command again in the channel containing the tree if you haven't done so already. Run ``/setupinfo`` to see if the message is set."));
} else if (treeFound && rankFound) {
interaction.reply(fn.builders.embed("Tree and leaderboard messages were both found, setup is complete. Run ``/setupinfo`` to verify."));
}
});
},
};

View File

@ -0,0 +1,12 @@
const { SlashCommandBuilder } = require('discord.js');
const fn = require('../modules/functions.js');
module.exports = {
data: new SlashCommandBuilder()
.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);
},
};