Compare commits

...

23 Commits

Author SHA1 Message Date
Skylar Grant e6aeb685ee Fixes, ready for testing 2023-02-12 16:00:04 -05:00
Skylar Grant d8e7ec2c1a Add missing emojis 2023-02-12 15:33:26 -05:00
Skylar Grant e9708f8045 Role menu ready for testing 2023-02-12 15:29:38 -05:00
Skylar Grant c93077016d Begin implementing role menu 2023-02-12 11:46:06 -05:00
Skylar Grant 1a9d14e3e3
Update to add includes check 2023-02-12 11:08:14 -05:00
Skylar Grant 077bb6c1b2
Update filter to check for embeds 2023-02-12 10:57:28 -05:00
Skylar Grant e3ed133830
Update to read contents of embeds 2023-02-12 10:25:32 -05:00
Skylar Grant 33ee3e9c4f Fix CI for new env vars 2023-02-12 00:00:30 -05:00
Skylar Grant a757b988b0 Classes, Collections, and new Notification Relay 2023-02-11 23:57:34 -05:00
Skylar Grant 54fd78659e
versioning 2023-02-09 23:13:31 -05:00
Skylar Grant 87e10db50b
new ci 2023-02-09 23:07:42 -05:00
Skylar Grant f93fa41de1
fix const 2023-02-09 23:00:59 -05:00
Skylar Grant a17aac9d6a
undev 2023-02-09 22:55:02 -05:00
Skylar Grant 8339d34dad
oops 2023-02-09 22:53:12 -05:00
Skylar Grant ac23f36f59
f 2023-02-09 22:49:34 -05:00
Skylar Grant c0ea770149
f 2023-02-09 22:48:12 -05:00
Skylar Grant 866479a491
testing 2023-02-09 22:36:13 -05:00
Skylar Grant b9f472f5f8
remove unnecessary call to build the tables 2023-02-09 22:32:16 -05:00
Skylar Grant b501989d46
Install modules 2023-02-09 22:27:03 -05:00
Skylar Grant 3fb8bfc827
new fix 2023-02-09 22:24:24 -05:00
Skylar Grant 8a62c5d5b2
fix 2023-02-09 22:19:46 -05:00
Skylar Grant 9b61e6a32a
improved workflow 2023-02-09 22:14:18 -05:00
Skylar Grant d0d40286b1
make the beginning height optional, defaulting to the trees current heigt 2023-02-09 22:00:12 -05:00
17 changed files with 625 additions and 531 deletions

View File

@ -22,3 +22,9 @@ jobs:
run: docker login -u $DHUB_UNAME -p $DHUB_PWORD
- name: Push image to Docker Hub
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

@ -1,4 +1,4 @@
name: Silvanus Dockerization
name: Silvanus-Dev Dockerization
on:
pull_request:
@ -22,3 +22,9 @@ jobs:
run: docker login -u $DHUB_UNAME -p $DHUB_PWORD
- name: Push image to Docker Hub
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

@ -19,14 +19,25 @@
},
"embeds": {
"footer": "Silvanus is not affiliated with Grow A Tree or Limbo Labs",
"color": "0x55FF55"
"color": "0x55FF55",
"roleMenuTitle": "Role Menu",
"treeRoleMenu": [
"Use the buttons below to give yourself roles.\n\n",
"``💧`` - ",
": Get notifications when the tree is ready to be watered.",
"\n``🍎`` - ",
": Get notifications when fruit is falling from the tree."
],
"roleMenuFooter": "Tip: Tap the button again to remove the role."
},
"emoji": {
"joint": "<:joint:862082955902976000>",
"next": "⏭️",
"previous": "⏮️",
"confirm": "☑️",
"cancel": "❌"
"cancel": "❌",
"water": "💧",
"fruit": "🍎"
},
"urls": {
"avatar": "https://cdn.discordapp.com/avatars/513184762073055252/12227aa23a06d5178853e59b72c7487b.webp?size=128",
@ -44,5 +55,9 @@
"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."
},
"notifications": {
"water": "is ready to be watered again!",
"fruit": "Fruit is appearing!"
},
"temp": {}
}

52
main.js
View File

@ -27,11 +27,12 @@ const strings = require('./data/strings.json');
const dbfn = require('./modules/dbfn.js');
const isDev = process.env.isDev;
client.once('ready', () => {
fn.collections.slashCommands(client);
client.once('ready', async () => {
await fn.collectionBuilders.slashCommands(client);
await fn.collectionBuilders.guildInfos(client);
await fn.setupCollectors(client);
console.log('Ready!');
client.user.setActivity({ name: strings.activity.name, type: ActivityType.Watching });
fn.checkReady(client);
if (isDev == 'false') {
client.channels.fetch(statusChannelId).then(channel => {
channel.send(`${new Date().toISOString()} -- \nStartup Sequence Complete <@481933290912350209>`);
@ -55,26 +56,31 @@ client.on('interactionCreate', async interaction => {
}
}
if (interaction.isButton() && interaction.component.customId == 'refresh') {
// console.log(JSON.stringify(interaction));
await fn.refresh(interaction).catch(err => {
interaction.channel.send(fn.builders.errorEmbed(err));
});
} else if (interaction.isButton() && interaction.component.customId == 'resetping') {
await fn.resetPing(interaction);
await fn.refresh(interaction).catch(err => {
interaction.channel.send(fn.builders.errorEmbed(err));
});
} else if (interaction.isButton() && interaction.component.customId == 'deleteping') {
if (interaction.message.deletable) {
await dbfn.setRemindedStatus(interaction.guildId, 0);
await dbfn.getGuildInfo(interaction.guildId).then(async res => {
const guildInfo = res.data;
await fn.refreshComparisonMessage(interaction.client, guildInfo);
});
await interaction.message.delete().catch(err => {
console.error(err);
});
if (interaction.isButton()) {
switch (interaction.component.customId) {
case 'refresh':
// console.log(JSON.stringify(interaction));
await fn.refresh(interaction).catch(err => {
interaction.channel.send(fn.builders.errorEmbed(err));
});
break;
case 'deleteping':
if (interaction.message.deletable) {
await interaction.message.delete().catch(err => {
console.error(err);
});
}
break;
case 'waterpingrole':
const waterPingStatus = await fn.buttonHandlers.waterPing(interaction);
await interaction.reply(waterPingStatus).catch(err => console.error(err));
break;
case 'fruitpingrole':
const fruitPingStatus = await fn.buttonHandlers.fruitPing(interaction);
await interaction.reply(fruitPingStatus).catch(err => console.error(err));
break;
default:
break;
}
}
});

142
modules/CustomClasses.js Normal file
View File

@ -0,0 +1,142 @@
const mysql = require('mysql');
const db = mysql.createConnection({
host: process.env.DBHOST,
user: process.env.DBUSER,
password: process.env.DBPASS,
database: process.env.DBNAME,
port: process.env.DBPORT
});
module.exports = {
GuildInfo: class {
constructor() {
this.guildId = "";
this.treeName = "";
this.treeHeight = 0;
this.treeMessageId = "";
this.treeChannelId = "";
this.leaderboardMessageId = "";
this.leaderboardChannelId = "";
this.waterMessage = "";
this.waterRoleId = "";
this.fruitMessage = "";
this.fruitRoleId = "";
this.reminderChannelId = "";
this.watchChannelId = "";
}
setId(id) {
this.guildId = id;
return this;
}
setName(name) {
this.treeName = name;
return this;
}
setHeight(height) {
this.treeHeight = height;
return this;
}
setTreeMessage(messageId, channelId) {
this.treeMessageId = messageId;
this.treeChannelId = channelId;
return this;
}
setLeaderboardMessage(messageId, channelId) {
this.leaderboardMessageId = messageId;
this.leaderboardChannelId = channelId;
return this;
}
setReminders(waterMessage, fruitMessage, reminderChannelId, watchChannelId) {
this.waterMessage = waterMessage;
this.fruitMessage = fruitMessage;
this.reminderChannelId = reminderChannelId;
this.watchChannelId = watchChannelId;
return this;
}
setRoles(waterRoleId, fruitRoleId) {
this.waterRoleId = waterRoleId;
if (fruitRoleId) this.fruitRoleId = fruitRoleId;
return this;
}
queryBuilder(query) {
let queryParts = [];
switch (query) {
case "setAll":
queryParts = [
`INSERT INTO guild_info `,
`(guild_id, `,
`tree_name, `,
`tree_height, `,
`tree_message_id, `,
`tree_channel_id, `,
`leaderboard_message_id, `,
`leaderboard_channel_id, `,
`water_message, `,
`fruit_message, `,
`reminder_channel_id, `,
`watch_channel_id) `,
`VALUES (${db.escape(this.guildId)}, `,
`${db.escape(this.treeName)}, `,
`${db.escape(this.treeHeight)}, `,
`${db.escape(this.treeMessageId)}, `,
`${db.escape(this.treeChannelId)}, `,
`${db.escape(this.leaderboardMessageId)}, `,
`${db.escape(this.leaderboardChannelId)}, `,
`${db.escape(this.waterMessage)}, `,
`${db.escape(this.fruitMessage)}, `,
`${db.escape(this.reminderChannelId)}, `,
`${db.escape(this.watchChannelId)}) `,
`ON DUPLICATE KEY UPDATE tree_name = ${db.escape(this.treeName)}, `,
`tree_height = ${db.escape(this.treeHeight)}, `,
`tree_message_id = ${db.escape(this.treeMessageId)}, `,
`tree_channel_id = ${db.escape(this.treeChannelId)}, `,
`leaderboard_message_id = ${db.escape(this.leaderboardMessageId)}, `,
`leaderboard_channel_id = ${db.escape(this.leaderboardChannelId)}, `,
`water_message = ${db.escape(this.waterMessage)}, `,
`fruit_message = ${db.escape(this.fruitMessage)}, `,
`reminder_channel_id = ${db.escape(this.reminderChannelId)}, `,
`watch_channel_id = ${db.escape(this.watchChannelId)})`
];
return queryParts.join('');
break;
case "setReminders":
queryParts = [
`UPDATE guild_info SET water_message = ${db.escape(this.waterMessage)}, `,
`fruit_message = ${db.escape(this.fruitMessage)}, `,
`reminder_channel_id = ${db.escape(this.reminderChannelId)}, `,
`watch_channel_id = ${db.escape(this.watchChannelId)} `,
`WHERE guild_id = ${db.escape(this.guildId)}`
];
return queryParts.join('');
break;
case "setTreeMessage":
queryParts = [
`UPDATE guild_info SET tree_message_id = ${db.escape(this.treeMessageId)}, `,
`tree_channel_id = ${db.escape(this.treeChannelId)}, `,
`WHERE guild_id = ${db.escape(this.guildId)}`
];
return queryParts.join('');
break;
case "setLeaderboardMessage":
queryParts = [
`UPDATE guild_info SET leaderboard_message_id = ${db.escape(this.leaderboardMessageId)}, `,
`leaderboard_channel_id = ${db.escape(this.leaderboardChannelId)}, `,
`WHERE guild_id = ${db.escape(this.guildId)}`
];
return queryParts.join('');
break;
case "setRoles":
queryParts = [
`UPDATE guild_info SET water_role_id = ${db.escape(this.waterRoleId)}, `,
`fruit_role_id = ${db.escape(this.fruitRoleId)} `,
`WHERE guild_id = ${db.escape(this.guildId)}`
];
return queryParts.join('');
break;
default:
break;
}
}
}
}

View File

@ -4,7 +4,7 @@ dotenv.config();
const { REST } = require('@discordjs/rest');
const { Routes } = require('discord-api-types/v9');
const clientId = process.env.clientId;
const clientId = process.env.BOTID;
const token = process.env.TOKEN;
const fs = require('fs');
@ -18,7 +18,7 @@ for (const file of commandFiles) {
}
}
console.log(commands);
console.log(`Token: ${token} Client ID: ${clientId}`);
const rest = new REST({ version: '9' }).setToken(token);

View File

@ -2,6 +2,7 @@ const dotenv = require('dotenv');
dotenv.config();
const debugMode = process.env.DEBUG || true;
const mysql = require('mysql');
const { GuildInfo } = require('./CustomClasses.js');
/* Table Structures
guild_info
@ -33,42 +34,6 @@ leaderboard
*/
module.exports = {
createGuildTables(guildId) {
const db = mysql.createConnection({
host: process.env.DBHOST,
user: process.env.DBUSER,
password: process.env.DBPASS,
database: process.env.DBNAME,
port: process.env.DBPORT
});
db.connect((err) => {
if (err) throw `Error connecting to the database: ${err.message}`;
});
// Create the guild-information and rank-information tables to be used.
const createGuildInfoTableQuery = "CREATE TABLE IF NOT EXISTS guild_info(guild_id VARCHAR(50) NOT NULL, tree_name VARCHAR(100) NOT NULL DEFAULT 'Run /setup where your tree is.', tree_height INT(10) NOT NULL DEFAULT 0, tree_message_id VARCHAR(50) NOT NULL DEFAULT 'Run /setup where your tree is.', tree_channel_id VARCHAR(50) NOT NULL DEFAULT 'Run /setup where your tree is.', leaderboard_message_id VARCHAR(50) NOT NULL DEFAULT 'Run /setup where your leaderboard is.', leaderboard_channel_id VARCHAR(50) NOT NULL DEFAULT 'Run /setup where your leaderboard is.', CONSTRAINT guild_pk PRIMARY KEY (guild_id))";
const createLeaderboardTableQuery = "CREATE TABLE IF NOT EXISTS leaderboard(id INT(10) NOT NULL AUTO_INCREMENT,guild_id VARCHAR(50) NOT NULL,tree_name VARCHAR(100) NOT NULL,tree_rank INT(10) NOT NULL,tree_height INT(10) NOT NULL DEFAULT 1,has_pin TINYINT(1) NOT NULL DEFAULT 0,timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, CONSTRAINT id_pk PRIMARY KEY(id))";
// TODO run the queries, then add a call to this function at the beginning of main.js or functions.js
return new Promise((resolve, reject) => {
db.query(createGuildInfoTableQuery, (err) => {
if (err) {
reject("Error creating the guild_info table: " + err.message);
console.error("Offending query: " + createGuildInfoTableQuery);
db.end();
return;
}
db.query(createLeaderboardTableQuery, (err) => {
if (err) {
reject("Error creating the leaderboard table: " + err.message);
console.error("Offending query: " + createLeaderboardTableQuery);
db.end();
return;
}
resolve({ "status": "Successfully checked both tables.", "data": null });
db.end();
});
});
});
},
getGuildInfo(guildId) {
const db = mysql.createConnection({
host: process.env.DBHOST,
@ -91,45 +56,26 @@ module.exports = {
db.end();
return;
}
/*const guildInfo = { "guildId": "123",
"treeName": "name",
"treeHeight": 123,
"treeMessageId": "123",
"treeChannelId": "123",
"leaderboardMessageId": "123",
"leaderboardChannelId": "123",
"reminderMessage": "Abc",
"reminderChannelId": "123",
"remindedStatus": 0,
"comparisonMessageId": "123"
};*/
if (res.length == 0) {
reject("There is no database entry for your guild yet. Try running /setup");
db.end();
return;
}
row = res[0];
const guildInfo = {
"guildId": guildId,
"treeName": row.tree_name,
"treeHeight": row.tree_height,
"treeMessageId": row.tree_message_id,
"treeChannelId": row.tree_channel_id,
"leaderboardMessageId": row.leaderboard_message_id,
"leaderboardChannelId": row.leaderboard_channel_id,
"reminderMessage": row.ping_role_id,
"reminderChannelId": row.ping_channel_id,
"remindedStatus": row.reminded_status,
"reminderOptIn": row.reminder_optin,
"comparisonMessageId": row.comparison_message_id,
"comparisonChannelId": row.comparison_channel_id
};
const guildInfo = new GuildInfo()
.setId(row.guild_id)
.setName(row.tree_name)
.setHeight(row.tree_height)
.setTreeMessage(row.tree_message_id, row.tree_channel_id)
.setLeaderboardMessage(row.leaderboard_message_id, row.leaderboard_channel_id)
.setReminders(row.water_message, row.fruit_message, row.reminder_channel_id, row.watch_channel_id)
.setRoles(row.water_role_id, row.fruit_role_id);
db.end();
resolve({ "status": "Successfully fetched guild information", "data": guildInfo });
resolve(guildInfo);
});
});
},
setGuildInfo(guildInfo) {
getAllGuildInfos() {
const db = mysql.createConnection({
host: process.env.DBHOST,
user: process.env.DBUSER,
@ -140,25 +86,42 @@ module.exports = {
db.connect((err) => {
if (err) throw `Error connecting to the database: ${err.message}`;
});
// Returns a Promise, resolve({ "status": "", "data": null })
// guildInfo = { "guildId": "123", "treeName": "name", "treeHeight": 123, "treeMessageId": "123", "treeChannelId": "123", "leaderboardMessageId": "123", "leaderboardChannelId": "123"}
// Set a server's tree information in the database
const insertGuildInfoQuery = `INSERT INTO guild_info (guild_id, tree_name, tree_height, tree_message_id, tree_channel_id, leaderboard_message_id, leaderboard_channel_id) VALUES (${db.escape(guildInfo.guildId)}, ${db.escape(guildInfo.treeName)}, ${db.escape(guildInfo.treeHeight)},${db.escape(guildInfo.treeMessageId)}, ${db.escape(guildInfo.treeChannelId)}, ${db.escape(guildInfo.leaderboardMessageId)}, ${db.escape(guildInfo.leaderboardChannelId)}) ON DUPLICATE KEY UPDATE tree_name = ${db.escape(guildInfo.treeName)},tree_height = ${db.escape(guildInfo.treeHeight)},tree_message_id = ${db.escape(guildInfo.treeMessageId)},tree_channel_id = ${db.escape(guildInfo.treeChannelId)},leaderboard_message_id = ${db.escape(guildInfo.leaderboardMessageId)},leaderboard_channel_id = ${db.escape(guildInfo.leaderboardChannelId)}`;
// TODO run this query and return a promise, then resolve with { "status": , "data": null }
// Get a server's tree information from the database
const query = 'SELECT * FROM guild_info';
// TODO run this query and return a promise then structure the output into a GuildInfo object. resolve with { "status": , "data": guildInfo }
return new Promise((resolve, reject) => {
db.query(insertGuildInfoQuery, (err, res) => {
db.query(query, (err, res) => {
if (err) {
console.error(err);
reject("Error setting the guild info: " + err.message);
reject("Error fetching all guild infos: " + err.message);
db.end();
return;
}
if (res.length == 0) {
reject("There are no servers yet!");
db.end();
return;
}
let guildInfos = [];
for (let i = 0; i < res.length; i++) {
let row = res[i];
guildInfos.push(new GuildInfo()
.setId(row.guild_id)
.setName(row.tree_name)
.setHeight(row.tree_height)
.setTreeMessage(row.tree_message_id, row.tree_channel_id)
.setLeaderboardMessage(row.leaderboard_message_id, row.leaderboard_channel_id)
.setReminders(row.water_message, row.fruit_message, row.reminder_channel_id, row.watch_channel_id)
.setRoles(row.water_role_id, row.fruit_role_id)
);
}
db.end();
resolve({ "status": "Successfully set the guild information", "data": null });
resolve(guildInfos);
});
});
},
setTreeInfo(guildInfo) {
setGuildInfo(query) {
const db = mysql.createConnection({
host: process.env.DBHOST,
user: process.env.DBUSER,
@ -169,21 +132,16 @@ module.exports = {
db.connect((err) => {
if (err) throw `Error connecting to the database: ${err.message}`;
});
// Returns a Promise, resolve({ "status": "", "data": null })
// guildInfo = { "guildId": "123", "treeName": "name", "treeHeight": 123, "treeMessageId": "123", "treeChannelId": "123", "leaderboardMessageId": "123", "leaderboardChannelId": "123"}
// Set a server's tree information in the database)
const insertGuildInfoQuery = `INSERT INTO guild_info (guild_id, tree_name, tree_height, tree_message_id, tree_channel_id) VALUES (${db.escape(guildInfo.guildId)}, ${db.escape(guildInfo.treeName)}, ${db.escape(guildInfo.treeHeight)},${db.escape(guildInfo.treeMessageId)}, ${db.escape(guildInfo.treeChannelId)}) ON DUPLICATE KEY UPDATE tree_name = ${db.escape(guildInfo.treeName)},tree_height = ${db.escape(guildInfo.treeHeight)},tree_message_id = ${db.escape(guildInfo.treeMessageId)},tree_channel_id = ${db.escape(guildInfo.treeChannelId)}`;
// TODO run this query and return a promise, then resolve with { "status": , "data": null }
return new Promise((resolve, reject) => {
db.query(insertGuildInfoQuery, (err, res) => {
db.query(query, (err, res) => {
if (err) {
console.error(err);
db.end();
console.error(err + "\n" + query);
reject("Error setting the guild info: " + err.message);
db.end();
return;
}
db.end();
resolve({ "status": "Successfully set the guild information", "data": null });
resolve();
});
});
},
@ -352,177 +310,5 @@ module.exports = {
resolve({ "status": "Successfully fetched historic 24hr tree.", "data": hist24hTree });
});
});
},
setReminderInfo(guildId, reminderMessage, reminderChannelId) {
const db = mysql.createConnection({
host: process.env.DBHOST,
user: process.env.DBUSER,
password: process.env.DBPASS,
database: process.env.DBNAME,
port: process.env.DBPORT
});
db.connect((err) => {
if (err) throw `Error connecting to the database: ${err.message}`;
});
// Returns a Promise, resolve({ "status": "", "data": leaderboard })
const insertReminderInfoQuery = `UPDATE guild_info SET ping_role_id = ${db.escape(reminderMessage)}, ping_channel_id = ${db.escape(reminderChannelId)} WHERE guild_id = ${db.escape(guildId)}`;
// TODO run the query and return a promise then process the results. resolve with { "status": , "data": leaderboard }
return new Promise((resolve, reject) => {
db.query(insertReminderInfoQuery, (err, res) => {
if (err) {
console.error(err);
db.end();
reject("Error updating the reminder info: " + err.message);
return;
}
db.end();
resolve({ "status": `Successfully set the reminder message to "${reminderMessage}" in <#${reminderChannelId}>`, "data": res });
});
});
},
setRemindedStatus(guildId, remindedStatus) {
const db = mysql.createConnection({
host: process.env.DBHOST,
user: process.env.DBUSER,
password: process.env.DBPASS,
database: process.env.DBNAME,
port: process.env.DBPORT
});
db.connect((err) => {
if (err) throw `Error connecting to the database: ${err.message}`;
});
// Returns a Promise, resolve({ "status": "", "data": leaderboard })
const setRemindedStatusQuery = `UPDATE guild_info SET reminded_status = ${db.escape(remindedStatus)} WHERE guild_id = ${db.escape(guildId)}`;
// TODO run the query and return a promise then process the results. resolve with { "status": , "data": leaderboard }
return new Promise((resolve, reject) => {
db.query(setRemindedStatusQuery, (err, res) => {
if (err) {
console.error(err);
db.end();
reject("Error updating the reminded status: " + err.message);
return;
}
db.end();
resolve({ "status": `Successfully set the reminded status to ${remindedStatus}`, "data": res });
// console.log("Boop: " + remindedStatus);
});
});
},
setReminderOptIn(guildId, optIn) {
const db = mysql.createConnection({
host: process.env.DBHOST,
user: process.env.DBUSER,
password: process.env.DBPASS,
database: process.env.DBNAME,
port: process.env.DBPORT
});
db.connect((err) => {
if (err) throw `Error connecting to the database: ${err.message}`;
});
// Returns a Promise, resolve({ "status": "", "data": leaderboard })
const setReminderOptInQuery = `UPDATE guild_info SET reminder_optin = ${db.escape(optIn)} WHERE guild_id = ${db.escape(guildId)}`;
// TODO run the query and return a promise then process the results. resolve with { "status": , "data": leaderboard }
return new Promise((resolve, reject) => {
db.query(setReminderOptInQuery, (err, res) => {
if (err) {
console.error(err);
db.end();
reject("Error updating the reminder opt-in status: " + err.message);
return;
}
db.end();
resolve({ "status": `Successfully set the reminder opt-in status to ${optIn}`, "data": res });
});
});
},
getOptedInGuilds() {
const db = mysql.createConnection({
host: process.env.DBHOST,
user: process.env.DBUSER,
password: process.env.DBPASS,
database: process.env.DBNAME,
port: process.env.DBPORT
});
db.connect((err) => {
if (err) throw `Error connecting to the database: ${err.message}`;
});
// Get a server's tree information from the database
const getOptedInGuildsQuery = `SELECT * FROM guild_info WHERE reminder_optin = 1`;
// TODO run this query and return a promise then structure the output into a GuildInfo object. resolve with { "status": , "data": guildInfo }
return new Promise((resolve, reject) => {
db.query(getOptedInGuildsQuery, (err, res) => {
if (err) {
console.error(err);
reject("Error fetching guild information: " + err.message);
db.end();
return;
}
/*const guildInfo = { "guildId": "123",
"treeName": "name",
"treeHeight": 123,
"treeMessageId": "123",
"treeChannelId": "123",
"leaderboardMessageId": "123",
"leaderboardChannelId": "123",
"reminderMessage": "Abc",
"reminderChannelId": "123",
"remindedStatus": 0,
"comparisonMessageId": "123"
};*/
if (res.length == 0) {
resolve({ "status": "No servers have opted in yet" });
db.end();
return;
}
row = res[0];
let guilds = [];
res.forEach(row => {
guilds.push({
"guildId": row.guild_id,
"treeName": row.tree_name,
"treeHeight": row.tree_height,
"treeMessageId": row.tree_message_id,
"treeChannelId": row.tree_channel_id,
"leaderboardMessageId": row.leaderboard_message_id,
"leaderboardChannelId": row.leaderboard_channel_id,
"reminderMessage": row.ping_role_id,
"reminderChannelId": row.ping_channel_id,
"remindedStatus": row.reminded_status,
"comparisonMessageId": row.comparison_message_id,
"comparisonChannelId": row.comparison_channel_id
});
});
db.end();
resolve({ "status": "Successfully fetched guild information", "data": guilds });
});
});
},
setComparisonMessage(comparisonMessage, guildId) {
const db = mysql.createConnection({
host: process.env.DBHOST,
user: process.env.DBUSER,
password: process.env.DBPASS,
database: process.env.DBNAME,
port: process.env.DBPORT
});
db.connect((err) => {
if (err) throw `Error connecting to the database: ${err.message}`;
});
// Returns a Promise, resolve({ "status": "", "data": leaderboard })
const setComparisonMessageQuery = `UPDATE guild_info SET comparison_message_id = ${db.escape(comparisonMessage.id)}, comparison_channel_id = ${db.escape(comparisonMessage.channel.id)} WHERE guild_id = ${db.escape(guildId)}`;
// console.log(JSON.stringify(comparisonMessage));
// TODO run the query and return a promise then process the results. resolve with { "status": , "data": leaderboard }
return new Promise((resolve, reject) => {
db.query(setComparisonMessageQuery, (err, res) => {
if (err) {
console.error(err);
db.end();
reject("Error updating the comparison message ID: " + err.message);
return;
}
db.end();
resolve({ "status": `Successfully set the comparison message ID: ${comparisonMessage}`, "data": res });
});
});
}
};

View File

@ -11,6 +11,7 @@ const fs = require('fs');
// Discord.js
const Discord = require('discord.js');
const { EmbedBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle } = Discord;
const { GuildInfo } = require('./CustomClasses');
// Various imports from other files
const config = require('../data/config.json');
@ -19,15 +20,9 @@ const slashCommandFiles = fs.readdirSync('./slash-commands/').filter(file => fil
const dbfn = require('./dbfn.js');
const { finished } = require('stream');
dbfn.createGuildTables().then(res => {
console.log(res.status);
}).catch(err => {
console.error(err);
});
const functions = {
// Functions for managing and creating Collections
collections: {
collectionBuilders: {
// Create the collection of slash commands
slashCommands(client) {
if (!client.slashCommands) client.slashCommands = new Discord.Collection();
@ -39,6 +34,15 @@ const functions = {
}
}
if (isDev) console.log('Slash Commands Collection Built');
},
async guildInfos(client) {
const guildInfos = await dbfn.getAllGuildInfos();
if (!client.guildInfos) client.guildInfos = new Discord.Collection();
client.guildInfos.clear();
for (const guildInfo of guildInfos) {
client.guildInfos.set(guildInfo.guildId, guildInfo);
}
return 'guildInfos Collection Built';
}
},
builders: {
@ -64,43 +68,51 @@ const functions = {
.addComponents(
refreshButton
);
if (guildInfo.reminderOptIn == 1 && guildInfo.remindedStatus == 1) {
const resetPingButton = new ButtonBuilder()
.setCustomId('resetping')
.setLabel('Reset Ping')
.setStyle(ButtonStyle.Secondary);
refreshActionRow.addComponents(resetPingButton);
} else if (guildInfo.reminderOptIn == 1 && guildInfo.remindedStatus == 0) {
const resetPingButton = new ButtonBuilder()
.setCustomId('resetping')
.setLabel('[Armed]')
.setStyle(ButtonStyle.Secondary);
refreshActionRow.addComponents(resetPingButton);
}
return refreshActionRow;
},
treeRoleMenu() {
return new ActionRowBuilder()
.addComponents(
this.buttons.waterPing(),
this.buttons.fruitPing()
);
},
buttons: {
acceptRules() {
return new ButtonBuilder()
.setCustomId('acceptrules')
.setLabel(`${strings.emoji.confirm} Accept Rules`)
.setStyle(ButtonStyle.Primary);
},
waterPing() {
return new ButtonBuilder()
.setCustomId('waterpingrole')
.setLabel(strings.emoji.water)
.setStyle(ButtonStyle.Primary);
},
fruitPing() {
return new ButtonBuilder()
.setCustomId('fruitpingrole')
.setLabel(strings.emoji.fruit)
.setStyle(ButtonStyle.Primary);
}
}
},
async refreshAction(guildId) {
// Create the button to go in the Action Row
const refreshButton = new ButtonBuilder()
.setCustomId('refresh')
.setLabel('Refresh')
.setStyle(ButtonStyle.Primary);
const resetPingButton = new ButtonBuilder()
.setCustomId('resetping')
.setLabel('Reset Ping')
.setStyle(ButtonStyle.Secondary);
// Create the Action Row with the Button in it, to be sent with the Embed
let refreshActionRow = new ActionRowBuilder()
.addComponents(
refreshButton
);
const getGuildInfoResponse = await dbfn.getGuildInfo(guildId);
const guildInfo = getGuildInfoResponse.data;
if (guildInfo.reminderMessage != "" && guildInfo.reminderChannelId != "") {
refreshActionRow.addComponents(resetPingButton);
embeds: {
treeRoleMenu(guildInfo) {
const actionRow = functions.builders.actionRows.treeRoleMenu();
let tempStrings = strings.embeds.treeRoleMenu;
let description = tempStrings[0] + tempStrings[1] + `<@&${guildInfo.waterRoleId}>` + tempStrings[2];
if (guildInfo.fruitRoleId != undefined) {
description += tempStrings[3] + `<@&${guildInfo.fruitRoleId}>` + tempStrings[4];
}
const embed = new EmbedBuilder()
.setColor(strings.embeds.color)
.setTitle(strings.embeds.roleMenuTitle)
.setDescription(description)
.setFooter({ text: strings.embeds.roleMenuFooter });
return { embeds: [embed], components: [actionRow] };
}
return refreshActionRow;
},
comparisonEmbed(content, guildInfo) {
// Create the embed using the content passed to this function
@ -116,7 +128,7 @@ const functions = {
// Create the embed using the content passed to this function
const embed = new EmbedBuilder()
.setColor(strings.embeds.color)
.setTitle('Water Reminder')
.setTitle('Notification Relay')
.setDescription(`[Click here to go to your Tree](https://discord.com/channels/${guildInfo.guildId}/${guildInfo.treeChannelId}/${guildInfo.treeMessageId})`)
.setFooter({ text: `Click ♻️ to delete this message` });
const messageContents = { content: content, embeds: [embed], components: [this.actionRows.reminderActionRow()] };
@ -423,8 +435,9 @@ const functions = {
// await dbfn.setGuildInfo(guildInfo);
// Bundle guildInfo into the response
const getGuildInfoResponse = await dbfn.getGuildInfo(guildInfo.guildId);
response.data = getGuildInfoResponse.data;
// const getGuildInfoResponse = await dbfn.getGuildInfo(guildInfo.guildId);
await functions.collectionBuilders.guildInfos(interaction.client);
response.data = interaction.client.guildInfos.get(guildInfo.guildId);
// Set the response status, this is only used as a response to /setup
if (treeFound && leaderboardFound) { // we found both the tree and leaderboard
@ -458,9 +471,57 @@ const functions = {
}
}
},
buttonHandlers: {
async fruitPing(interaction) {
if (interaction.client.guildInfos.has(interaction.guildId)) {
let guildInfo = interaction.client.guildInfos.get(interaction.guildId);
const role = await functions.roles.fetchRole(interaction.guild, guildInfo.fruitRoleId);
let status = "No Changes Made";
if (interaction.member.roles.cache.some(role => role.id == guildInfo.fruitRoleId)) {
await functions.roles.takeRole(interaction.member, role);
status = "Removed the fruit role.";
} else {
await functions.roles.giveRole(interaction.member, role);
status = "Added the fruit role.";
}
return functions.builders.embed(status);
} else {
throw "Guild doesn't exist in database!";
}
},
async waterPing(interaction) {
if (interaction.client.guildInfos.has(interaction.guildId)) {
let guildInfo = interaction.client.guildInfos.get(interaction.guildId);
let status = "No Changes Made";
const role = await functions.roles.fetchRole(interaction.guild, guildInfo.waterRoleId);
if (interaction.member.roles.cache.some(role => role.id == guildInfo.waterRoleId)) {
await functions.roles.takeRole(interaction.member, role);
status = "Removed the water role.";
} else {
await functions.roles.giveRole(interaction.member, role);
status = "Added the water role.";
}
return functions.builders.embed(status);
} else {
throw "Guild doesn't exist in database!";
}
}
},
roles: {
async fetchRole(guild, roleId) {
return await guild.roles.fetch(roleId).catch(err => console.error("Error fetching the role: " + err + "\n" + roleId));
},
async giveRole(member, role) {
await member.roles.add(role).catch(err => console.error("Error giving the role: " + err + "\n" + JSON.stringify(role)));
},
async takeRole(member, role) {
await member.roles.remove(role).catch(err => console.error("Error taking the role: " + err + "\n" + JSON.stringify(role)));
}
},
async refresh(interaction) {
const getGuildInfoResponse = await dbfn.getGuildInfo(interaction.guildId);
let guildInfo = getGuildInfoResponse.data;
// const getGuildInfoResponse = await dbfn.getGuildInfo(interaction.guildId);
// let guildInfo = getGuildInfoResponse.data;
let guildInfo = interaction.client.guildInfos.get(interaction.guild.id);
const findMessagesResponse = await this.messages.find(interaction, guildInfo);
if (findMessagesResponse.code == 1) {
guildInfo = findMessagesResponse.data;
@ -472,10 +533,7 @@ const functions = {
const comparedRankings = await this.rankings.compare(interaction, guildInfo);
const embed = this.builders.comparisonEmbed(comparedRankings, guildInfo);
await interaction.update(embed).then(async interactionResponse => {
// console.log(interactionResponse.interaction.message);
await dbfn.setComparisonMessage(interactionResponse.interaction.message, interaction.guildId);
});
await interaction.update(embed).catch(err => console.error(err));
} else {
await interaction.update(this.builders.errorEmbed(findMessagesResponse.status));
}
@ -491,20 +549,12 @@ const functions = {
});
});
},
getInfo(guildId) {
return new Promise((resolve, reject) => {
dbfn.getGuildInfo(guildId).then(res => {
let guildInfo = res.data;
let guildInfoString = "";
guildInfoString += `Tree Message: https://discord.com/channels/${guildId}/${guildInfo.treeChannelId}/${guildInfo.treeMessageId}\n`;
guildInfoString += `Rank Message: https://discord.com/channels/${guildId}/${guildInfo.leaderboardChannelId}/${guildInfo.leaderboardMessageId}\n`;
resolve(`Here is your servers setup info:\n${guildInfoString}`);
}).catch(err => {
console.error(err);
reject(err);
return;
})
});
getInfo(interaction) {
let guildInfo = interaction.client.guildInfos.get(interaction.guild.id);
let guildInfoString = "";
guildInfoString += `Tree Message: https://discord.com/channels/${guildId}/${guildInfo.treeChannelId}/${guildInfo.treeMessageId}\n`;
guildInfoString += `Rank Message: https://discord.com/channels/${guildId}/${guildInfo.leaderboardChannelId}/${guildInfo.leaderboardMessageId}\n`;
return `Here is your servers setup info:\n${guildInfoString}`;
},
getWaterTime(size) {
return Math.floor(Math.pow(size * 0.07 + 5, 1.1)); // Seconds
@ -545,89 +595,13 @@ const functions = {
}, ms);
});
},
async sendReminder(guildInfo, guild) {
const { guildId, reminderChannelId, reminderMessage } = guildInfo;
const reminderChannel = await guild.channels.fetch(reminderChannelId);
const reminderEmbed = functions.builders.reminderEmbed(reminderMessage, guildInfo);
await reminderChannel.send(reminderEmbed).then(async m => {
const setRemindedStatusReponse = await dbfn.setRemindedStatus(guildId, 1);
return 1;
}).catch(err => {
async sendReminder(guildInfo, message, channelId, guild) {
const reminderChannel = await guild.channels.fetch(channelId);
const reminderEmbed = functions.builders.reminderEmbed(message, guildInfo);
await reminderChannel.send(reminderEmbed).catch(err => {
console.error(err);
});
},
async setReminder(interaction, ms) {
setTimeout(this.sendReminder(interaction), ms);
},
async checkReady(client) { // Check if the guilds trees are ready to water
// let time = new Date(Date.now());
// console.log("Ready check " + time.getSeconds());
try {
// Get the guildInfos for each guild that is opted in and waiting to send a reminder
const getOptedInGuildsResponse = await dbfn.getOptedInGuilds();
// getOptedInGuilds will return this if it gets an empty set from the database
if (getOptedInGuildsResponse.status != "No servers have opted in yet") {
// Get the Array of Guilds from the response
const guilds = getOptedInGuildsResponse.data;
// Iterate over the Array
for (let i = 0; i < guilds.length; i++) {
// console.log(`iter: ${i}`);
// Save the 'old' guild info that came from getOptedInGuilds
const oldGuildInfo = guilds[i];
// Get up-to-date guildInfo from the database, probably unnecessary and redundant
const getGuildInfoResponse = await dbfn.getGuildInfo(oldGuildInfo.guildId);
// Save the new guildInfo so we can reference its remindedStatus
const guildInfo = getGuildInfoResponse.data;
const { guildId, treeChannelId, treeMessageId, remindedStatus } = guildInfo;
// console.log(`${guildInfo.treeName}: ${remindedStatus}`);
// Double check the remindedStatus to prevent double pings
if (remindedStatus == 0) {
// Fetch the guild
const guild = await client.guilds.fetch(guildId);
// Fetch the tree channel
const treeChannel = await guild.channels.fetch(treeChannelId);
// Fetch the tree message
const treeMessage = await treeChannel.messages.fetch(treeMessageId);
// Get the description from the embed of the tree message
const description = treeMessage.embeds[0].description;
// Default to not being ready to water
let readyToWater = false;
// Obviously if the tree says it's Ready to be watered, it's ready
if (description.includes("Ready to be watered")) {
readyToWater = true;
// But sometimes the tree doesn't refresh the embed, in that case we'll do a secondary check using the
// timestamp included in the embed.
} else {
const beginWaterTimestamp = description.indexOf("<t:") + 3;
const endWaterTimestamp = description.indexOf(":>");
// Split the description starting at "<t:" and ending at ":>" to get just the numerical timestamp
const waterTimestamp = parseInt(description.slice(beginWaterTimestamp, endWaterTimestamp));
// The Discord timestamp is in seconds, not ms so we need to divide by 1000
const nowTimestamp = (Date.now() / 1000);
readyToWater = (nowTimestamp > waterTimestamp);
}
if (readyToWater) {
// Send the reminder message
await this.sendReminder(guildInfo, guild);
guildInfo.remindedStatus = 1;
await this.refreshComparisonMessage(client, guildInfo);
}
}
}
await this.sleep(5000);
this.checkReady(client);
} else {
// console.log(getOptedInGuildsResponse.status);
await this.sleep(5000);
this.checkReady(client);
}
} catch (err) {
console.error(err);
await this.sleep(30000);
this.checkReady(client);
}
},
async refreshComparisonMessage(client, guildInfo) {
if (guildInfo.comparisonChannelId != "" && guildInfo.comparisonMessageId != "") {
const guild = await client.guilds.fetch(guildInfo.guildId);
@ -639,8 +613,29 @@ const functions = {
return;
}
},
async resetPing(interaction) {
await dbfn.setRemindedStatus(interaction.guildId, 0);
async setupCollectors(client) {
let guildInfos = client.guildInfos;
guildInfos.set("collectors", []);
await guildInfos.forEach(async guildInfo => {
if (guildInfo.watchChannelId != "" && guildInfo instanceof GuildInfo) {
const guild = await client.guilds.fetch(guildInfo.guildId);
// console.log(guildInfo instanceof GuildInfo);
const channel = await guild.channels.fetch(guildInfo.watchChannelId);
const filter = message => {
return message.author.id != process.env.BOTID && message.embeds != undefined;
}
const collector = channel.createMessageCollector({ filter });
collector.on('collect', message => {
if (message.embeds.length == 0) return;
console.log(message.embeds);
if (message.embeds[0].data.description.includes(strings.notifications.water)) {
this.sendReminder(guildInfo, guildInfo.waterMessage, guildInfo.reminderChannelId, guild);
} else if (message.embeds[0].data.description.includes(strings.notifications.fruit)) {
this.sendReminder(guildInfo, guildInfo.fruitMessage, guildInfo.reminderChannelId, guild);
}
});
}
});
}
};

124
modules/testing.js Normal file
View File

@ -0,0 +1,124 @@
const GuildInfo = class {
constructor() {
this.guildId = "";
this.treeName = "";
this.treeHeight = 0;
this.treeMessageId = "";
this.treeChannelId = "";
this.leaderboardMessageId = "";
this.leaderboardChannelId = "";
this.waterMessage = "";
this.fruitMessage = "";
this.reminderChannelId = "";
this.watchChannelId = "";
}
setId(id) {
this.guildId = id;
return this;
}
setName(name) {
this.treeName = name;
return this;
}
setHeight(height) {
this.treeHeight = height;
return this;
}
setTreeMessage(messageId, channelId) {
this.treeMessageId = messageId;
this.treeChannelId = channelId;
return this;
}
setLeaderboardMessage(messageId, channelId) {
this.leaderboardMessageId = messageId;
this.leaderboardChannelId = channelId;
return this;
}
setReminders(waterMessage, fruitMessage, reminderChannelId, watchChannelId) {
this.waterMessage = waterMessage;
this.fruitMessage = fruitMessage;
this.reminderChannelId = reminderChannelId;
this.watchChannelId = watchChannelId;
return this;
}
queryBuilder(query) {
let queryParts = [];
switch (query) {
case "setAll":
queryParts = [
`INSERT INTO guild_info `,
`(guild_id, `,
`tree_name, `,
`tree_height, `,
`tree_message_id, `,
`tree_channel_id, `,
`leaderboard_message_id, `,
`leaderboard_channel_id, `,
`water_message, `,
`fruit_message, `,
`reminder_channel_id, `,
`watch_channel_id) `,
`VALUES (${db.escape(this.guildId)}, `,
`${db.escape(this.treeName)}, `,
`${db.escape(this.treeHeight)}, `,
`${db.escape(this.treeMessageId)}, `,
`${db.escape(this.treeChannelId)}, `,
`${db.escape(this.leaderboardMessageId)}, `,
`${db.escape(this.leaderboardChannelId)}, `,
`${db.escape(this.waterMessage)}, `,
`${db.escape(this.fruitMessage)}, `,
`${db.escape(this.reminderChannelId)}, `,
`${db.escape(this.watchChannelId)}) `,
`ON DUPLICATE KEY UPDATE tree_name = ${db.escape(this.treeName)}, `,
`tree_height = ${db.escape(this.treeHeight)}, `,
`tree_message_id = ${db.escape(this.treeMessageId)}, `,
`tree_channel_id = ${db.escape(this.treeChannelId)}, `,
`leaderboard_message_id = ${db.escape(this.leaderboardMessageId)}, `,
`leaderboard_channel_id = ${db.escape(this.leaderboardChannelId)}, `,
`water_message = ${db.escape(this.waterMessage)}, `,
`fruit_message = ${db.escape(this.fruitMessage)}, `,
`reminder_channel_id = ${db.escape(this.reminderChannelId)}, `,
`watch_channel_id = ${db.escape(this.watchChannelId)})`
];
return queryParts.join();
break;
case "setReminders":
queryParts = [
`UPDATE guildInfo SET water_message = ${db.escape(this.waterMessage)}, `,
`fruit_message = ${db.escape(this.fruitMessage)}, `,
`reminder_channel_id = ${db.escape(this.reminderChannelId)}, `,
`watch_channel_id = ${db.escape(this.watchChannelId)} `,
`WHERE guild_id = ${db.escape(this.guildId)}`
];
return queryParts.join();
break;
case "setTreeMessage":
queryParts = [
`UPDATE guildInfo SET tree_message_id = ${db.escape(this.treeMessageId)}, `,
`tree_channel_id = ${db.escape(this.treeChannelId)}, `,
`WHERE guild_id = ${db.escape(this.guildId)}`
];
return queryParts.join();
break;
case "setLeaderboardMessage":
queryParts = [
`UPDATE guildInfo SET leaderboard_message_id = ${db.escape(this.leaderboardMessageId)}, `,
`leaderboard_channel_id = ${db.escape(this.leaderboardChannelId)}, `,
`WHERE guild_id = ${db.escape(this.guildId)}`
];
return queryParts.join();
break;
default:
break;
}
}
}
new GuildInfo()
.setId(row.guild_id)
.setName(row.tree_name)
.setHeight(row.tree_height)
.setTreeMessage(row.tree_message_id, row.tree_channel_id)
.setLeaderboardMessage(row.leaderboard_message_id, row.leaderboard_channel_id)
.setReminders(row.water_message, row.fruit_message, row.reminder_channel_id);

View File

@ -1,6 +1,6 @@
{
"name": "silvanus",
"version": "1.1.4",
"version": "1.1.5",
"description": "Grow A Tree Companion Bot",
"main": "main.js",
"scripts": {

View File

@ -1,6 +1,7 @@
const { SlashCommandBuilder } = require('discord.js');
const { SlashCommandBuilder, Guild } = require('discord.js');
const dbfn = require('../modules/dbfn.js');
const fn = require('../modules/functions.js');
const { GuildInfo } = require('../modules/CustomClasses.js');
module.exports = {
data: new SlashCommandBuilder()
@ -10,61 +11,40 @@ module.exports = {
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
if (interaction.client.guildInfos.has(interaction.guildId)) {
let guildInfo = interaction.client.guildInfos.get(interaction.guildId);
const findMessagesResponse = await fn.messages.find(interaction, guildInfo);
if (findMessagesResponse.code == 1) {
guildInfo = findMessagesResponse.data;
let guildInfo = interaction.client.guildInfos.get(interaction.guildId);
// 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);
const embed = fn.builders.comparisonEmbed(comparedRankings, guildInfo);
await interaction.editReply(embed).then(async message => {
await dbfn.setComparisonMessage(message, interaction.guildId);
});
await interaction.editReply(embed).catch(err => console.error(err));
} 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);
const embed = fn.builders.comparisonEmbed(comparedRankings, guildInfo);
await interaction.editReply(embed).then(async message => {
await dbfn.setComparisonMessage(message.id, interaction.guildId);
});
} else {
await interaction.editReply(fn.builders.errorEmbed(findMessagesResponse.status));
}
} else {
// Create a basic guildInfo with blank data
let guildInfo = new GuildInfo()
.setId(interaction.guildId)
.setTreeMessage("", interaction.channelId)
.setLeaderboardMessage("", interaction.channelId)
// 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);
const embed = fn.builders.comparisonEmbed(comparedRankings, guildInfo);
await interaction.editReply(embed).catch(err => console.error(err));
} else {
await interaction.editReply(fn.builders.errorEmbed("An unknown error occurred while running the compare command."));
console.error(err);
await interaction.editReply(fn.builders.errorEmbed(findMessagesResponse.status));
}
});
}
} catch (err) {
interaction.editReply(fn.builders.errorEmbed(err)).catch(err => {
console.error(err);

View File

@ -0,0 +1,50 @@
const { SlashCommandBuilder, PermissionFlagsBits } = require('discord.js');
const dbfn = require('../modules/dbfn.js');
const fn = require('../modules/functions.js');
module.exports = {
data: new SlashCommandBuilder()
.setName('notifications')
.setDescription('Setup a notification relay for improved water and fruit notifications')
.addChannelOption(o =>
o
.setName('watchchannel')
.setDescription('The channel Grow A Tree sends your notifications in')
.setRequired(true))
.addStringOption(o =>
o
.setName('watermessage')
.setDescription('Message to send for water reminders')
.setRequired(true))
.addChannelOption(o =>
o
.setName('pingchannel')
.setDescription('The channel to send the water reminder in')
.setRequired(true))
.addStringOption(o =>
o
.setName('fruitmessage')
.setDescription("Message to send for fruit reminders")
.setRequired(false))
.setDefaultMemberPermissions(PermissionFlagsBits.ManageRoles),
async execute(interaction) {
try {
await interaction.deferReply({ ephemeral: true });
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);
guildInfo.setReminders(waterMessage, fruitMessage, reminderChannel.id, watchChannel.id);
let query = guildInfo.queryBuilder("setReminders");
console.log(query);
await dbfn.setGuildInfo(query);
await interaction.editReply(`I'll watch <#${watchChannel.id}> for Grow A Tree Notifications and relay them to <#${reminderChannel.id}>.`).catch(e => console.error(e));
fn.collectionBuilders.guildInfos(interaction.client);
}
} catch (err) {
console.error("Error occurred while setting up a notification relay: " + err);
}
},
};

View File

@ -0,0 +1,29 @@
const { SlashCommandBuilder } = require('discord.js');
const dbfn = require('../modules/dbfn.js');
const fn = require('../modules/functions.js');
module.exports = {
data: new SlashCommandBuilder()
.setName('rolemenu')
.setDescription('Send a self-assignable role selection menu')
.addRoleOption(o =>
o.setName('waterrole')
.setDescription('The role for water reminder pings')
.setRequired(true))
.addRoleOption(o =>
o.setName('fruitrole')
.setDescription('The role for fruit alert pings')
.setRequired(false)),
async execute(interaction) {
await interaction.deferReply().catch(err => console.error(err));
if (interaction.client.guildInfos.has(interaction.guildId)) {
let guildInfo = interaction.client.guildInfos.get(interaction.guildId);
guildInfo.setRoles(interaction.options.getRole('waterrole').id, interaction.options.getRole('fruitrole').id);
await dbfn.setGuildInfo(guildInfo.queryBuilder("setRoles"));
await fn.collectionBuilders.guildInfos(interaction.client);
await interaction.editReply(fn.builders.embeds.treeRoleMenu(guildInfo)).catch(err => console.error(err));
} else {
await interaction.editReply(fn.builders.errorEmbed("No information is known about your server yet, please run /setup or /compare")).catch(err => console.error(err));
}
},
};

View File

@ -1,31 +0,0 @@
const { SlashCommandBuilder, PermissionFlagsBits } = require('discord.js');
const dbfn = require('../modules/dbfn.js');
const fn = require('../modules/functions.js');
module.exports = {
data: new SlashCommandBuilder()
.setName('setping')
.setDescription('Opt-in to automatic water reminders')
.addStringOption(o =>
o.setName('pingmsg')
.setDescription('The message to send for a water reminder')
.setRequired(true))
.addChannelOption(o =>
o.setName('pingchannel')
.setDescription('The channel to send the water reminder in')
.setRequired(true))
.setDefaultMemberPermissions(PermissionFlagsBits.ManageRoles),
async execute(interaction) {
try {
await interaction.deferReply({ ephemeral: true });
const reminderMessage = interaction.options.getString('pingmsg');
const reminderChannel = interaction.options.getChannel('pingchannel');
const setPingRoleResponse = await dbfn.setReminderInfo(interaction.guildId, reminderMessage, reminderChannel.id);
await dbfn.setReminderOptIn(interaction.guildId, 1);
interaction.editReply(setPingRoleResponse.status);
} catch(err) {
console.error(err);
await interaction.editReply(fn.builders.errorEmbed(err));
}
},
};

View File

@ -1,18 +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('setping')
.setDescription('Run this command when you water your tree to have a reminder sent.'),
async execute(interaction) {
await interaction.deferReply({ ephemeral: true });
const getGuildInfoResponse = await dbfn.getGuildInfo(interaction.guildId);
const guildInfo = getGuildInfoResponse.data;
const reminderTimeS = fn.getWaterTime(guildInfo.treeHeight);
const reminderTimeMs = reminderTimeS * 1000;
fn.setReminder(interaction, reminderTimeMs, guildInfo.pingRoleId);
interaction.editReply("A reminder has been set.");
},
};

View File

@ -7,7 +7,7 @@ module.exports = {
.setDescription('View information about how the bot is set up in your server'),
execute(interaction) {
interaction.deferReply({ephemeral: true}).then(() => {
fn.getInfo(interaction.guildId).then(res => {
fn.getInfo(interaction).then(res => {
const embed = fn.builders.embed(res);
interaction.editReply(embed);
}).catch(err => {

View File

@ -6,18 +6,22 @@ module.exports = {
data: new SlashCommandBuilder()
.setName('timetoheight')
.setDescription('Calculate how long it would take to reach a given height')
.addStringOption(o =>
o.setName('beginheight')
.setDescription('Begining tree height in feet, numbers ONLY')
.setRequired(true))
.addStringOption(o =>
.addIntegerOption(o =>
o.setName('endheight')
.setDescription('Ending tree height in feet, numbers ONLY')
.setRequired(true)),
.setDescription('Ending tree height in feet')
.setRequired(true))
.addIntegerOption(o =>
o.setName('beginheight')
.setDescription('Beginning tree height in feet')
.setRequired(false)),
async execute(interaction) {
await interaction.deferReply({ ephemeral: true });
const beginHeight = interaction.options.getString('beginheight');
const endHeight = interaction.options.getString('endheight');
let beginHeight = interaction.options.getInteger('beginheight');
const endHeight = interaction.options.getInteger('endheight');
if (!beginHeight) {
const guildInfo = interaction.client.guildInfos.get(interaction.guild.id);
beginHeight = guildInfo.treeHeight;
}
fn.timeToHeight(beginHeight, endHeight).then(res => {
interaction.editReply(`It will take a tree that is ${beginHeight}ft tall ${res} to reach ${endHeight}ft.`);
}).catch(err => {