Compare commits

...

22 Commits

Author SHA1 Message Date
Skylar Grant
51dbff6f28
Catch errors 2023-03-11 14:47:54 -05:00
Skylar Grant
dbd66c248c Testing new tree update detection 2023-03-10 21:16:32 -05:00
Skylar Grant
3eaa7db561 Improvements to timetoheights calculations display 2023-02-26 16:11:05 -05:00
Skylar Grant
a0822cb002 Versioning 2023-02-26 12:41:48 -05:00
Skylar Grant
03202db21d Stricter check 2023-02-26 12:30:55 -05:00
Skylar Grant
15a1093aa3 Add privacy switch 2023-02-26 12:26:52 -05:00
Skylar Grant
b880503059 Some more logging 2023-02-26 12:18:44 -05:00
Skylar Grant
915ca4bf7c Add percentage modifiers to timetoheight 2023-02-26 12:09:24 -05:00
Skylar Grant
f4ecbf9ba3 Rate limits 2023-02-26 12:09:04 -05:00
Skylar Grant
291db33692 Stop checking ratelimits 2023-02-21 18:41:47 -05:00
Skylar Grant
1b0269c304 Documentation update 2023-02-20 15:17:37 -05:00
Skylar Grant
b2ff3030f9 Stop deleting commands 2023-02-20 15:15:08 -05:00
Skylar Grant
555d0436f8 Fix /setup compare 2023-02-20 15:14:57 -05:00
Skylar Grant
c2fe7bfa9a Documentation update 2023-02-20 15:14:41 -05:00
Skylar Grant
d3a04921d6 CI Update 2023-02-20 15:14:13 -05:00
Skylar Grant
3127305c03 Removed update event 2023-02-20 14:25:39 -05:00
Skylar Grant
989d26af83 Self-destructing water relay 2023-02-20 14:14:54 -05:00
Skylar Grant
30c43ee0cf Error update 2023-02-20 14:07:50 -05:00
Skylar Grant
6fe4456513 Bugfixes in notifications and update to relay 2023-02-20 14:07:38 -05:00
Skylar Grant
3912ccf40e Versioning 2023-02-19 21:46:01 -05:00
Skylar Grant
38a70d5457 Start collector when the command is run 2023-02-19 21:44:58 -05:00
Skylar Grant
53ccfa31d2 Disappearing fruit pings 2023-02-19 21:44:46 -05:00
15 changed files with 473 additions and 166 deletions

View File

@ -20,10 +20,4 @@ jobs:
- name: Log into Docker Hub - name: Log into Docker Hub
run: docker login -u $DHUB_UNAME -p $DHUB_PWORD run: docker login -u $DHUB_UNAME -p $DHUB_PWORD
- name: Push image to Docker Hub v0idf1sh/silvanus-dev - name: Push image to Docker Hub v0idf1sh/silvanus-dev
run: docker push v0idf1sh/silvanus-dev run: docker push v0idf1sh/silvanus-dev
- name: Set up a skeleton .env file
run: echo "TOKEN=${{secrets.DEVTOKEN}}" > .env && echo "BOTID=${{ secrets.CLIENTID }}" >> .env
- name: Install modules
run: npm i
- name: Refresh commands with Discord
run: node modules/_deploy-global.js

View File

@ -20,10 +20,4 @@ jobs:
- name: Log into Docker Hub - name: Log into Docker Hub
run: docker login -u $DHUB_UNAME -p $DHUB_PWORD run: docker login -u $DHUB_UNAME -p $DHUB_PWORD
- name: Push image to Docker Hub v0idf1sh/silvanus - name: Push image to Docker Hub v0idf1sh/silvanus
run: docker push v0idf1sh/silvanus run: docker push v0idf1sh/silvanus
- name: Set up a skeleton .env file
run: echo "TOKEN=${{secrets.PRODTOKEN}}" > .env && echo "clientId=${{ secrets.PRODCLIENTID }}" >> .env
- name: Install modules
run: npm i
- name: Refresh commands with Discord
run: node modules/_deploy-global.js

View File

@ -13,34 +13,24 @@ Silvanus is not affiliated with Grow A Tree or Limbo Labs.
## Setup ## Setup
If your `/tree` and `/top trees` messages are in the same channel, simple run `/compare` in that channel and you're good to go! If your `/tree` and `/top trees` messages are in the same channel, simply 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. Otherwise, run `/setup compare` to set the proper channels for the bot to look in for the `/tree` and `/top trees` messages.
Use `/commands` to view a description of all my commands. Use `/commands` to view a description of all my commands.
## Permissions ## Permissions
Silvanus requires permissions to `Send Messages` and `Send Messages in Threads` if applicable. If you plan to use the Role Menu Silvanus will also need permission to `Manage Roles` Silvanus requires permissions to `Send Messages` and `Send Messages in Threads` if applicable. If you plan to use the Role Menu Silvanus will also need permission to `Manage Roles`.
## Commands ## Commands
* `/setup` - You only need to run this command if your server has its `/tree` and `/top trees` messages in separate channels. * `/compare` - Compare your tree to others on the leaderboard
* `/setupinfo` - Displays your server's configuration information. * `/relay set` - Setup a Notification Relay for the first time
* `/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 (`[💧]`). * `/relay update` - Update an already configured Notification Relay
* `/notifications` - Guild members with the `Manage Roles` permission can run this command to set up automatic reminders when your tree is ready to be watered or when fruit is dropping. * `/relay disable` - Disable the Notification Relay
* This feature relies on Grow A Tree's built-in Notification system. Refer to Grow A Tree for instructions on setting them up. * `/rolemenu` - Send a self-assignable role menu for relay pings
* `watchchannel`: Select the channel you've configured Grow A Tree to send notifications in. * `/watertime` - Calculates the time between waters for a tree of a given height
* `watermessage`: This option sets the message to send when the tree is ready to be watered. This can include `@pings`, links, etc. * `/timetoheight` - Calculates how long it would take a tree to grow to a given height
* `pingchannel`: Select the channel you want Silvanus to forward the notifications to. * `/setup compare` - Set the channels to use with `/compare`
* `fruitmessage`: Optional: This sets the message to send when the tree is dropping fruit. If not set, the `watermessage` will be used instead. * `/setup view` - View your server's configuration
* `/rolemenu` - Creates a menu for users to give themselves Water and Fruit pingable roles. * `/setup reset` - Delete your server's configuration
* Requires `Manage Roles` permission to run. * `/help` - Displays the bot's help page
* `waterrole`: Select the role to give users when they select the Water button
* `fruitrole`: Optional: Select the role to give users when they select the Fruit button
* If this option isn't set, no Fruit Role will be available for self-assignment.
* `/watertime` - Calculates the wait time between waters for a tree of a given height.
* `height`: The height in feet to calculate for.
* `/timetoheight` - Calculates how long it would take to grow to a height
* `endheight`: The destination height, in feet.
* `beginheight`: Optional: The starting height, in feet. If this option isn't set, the current height of your tree will be used insead.
* `/reset` - Removes your server's configuration from the database.
* `/help` - Displays the bot's help page and links to each command.

View File

@ -5,9 +5,9 @@
"help": { "help": {
"title": "Silvanus 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!\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.", "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.\n\nUse </commands:1069501270454456331> to view a description of all my commands.", "setup": "If your </tree:972648557796524032> and </top trees:1051840665362894950> messages are in the same channel, simply run </compare:1065346941166297128> in that channel and you're good to go!\n\nOtherwise, run </setup compare:1065407649363005561> to set the proper channels for the bot to look in for the </tree:972648557796524032> and </top trees:1051840665362894950> messages.\n\nUse </commands:1077058896469966888> to view a description of all my commands.",
"permissions": "At a minimum, Silvanus requires permissions to `Send Messages` and `Send Messages in Threads` if applicable. If Analyzer is given permission to `Manage Messages`, the bot will delete the `.settree` and `.setranks` messages to reduce spam.", "permissions": "At a minimum, Silvanus requires permissions to `Send Messages` and `Send Messages in Threads` if applicable. If Analyzer is given permission to `Manage Messages`, the bot will delete the `.settree` and `.setranks` messages to reduce spam.",
"allCommands": "</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 your server's configuration information.\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</notifications:0> - Guild members with the ``Manage Roles`` permission can run this command to set up automatic reminders when your tree is ready to be watered or when fruit is dropping.\n This feature relies on Grow A Tree's built-in Notification system. Refer to Grow A Tree for instructions on setting them up.\n ``watchchannel``: Select the channel you've configured Grow A Tree to send notifications in.\n ``watermessage``: This option sets the message to send when the tree is ready to be watered. This can include ``@pings``, links, etc.\n ``pingchannel``: Select the channel you want Silvanus to forward the notifications to.\n ``fruitmessage``: Optional: This sets the message to send when the tree is dropping fruit. If not set, the ``watermessage`` will be used instead.\n</rolemenu:0> - Creates a menu for users to give themselves Water and Fruit pingable roles.\n Requires ``Manage Roles`` permission to run.\n ``waterrole``: Select the role to give users when they select the Water button\n ``fruitrole``: Optional: Select the role to give users when they select the Fruit button\n If this option isn't set, no Fruit Role will be available for self-assignment.\n</watertime:1066970330029113444> - Calculates the wait time between waters for a tree of a given height.\n ``height``: The height in feet to calculate for.\n</timetoheight:1067727254634889227> - Calculates how long it would take to grow to a height\n ``endheight``: The destination height, in feet.\n ``beginheight``: Optional: The starting height, in feet. If this option isn't set, the current height of your tree will be used insead.\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": "</compare:1077058896469966889> - Compare your tree to others on the leaderboard\n</relay set:1077322799032578152> - Setup a Notification Relay for the first time\n</relay update:1077322799032578152> - Update an already configured Notification Relay\n</relay disable:1077322799032578152> - Disable the Notification Relay\n</rolemenu:1077058896469966892> - Send a self-assignable role menu for relay pings\n</watertime:1077058896469966895> - Calculates the time between waters for a tree of a given height\n</timetoheight:1077058896469966894> - Calculates how long it would take a tree to grow to a given height\n</setup compare:1077058896469966893> - Set the channels to use with </compare:1077058896469966889>\n</setup view:1077058896469966893> - View your server's configuration\n</setup reset:1077058896469966893> - Delete your server's configuration\n</help:1077058896469966890> - Displays the bot's help page"
}, },
"commands": { "commands": {
"compare": "</compare:1065346941166297128>", "compare": "</compare:1065346941166297128>",
@ -70,5 +70,8 @@
"water": "is ready to be watered again!", "water": "is ready to be watered again!",
"fruit": "Fruit is appearing!" "fruit": "Fruit is appearing!"
}, },
"ids": {
"growATree": "972637072991068220"
},
"temp": {} "temp": {}
} }

33
main.js
View File

@ -28,9 +28,10 @@ const dbfn = require('./modules/dbfn.js');
const isDev = process.env.DEBUG; const isDev = process.env.DEBUG;
client.once('ready', async () => { client.once('ready', async () => {
await fn.collectionBuilders.slashCommands(client); fn.collectionBuilders.slashCommands(client);
await fn.collectionBuilders.guildInfos(client); await fn.collectionBuilders.guildInfos(client);
await fn.setupCollectors(client); await fn.collectionBuilders.messageCollectors(client);
// checkRateLimits();
console.log('Ready!'); console.log('Ready!');
client.user.setActivity({ name: strings.activity.name, type: ActivityType.Watching }); client.user.setActivity({ name: strings.activity.name, type: ActivityType.Watching });
if (isDev == 'false') { if (isDev == 'false') {
@ -85,6 +86,34 @@ client.on('interactionCreate', async interaction => {
} }
}); });
client.on('messageUpdate', async message => {
await fn.messages.updateHandler(message).catch(e => console.error(e));
});
async function checkRateLimits(hi) {
const axios = require('axios');
// Make a GET request to the Discord API
await axios.get('https://discord.com/api/v10/users/@me', {
headers: {
'Authorization': `Bot ${token}`
}
}).then(response => {
// Get the rate limit headers
const remaining = response.headers['x-ratelimit-remaining'];
const reset = response.headers['x-ratelimit-reset'];
// Log the rate limit headers
console.log(`Remaining requests: ${remaining}`);
console.log(`Reset time (Unix epoch seconds): ${reset}`);
}).catch(error => {
console.error(error);
});
await fn.sleep(500).then(async () =>{
await checkRateLimits();
})
}
process.on('unhandledRejection', error => { process.on('unhandledRejection', error => {
console.error('Unhandled promise rejection:', error); console.error('Unhandled promise rejection:', error);
}); });

View File

@ -39,12 +39,12 @@ module.exports = {
return this; return this;
} }
setTreeMessage(messageId, channelId) { setTreeMessage(messageId, channelId) {
this.treeMessageId = messageId; this.treeMessageId = messageId ? messageId : this.treeMessageId;
this.treeChannelId = channelId; this.treeChannelId = channelId;
return this; return this;
} }
setLeaderboardMessage(messageId, channelId) { setLeaderboardMessage(messageId, channelId) {
this.leaderboardMessageId = messageId; this.leaderboardMessageId = messageId ? messageId : this.leaderboardMessageId;
this.leaderboardChannelId = channelId; this.leaderboardChannelId = channelId;
return this; return this;
} }
@ -122,7 +122,7 @@ module.exports = {
case "setTreeMessage": case "setTreeMessage":
queryParts = [ queryParts = [
`UPDATE guild_info SET tree_message_id = ${db.escape(this.treeMessageId)}, `, `UPDATE guild_info SET tree_message_id = ${db.escape(this.treeMessageId)}, `,
`tree_channel_id = ${db.escape(this.treeChannelId)}, `, `tree_channel_id = ${db.escape(this.treeChannelId)} `,
`WHERE guild_id = ${db.escape(this.guildId)}` `WHERE guild_id = ${db.escape(this.guildId)}`
]; ];
return queryParts.join(''); return queryParts.join('');
@ -130,7 +130,7 @@ module.exports = {
case "setLeaderboardMessage": case "setLeaderboardMessage":
queryParts = [ queryParts = [
`UPDATE guild_info SET leaderboard_message_id = ${db.escape(this.leaderboardMessageId)}, `, `UPDATE guild_info SET leaderboard_message_id = ${db.escape(this.leaderboardMessageId)}, `,
`leaderboard_channel_id = ${db.escape(this.leaderboardChannelId)}, `, `leaderboard_channel_id = ${db.escape(this.leaderboardChannelId)} `,
`WHERE guild_id = ${db.escape(this.guildId)}` `WHERE guild_id = ${db.escape(this.guildId)}`
]; ];
return queryParts.join(''); return queryParts.join('');
@ -153,7 +153,17 @@ module.exports = {
} }
return queryParts.join(''); return queryParts.join('');
break; break;
default: case "setTreeInfo":
queryParts = [
`INSERT INTO guild_info (`,
`guild_id, tree_name, tree_height`,
`) VALUES (`,
`${db.escape(this.guildId)}, ${db.escape(this.treeName)}, ${db.escape(this.treeHeight)}`,
`) ON DUPLICATE KEY UPDATE tree_name = ${db.escape(this.treeName)}, `,
`tree_height = ${db.escape(this.treeHeight)}`
];
return queryParts.join('');
default:
break; break;
} }
} }

View File

@ -54,6 +54,6 @@ async function uploadCommands() {
} }
(async () => { (async () => {
await deleteCommands(); // await deleteCommands();
await uploadCommands(); await uploadCommands();
})(); })();

View File

@ -1,34 +1,29 @@
/* const commands = [
</setup:1065407649363005561> {raw: "`/compare`", clickable: "</compare:1077058896469966889>"},
</setupinfo:1065413032374706196> {raw: "`/relay set`", clickable: "</relay set:1077322799032578152>"},
</compare:1065346941166297128> {raw: "`/relay update`", clickable: "</relay update:1077322799032578152>"},
</setping:1068373237995683902> {raw: "`/relay disable`", clickable: "</relay disable:1077322799032578152>"},
</optout:1068753032801693758> {raw: "`/rolemenu`", clickable: "</rolemenu:1077058896469966892>"},
</watertime:1066970330029113444> {raw: "`/watertime`", clickable: "</watertime:1077058896469966895>"},
</timetoheight:1067727254634889227> {raw: "`/timetoheight`", clickable: "</timetoheight:1077058896469966894>"},
</reset:1065412317052944476> {raw: "`/setup compare`", clickable: "</setup compare:1077058896469966893>"},
</help:1065346941166297129> {raw: "`/setup view`", clickable: "</setup view:1077058896469966893>"},
</tree:972648557796524032> {raw: "`/setup reset`", clickable: "</setup reset:1077058896469966893>"},
</top trees:1051840665362894950> {raw: "`/help`", clickable: "</help:1077058896469966890>"},
*/ {raw: "`/tree`", clickable: "</tree:972648557796524032>"},
{raw: "`/top trees`", clickable: "</top trees:1051840665362894950>"},
{raw: "`/commands`", clickable: "</commands:1077058896469966888>"}
];
const fs = require('fs'); const fs = require('fs');
const replaceAll = require('string.prototype.replaceall'); const replaceAll = require('string.prototype.replaceall');
const path = "./modules/input.txt"; const path = "./modules/input.txt";
const string = fs.readFileSync(path).toString(); const string = fs.readFileSync(path).toString();
let newString = replaceAll(string, '\* ', ''); let newString = replaceAll(string, '\* ', '');
newString = replaceAll(newString, '\r\n', '\\n'); newString = replaceAll(newString, '\n', '\\n');
newString = replaceAll(newString, '\t', ' - '); newString = replaceAll(newString, '\t', ' - ');
newString = replaceAll(newString, '`/setup`', '</setup:1065407649363005561>'); commands.forEach(command => {
newString = replaceAll(newString, '`/setupinfo`', '</setupinfo:1065413032374706196>'); newString = replaceAll(newString, command.raw, command.clickable);
newString = replaceAll(newString, '`/compare`', '</compare:1065346941166297128>'); });
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, '`/commands`', '</commands:1069501270454456331>');
newString = replaceAll(newString, '`/notifications`', '</notifications:0>');
newString = replaceAll(newString, '`/rolemenu`', '</rolemenu:0>');
newString = replaceAll(newString, '`', '``'); newString = replaceAll(newString, '`', '``');
fs.writeFileSync(path, newString); fs.writeFileSync(path, newString);
return "Done"; return "Done";

View File

@ -43,6 +43,17 @@ const functions = {
client.guildInfos.set(guildInfo.guildId, guildInfo); client.guildInfos.set(guildInfo.guildId, guildInfo);
} }
return 'guildInfos Collection Built'; return 'guildInfos Collection Built';
},
async messageCollectors(client) {
// Create an empty collection for MessageCollectors
if (!client.messageCollectors) client.messageCollectors = new Discord.Collection();
client.messageCollectors.clear();
// Get all of the guild infos from the client
const { guildInfos, messageCollectors } = client;
// Iterate over each guild info
await guildInfos.forEach(async guildInfo => {
await functions.collectors.create(client, guildInfo);
});
} }
}, },
builders: { builders: {
@ -112,6 +123,16 @@ const functions = {
.setDescription(description) .setDescription(description)
.setFooter({ text: strings.embeds.roleMenuFooter }); .setFooter({ text: strings.embeds.roleMenuFooter });
return { embeds: [embed], components: [actionRow] }; return { embeds: [embed], components: [actionRow] };
},
information(content, fields) {
const embed = new EmbedBuilder()
.setColor(strings.embeds.color)
.setTitle('Information')
.setDescription(content)
.setFooter({ text: `v${package.version} - ${strings.embeds.footer}` });
if (fields) embed.addFields(fields);
const messageContents = { embeds: [embed], ephemeral: true };
return messageContents;
} }
}, },
comparisonEmbed(content, guildInfo) { comparisonEmbed(content, guildInfo) {
@ -303,7 +324,6 @@ const functions = {
}, },
tree: { tree: {
parse(interaction, guildInfo) { parse(interaction, guildInfo) {
let input;
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (guildInfo == undefined) { if (guildInfo == undefined) {
reject(`The guild entry hasn't been created yet. [${interaction.guildId || interaction.commandGuildId}]`); reject(`The guild entry hasn't been created yet. [${interaction.guildId || interaction.commandGuildId}]`);
@ -316,6 +336,7 @@ const functions = {
reject("This doesn't appear to be a valid ``/tree`` message."); reject("This doesn't appear to be a valid ``/tree`` message.");
return; return;
} }
let input;
input = m.embeds[0].data.description; input = m.embeds[0].data.description;
let treeName = m.embeds[0].data.title; let treeName = m.embeds[0].data.title;
let lines = input.split('\n'); let lines = input.split('\n');
@ -472,12 +493,61 @@ const functions = {
}, },
isTree(message) { isTree(message) {
if (message.embeds.length > 0) { if (message.embeds.length > 0) {
return message.embeds[0].data.description.includes("Your tree is"); // Grab the description and title
const {description, title} = message.embeds[0].data;
// Make sure it's a tree message
if (description.includes("Your tree is")) {
// Grab the name
const treeName = title;
// Grab the tree's height
const indices = [description.indexOf("Your tree is ") + 13, description.indexOf("ft")];
const treeHeightStr = description.slice(indices[0], indices[1]);
const treeHeightFloat = parseFloat(treeHeightStr).toFixed(1);
// Return the info gathered
return {
treeName: treeName,
treeHeight: treeHeightFloat
};
}
} else {
return false;
} }
}, },
isLeaderboard(message) { isLeaderboard(message) {
if (message.embeds.length > 0) { if (message.embeds.length > 0) {
return message.embeds[0].data.title == "Tallest Trees"; return message.embeds[0].data.title == "Tallest Trees";
} else {
return false;
}
},
async updateHandler(message) {
if (message.partial) {
message = await message.fetch().catch(e => console.error(e));
}
// Make sure the message is from Grow A Tree
if (message.author.id != strings.ids.growATree) return;
// Check and store the message types
const isLeaderboard = this.isLeaderboard(message);
const isTree = this.isTree(message);
// Check if the message is a leaderboard
if (isLeaderboard) {
// Need to actually handle this later
// console.log("I've seen a leaderboard update.");
} else if (isTree) { // Check if the message is a tree
// console.log(`I've seen a tree update: ${isTree.treeName}: ${isTree.treeHeight}ft`);
let guildInfo;
if (message.client.guildInfos.has(message.guildId)) {
guildInfo = message.client.guildInfos.get(message.guildId);
guildInfo.setName(isTree.treeName)
.setHeight(isTree.treeHeight);
} else {
guildInfo = new GuildInfo().setId(message.guildId)
.setName(isTree.treeName)
.setHeight(isTree.treeHeight);
}
const query = guildInfo.queryBuilder("setTreeInfo");
await dbfn.setGuildInfo(query);
} }
} }
}, },
@ -528,6 +598,59 @@ const functions = {
await member.roles.remove(role).catch(err => console.error("Error taking the role: " + err + "\n" + JSON.stringify(role))); await member.roles.remove(role).catch(err => console.error("Error taking the role: " + err + "\n" + JSON.stringify(role)));
} }
}, },
collectors: {
async create(client, guildInfo) {
// If a collector is already setup
if (client.messageCollectors.has(guildInfo.guildId)) {
// Close the collector
await this.end(client, guildInfo);
}
// Make sure guildInfo is what we expect, the watch channel isnt blank, and notifications are enabled
if (guildInfo instanceof GuildInfo && guildInfo.watchChannelId != "" && guildInfo.notificationsEnabled) {
// Fetch the Guild
const guild = await client.guilds.fetch(guildInfo.guildId);
// Fetch the Channel
const channel = await guild.channels.fetch(guildInfo.watchChannelId);
// Create the filter function
const filter = message => {
// Discard any messages sent by Silvanus
return message.author.id != process.env.BOTID;
}
// Create the collector
const collector = channel.createMessageCollector({ filter });
// Add the collector to the messageCollectors Collection
client.messageCollectors.set(guildInfo.guildId, collector);
collector.on('collect', message => {
// Check for manual relay use with "water ping" and "fruit ping"
if (message.content.toLowerCase().includes("water ping")) {
functions.sendWaterReminder(guildInfo, guildInfo.waterMessage, guildInfo.reminderChannelId, guild);
return;
} else if (message.content.toLowerCase().includes("fruit ping")) {
functions.sendFruitReminder(guildInfo, guildInfo.fruitMessage, guildInfo.reminderChannelId, guild);
return;
}
// If the message doesn't contain an embed, we can ignore it
if (message.embeds == undefined) return;
if (message.embeds.length == 0) return;
// Check the description field of the embed to determine if it matches Grow A Tree's notification texts
if (message.embeds[0].data.description.includes(strings.notifications.water)) {
functions.sendWaterReminder(guildInfo, guildInfo.waterMessage, guildInfo.reminderChannelId, guild);
} else if (message.embeds[0].data.description.includes(strings.notifications.fruit)) {
functions.sendFruitReminder(guildInfo, guildInfo.fruitMessage, guildInfo.reminderChannelId, guild);
}
});
}
},
async end(client, guildInfo) {
if (!client.messageCollectors) throw "No Message Collectors";
if (!client.messageCollectors.has(guildInfo.guildId)) throw "Guild doesn't have a Message Collector";
const collector = client.messageCollectors.get(guildInfo.guildId);
// Close the collector
await collector.stop();
// Remove the collector from the messageCollectors Collection
client.messageCollectors.delete(guildInfo.guildId);
}
},
async refresh(interaction) { async refresh(interaction) {
// const getGuildInfoResponse = await dbfn.getGuildInfo(interaction.guildId); // const getGuildInfoResponse = await dbfn.getGuildInfo(interaction.guildId);
// let guildInfo = getGuildInfoResponse.data; // let guildInfo = getGuildInfoResponse.data;
@ -585,31 +708,48 @@ const functions = {
} }
return `${waterParts.value} ${waterParts.units}`; return `${waterParts.value} ${waterParts.units}`;
}, },
timeToHeight(beginHeight, destHeight) { timeToHeight(beginHeight, destHeight, efficiency, quality) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let time = 0; let time = 0;
for (let i = beginHeight; i < destHeight; i++) { let oldTime = 0;
const waterTime = parseFloat(functions.getWaterTime(i)); let compostAppliedCount = 0;
// console.log("Height: " + i + "Time: " + waterTime); let totalWaterCount = 0;
time += waterTime; if ((efficiency) && (quality)) {
for (let i = beginHeight; i < destHeight; i++) {
const randNum = Math.floor(Math.random() * 100);
const compostApplied = randNum <= efficiency;
if (compostApplied) {
let qualityPercent = quality / 100;
let waterTime = functions.getWaterTime(i);
let reductionTime = waterTime * qualityPercent;
let finalTime = waterTime - reductionTime;
compostAppliedCount++;
totalWaterCount++;
time += parseFloat(finalTime);
oldTime += waterTime;
} else {
totalWaterCount++;
let waterTime = parseFloat(functions.getWaterTime(i));
time += waterTime;
oldTime += waterTime;
}
}
} else {
for (let i = beginHeight; i < destHeight; i++) {
const waterTime = parseFloat(functions.getWaterTime(i));
// console.log("Height: " + i + "Time: " + waterTime);
time += waterTime;
}
} }
const readableWaterTime = this.parseWaterTime(time);
// 60 secs in min const savedTime = this.parseWaterTime(oldTime - time);
// 3600 secs in hr resolve({
// 86400 sec in day time: readableWaterTime,
totalWaterCount: totalWaterCount ? totalWaterCount : undefined,
let units = " secs"; compostAppliedCount: compostAppliedCount ? compostAppliedCount : undefined,
if (60 < time && time <= 3600) { // Minutes average: totalWaterCount ? parseFloat((compostAppliedCount / totalWaterCount) * 100).toFixed(1) : undefined,
time = parseFloat(time / 60).toFixed(1); savedTime: savedTime
units = " mins"; });
} else if (3600 < time && time <= 86400) {
time = parseFloat(time / 3600).toFixed(1);
units = " hrs";
} else if (86400 < time) {
time = parseFloat(time / 86400).toFixed(1);
units = " days";
}
resolve(time + units);
}); });
}, },
sleep(ms) { sleep(ms) {
@ -624,22 +764,34 @@ const functions = {
async sendWaterReminder(guildInfo, message, channelId, guild) { async sendWaterReminder(guildInfo, message, channelId, guild) {
const reminderChannel = await guild.channels.fetch(channelId); const reminderChannel = await guild.channels.fetch(channelId);
const reminderEmbed = functions.builders.waterReminderEmbed(message, guildInfo); const reminderEmbed = functions.builders.waterReminderEmbed(message, guildInfo);
await reminderChannel.send(reminderEmbed).catch(err => { console.log(`Water Relay: ${guild.name}: ${guildInfo.treeName}`);
await reminderChannel.send(reminderEmbed).then(async m => {
if (!m.deletable) return;
await this.sleep(500).then(async () => {
await m.delete().catch(e => console.error(e));
});
}).catch(err => {
console.error(err); console.error(err);
}); });
}, },
async sendFruitReminder(guildInfo, message, channelId, guild) { async sendFruitReminder(guildInfo, message, channelId, guild) {
const reminderChannel = await guild.channels.fetch(channelId); const reminderChannel = await guild.channels.fetch(channelId);
const reminderEmbed = functions.builders.fruitReminderEmbed(message, guildInfo); const reminderEmbed = functions.builders.fruitReminderEmbed(message, guildInfo);
await reminderChannel.send(reminderEmbed).catch(err => { console.log(`Fruit Relay: ${guild.name}: ${guildInfo.treeName}`);
await reminderChannel.send(reminderEmbed).then(async m => {
if (!m.deletable) return;
await this.sleep(500).then(async () => {
await m.delete().catch(e => console.error(e));
});
}).catch(err => {
console.error(err); console.error(err);
}); });
}, },
async setupCollectors(client) { async setupCollectors(client) {
let guildInfos = client.guildInfos; let guildInfos = client.guildInfos;
guildInfos.set("collectors", []); let collectorsArray = [];
await guildInfos.forEach(async guildInfo => { await guildInfos.forEach(async guildInfo => {
if ( guildInfo instanceof GuildInfo && guildInfo.watchChannelId != "" && guildInfo.notificationsEnabled) { if (guildInfo instanceof GuildInfo && guildInfo.watchChannelId != "" && guildInfo.notificationsEnabled) {
const guild = await client.guilds.fetch(guildInfo.guildId); const guild = await client.guilds.fetch(guildInfo.guildId);
// console.log(guildInfo instanceof GuildInfo); // console.log(guildInfo instanceof GuildInfo);
const channel = await guild.channels.fetch(guildInfo.watchChannelId); const channel = await guild.channels.fetch(guildInfo.watchChannelId);
@ -666,6 +818,36 @@ const functions = {
}); });
} }
}); });
guildInfos.set("collectors", collectorsArray);
},
async setupCollector(channel, interaction) {
if (interaction.client.guildInfos.has(interaction.guildId)) {
let collectors = interaction.client.guildInfos.get('collectors');
let guildInfo = interaction.client.guildInfos.get(interaction.guildId);
const filter = message => {
return message.author.id != process.env.BOTID;
}
const collector = channel.createMessageCollector({ filter });
collectors.push(collector);
collector.on('collect', message => {
if (message.content.toLowerCase().includes("water ping")) {
this.sendWaterReminder(guildInfo, guildInfo.waterMessage, guildInfo.reminderChannelId, guild);
return;
} else if (message.content.toLowerCase().includes("fruit ping")) {
this.sendFruitReminder(guildInfo, guildInfo.fruitMessage, guildInfo.reminderChannelId, guild);
return;
}
if (message.embeds == undefined) return;
if (message.embeds.length == 0) return;
if (message.embeds[0].data.description.includes(strings.notifications.water)) {
this.sendWaterReminder(guildInfo, guildInfo.waterMessage, guildInfo.reminderChannelId, guild);
} else if (message.embeds[0].data.description.includes(strings.notifications.fruit)) {
this.sendFruitReminder(guildInfo, guildInfo.fruitMessage, guildInfo.reminderChannelId, guild);
}
});
} else {
throw "Guild doesn't exist in database!";
}
} }
}; };

View File

@ -0,0 +1,21 @@
information(content, fields) {
const embed = new EmbedBuilder()
.setColor(strings.embeds.color)
.setTitle('Information')
.setDescription(content)
.setFooter({ text: `v${package.version} - ${strings.embeds.footer}` });
if (fields) embed.addFields(fields);
const messageContents = { embeds: [embed], ephemeral: true };
return messageContents;
}
replyContent = `I estimate that a tree with ${efficiency}% Composter Efficiency and ${quality}% Compost Quality growing from ${beginHeight}ft to ${endHeight}ft will take ${time}`;
replyFields = [
{ name: `Start Height:`, value: `**${beginHeight}ft**`, inline: true },
{ name: `End Height:`, value: `**${endHeight}**`, inline: true },
{ name: `Efficiency:`, value: `**${efficiency}%**`, inline: true },
{ name: `Quality:`, value: `**${quality}%**`, inline: true },
{ name: `Summary`, value: `Compost Applied **${compostAppliedCount}** times, out of **${totalWaterCount}** waterings, for an average of **${average}%**` }
];
const reply = information(replyContent, replyFields);

View File

@ -1,6 +1,6 @@
{ {
"name": "silvanus", "name": "silvanus",
"version": "1.2.1", "version": "1.2.3",
"description": "Grow A Tree Companion Bot", "description": "Grow A Tree Companion Bot",
"main": "main.js", "main": "main.js",
"scripts": { "scripts": {
@ -17,6 +17,7 @@
}, },
"homepage": "https://github.com/voidf1sh/silvanus#readme", "homepage": "https://github.com/voidf1sh/silvanus#readme",
"dependencies": { "dependencies": {
"axios": "^1.3.3",
"discord.js": "^14.7.1", "discord.js": "^14.7.1",
"dotenv": "^16.0.3", "dotenv": "^16.0.3",
"mysql": "^2.18.1", "mysql": "^2.18.1",

View File

@ -14,7 +14,7 @@ module.exports = {
{ name: "True", value: "true" }, { name: "True", value: "true" },
{ name: "False", value: "false" })), { name: "False", value: "false" })),
execute(interaction) { execute(interaction) {
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')); const helpEmbed = fn.builders.helpEmbed(`${strings.help.info}\n\n**Setup**\n${strings.help.setup}\n\n**Support Server**\n${strings.urls.supportServer}`, interaction.options.getString('private'));
interaction.reply(helpEmbed); interaction.reply(helpEmbed);
}, },
}; };

View File

@ -6,7 +6,7 @@ const strings = require('../data/strings.json');
module.exports = { module.exports = {
data: new SlashCommandBuilder() data: new SlashCommandBuilder()
.setName('notifications') .setName('relay')
.setDescription('A notification relay for improved water and fruit notifications') .setDescription('A notification relay for improved water and fruit notifications')
.addSubcommand(sc => .addSubcommand(sc =>
sc.setName('set') sc.setName('set')
@ -67,75 +67,115 @@ module.exports = {
const subcommand = interaction.options.getSubcommand(); const subcommand = interaction.options.getSubcommand();
// if (process.env.DEBUG) console.log(`${typeof subcommand}: ${subcommand}`); // if (process.env.DEBUG) console.log(`${typeof subcommand}: ${subcommand}`);
switch (subcommand) { switch (subcommand) {
// Set all components for the first time
case "set": case "set":
// If there is already a guildInfo object for this server
if (interaction.client.guildInfos.has(interaction.guildId)) { if (interaction.client.guildInfos.has(interaction.guildId)) {
const watchChannel = interaction.options.getChannel('watchchannel');
const waterMessage = interaction.options.getString('watermessage');
const fruitMessage = interaction.options.getString('fruitmessage') ? interaction.options.getString('fruitmessage') : interaction.options.getString('watermessage');
const reminderChannel = interaction.options.getChannel('pingchannel');
let guildInfo = interaction.client.guildInfos.get(interaction.guildId); let guildInfo = interaction.client.guildInfos.get(interaction.guildId);
guildInfo.setReminders(waterMessage, fruitMessage, reminderChannel.id, watchChannel.id, true); // Get options from the interaction
let query = guildInfo.queryBuilder("setReminders");
await dbfn.setGuildInfo(query);
const replyParts = [
`I'll watch <#${watchChannel.id}> for Grow A Tree Notifications and relay them to <#${reminderChannel.id}>.`,
`Water Message: ${waterMessage}`
];
if (fruitMessage != "") replyParts.push(`Fruit Message: ${fruitMessage}`);
await interaction.editReply(replyParts.join("\n")).catch(e => console.error(e));
fn.collectionBuilders.guildInfos(interaction.client);
} else {
const watchChannel = interaction.options.getChannel('watchchannel'); const watchChannel = interaction.options.getChannel('watchchannel');
const waterMessage = interaction.options.getString('watermessage'); const waterMessage = interaction.options.getString('watermessage');
// If the fruit message is set, use it, otherwise default to the water message.
const fruitMessage = interaction.options.getString('fruitmessage') ? interaction.options.getString('fruitmessage') : interaction.options.getString('watermessage'); const fruitMessage = interaction.options.getString('fruitmessage') ? interaction.options.getString('fruitmessage') : interaction.options.getString('watermessage');
const reminderChannel = interaction.options.getChannel('pingchannel'); const reminderChannel = interaction.options.getChannel('pingchannel');
// Set the reminder configuration in the GuildInfo object
guildInfo.setReminders(waterMessage, fruitMessage, reminderChannel.id, watchChannel.id, true);
// Update the guildInfos Collection
interaction.client.guildInfos.set(interaction.guildId, guildInfo);
// Build a query to update the database
let query = guildInfo.queryBuilder("setReminders");
// Run the query
await dbfn.setGuildInfo(query);
// Set up a collector on the watch channel
fn.collectors.create(interaction.client, guildInfo);
// Compose a reply
const reply = [
`I'll watch <#${watchChannel.id}> for Grow A Tree Notifications and relay them to <#${reminderChannel.id}>.`,
`Water Message: ${waterMessage}`,
`Fruit Message: ${fruitMessage}`
].join("\n");
// Send the reply
await interaction.editReply(fn.builders.embed(reply)).catch(e => console.error(e));
} else {
// Get options from the interaction
const watchChannel = interaction.options.getChannel('watchchannel');
const waterMessage = interaction.options.getString('watermessage');
// If the fruit message is set, use it. Otherwise default to the water message
const fruitMessage = interaction.options.getString('fruitmessage') ? interaction.options.getString('fruitmessage') : interaction.options.getString('watermessage');
const reminderChannel = interaction.options.getChannel('pingchannel');
// Create a new GuildInfo object
let guildInfo = new GuildInfo() let guildInfo = new GuildInfo()
.setId(interaction.guildId) .setId(interaction.guildId)
// Set the reminder configuration
.setReminders(waterMessage, fruitMessage, reminderChannel.id, watchChannel.id, true); .setReminders(waterMessage, fruitMessage, reminderChannel.id, watchChannel.id, true);
// Update the guildInfos Collection
interaction.client.guildInfos.set(interaction.guildId, guildInfo);
// Build a query to update the database
let query = guildInfo.queryBuilder("setReminders"); let query = guildInfo.queryBuilder("setReminders");
// Run the query
await dbfn.setGuildInfo(query); await dbfn.setGuildInfo(query);
const replyParts = [ // Create a messageCollector on the watch channel
fn.collectors.create(interaction.client, guildInfo);
// Compose a reply
const reply = [
`I'll watch <#${watchChannel.id}> for Grow A Tree Notifications and relay them to <#${reminderChannel.id}>.`, `I'll watch <#${watchChannel.id}> for Grow A Tree Notifications and relay them to <#${reminderChannel.id}>.`,
`Water Message: ${waterMessage}` `Water Message: ${waterMessage}`,
]; `Fruit Message: ${fruitMessage}`
if (fruitMessage != "") replyParts.push(`Fruit Message: ${fruitMessage}`); ].join("\n");
await interaction.editReply(replyParts.join("\n")).catch(e => console.error(e)); // Send the reply
fn.collectionBuilders.guildInfos(interaction.client); await interaction.editReply(reply).catch(e => console.error(e));
} }
break; break;
case "update": case "update": // Update the relay configuration piecemeal
if (interaction.client.guildInfos.has(interaction.guildId)) { if (interaction.client.guildInfos.has(interaction.guildId)) {
let guildInfo = interaction.client.guildInfos.get(interaction.guildId); let guildInfo = interaction.client.guildInfos.get(interaction.guildId);
// Get all possible options from the interaction
const inWatchChannel = interaction.options.getChannel('watchchannel'); const inWatchChannel = interaction.options.getChannel('watchchannel');
const inWaterMessage = interaction.options.getString('watermessage'); const inWaterMessage = interaction.options.getString('watermessage');
const inFruitMessage = interaction.options.getString('fruitmessage'); const inFruitMessage = interaction.options.getString('fruitmessage');
const inReminderChannel = interaction.options.getChannel('pingchannel'); const inReminderChannel = interaction.options.getChannel('pingchannel');
// Check if each option is set, if it is, use it. Otherwise use what was already set
const outWatchChannelId = inWatchChannel ? inWatchChannel.id : guildInfo.watchChannelId; const outWatchChannelId = inWatchChannel ? inWatchChannel.id : guildInfo.watchChannelId;
const outWaterMessage = inWaterMessage ? inWaterMessage : guildInfo.waterMessage; const outWaterMessage = inWaterMessage ? inWaterMessage : guildInfo.waterMessage;
const outFruitMessage = inFruitMessage ? inFruitMessage : guildInfo.fruitMessage; const outFruitMessage = inFruitMessage ? inFruitMessage : guildInfo.fruitMessage;
const outReminderChannelId = inReminderChannel ? inReminderChannel.id : guildInfo.reminderChannelId; const outReminderChannelId = inReminderChannel ? inReminderChannel.id : guildInfo.reminderChannelId;
// Update the relay configuration
guildInfo.setReminders(outWaterMessage, outFruitMessage, outReminderChannelId, outWatchChannelId, true); guildInfo.setReminders(outWaterMessage, outFruitMessage, outReminderChannelId, outWatchChannelId, true);
// Update the guildInfos Collection
interaction.client.guildInfos.set(interaction.guildId, guildInfo);
// Build a query to update the database
let query = guildInfo.queryBuilder("setReminders"); let query = guildInfo.queryBuilder("setReminders");
// Run the query
await dbfn.setGuildInfo(query); await dbfn.setGuildInfo(query);
const replyParts = [ // Create a messageCollector on the watch channel
fn.collectors.create(interaction.client, guildInfo);
// Compose a reply
const reply = [
`I'll watch <#${outWatchChannelId}> for Grow A Tree Notifications and relay them to <#${outReminderChannelId}>.`, `I'll watch <#${outWatchChannelId}> for Grow A Tree Notifications and relay them to <#${outReminderChannelId}>.`,
`Water Message: ${outWaterMessage}` `Water Message: ${outWaterMessage}`,
]; `Fruit Message: ${outFruitMessage}`
if (outFruitMessage != "") replyParts.push(`Fruit Message: ${outFruitMessage}`); ].join("\n");
await interaction.editReply(replyParts.join("\n")).catch(e => console.error(e)); // Send the reply
fn.collectionBuilders.guildInfos(interaction.client); await interaction.editReply(reply).catch(e => console.error(e));
} else { } else {
await interaction.editReply(fn.builders.errorEmbed("There is no existing notification relay to update!")).catch(e => console.error(e)); await interaction.editReply(fn.builders.errorEmbed("There is no existing notification relay to update!")).catch(e => console.error(e));
} }
break; break;
case 'disable': case 'disable': // Disable the relay
if (interaction.client.guildInfos.has(interaction.guildId)) { if (interaction.client.guildInfos.has(interaction.guildId)) {
let guildInfo = interaction.client.guildInfos.get(interaction.guildId); let guildInfo = interaction.client.guildInfos.get(interaction.guildId);
// Update the relay config with all undefined except for `enabled` which is false
guildInfo.setReminders(undefined, undefined, undefined, undefined, false); guildInfo.setReminders(undefined, undefined, undefined, undefined, false);
// Update the guildInfos Collection
interaction.client.guildInfos.set(interaction.guildId, guildInfo);
// Update the database
await dbfn.setGuildInfo(guildInfo.queryBuilder("setReminders")).catch(e => console.error(e)); await dbfn.setGuildInfo(guildInfo.queryBuilder("setReminders")).catch(e => console.error(e));
await fn.collectionBuilders.guildInfos(interaction.client); // Close the collector
await fn.collectors.end(interaction.client, guildInfo).catch(e => console.error(e));
// Reply confirming disabling of relay
await interaction.editReply(fn.builders.embed(strings.status.optout)).catch(e => console.error(e)); await interaction.editReply(fn.builders.embed(strings.status.optout)).catch(e => console.error(e));
} else { } else {
await interaction.editReply(fn.builders.errorEmbed("A notification relay has not been set up yet!")).catch(e => console.error(e)); await interaction.editReply(fn.builders.errorEmbed("A notification relay has not been set up yet!")).catch(e => console.error(e));

View File

@ -18,8 +18,8 @@ module.exports = {
) )
.addChannelOption(o => .addChannelOption(o =>
o.setName('leaderboardchannel') o.setName('leaderboardchannel')
.setDescription('If your leaderboard isn\'t in the same channel, where is it?') .setDescription('What channel is your leaderboard in?')
.setRequired(false) .setRequired(true)
) )
) )
.addSubcommand(sc => .addSubcommand(sc =>
@ -41,12 +41,12 @@ module.exports = {
.setDescription('View your server\'s configuration')) .setDescription('View your server\'s configuration'))
.addSubcommand(sc => .addSubcommand(sc =>
sc.setName('reset') sc.setName('reset')
.setDescription('Remove all server configuration from the database') .setDescription('Remove all server configuration from the database')
.addBooleanOption(o => .addBooleanOption(o =>
o.setName('confirm') o.setName('confirm')
.setDescription('WARNING THIS IS IRREVERSIBLE') .setDescription('WARNING THIS IS IRREVERSIBLE')
.setRequired(true) .setRequired(true)
) )
) )
.setDefaultMemberPermissions(PermissionFlagsBits.Administrator), .setDefaultMemberPermissions(PermissionFlagsBits.Administrator),
async execute(interaction) { async execute(interaction) {
@ -54,15 +54,27 @@ module.exports = {
const subcommand = interaction.options.getSubcommand(); const subcommand = interaction.options.getSubcommand();
switch (subcommand) { switch (subcommand) {
case "compare": case "compare":
const treeChannel = interaction.options.getChannel('treechannel');
const leaderboardChannel = interaction.options.getChannel('leaderboardchannel');
if (interaction.client.guildInfos.has(interaction.guildId)) { if (interaction.client.guildInfos.has(interaction.guildId)) {
let guildInfo = interaction.client.guildInfos.get(interaction.guildId); let guildInfo = interaction.client.guildInfos.get(interaction.guildId);
const findMessagesResponse = await fn.messages.find(interaction, guildInfo); guildInfo.setTreeMessage(undefined, treeChannel.id);
await interaction.editReply(findMessagesResponse.status).catch(e => console.error(e)); guildInfo.setLeaderboardMessage(undefined, leaderboardChannel.id);
// Update the database
await dbfn.setGuildInfo(guildInfo.queryBuilder("setTreeMessage")).catch(e => console.error(e));
await dbfn.setGuildInfo(guildInfo.queryBuilder("setLeaderboardMessage")).catch(e => console.error(e));
const reply = `Tree Channel: <#${treeChannel.id}> | Leaderboard Channel: <#${leaderboardChannel.id}>`;
await interaction.editReply(fn.builders.embed(reply)).catch(e => console.error(e));
} else { } else {
let guildInfo = new GuildInfo() let guildInfo = new GuildInfo()
.setId(interaction.guildId); .setId(interaction.guildId)
const findMessagesResponse = await fn.messages.find(interaction, guildInfo); .setTreeMessage(undefined, treeChannel.id)
await interaction.editReply(findMessagesResponse.status).catch(e => console.error(e)); .setLeaderboardMessage(undefined, leaderboardChannel.id);
// Update the database
await dbfn.setGuildInfo(guildInfo.queryBuilder("setTreeMessage")).catch(e => console.error(e));
await dbfn.setGuildInfo(guildInfo.queryBuilder("setLeaderboardMessage")).catch(e => console.error(e));
const reply = `Tree Channel: <#${treeChannel.id}> | Leaderboard Channel: <#${leaderboardChannel.id}>`;
await interaction.editReply(fn.builders.embed(reply)).catch(e => console.error(e));
} }
break; break;
case "rolemenu": case "rolemenu":
@ -114,7 +126,7 @@ module.exports = {
await interaction.editReply(fn.builders.embed("You must select 'true' to confirm setup reset. No changes have been made.")).catch(e => console.error(e)); await interaction.editReply(fn.builders.embed("You must select 'true' to confirm setup reset. No changes have been made.")).catch(e => console.error(e));
} }
} else { } else {
throw "Guild doesn't exist in database!"; await interaction.editReply(fn.builders.errorEmbed("There is no configuration to delete.")).catch(e => console.error(e));
} }
break; break;
default: default:

View File

@ -6,28 +6,64 @@ module.exports = {
data: new SlashCommandBuilder() data: new SlashCommandBuilder()
.setName('timetoheight') .setName('timetoheight')
.setDescription('Calculate how long it would take to reach a given height') .setDescription('Calculate how long it would take to reach a given height')
.addIntegerOption(o => .addIntegerOption(o =>
o.setName('endheight') o.setName('endheight')
.setDescription('Ending tree height in feet') .setDescription('Ending tree height in feet')
.setRequired(true)) .setRequired(true)
.addIntegerOption(o => )
.addIntegerOption(o =>
o.setName('beginheight') o.setName('beginheight')
.setDescription('Beginning tree height in feet') .setDescription('Beginning tree height in feet')
.setRequired(false)), .setRequired(false)
)
.addIntegerOption(o =>
o.setName('efficiency')
.setDescription('Composter efficiency percentage, rounded')
.setRequired(false)
)
.addIntegerOption(o =>
o.setName('quality')
.setDescription('Compost quality percentage, rounded')
.setRequired(false)
)
.addBooleanOption(o =>
o.setName('private')
.setDescription('Should the reply be visible only to you?')
.setRequired(false)
),
async execute(interaction) { async execute(interaction) {
await interaction.deferReply({ ephemeral: true }); const private = interaction.options.getBoolean('private') != undefined ? interaction.options.getBoolean('private') : true;
let beginHeight = interaction.options.getInteger('beginheight'); await interaction.deferReply({ ephemeral: private });
const inBeginHeight = interaction.options.getInteger('beginheight');
const endHeight = interaction.options.getInteger('endheight'); const endHeight = interaction.options.getInteger('endheight');
if (!beginHeight) { const efficiency = interaction.options.getInteger('efficiency') != undefined ? interaction.options.getInteger('efficiency') : 10;
const quality = interaction.options.getInteger('quality') != undefined ? interaction.options.getInteger('quality') : 5;
let beginHeight, replyContent;
if (!inBeginHeight) {
const guildInfo = interaction.client.guildInfos.get(interaction.guild.id); const guildInfo = interaction.client.guildInfos.get(interaction.guild.id);
beginHeight = guildInfo.treeHeight; beginHeight = guildInfo.treeHeight;
} else {
beginHeight = inBeginHeight;
} }
fn.timeToHeight(beginHeight, endHeight).then(res => {
interaction.editReply(`It will take a tree that is ${beginHeight}ft tall ${res} to reach ${endHeight}ft.`); const timeToHeightResults = await fn.timeToHeight(beginHeight, endHeight, efficiency, quality);
}).catch(err => { const { time, totalWaterCount, compostAppliedCount, average, savedTime } = timeToHeightResults;
interaction.editReply("Error: " + err); let replyFields = undefined;
console.error(err); if (efficiency && quality) {
return; replyContent = `I estimate that a tree with ${efficiency}% Composter Efficiency and ${quality}% Compost Quality growing from ${beginHeight}ft to ${endHeight}ft will take ${time}`;
}); replyFields = [
}, { name: `Height Gained:`, value: `${endHeight - beginHeight}ft`, inline: true},
{ name: `E/Q:`, value: `${efficiency}%/${quality}%`, inline: true},
{ name: `Compost Applied`, value: `${compostAppliedCount} times`, inline: true },
{ name: `Compost Average`, value: `${average}%`, inline: true },
{ name: `Saved Time`, value: savedTime, inline: true }
];
} else {
replyContent = `I estimate that a tree growing from ${beginHeight}ft to ${endHeight}ft will take ${time}`;
}
const reply = fn.builders.embeds.information(replyContent, replyFields)
await interaction.editReply(reply);
}
}; };