Restructuring and new help messages

This commit is contained in:
Skylar Grant 2023-01-30 01:12:26 -05:00
parent c71154f1c9
commit 699652e97f
14 changed files with 492 additions and 259 deletions

View File

@ -22,12 +22,15 @@ To begin analyzing your Tree, first you must set up the reference messages.
Silvanus requires permissions to `Send Messages` and `Send Messages in Threads` if applicable.
## 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` - You only need to run this command if your server has its `/tree` and `/top trees` messages in separate channels.
* `/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.
* `/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 (`[💧]`).
* `/setping` - Guild members with the `Manage Roles` permission can run this command to set up automatic reminders when your tree is ready to be watered.
* Type a reminder message (including any `@pings` desired) and select a channel to send the reminders in.
* Once this command has been run a new `Reset Ping` button will appear next time you refresh the `/compare` message.
* Click the `Reset Ping` button to be sent a reminder the next time the tree is ready to be watered.
* Use `/optout` to disable reminder messages for your server.
* `/watertime` - Calculates the wait time between waters for a tree of a given height.
* `/timetoheight` - Calculates how long it would take to go from `beginheight` to `endheight`.
* `/setping` - Guild members with the `Manage Roles` permission can run this command to set a Ready to Water reminder meaage and set the channel to send it in. Once this command has been run a new `Reset Ping` button will appear next time you refresh the `/compare` message.
* `/optout` - Disable automatic water reminder messages.
* `/reset` - Removes your server's configuration from the database.
* `/help` - Displays the bot's help page and links to each command.

27
TODO.md Normal file
View File

@ -0,0 +1,27 @@
## In Progress
☑ Switch `/setup` to ask for the tree and leaderboard channels
* Switch `/compare` to check for newer trees and leaderboards when run **and** on every refresh
## Future Ideas
* Go through and comment the code
## Variable Structures
guildInfo = {
guildId: "",
treeName: "name",
treeHeight: 0,
treeMessageId: "",
treeChannelId: "",
leaderboardMessageId: "",
leaderboardChannelId: "",
reminderMessage: "",
reminderChannelId: "",
remindedStatus: 0,
reminderOptIn: 0,
}
## Expected Behaviors
* Run `/compare` before `/setup`: `/compare` will search the current channel for tree and leaderboard messages, then create a comparison embed. If it can't find `/tree` or `/top trees` messages, it'll return an error saying as much.
* Run `/compare` after `/setup`: ``/compare` will search the current channel for tree and leaderboard messages, then create a comparison embed. If it can't find `/tree` or `/top trees` messages, it'll just use old data silently (odds are `/compare` is being run from another channel, that's fine)

View File

@ -4,10 +4,10 @@
},
"help": {
"title": "Silvanus Help",
"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 </help:1065346941166297129> and </setup:1065407649363005561>, then check out </compare:1065346941166297128>.\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 </tree:972648557796524032> and </top trees:1051840665362894950> message, otherwise make sure to run </setup:1065407649363005561> each time you run </compare:1065346941166297128>.",
"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.",
"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!\n\nImportant Note: Silvanus is only as up-to-date as your server's newest Tree and Tallest Trees messages. Make sure to refresh them before refreshing Silvanus' Compare message.",
"setup": "If your ``/tree`` and ``/top trees`` messages are in the same channel, simple run </compare:1065346941166297128> in that channel and you're good to go!\n\nOtherwise, run </setup:1065407649363005561> to set the proper channels for the bot to look in for the ``/tree`` and ``/top trees`` messages;",
"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": "</setup:1065407649363005561> - 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.\n</setupinfo:1065413032374706196> - Displays links to the current Tree and Tallest Trees messages configured in your server.\n</compare:1065346941166297128> - 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]`)\n</watertime:1066970330029113444> - Calculates the wait time between waters for a tree of a given height.\n</timetoheight:1067727254634889227> - Calculates how long it would take to go from `beginheight` to `endheight`.\n</setping:1068373237995683902> - Guild members with the `Manage Roles` permission can run this command to set a Ready to Water reminder meaage and set the channel to send it in. Once this command has been run a new `Reset Ping` button will appear next time you refresh the `/compare` message.\n</optout:1068753032801693758> - Disable automatic water reminder messages.\n</reset:1065412317052944476> - Removes your server's configuration from the database.\n</help:1065346941166297129> - Displays the bot's help page and links to each command."
"allCommands": "</setup:1065407649363005561> - You only need to run this command if your server has its ``/tree`` and ``/top trees`` messages in separate channels.\n</setupinfo:1065413032374706196> - Displays links to the current Tree and Tallest Trees messages configured in your server.\n</compare:1065346941166297128> - 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 (``[💧]``).\n</setping:1068373237995683902> - Guild members with the ``Manage Roles`` permission can run this command to set up automatic reminders when your tree is ready to be watered.\n Type a reminder message (including any ``@pings`` desired) and select a channel to send the reminders in.\n Once this command has been run a new ``Reset Ping`` button will appear next time you refresh the </compare:1065346941166297128> message.\n Click the ``Reset Ping`` button to be sent a reminder the next time the tree is ready to be watered.\n Use </optout:1068753032801693758> to disable reminder messages for your server.\n</watertime:1066970330029113444> - Calculates the wait time between waters for a tree of a given height.\n</timetoheight:1067727254634889227> - Calculates how long it would take to go from ``beginheight`` to ``endheight``.\n</reset:1065412317052944476> - Removes your server's configuration from the database.\n</help:1065346941166297129> - Displays the bot's help page and links to each command."
},
"commands": {
"compare": "</compare:1065346941166297128>",
@ -37,8 +37,12 @@
},
"status": {
"treeAndLeaderboard": "Tree and leaderboard messages were both found, setup is complete. Run </setupinfo:1065413032374706196> to verify. Run </compare:1065346941166297128> to get started!",
"treeNoLeaderboard": "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:1065413032374706196> to see if the message is set.",
"leaderboardNoTree": "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:1065413032374706196> to see if the message is set."
"missingMessage": "I was unable to find both a ``/tree`` and ``/top trees`` message in this channel. Please run </setup:1065407649363005561> to set up separate ``/tree`` and ``/top trees`` channels.",
"noneFound": "Unable to find a tree or leaderboard in the last 20 messages in this channel, make sure you've run ``/tree`` and/or ``/top trees`` recently.",
"missingLeaderboardMessage": "There was a problem finding the Tallest Trees message. Please make sure the ``/tree`` and ``/top trees`` messages are in this channel, or run </setup:1065407649363005561> to set the ``/tree`` and ``/top trees`` channels.",
"missingLeaderboardChannel": "There was a problem finding the Tallest Trees channel, was it deleted? Please make sure the ``/tree`` and ``/top trees`` messages are in this channel, or run </setup:1065407649363005561> to set the ``/tree`` and ``/top trees`` channels.",
"missingTreeMessage": "There was a problem finding the Tree message. Please make sure the ``/tree`` and ``/top trees`` messages are this channel, or run </setup:1065407649363005561> to set the ``/tree`` and ``/top trees`` channels.",
"missingTreeChannel": "There was a problem finding the Tree channel, was it deleted? Please make sure the ``/tree`` and ``/top trees`` messages are in this channel, or run </setup:1065407649363005561> to set the ``/tree`` and ``/top trees`` channels."
},
"temp": {}
}

View File

@ -35,9 +35,6 @@ client.once('ready', () => {
client.channels.fetch(statusChannelId).then(channel => {
channel.send(`${new Date().toISOString()} -- \nStartup Sequence Complete <@481933290912350209>`);
});
} else {
// Dev shit
fn.checkReady(client);
}
});
@ -58,7 +55,9 @@ client.on('interactionCreate', async interaction => {
}
if (interaction.isButton() && interaction.component.customId == 'refresh') {
fn.refresh(interaction);
fn.refresh(interaction).catch(err => {
interaction.update(fn.builders.errorEmbed(err));
});
} else if (interaction.isButton() && interaction.component.customId == 'resetping') {
fn.resetPing(interaction);
interaction.reply({ content: "Water Readiness Detection System: [ARMED]", ephemeral: true });

View File

@ -0,0 +1,36 @@
/*
</setup:1065407649363005561>
</setupinfo:1065413032374706196>
</compare:1065346941166297128>
</setping:1068373237995683902>
</optout:1068753032801693758>
</watertime:1066970330029113444>
</timetoheight:1067727254634889227>
</reset:1065412317052944476>
</help:1065346941166297129>
</tree:972648557796524032>
</top trees:1051840665362894950>
*/
const fs = require('fs');
const replaceAll = require('string.prototype.replaceall');
function convertHelpString() {
const string = fs.readFileSync('./string.txt').toString();
let newString = replaceAll(string, '\* ', '');
newString = replaceAll(newString, '\n', '\\n');
newString = replaceAll(newString, '\t', ' - ');
newString = replaceAll(newString, '`/setup`', '</setup:1065407649363005561>');
newString = replaceAll(newString, '`/setupinfo`', '</setupinfo:1065413032374706196>');
newString = replaceAll(newString, '`/compare`', '</compare:1065346941166297128>');
newString = replaceAll(newString, '`/setping`', '</setping:1068373237995683902>');
newString = replaceAll(newString, '`/optout`', '</optout:1068753032801693758>');
newString = replaceAll(newString, '`/watertime`', '</watertime:1066970330029113444>');
newString = replaceAll(newString, '`/timetoheight`', '</timetoheight:1067727254634889227>');
newString = replaceAll(newString, '`/reset`', '</reset:1065412317052944476>');
newString = replaceAll(newString, '`/help`', '</help:1065346941166297129>');
newString = replaceAll(newString, '`', '``');
return newString;
}
module.exports = convertHelpString();

View File

@ -108,7 +108,7 @@ module.exports = {
return;
}
row = res[0];
const guildInfo = { "guildId": row.guild_id,
const guildInfo = { "guildId": guildId,
"treeName": row.tree_name,
"treeHeight": row.tree_height,
"treeMessageId": row.tree_message_id,

View File

@ -70,7 +70,7 @@ const functions = {
.setColor(strings.embeds.color)
.setTitle('Tree Growth Comparison')
.setDescription(content)
.setFooter({text: `v${package.version} - ${strings.embeds.footer}`});
.setFooter({ text: `v${package.version} - ${strings.embeds.footer}` });
const messageContents = { embeds: [embed], components: [refreshActionRow] };
return messageContents;
},
@ -79,9 +79,9 @@ const functions = {
const embed = new EmbedBuilder()
.setColor(strings.embeds.color)
.setTitle('Water Reminder')
.setDescription(`${content}\n[Click Here To Go To Your Tree](https://discord.com/channels/${guildInfo.guildId}/${guildInfo.treeChannelId}/${guildInfo.treeMessageId})`)
.setFooter({text: `This message will self-destruct in 60 seconds...`});
const messageContents = { embeds: [embed] };
.setDescription(`[Click here to go to your Tree](https://discord.com/channels/${guildInfo.guildId}/${guildInfo.treeChannelId}/${guildInfo.treeMessageId})`)
.setFooter({ text: `This message will self-destruct in 60 seconds.` });
const messageContents = { content: content, embeds: [embed] };
return messageContents;
},
helpEmbed(content, private) {
@ -114,91 +114,88 @@ const functions = {
}
},
rankings: {
parse(interaction) {
return new Promise ((resolve, reject) => {
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 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;
});
});
});
} else {
reject("The leaderboardMessageId is undefined somehow");
return;
}
}).catch(err => {
reject(err);
parse(interaction, guildInfo) {
return new Promise((resolve, reject) => {
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 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 => {
resolve(res.status);
}).catch(err => {
console.error(err);
reject(err);
return;
});
}).catch(err => {
reject(strings.status.missingLeaderboardMessage);
console.error(err);
return;
});
}).catch(err => {
reject(strings.status.missingLeaderboardChannel);
console.error(err);
return;
});
} else {
reject("The leaderboardMessageId is undefined somehow");
return;
}
});
},
async compare(interaction) {
async compare(interaction, guildInfo) {
try {
// fetch the guild's settings from the database
const guildInfoResponse = await dbfn.getGuildInfo(interaction.guildId);
const guildInfo = guildInfoResponse.data; // { "guildId": "123","treeName": "name","treeHeight": 123,"treeMessageId": "123","treeChannelId": "123","leaderboardMessageId": "123","leaderboardChannelId": "123"};
// Get the most recent leaderboard listing from the database
const getLeaderboardResponse = await dbfn.getLeaderboard(interaction.guildId);
const leaderboard = getLeaderboardResponse.data; // [ { treeName: "Name", treeHeight: 1234.5, treeRank: 67 }, {...}, {...} ]
@ -240,70 +237,209 @@ const functions = {
}
return comparisonReplyString;
} catch (err) {
console.error(err);
return 'An error occurred while comparing the trees.';
throw err;
}
}
},
tree: {
parse(interaction) {
parse(interaction, guildInfo) {
let input;
return new Promise((resolve, reject) => {
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);
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.");
});
}).catch(err => {
reject(strings.status.missingTreeMessage);
console.error(err);
return;
});
}).catch(err => {
reject(strings.status.missingTreeChannel);
console.error(err);
return;
});
} 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 => {
functions.rankings.compare(interaction).then(async res => {
const refreshActionRow = await functions.builders.refreshAction(interaction.guildId);
const embed = functions.builders.comparisonEmbed(res, refreshActionRow)
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));
});
messages: {
async find(interaction, guildInfo) {
try {
let response = { status: "Incomplete", data: undefined, code: 0 };
// If the tree channel ID and leaderboard channel ID are both set
if (guildInfo.treeChannelId != "" || guildInfo.leaderboardChannelId != "") {
// If one us unset, we'll set it to the current channel just to check
if (guildInfo.treeChannelId == "") {
guildInfo.treeChannelId = `${guildInfo.leaderboardChannelId}`;
} else if (guildInfo.leaderboardChannelId == "") {
guildInfo.leaderboardChannelId = `${guildInfo.treeChannelId}`;
}
let treeFound, leaderboardFound = false;
// If these values have already been set in the database, we don't want to report that they weren't found
// they'll still get updated later if applicable.
treeFound = (guildInfo.treeMessageId != "");
leaderboardFound = (guildInfo.leaderboardMessageId != "");
// If the Tree and Leaderboard messages are in the same channel
if (guildInfo.treeChannelId == guildInfo.leaderboardChannelId) {
// Fetch the tree channel so we can get the most recent messages
const treeChannel = await interaction.guild.channels.fetch(guildInfo.treeChannelId);
// Fetch the last 20 messages in the channel
const treeChannelMessageCollection = await treeChannel.messages.fetch({ limit: 20 });
// Create a basic array of [Message, Message, ...] from the Collection
const treeChannelMessages = Array.from(treeChannelMessageCollection.values());
// Iterate over the Messages in reverse order (newest messages first)
for (let i = treeChannelMessages.length - 1; i >= 0; i--) {
let treeChannelMessage = treeChannelMessages[i];
if (this.isTree(treeChannelMessage)) { // This is a tree message
// Set the tree message ID
guildInfo.treeMessageId = treeChannelMessage.id;
// Parse out the tree name
input = treeChannelMessage.embeds[0].data.description;
let treeName = treeChannelMessage.embeds[0].data.title;
// And tree height
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;
// Upload the found messages to the database
await dbfn.setTreeInfo(guildInfo);
// Let the end of the function know we found a tree message and successfully uploaded it
treeFound = true;
} else if (this.isLeaderboard(treeChannelMessage)) { // This is a leaderboard message
// Set the leaderboard message ID
guildInfo.leaderboardMessageId = treeChannelMessage.id;
// Upload it to the database
await dbfn.setLeaderboardInfo(guildInfo);
// Let the end of the function know we found a leaderboard message and successfully uploaded it
leaderboardFound = true;
}
}
} else { // If the tree and leaderboard are in different channels
// Fetch the tree channel so we can get the most recent messages
const treeChannel = await interaction.guild.channels.fetch(guildInfo.treeChannelId);
// Fetch the last 20 messages in the tree channel
const treeChannelMessageCollection = await treeChannel.messages.fetch({ limit: 20 });
// Create an Array like [Message, Message, ...] from the Collection
const treeChannelMessages = Array.from(treeChannelMessageCollection.values());
// Iterate over the Array of Messages in reverse order (newest messages first)
for (let i = treeChannelMessages.length - 1; i >= 0; i--) {
let treeChannelMessage = treeChannelMessages[i];
if (this.isTree(treeChannelMessage)) { // This is a tree message
// Set the tree message ID
guildInfo.treeMessageId = treeChannelMessage.id;
// Parse out the tree name
input = treeChannelMessage.embeds[0].data.description;
let treeName = treeChannelMessage.embeds[0].data.title;
// And tree height
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;
// Upload the found messages to the database
await dbfn.setTreeInfo(guildInfo);
// Let the end of the function know we found a tree message and successfully uploaded it
treeFound = true;
}
}
// Fetch the tree channel so we can get the most recent messages
const leaderboardChannel = await interaction.guild.channels.fetch(guildInfo.leaderboardChannelId);
// Fetch the last 20 messages in the leaderboard channel
const leaderboardChannelMessageCollection = await leaderboardChannel.messages.fetch({ limit: 20 });
// Create an Array like [Message, Message, ...] from the Collection
const leaderboardChannelMessages = Array.from(leaderboardChannelMessageCollection.values());
// Iterate over the Array of Messages in reverse order (newest messages first)
for (let i = leaderboardChannelMessages.length - 1; i >= 0; i--) {
let leaderboardChannelMessage = leaderboardChannelMessages[i];
if (this.isLeaderboard(leaderboardChannelMessage)) { // This is a leaderboard message
// Set the leaderboard message ID
guildInfo.leaderboardMessageId = leaderboardChannelMessage.id;
// Upload it to the database
await dbfn.setLeaderboardInfo(guildInfo);
// Let the end of the function know we've found a leaderboard
leaderboardFound = true;
}
}
}
// await dbfn.setGuildInfo(guildInfo);
// Bundle guildInfo into the response
const getGuildInfoResponse = await dbfn.getGuildInfo(guildInfo.guildId);
response.data = getGuildInfoResponse.data;
// Set the response status, this is only used as a response to /setup
if (treeFound && leaderboardFound) { // we found both the tree and leaderboard
response.status = strings.status.treeAndLeaderboard;
response.code = 1;
} else if (treeFound || leaderboardFound) { // Only found the tree
response.status = strings.status.missingMessage;
response.code = 2;
} else { // Didn't find any
response.status = strings.status.noneFound;
response.code = 3;
}
return response;
} else { // This should only ever occur if some weird database stuff happens
response.status = "It looks like this channel doesn't contain both your ``/tree`` and ``/top trees`` messages, please run ``/setup``";
return response;
}
} catch (err) {
throw "Problem checking messages: " + err;
}
},
isTree(message) {
if (message.embeds.length > 0) {
return message.embeds[0].data.description.includes("Your tree is");
}
},
isLeaderboard(message) {
if (message.embeds.length > 0) {
return message.embeds[0].data.title == "Tallest Trees";
}
}
},
async refresh(interaction) {
const getGuildInfoResponse = await dbfn.getGuildInfo(interaction.guildId);
let guildInfo = getGuildInfoResponse.data;
const findMessagesResponse = await this.messages.find(interaction, guildInfo);
if (findMessagesResponse.code == 1) {
guildInfo = findMessagesResponse.data;
// Parse the tree
await this.tree.parse(interaction, guildInfo);
// Parse the leaderboard
await this.rankings.parse(interaction, guildInfo);
// Build the string that shows the comparison // TODO Move the string building section to fn.builders?
const comparedRankings = await this.rankings.compare(interaction, guildInfo);
// Build the Action Row that will contain the Refresh and Reset Ping buttons
const compareActionRow = await this.builders.refreshAction(interaction.guildId);
const embed = this.builders.comparisonEmbed(comparedRankings, compareActionRow);
await interaction.update(embed);
} else {
await interaction.update(this.builders.errorEmbed(findMessagesResponse.status));
}
},
reset(guildId) {
return new Promise((resolve, reject) => {
@ -348,13 +484,13 @@ const functions = {
// 86400 sec in day
let units = " secs";
if ( 60 < time && time <= 3600 ) { // Minutes
if (60 < time && time <= 3600) { // Minutes
time = parseFloat(time / 60).toFixed(1);
units = " mins";
} else if ( 3600 < time && time <= 86400 ) {
} else if (3600 < time && time <= 86400) {
time = parseFloat(time / 3600).toFixed(1);
units = " hrs";
} else if ( 86400 < time ) {
} else if (86400 < time) {
time = parseFloat(time / 86400).toFixed(1);
units = " days";
}
@ -371,7 +507,7 @@ const functions = {
reminderChannel.send(reminderEmbed).then(async m => {
await dbfn.setRemindedStatus(guildId, 1);
if (m.deletable) {
setTimeout(function() {
setTimeout(function () {
m.delete();
}, 60000);
}
@ -399,13 +535,13 @@ const functions = {
const readyToWater = treeMessage.embeds[0].description.includes('Ready to be watered');
if (readyToWater) {
// console.log("Ready to water");
this.sendReminder(guildInfo, guild);
this.sleep(5000).then(() => {
await this.sendReminder(guildInfo, guild);
await this.sleep(5000).then(() => {
this.checkReady(client);
});
} else {
// console.log("Not ready to water");
this.sleep(5000).then(() => {
await this.sleep(5000).then(() => {
this.checkReady(client);
});
}
@ -413,13 +549,13 @@ const functions = {
});
} else {
// console.log(getOptedInGuildsResponse.status);
this.sleep(5000).then(() => {
await this.sleep(5000).then(() => {
this.checkReady(client);
});
}
} catch(err) {
} catch (err) {
console.error(err);
this.sleep(30000).then(() => {
await this.sleep(30000).then(() => {
this.checkReady(client);
});
}

3
modules/string.txt Normal file
View File

@ -0,0 +1,3 @@
If your `/tree` and `/top trees` messages are in the same channel, simple run `/compare` in that channel and you're good to go!
Otherwise, run `/setup` to set the proper channels for the bot to look in for the `/tree` and `/top trees` messages;

View File

@ -19,6 +19,8 @@
"dependencies": {
"discord.js": "^14.7.1",
"dotenv": "^16.0.3",
"mysql": "^2.18.1"
"mysql": "^2.18.1",
"string-replace-all": "^2.0.0",
"string.prototype.replaceall": "^1.0.7"
}
}

View File

@ -1,20 +0,0 @@
const { SlashCommandBuilder } = require('discord.js');
const dbfn = require('../modules/dbfn.js');
const fn = require('../modules/functions.js');
module.exports = {
data: new SlashCommandBuilder()
.setName('dumptree')
.setDescription('Dump the contents of the tree message to console'),
async execute(interaction) {
await interaction.deferReply({ ephemeral: true });
const getGuildInfoResponse = await dbfn.getGuildInfo(interaction.guildId);
const { treeMessageId, treeChannelId } = getGuildInfoResponse.data;
interaction.guild.channels.fetch(treeChannelId).then(treeChannel => {
treeChannel.messages.fetch(treeMessageId).then(treeMessage => {
interaction.editReply("done");
console.log(JSON.stringify(treeMessage.embeds[0]));
});
});
},
};

View File

@ -0,0 +1,20 @@
const { SlashCommandBuilder, messageLink } = require('discord.js');
const fn = require('../modules/functions.js');
const strings = require('../data/strings.json');
module.exports = {
data: new SlashCommandBuilder()
.setName('commands')
.setDescription('Get a list of all my commands')
.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(`**All Commands**\n${strings.help.allCommands}`, interaction.options.getString('private'));
interaction.reply(helpEmbed);
},
};

View File

@ -1,4 +1,5 @@
const { SlashCommandBuilder } = require('discord.js');
const dbfn = require('../modules/dbfn.js');
const fn = require('../modules/functions.js');
module.exports = {
@ -6,19 +7,71 @@ module.exports = {
.setName('compare')
.setDescription('See how your tree compares to other trees!'),
async execute(interaction) {
interaction.deferReply().then(() => {
fn.rankings.compare(interaction).then(async res => {
const refreshActionRow = await fn.builders.refreshAction(interaction.guildId);
const embed = fn.builders.comparisonEmbed(res, refreshActionRow);
interaction.editReply(embed).catch(err => {
try {
await interaction.deferReply();
// Get the guildInfo from the database
dbfn.getGuildInfo(interaction.guildId).then(async getGuildInfoResponse => {
let guildInfo = getGuildInfoResponse.data;
// Find the most recent tree and leaderboard messages in their respective channels
const findMessagesResponse = await fn.messages.find(interaction, guildInfo);
if (findMessagesResponse.code == 1) {
guildInfo = findMessagesResponse.data;
// Parse the leaderboard message
await fn.rankings.parse(interaction, guildInfo);
// Build the string that shows the comparison // TODO Move the string building section to fn.builders?
const comparedRankings = await fn.rankings.compare(interaction, guildInfo);
// Build the Action Row that will contain the Refresh and Reset Ping buttons
const compareActionRow = await fn.builders.refreshAction(interaction.guildId);
const embed = fn.builders.comparisonEmbed(comparedRankings, compareActionRow);
await interaction.editReply(embed);
} else {
await interaction.editReply(fn.builders.errorEmbed(findMessagesResponse.status));
}
}).catch(async err => { // If we fail to fetch the guild's info from the database
// If the error is because the guild hasn't been setup yet, set it up
if (err === "There is no database entry for your guild yet. Try running /setup") {
// Create a basic guildInfo with blank data
let guildInfo = {
guildId: `${interaction.guildId}`,
treeName: "",
treeHeight: 0,
treeMessageId: "",
treeChannelId: `${interaction.channelId}`, // Use this interaction channel for the initial channel IDs
leaderboardMessageId: "",
leaderboardChannelId: `${interaction.channelId}`,
reminderMessage: "",
reminderChannelId: "",
remindedStatus: 0,
reminderOptIn: 0,
}
// Using the above guildInfo, try to find the Grow A Tree messages
const findMessagesResponse = await fn.messages.find(interaction, guildInfo);
guildInfo = findMessagesResponse.data;
if (findMessagesResponse.code == 1) {
// Build the string that shows the comparison // TODO Move the string building section to fn.builders?
const comparedRankings = await fn.rankings.compare(interaction, guildInfo);
// Build the Action Row that will contain the Refresh and Reset Ping buttons
const compareActionRow = await fn.builders.refreshAction(interaction.guildId);
const embed = fn.builders.comparisonEmbed(comparedRankings, compareActionRow);
await interaction.editReply(embed);
} else {
await interaction.editReply(fn.builders.errorEmbed(findMessagesResponse.status));
}
} else {
await interaction.editReply(fn.builders.errorEmbed("An unknown error occurred while running the compare command."));
console.error(err);
});
}).catch(err => {
interaction.editReply(fn.builders.errorEmbed(err)).catch(err => {
console.error(err);
});
}
});
} catch (err) {
interaction.editReply(fn.builders.errorEmbed(err)).catch(err => {
console.error(err);
});
})
console.error(err);
}
},
};

View File

@ -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}\n\n**All Commands**\n${strings.help.allCommands}\n\n**Support Server**\n${strings.urls.supportServer}`, interaction.options.getString('private'));
const helpEmbed = fn.builders.helpEmbed(`${strings.help.info}\n\n**Setup**\n${strings.help.setup}\n\nSee </commands:0> for a list of all my commands\n\n**Support Server**\n${strings.urls.supportServer}`, interaction.options.getString('private'));
interaction.reply(helpEmbed);
},
};

View File

@ -6,62 +6,32 @@ const dbfn = require('../modules/dbfn.js');
module.exports = {
data: new SlashCommandBuilder()
.setName('setup')
.setDescription('Attempt automatic configuration of the bot.'),
execute(interaction) {
interaction.deferReply({ ephemeral: true }).then(function () {
/*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 leaderboardFound = false;
msgs.reverse().forEach(msg => {
if (msg.embeds.length > 0) {
if (msg.embeds[0].data.description.includes("Your tree is")) {
treeFound = true;
guildInfo.treeName = msg.embeds[0].title;
guildInfo.treeChannelId = msg.channelId;
guildInfo.treeMessageId = msg.id;
} else if (msg.embeds[0].data.title == "Tallest Trees") {
leaderboardFound = true;
guildInfo.leaderboardChannelId = msg.channelId;
guildInfo.leaderboardMessageId = msg.id;
}
}
});
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);
});
}
});
});
.setDescription('Attempt automatic configuration of the bot.')
.addChannelOption(o =>
o.setName('treechannel')
.setDescription('What channel is your tree in?')
.setRequired(true))
.addChannelOption(o =>
o.setName('leaderboardchannel')
.setDescription('If your leaderboard isn\'t in the same channel, where is it?')
.setRequired(false)),
async execute(interaction) {
await interaction.deferReply({ ephemeral: true });
/**/
let guildInfo = {
"guildId": interaction.guildId,
"treeName": "",
"treeHeight": 0,
"treeMessageId": "",
"treeChannelId": `${interaction.options.getChannel('treechannel').id }`,
"leaderboardMessageId": "",
"leaderboardChannelId": `${interaction.options.getChannel('leaderboardchannel').id || interaction.options.getChannel('treechannel').id }`,
"reminderMessage": "",
"reminderChannelId": "",
"remindedStatus": 0,
"reminderOptIn": 0,
};
const findMessagesResponse = await fn.messages.find(interaction, guildInfo);
interaction.editReply(findMessagesResponse.status);
},
};