2023-01-18 00:35:24 +00:00
/* eslint-disable comma-dangle */
// dotenv for handling environment variables
const dotenv = require ( 'dotenv' ) ;
dotenv . config ( ) ;
const isDev = process . env . isDev ;
2023-01-25 08:07:41 +00:00
const package = require ( '../package.json' ) ;
2023-01-18 00:35:24 +00:00
// filesystem
const fs = require ( 'fs' ) ;
// Discord.js
const Discord = require ( 'discord.js' ) ;
2023-01-19 01:10:05 +00:00
const { EmbedBuilder , ActionRowBuilder , ButtonBuilder , ButtonStyle } = Discord ;
2023-01-18 00:35:24 +00:00
// Various imports from other files
2023-01-19 17:44:49 +00:00
const config = require ( '../data/config.json' ) ;
const strings = require ( '../data/strings.json' ) ;
2023-01-18 00:35:24 +00:00
const slashCommandFiles = fs . readdirSync ( './slash-commands/' ) . filter ( file => file . endsWith ( '.js' ) ) ;
2023-01-25 08:07:41 +00:00
const dbfn = require ( './dbfn.js' ) ;
2023-01-26 04:11:49 +00:00
const { finished } = require ( 'stream' ) ;
2023-01-25 08:07:41 +00:00
dbfn . createGuildTables ( ) . then ( res => {
console . log ( res . status ) ;
} ) . catch ( err => {
console . error ( err ) ;
} ) ;
2023-01-18 00:35:24 +00:00
const functions = {
// Functions for managing and creating Collections
collections : {
// Create the collection of slash commands
slashCommands ( client ) {
if ( ! client . slashCommands ) client . slashCommands = new Discord . Collection ( ) ;
client . slashCommands . clear ( ) ;
for ( const file of slashCommandFiles ) {
2023-01-19 17:44:49 +00:00
const slashCommand = require ( ` ../slash-commands/ ${ file } ` ) ;
2023-01-18 00:35:24 +00:00
if ( slashCommand . data != undefined ) {
client . slashCommands . set ( slashCommand . data . name , slashCommand ) ;
}
}
if ( isDev ) console . log ( 'Slash Commands Collection Built' ) ;
2023-01-19 01:10:05 +00:00
}
} ,
builders : {
2023-01-27 23:14:01 +00:00
async refreshAction ( guildId ) {
2023-01-19 01:10:05 +00:00
// Create the button to go in the Action Row
const refreshButton = new ButtonBuilder ( )
. setCustomId ( 'refresh' )
. setLabel ( 'Refresh' )
. setStyle ( ButtonStyle . Primary ) ;
2023-01-27 23:14:01 +00:00
const resetPingButton = new ButtonBuilder ( )
. setCustomId ( 'resetping' )
. setLabel ( 'Reset Ping' )
. setStyle ( ButtonStyle . Secondary ) ;
2023-01-19 01:10:05 +00:00
// Create the Action Row with the Button in it, to be sent with the Embed
2023-01-27 23:14:01 +00:00
let refreshActionRow = new ActionRowBuilder ( )
2023-01-19 01:10:05 +00:00
. addComponents (
refreshButton
) ;
2023-01-27 23:14:01 +00:00
const getGuildInfoResponse = await dbfn . getGuildInfo ( guildId ) ;
const guildInfo = getGuildInfoResponse . data ;
if ( guildInfo . reminderMessage != "" && guildInfo . reminderChannelId != "" ) {
refreshActionRow . addComponents ( resetPingButton ) ;
}
2023-01-19 01:10:05 +00:00
return refreshActionRow ;
2023-01-18 00:35:24 +00:00
} ,
2023-01-21 14:58:32 +00:00
comparisonEmbed ( content , refreshActionRow ) {
// Create the embed using the content passed to this function
const embed = new EmbedBuilder ( )
. setColor ( strings . embeds . color )
. setTitle ( 'Tree Growth Comparison' )
. setDescription ( content )
2023-01-25 08:07:41 +00:00
. setFooter ( { text : ` v ${ package . version } - ${ strings . embeds . footer } ` } ) ;
2023-01-21 14:58:32 +00:00
const messageContents = { embeds : [ embed ] , components : [ refreshActionRow ] } ;
return messageContents ;
} ,
2023-01-27 23:14:01 +00:00
reminderEmbed ( content , guildInfo ) {
// Create the embed using the content passed to this function
const embed = new EmbedBuilder ( )
. setColor ( strings . embeds . color )
. setTitle ( 'Water Reminder' )
2023-01-28 01:57:40 +00:00
. setDescription ( ` ${ content } \n [Click Here To Go To Your Tree](https://discord.com/channels/ ${ guildInfo . guildId } / ${ guildInfo . treeChannelId } / ${ guildInfo . treeMessageId } ) ` )
2023-01-27 23:14:01 +00:00
. setFooter ( { text : ` This message will self-destruct in 60 seconds... ` } ) ;
const messageContents = { embeds : [ embed ] } ;
return messageContents ;
} ,
2023-01-19 01:10:05 +00:00
helpEmbed ( content , private ) {
const embed = new EmbedBuilder ( )
. setColor ( strings . embeds . color )
2023-01-24 02:36:44 +00:00
. setTitle ( strings . help . title )
2023-01-19 01:10:05 +00:00
. setDescription ( content )
2023-01-25 08:07:41 +00:00
. setFooter ( { text : ` v ${ package . version } - ${ strings . embeds . footer } ` } ) ;
2023-01-19 01:10:05 +00:00
const privateBool = private == 'true' ;
const messageContents = { embeds : [ embed ] , ephemeral : privateBool } ;
return messageContents ;
} ,
errorEmbed ( content ) {
const embed = new EmbedBuilder ( )
. setColor ( 0xFF0000 )
. setTitle ( 'Error!' )
2023-01-24 00:37:51 +00:00
. setDescription ( "Error: " + content )
2023-01-25 08:07:41 +00:00
. setFooter ( { text : ` v ${ package . version } - ${ strings . embeds . footer } ` } ) ;
2023-01-19 01:10:05 +00:00
const messageContents = { embeds : [ embed ] , ephemeral : true } ;
return messageContents ;
2023-01-18 00:35:24 +00:00
} ,
2023-01-19 01:10:05 +00:00
embed ( content ) {
const embed = new EmbedBuilder ( )
. setColor ( 0x8888FF )
. setTitle ( 'Information' )
. setDescription ( content )
2023-01-25 08:07:41 +00:00
. setFooter ( { text : ` v ${ package . version } - ${ strings . embeds . footer } ` } ) ;
2023-01-19 01:10:05 +00:00
const messageContents = { embeds : [ embed ] , ephemeral : true } ;
return messageContents ;
}
2023-01-18 00:35:24 +00:00
} ,
2023-01-21 14:58:32 +00:00
rankings : {
parse ( interaction ) {
return new Promise ( ( resolve , reject ) => {
2023-01-25 08:07:41 +00:00
dbfn . getGuildInfo ( interaction . guildId ) . then ( res => {
const guildInfo = res . data ;
if ( guildInfo . guildId == "" ) {
reject ( strings . error . noGuild ) ;
return ;
}
if ( guildInfo . leaderboardMessageId != undefined ) {
interaction . guild . channels . fetch ( guildInfo . leaderboardChannelId ) . then ( c => {
c . messages . fetch ( guildInfo . leaderboardMessageId ) . then ( leaderboardMessage => {
if ( ( leaderboardMessage . embeds . length == 0 ) || ( leaderboardMessage . embeds [ 0 ] . data . title != 'Tallest Trees' ) ) {
reject ( "This doesn't appear to be a valid ``/top trees`` message." ) ;
return ;
}
let lines = leaderboardMessage . embeds [ 0 ] . data . description . split ( '\n' ) ;
let leaderboard = {
"guildId" : interaction . guildId ,
"entries" : [ ]
} ;
for ( let i = 0 ; i < 10 ; i ++ ) {
// Breakdown each line separating it on each -
let breakdown = lines [ i ] . split ( ' - ' ) ;
// Check if the first part, the ranking, has these emojis to detect first second and third place
if ( breakdown [ 0 ] . includes ( '🥇' ) ) {
breakdown [ 0 ] = '``#1 ``'
} else if ( breakdown [ 0 ] . includes ( '🥈' ) ) {
breakdown [ 0 ] = '``#2 ``'
} else if ( breakdown [ 0 ] . includes ( '🥉' ) ) {
breakdown [ 0 ] = '``#3 ``'
}
// Clean off the excess and get just the number from the rank, make sure it's an int not string
let trimmedRank = parseInt ( breakdown [ 0 ] . slice ( breakdown [ 0 ] . indexOf ( '#' ) + 1 , breakdown [ 0 ] . lastIndexOf ( '``' ) ) ) ;
// Clean off the excess and get just the tree name
let trimmedName = breakdown [ 1 ] . slice ( breakdown [ 1 ] . indexOf ( '``' ) + 2 ) ;
trimmedName = trimmedName . slice ( 0 , trimmedName . indexOf ( '``' ) ) ;
// Clean off the excess and get just the tree height, make sure it's a 1 decimal float
let trimmedHeight = parseFloat ( breakdown [ 2 ] . slice ( 0 , breakdown [ 2 ] . indexOf ( 'ft' ) ) ) . toFixed ( 1 ) ;
let isMyTree = false ;
let isMaybeMyTree = false ;
if ( breakdown [ 2 ] . includes ( '📍' ) ) isMyTree = true ;
if ( breakdown [ 1 ] . includes ( guildInfo . treeName ) ) maybeMyTree = true ;
// "entries": [ { "treeHeight": 12, "treeRank": 34, "treeName": "name" }, ] }
leaderboard . entries . push ( {
treeRank : trimmedRank ,
treeName : trimmedName ,
treeHeight : trimmedHeight ,
hasPin : isMyTree
} ) ;
2023-01-21 14:58:32 +00:00
}
2023-01-25 08:07:41 +00:00
dbfn . uploadLeaderboard ( leaderboard ) . then ( res => {
console . log ( res . status ) ;
resolve ( res . status ) ;
} ) . catch ( err => {
console . error ( err ) ;
reject ( err ) ;
return ;
2023-01-21 14:58:32 +00:00
} ) ;
2023-01-25 08:07:41 +00:00
} ) ;
2023-01-21 14:58:32 +00:00
} ) ;
2023-01-25 08:07:41 +00:00
} else {
reject ( "The leaderboardMessageId is undefined somehow" ) ;
return ;
}
} ) . catch ( err => {
reject ( err ) ;
2023-01-21 14:58:32 +00:00
return ;
2023-01-25 08:07:41 +00:00
} ) ;
2023-01-21 14:58:32 +00:00
} ) ;
} ,
2023-01-26 04:11:49 +00:00
async compare ( interaction ) {
try {
// fetch the guild's settings from the database
const guildInfoResponse = await dbfn . getGuildInfo ( interaction . guildId ) ;
const guildInfo = guildInfoResponse . data ; // { "guildId": "123","treeName": "name","treeHeight": 123,"treeMessageId": "123","treeChannelId": "123","leaderboardMessageId": "123","leaderboardChannelId": "123"};
// Get the most recent leaderboard listing from the database
const getLeaderboardResponse = await dbfn . getLeaderboard ( interaction . guildId ) ;
const leaderboard = getLeaderboardResponse . data ; // [ { treeName: "Name", treeHeight: 1234.5, treeRank: 67 }, {...}, {...} ]
// Prepare the beginning of the comparison message
let comparisonReplyString = ` Here \' s how your tree compares: \n Current Tree Height: ${ guildInfo . treeHeight } ft \n \n ` ;
// Iterate over the leaderboard entries, backwards
for ( let i = leaderboard . length - 1 ; i >= 0 ; i -- ) {
const leaderboardEntry = leaderboard [ i ] ;
// Setup the status indicator, default to blank, we'll change it later
2023-01-28 01:57:40 +00:00
let statusIndicator = "" ;
if ( ( leaderboardEntry . treeHeight % 1 ) . toFixed ( 1 ) > 0 ) statusIndicator += "``[💧]``" ;
2023-01-26 04:11:49 +00:00
// Get the data for this tree from 24 hours ago
2023-01-28 01:57:40 +00:00
// const get24hTreeResponse = await dbfn.get24hTree(interaction.guildId, leaderboardEntry.treeName);
// const dayAgoTree = get24hTreeResponse.data;
// const hist24hDifference = (leaderboardEntry.treeHeight - dayAgoTree.treeHeight).toFixed(1);
// statusIndicator += `+${hist24hDifference}ft|`
2023-01-26 04:11:49 +00:00
2023-01-27 02:30:41 +00:00
// Get the 24h watering time for this tree
2023-01-28 01:57:40 +00:00
// const totalWaterTime = await functions.timeToHeight(dayAgoTree.treeHeight, leaderboardEntry.treeHeight);
// statusIndicator += `${totalWaterTime}]\`\``;
2023-01-26 04:11:49 +00:00
// Determine if this tree is the guild's tree
if ( leaderboardEntry . hasPin ) {
2023-01-26 04:17:07 +00:00
comparisonReplyString += ` # ${ leaderboardEntry . treeRank } - This is your tree ` ;
2023-01-26 04:11:49 +00:00
} else { // If it's another guild's tree
// Calculate the current height difference
const currentHeightDifference = guildInfo . treeHeight - leaderboardEntry . treeHeight ;
if ( currentHeightDifference > 0 ) { // Guild Tree is taller than the leaderboard tree
2023-01-26 04:14:40 +00:00
comparisonReplyString += ` # ${ leaderboardEntry . treeRank } - ${ currentHeightDifference . toFixed ( 1 ) } ft taller ` ;
2023-01-26 04:11:49 +00:00
} else {
2023-01-26 04:14:40 +00:00
comparisonReplyString += ` # ${ leaderboardEntry . treeRank } - ${ Math . abs ( currentHeightDifference ) . toFixed ( 1 ) } ft shorter ` ;
2023-01-26 04:11:49 +00:00
}
}
2023-01-26 04:17:07 +00:00
// Build a string using the current leaderboard entry and the historic entry from 24 hours ago
2023-01-28 01:57:40 +00:00
comparisonReplyString += ` ${ statusIndicator } \n ` ;
2023-01-27 23:14:01 +00:00
// if (process.env.isDev == 'true') comparisonReplyString += `Current Height: ${leaderboardEntry.treeHeight} 24h Ago Height: ${dayAgoTree.treeHeight}\n`;
2023-01-26 04:11:49 +00:00
}
return comparisonReplyString ;
} catch ( err ) {
console . error ( err ) ;
return 'An error occurred while comparing the trees.' ;
}
2023-01-21 14:58:32 +00:00
}
} ,
tree : {
parse ( interaction ) {
let input ;
return new Promise ( ( resolve , reject ) => {
2023-01-25 08:07:41 +00:00
dbfn . getGuildInfo ( interaction . guildId ) . then ( res => {
const guildInfo = res . data ;
guildInfo . guildId = interaction . guildId ;
if ( guildInfo == undefined ) {
reject ( ` The guild entry hasn't been created yet. [ ${ interaction . guildId || interaction . commandGuildId } ] ` ) ;
return ;
}
if ( guildInfo . treeMessageId != "Run /setup where your tree is." ) {
interaction . guild . channels . fetch ( guildInfo . treeChannelId ) . then ( c => {
c . messages . fetch ( guildInfo . treeMessageId ) . then ( m => {
if ( ( m . embeds . length == 0 ) || ! ( m . embeds [ 0 ] . data . description . includes ( 'Your tree is' ) ) ) {
reject ( "This doesn't appear to be a valid ``/tree`` message." ) ;
return ;
}
input = m . embeds [ 0 ] . data . description ;
let treeName = m . embeds [ 0 ] . data . title ;
let lines = input . split ( '\n' ) ;
guildInfo . treeHeight = parseFloat ( lines [ 0 ] . slice ( lines [ 0 ] . indexOf ( 'is' ) + 3 , lines [ 0 ] . indexOf ( 'ft' ) ) ) . toFixed ( 1 ) ;
guildInfo . treeName = treeName ;
dbfn . setTreeInfo ( guildInfo ) . then ( res => {
resolve ( "The reference tree message has been saved/updated." ) ;
} ) ;
} ) ;
} )
} else {
console . error ( 'treeMessageId undefined' ) ;
reject ( "There was an unknown error while setting the tree message." ) ;
return ;
}
} ) . catch ( err => {
reject ( err ) ;
console . error ( err ) ;
2023-01-21 14:58:32 +00:00
return ;
2023-01-25 08:07:41 +00:00
} ) ;
2023-01-21 14:58:32 +00:00
} ) ;
}
} ,
2023-01-19 01:10:05 +00:00
refresh ( interaction ) {
functions . rankings . parse ( interaction ) . then ( r1 => {
functions . tree . parse ( interaction ) . then ( r2 => {
2023-01-27 23:14:01 +00:00
functions . rankings . compare ( interaction ) . then ( async res => {
const refreshActionRow = await functions . builders . refreshAction ( interaction . guildId ) ;
const embed = functions . builders . comparisonEmbed ( res , refreshActionRow )
2023-01-25 08:07:41 +00:00
interaction . update ( embed ) ;
} ) . catch ( err => {
console . error ( err ) ;
} ) ;
2023-01-19 01:10:05 +00:00
} ) . catch ( e => {
2023-01-25 08:07:41 +00:00
console . error ( e ) ;
2023-01-19 01:10:05 +00:00
interaction . reply ( functions . builders . errorEmbed ( e ) ) ;
} ) ;
} ) . catch ( e => {
2023-01-25 08:07:41 +00:00
console . error ( e ) ;
2023-01-19 01:10:05 +00:00
interaction . reply ( functions . builders . errorEmbed ( e ) ) ;
} ) ;
2023-01-21 14:58:32 +00:00
} ,
reset ( guildId ) {
2023-01-25 08:07:41 +00:00
return new Promise ( ( resolve , reject ) => {
dbfn . deleteGuildInfo ( guildId ) . then ( res => {
resolve ( res ) ;
} ) . catch ( err => {
console . error ( err ) ;
reject ( err ) ;
return ;
} ) ;
} ) ;
2023-01-21 14:58:32 +00:00
} ,
getInfo ( guildId ) {
2023-01-25 08:07:41 +00:00
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 ;
} )
} ) ;
2023-01-23 23:14:57 +00:00
} ,
getWaterTime ( size ) {
2023-01-27 02:30:41 +00:00
return Math . floor ( Math . pow ( size * 0.07 + 5 , 1.1 ) ) ; // Seconds
2023-01-25 09:53:49 +00:00
} ,
2023-01-27 02:30:41 +00:00
timeToHeight ( beginHeight , destHeight ) {
2023-01-25 09:53:49 +00:00
return new Promise ( ( resolve , reject ) => {
2023-01-27 02:30:41 +00:00
let time = 0 ;
for ( let i = beginHeight ; i < destHeight ; i ++ ) {
const waterTime = parseFloat ( functions . getWaterTime ( i ) ) ;
// console.log("Height: " + i + "Time: " + waterTime);
time += waterTime ;
}
// 60 secs in min
// 3600 secs in hr
// 86400 sec in day
let units = " secs" ;
if ( 60 < time && time <= 3600 ) { // Minutes
time = parseFloat ( time / 60 ) . toFixed ( 1 ) ;
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 ) ;
2023-01-25 09:53:49 +00:00
} ) ;
2023-01-27 03:31:48 +00:00
} ,
2023-01-27 23:14:01 +00:00
sleep ( ms ) {
return new Promise ( resolve => setTimeout ( resolve , ms ) ) ;
} ,
async sendReminder ( guildInfo , guild ) {
const { guildId , reminderChannelId , reminderMessage } = guildInfo ;
const reminderChannel = await guild . channels . fetch ( reminderChannelId ) ;
const reminderEmbed = functions . builders . reminderEmbed ( reminderMessage , guildInfo ) ;
reminderChannel . send ( reminderEmbed ) . then ( async m => {
await dbfn . setRemindedStatus ( guildId , 1 ) ;
if ( m . deletable ) {
setTimeout ( function ( ) {
m . delete ( ) ;
} , 60000 ) ;
}
} ) . 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
// TODO This is hard coded for the dev server, need to change it to lookup each server and iterate over them
// Would also be helpful to have an opt-in or opt-out ability for water checks
try {
const getOptedInGuildsResponse = await dbfn . getOptedInGuilds ( ) ;
if ( getOptedInGuildsResponse . status != "No servers have opted in yet" ) {
const guilds = getOptedInGuildsResponse . data ;
guilds . forEach ( async guildInfo => {
const { guildId , treeChannelId , treeMessageId , remindedStatus } = guildInfo ;
if ( remindedStatus == 0 ) {
const guild = await client . guilds . fetch ( guildId ) ;
const treeChannel = await guild . channels . fetch ( treeChannelId ) ;
const treeMessage = await treeChannel . messages . fetch ( treeMessageId ) ;
const readyToWater = treeMessage . embeds [ 0 ] . description . includes ( 'Ready to be watered' ) ;
if ( readyToWater ) {
2023-01-28 01:57:40 +00:00
// console.log("Ready to water");
2023-01-27 23:14:01 +00:00
this . sendReminder ( guildInfo , guild ) ;
this . sleep ( 5000 ) . then ( ( ) => {
this . checkReady ( client ) ;
} ) ;
} else {
2023-01-28 01:57:40 +00:00
// console.log("Not ready to water");
2023-01-27 23:14:01 +00:00
this . sleep ( 5000 ) . then ( ( ) => {
this . checkReady ( client ) ;
} ) ;
}
}
} ) ;
} else {
// console.log(getOptedInGuildsResponse.status);
this . sleep ( 5000 ) . then ( ( ) => {
this . checkReady ( client ) ;
} ) ;
}
} catch ( err ) {
console . error ( err ) ;
this . sleep ( 30000 ) . then ( ( ) => {
this . checkReady ( client ) ;
2023-01-27 03:31:48 +00:00
} ) ;
2023-01-27 23:14:01 +00:00
}
} ,
resetPing ( interaction ) {
dbfn . setRemindedStatus ( interaction . guildId , 0 ) ;
2023-01-19 01:10:05 +00:00
}
2023-01-18 00:35:24 +00:00
} ;
module . exports = functions ;