Automatically updating compare messages!

This commit is contained in:
Skylar Grant 2023-03-11 15:12:34 -05:00
parent 27169afcf0
commit 024defbbfc
7 changed files with 233 additions and 110 deletions

View File

@ -51,7 +51,10 @@
},
"error": {
"noGuild": "Setup has not been completed yet. Try running </setup:1065407649363005561> or </help setup:1065346941166297129>",
"invalidSubcommand": "Invalid subcommand detected."
"invalidSubcommand": "Invalid subcommand detected.",
"noTreeMessage": "</tree:0> - Make sure you've sent or refreshed a Tree recently.",
"noLeaderboardMessage": "</top trees:0> - Make sure you've sent or refreshed the Tallest Trees leaderboard recently.",
"noCompareMessage": "</compare:0> - This is awkward, I've lost my own comparison message!"
},
"status": {
"treeAndLeaderboard": "Tree and leaderboard messages were both found, setup is complete. Run </setupinfo:1065413032374706196> to verify. Run </compare:1065346941166297128> to get started!",

View File

@ -86,9 +86,12 @@ client.on('interactionCreate', async interaction => {
}
});
client.on('messageUpdate', async message => {
await fn.sleep(50);
await fn.messages.updateHandler(message);
client.on('messageUpdate', async (oldMessage, message) => {
await fn.messages.updateHandler(message).catch(e => console.error(e));
});
client.on('messageCreate', async message => {
await fn.messages.updateHandler(message).catch(e => console.error(e));
});
async function checkRateLimits(hi) {

View File

@ -24,6 +24,8 @@ module.exports = {
this.reminderChannelId = "";
this.watchChannelId = "";
this.notificationsEnabled = false;
this.compareChannelId = "";
this.compareMessageId = "";
}
setId(id) {
@ -43,9 +45,21 @@ module.exports = {
this.treeChannelId = channelId;
return this;
}
setTreeInfo(name, height, channelId, messageId) {
this.treeName = name ? name : this.treeName;
this.treeHeight = height;
this.treeChannelId = channelId ? channelId : this.treeChannelId;
this.treeMessageId = messageId ? messageId : this.treeMessageId;
return this;
}
setLeaderboardMessage(messageId, channelId) {
this.leaderboardMessageId = messageId ? messageId : this.leaderboardMessageId;
this.leaderboardChannelId = channelId;
this.leaderboardChannelId = channelId ? channelId : this.leaderboardChannelId;
return this;
}
setCompareMessage(channelId, messageId) {
this.compareChannelId = channelId;
this.compareMessageId = messageId;
return this;
}
setReminders(waterMessage, fruitMessage, reminderChannelId, watchChannelId, enabled) {
@ -156,11 +170,22 @@ module.exports = {
case "setTreeInfo":
queryParts = [
`INSERT INTO guild_info (`,
`guild_id, tree_name, tree_height`,
`guild_id, tree_name, tree_height, tree_channel_id, tree_message_id`,
`) VALUES (`,
`${db.escape(this.guildId)}, ${db.escape(this.treeName)}, ${db.escape(this.treeHeight)}`,
`${db.escape(this.guildId)}, ${db.escape(this.treeName)}, ${db.escape(this.treeHeight)}, ${db.escape(this.treeChannelId)}, ${db.escape(this.treeMessageId)}`,
`) ON DUPLICATE KEY UPDATE tree_name = ${db.escape(this.treeName)}, `,
`tree_height = ${db.escape(this.treeHeight)}`
`tree_height = ${db.escape(this.treeHeight)}, `,
`tree_channel_id = ${db.escape(this.treeChannelId)}, `,
`tree_message_id = ${db.escape(this.treeMessageId)}`
];
return queryParts.join('');
case "setCompareMessage":
queryParts = [
`INSERT INTO guild_info (`,
`guild_id, compare_channel_id, compare_message_id`,
`) VALUES (`,
`${db.escape(this.guildId)}, ${db.escape(this.compareChannelId)}, ${db.escape(this.compareMessageId)}`,
`) ON DUPLICATE KEY UPDATE compare_channel_id = ${db.escape(this.compareChannelId)}, compare_message_id = ${db.escape(this.compareMessageId)}`,
];
return queryParts.join('');
default:

View File

@ -69,7 +69,8 @@ module.exports = {
.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, row.notifications_enabled)
.setRoles(row.water_role_id, row.fruit_role_id);
.setRoles(row.water_role_id, row.fruit_role_id)
.setCompareMessage(row.compare_channel_id, row.compare_message_id);
db.end();
resolve(guildInfo);
});
@ -113,6 +114,7 @@ module.exports = {
.setLeaderboardMessage(row.leaderboard_message_id, row.leaderboard_channel_id)
.setReminders(row.water_message, row.fruit_message, row.reminder_channel_id, row.watch_channel_id, row.notifications_enabled)
.setRoles(row.water_role_id, row.fruit_role_id)
.setCompareMessage(row.compare_channel_id, row.compare_message_id)
);
}
@ -141,6 +143,7 @@ module.exports = {
return;
}
db.end();
console.log("Updated the database");
resolve();
});
});
@ -170,6 +173,7 @@ module.exports = {
return;
}
db.end();
console.log("Updated the database");
resolve({ "status": "Successfully set the guild information", "data": null });
});
});
@ -298,46 +302,5 @@ module.exports = {
resolve({ "status": "Successfully uploaded the leaderboard", "data": res });
});
});
},
get24hTree(guildId, treeName) {
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 select24hTreeQuery = `SELECT id, tree_name, tree_rank, tree_height, has_pin FROM leaderboard WHERE guild_id = ${db.escape(guildId)} AND tree_name = ${db.escape(treeName)} AND timestamp > date_sub(now(), interval 1 day) ORDER BY id ASC LIMIT 1`;
// TODO run the query and return a promise then process the results. resolve with { "status": , "data": leaderboard }
return new Promise((resolve, reject) => {
db.query(select24hTreeQuery, (err, res) => {
if (err) {
console.error(err);
db.end();
reject("Error fetching the historic 24hr tree height: " + err.message);
return;
}
let hist24hTree = {};
if (res.length > 0) {
hist24hTree = {
"treeName": res[0].tree_name,
"treeRank": res[0].tree_rank,
"treeHeight": res[0].tree_height,
"hasPin": res[0].has_pin
}
} else {
hist24hTree = {
}
}
db.end();
resolve({ "status": "Successfully fetched historic 24hr tree.", "data": hist24hTree });
});
});
}
};

View File

@ -2,7 +2,7 @@
// dotenv for handling environment variables
const dotenv = require('dotenv');
dotenv.config();
const isDev = process.env.isDev;
const isDev = process.env.DEBUG === "true";
const package = require('../package.json');
// filesystem
@ -275,9 +275,9 @@ const functions = {
});
},
async compare(interaction, guildInfo) {
async compare(guildInfo) {
try {
const getLeaderboardResponse = await dbfn.getLeaderboard(interaction.guildId);
const getLeaderboardResponse = await dbfn.getLeaderboard(guildInfo.guildId);
const leaderboard = getLeaderboardResponse.data; // [ { treeName: "Name", treeHeight: 1234.5, treeRank: 67 }, {...}, {...} ]
// Prepare the beginning of the comparison message
@ -494,7 +494,7 @@ const functions = {
isTree(message) {
if (message.embeds.length > 0) {
// Grab the description and title
const {description, title} = message.embeds[0].data;
const { description, title } = message.embeds[0].data;
// Make sure it's a tree message
if (description.includes("Your tree is")) {
// Grab the name
@ -509,21 +509,73 @@ const functions = {
treeName: treeName,
treeHeight: treeHeightFloat
};
} else {
return false;
}
} else {
return false;
}
},
// Checks if a message is a leaderboard message, and returns the entries if it is
isLeaderboard(message) {
if (message.embeds.length > 0) {
return message.embeds[0].data.title == "Tallest Trees";
// Grab the description and title from the embed
const { title, description } = message.embeds[0].data;
// Check that it's a Top Trees message
if (title == "Tallest Trees") {
// Break the description into an array of each line
const lines = description.split("\n");
const leaderboard = {
guildId: message.guildId,
entries: []
};
lines.forEach(line => {
// Skeleton entry:
const entry = {
treeRank: 0,
treeName: "",
treeHeight: 0,
hasPin: 0
}
// Break the line into parts separated by a hyphen -
const parts = line.split(" - ");
// Grab the rank
// Preset the indices to split the lines to get the data
const rankIndices = [parts[0].indexOf("#") + 1, parts[0].lastIndexOf("``")];
const nameIndices = [parts[1].indexOf("``") + 2, parts[1].lastIndexOf("``")];
const heightIndices = [0, parts[2].lastIndexOf("ft")];
// Set the data from the parts using the indices
entry.treeRank = parts[0].slice(...rankIndices);
entry.treeName = parts[1].slice(...nameIndices);
entry.treeHeight = parts[2].slice(...heightIndices);
// Go back and check for the first 3 as they use emojis instead of numbers, this will overwrite above
if (line.includes("🥇")) entry.treeRank = 1;
if (line.includes("🥈")) entry.treeRank = 2;
if (line.includes("🥉")) entry.treeRank = 3;
// Check for the pin showing ownership
if (line.includes("📍")) entry.hasPin = 1;
// Add the entry to the array
leaderboard.entries.push(entry);
});
return leaderboard;
} else {
return false;
}
} else {
return false;
}
},
async updateHandler(message) {
if (message.partial) {
message = await message.fetch();
message = await message.fetch().catch(e => {
throw e;
});
}
// Make sure the message is from Grow A Tree
if (message.author.id != strings.ids.growATree) return;
@ -532,24 +584,81 @@ const functions = {
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`);
if (isDev) console.log(`LU: ${message.guild.name}`);
let guildInfo;
let doDbUpdate = false;
if (message.client.guildInfos.has(message.guildId)) {
guildInfo = message.client.guildInfos.get(message.guildId);
guildInfo.setName(isTree.treeName)
.setHeight(isTree.treeHeight);
if ((guildInfo.leaderboardChannelId != message.channel.id) || (guildInfo.leaderboardMessageId != message.id)) {
guildInfo.setLeaderboardMessage(message.id, message.channel.id);
doDbUpdate = true;
}
} else {
guildInfo = new GuildInfo().setId(message.guildId)
.setName(isTree.treeName)
.setHeight(isTree.treeHeight);
.setLeaderboardMessage(message.id, message.channel.id);
doDbUpdate = true;
}
if (doDbUpdate) {
const query = guildInfo.queryBuilder("setLeaderboardMessage");
await dbfn.setGuildInfo(query);
}
await dbfn.uploadLeaderboard(isLeaderboard);
// Update the comparison message
// Check for valid message IDs
if (guildInfo.treeMessageId === "") throw strings.error.noTreeMessage;
if (guildInfo.leaderboardMessageId === "") throw strings.error.noLeaderboardMessage;
if (guildInfo.compareMessageId === "") throw strings.error.noCompareMessage;
// Fetch the messages
const { guild } = message;
// Tree
const treeChannel = await guild.channels.fetch(guildInfo.treeChannelId);
const treeMessage = await treeChannel.messages.fetch(guildInfo.treeMessageId);
// Leaderboard
const leaderboardChannel = await guild.channels.fetch(guildInfo.leaderboardChannelId);
const leaderboardMessage = await leaderboardChannel.messages.fetch(guildInfo.leaderboardMessageId);
// Comparison
const compareChannel = await guild.channels.fetch(guildInfo.compareChannelId);
const compareMessage = await compareChannel.messages.fetch(guildInfo.compareMessageId);
// Update the tree information
// Make sure we have a tree message and parse it
const isTree = this.isTree(treeMessage);
if (!isTree) throw "Tree message isn't actually a tree message!";
guildInfo.setTreeInfo(isTree.treeName, isTree.treeHeight);
// Grab the leaderboard
// Make sure it's a leaderboard and parse it
// const isLeaderboard = this.messages.isLeaderboard(leaderboardMessage);
// if (!isLeaderboard) throw "Leaderboard message isn't actually a leaderboard message!";
// Upload the leaderboard
// await dbfn.uploadLeaderboard(isLeaderboard);
// Build the string that shows the comparison // TODO Move the string building section to fn.builders?
const comparedRankings = await functions.rankings.compare(guildInfo);
const embed = functions.builders.comparisonEmbed(comparedRankings, guildInfo);
await compareMessage.edit(embed).catch(e => console.error(e));
} else if (isTree) {
// Check if the message is a tree
if (isDev) console.log(`TU: ${isTree.treeName}: ${isTree.treeHeight}ft`);
let guildInfo;
let doDbUpdate = false;
if (message.client.guildInfos.has(message.guildId)) {
guildInfo = message.client.guildInfos.get(message.guildId);
if ((guildInfo.treeName != isTree.treeName) || (guildInfo.treeHeight != isTree.treeHeight)) {
guildInfo.setTreeInfo(isTree.treeName, isTree.treeHeight, message.channel.id, message.id);
doDbUpdate = true;
}
} else {
guildInfo = new GuildInfo().setId(message.guildId)
.setTreeInfo(isTree.treeName, isTree.treeHeight, message.channel.id, message.id);
doDbUpdate = true;
}
if (doDbUpdate) {
const query = guildInfo.queryBuilder("setTreeInfo");
await dbfn.setGuildInfo(query);
}
}
}
},
buttonHandlers: {
async fruitPing(interaction) {
@ -652,23 +761,45 @@ const functions = {
}
},
async refresh(interaction) {
// 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;
// Parse the tree
await this.tree.parse(interaction, guildInfo);
// Parse the leaderboard
await this.rankings.parse(interaction, guildInfo);
// Build the string that shows the comparison // TODO Move the string building section to fn.builders?
const comparedRankings = await this.rankings.compare(interaction, guildInfo);
if (interaction.client.guildInfos.has(interaction.guildId)) {
let guildInfo = interaction.client.guildInfos.get(interaction.guildId);
// Check for valid message IDs
if (guildInfo.treeMessageId === "") throw strings.error.noTreeMessage;
if (guildInfo.leaderboardMessageId === "") throw strings.error.noLeaderboardMessage;
// Fetch the messages
const { guild } = interaction;
// Tree
const treeChannel = await guild.channels.fetch(guildInfo.treeChannelId);
const treeMessage = await treeChannel.messages.fetch(guildInfo.treeMessageId);
// Leaderboard
const leaderboardChannel = await guild.channels.fetch(guildInfo.leaderboardChannelId);
const leaderboardMessage = await leaderboardChannel.messages.fetch(guildInfo.leaderboardMessageId);
// Update the comparison message information
guildInfo.setCompareMessage(interaction.channel.id, interaction.message.id);
const query = guildInfo.queryBuilder("setCompareMessage");
await dbfn.setGuildInfo(query);
// Update the tree information
// Make sure we have a tree message and parse it
const isTree = this.messages.isTree(treeMessage);
if (!isTree) throw "Tree message isn't actually a tree message!";
guildInfo.setTreeInfo(isTree.treeName, isTree.treeHeight);
// Grab the leaderboard
// Make sure it's a leaderboard and parse it
const isLeaderboard = this.messages.isLeaderboard(leaderboardMessage);
if (!isLeaderboard) throw "Leaderboard message isn't actually a leaderboard message!";
// Upload the leaderboard
await dbfn.uploadLeaderboard(isLeaderboard);
// Build the string that shows the comparison // TODO Move the string building section to fn.builders?
const comparedRankings = await this.rankings.compare(guildInfo);
const embed = this.builders.comparisonEmbed(comparedRankings, guildInfo);
await interaction.update(embed).catch(e => console.error(e));
} else {
await interaction.update(this.builders.errorEmbed(findMessagesResponse.status));
throw "Guild doesn't exist in database!";
}
},
reset(interaction) {
@ -764,7 +895,7 @@ const functions = {
async sendWaterReminder(guildInfo, message, channelId, guild) {
const reminderChannel = await guild.channels.fetch(channelId);
const reminderEmbed = functions.builders.waterReminderEmbed(message, guildInfo);
console.log(`Water Relay: ${guild.name}: ${guildInfo.treeName}`);
if (isDev) console.log(`WR: ${guild.name}: ${guildInfo.treeName}`);
await reminderChannel.send(reminderEmbed).then(async m => {
if (!m.deletable) return;
await this.sleep(500).then(async () => {
@ -777,7 +908,7 @@ const functions = {
async sendFruitReminder(guildInfo, message, channelId, guild) {
const reminderChannel = await guild.channels.fetch(channelId);
const reminderEmbed = functions.builders.fruitReminderEmbed(message, guildInfo);
console.log(`Fruit Relay: ${guild.name}: ${guildInfo.treeName}`);
if (isDev) console.log(`FR: ${guild.name}: ${guildInfo.treeName}`);
await reminderChannel.send(reminderEmbed).then(async m => {
if (!m.deletable) return;
await this.sleep(500).then(async () => {

View File

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

View File

@ -1,6 +1,7 @@
const { SlashCommandBuilder, Guild } = require('discord.js');
const dbfn = require('../modules/dbfn.js');
const fn = require('../modules/functions.js');
const strings = require('../data/strings.json');
const { GuildInfo } = require('../modules/CustomClasses.js');
module.exports = {
@ -14,36 +15,33 @@ module.exports = {
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) {
let guildInfo = interaction.client.guildInfos.get(interaction.guildId);
// Parse the leaderboard message
await fn.rankings.parse(interaction, guildInfo);
// Handle a missing tree or leaderboard
let errors = [];
if (guildInfo.treeMessageId === "") {
errors.push(strings.error.noTreeMessage);
} else if (guildInfo.leaderboardMessageId === "") {
errors.push(strings.error.noLeaderboardMessage);
}
if (errors.length > 0) {
const embed = fn.builders.errorEmbed(`Unable to complete comparison, unable to find message(s):\n${errors.join("\n")}`);
await interaction.editReply(embed);
return;
}
// 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 comparedRankings = await fn.rankings.compare(guildInfo);
const embed = fn.builders.comparisonEmbed(comparedRankings, guildInfo);
await interaction.editReply(embed).catch(e => console.error(e));
await interaction.editReply(embed).then(async m => {
guildInfo.setCompareMessage(m.channel.id, m.id);
const query = guildInfo.queryBuilder("setCompareMessage");
await dbfn.setGuildInfo(query);
}).catch(e => console.error(e));
} 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(e => console.error(e));
} else {
await interaction.editReply(fn.builders.errorEmbed(findMessagesResponse.status));
}
let errors = [];
errors.push(strings.error.noTreeMessage);
errors.push(strings.error.noLeaderboardMessage);
const embed = fn.builders.errorEmbed(`Unable to complete comparison, unable to find message(s):\n${errors.join("\n")}`);
await interaction.editReply(embed);
}
} catch (err) {
interaction.editReply(fn.builders.errorEmbed(err)).catch(err => {