2023-01-18 00:35:24 +00:00
/* eslint-disable comma-dangle */
// dotenv for handling environment variables
const dotenv = require ( 'dotenv' ) ;
dotenv . config ( ) ;
2023-03-11 20:12:34 +00:00
const isDev = process . env . DEBUG === "true" ;
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-02-20 01:42:14 +00:00
const { GuildInfo } = require ( './CustomClasses' ) ;
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-05-16 17:24:42 +00:00
const dotCommandFiles = fs . readdirSync ( './dot-commands/' ) . filter ( file => file . endsWith ( '.js' ) ) ;
2023-01-25 08:07:41 +00:00
const dbfn = require ( './dbfn.js' ) ;
2023-02-01 03:51:10 +00:00
const { finished } = require ( 'stream' ) ;
2023-08-11 02:13:08 +00:00
const https = require ( 'https' ) ;
2023-01-25 08:07:41 +00:00
2023-01-18 00:35:24 +00:00
const functions = {
// Functions for managing and creating Collections
2023-02-20 01:42:14 +00:00
collectionBuilders : {
2023-01-18 00:35:24 +00:00
// 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-02-20 01:42:14 +00:00
} ,
2023-05-16 17:24:42 +00:00
dotCommands ( client ) {
// If the dotCommands collection doesn't exist already, create it
if ( ! client . dotCommands ) client . dotCommands = new Discord . Collection ( ) ;
// Empty the dotcommands collection
client . dotCommands . clear ( ) ;
// Iterate over each file within ./dot-commands that ends with .js
for ( const file of dotCommandFiles ) {
// Pull the file into a temporary variable
const dotCommand = require ( ` ../dot-commands/ ${ file } ` ) ;
// Create a new entry in the collection with the key of the command name, value of the command itself
client . dotCommands . set ( dotCommand . name , dotCommand ) ;
// Old code from NodBot to implement aliases for dot commands. Unused as of 5/16/23
// if (Array.isArray(dotCommand.alias)) {
// dotCommand.alias.forEach(element => {
// client.dotCommands.set(element, dotCommand);
// });
// } else if (dotCommand.alias != undefined) {
// client.dotCommands.set(dotCommand.alias, dotCommand);
// }
}
if ( isDev ) console . log ( 'Dot Commands Collection Built' ) ;
} ,
setvalidCommands ( client ) {
// Iterate over every entry in the dotCommands collection
for ( const entry of client . dotCommands . map ( command => command ) ) {
// Add the command's name to the valid commands list for validation later
config . validCommands . push ( entry . name ) ;
// Old code from NodBot to implement aliases for dot commands. Unused as of 5/16/23
// if (Array.isArray(entry.alias)) {
// entry.alias.forEach(element => {
// config.validCommands.push(element);
// });
// } else if (entry.alias != undefined) {
// config.validCommands.push(entry.alias);
// }
}
if ( isDev ) console . log ( ` Valid Commands Added to Config \n ${ config . validCommands } ` ) ;
} ,
2023-02-20 01:42:14 +00:00
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' ;
2023-02-20 19:07:38 +00:00
} ,
async messageCollectors ( client ) {
// Create an empty collection for MessageCollectors
if ( ! client . messageCollectors ) client . messageCollectors = new Discord . Collection ( ) ;
client . messageCollectors . clear ( ) ;
// Get all of the guild infos from the client
const { guildInfos , messageCollectors } = client ;
// Iterate over each guild info
await guildInfos . forEach ( async guildInfo => {
2023-06-03 20:14:00 +00:00
await functions . collectors . create ( client , guildInfo ) . catch ( async e => {
if ( e === "ERRNOGUILD" ) {
guildInfo . setReminders ( undefined , undefined , undefined , undefined , false ) ;
const query = guildInfo . queryBuilder ( "setReminders" ) ;
await dbfn . setGuildInfo ( query ) ;
await functions . collectionBuilders . guildInfos ( client ) ;
console . log ( "Disabled notification relay for a guild I'm no longer in: " + guildInfo . guildId ) ;
} else {
throw e ;
}
} ) ;
2023-02-20 19:07:38 +00:00
} ) ;
2023-01-19 01:10:05 +00:00
}
} ,
builders : {
2023-02-01 03:51:10 +00:00
actionRows : {
reminderActionRow ( ) {
const deleteButton = new ButtonBuilder ( )
. setCustomId ( 'deleteping' )
. setEmoji ( '♻️' )
. setStyle ( ButtonStyle . Danger ) ;
const actionRow = new ActionRowBuilder ( )
. addComponents ( deleteButton ) ;
return actionRow ;
} ,
comparisonActionRow ( guildInfo ) {
// console.log(guildInfo);
// Create the button to go in the Action Row
const refreshButton = new ButtonBuilder ( )
. setCustomId ( 'refresh' )
. setEmoji ( '🔄' )
. setStyle ( ButtonStyle . Primary ) ;
// Create the Action Row with the Button in it, to be sent with the Embed
let refreshActionRow = new ActionRowBuilder ( )
. addComponents (
refreshButton
) ;
return refreshActionRow ;
2023-02-20 01:42:14 +00:00
} ,
treeRoleMenu ( fruit ) {
let actionRow = new ActionRowBuilder ( ) . addComponents ( this . buttons . waterPing ( ) ) ;
if ( fruit ) {
actionRow . addComponents ( this . buttons . fruitPing ( ) ) ;
}
return actionRow ;
} ,
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 ) ;
}
2023-02-01 03:51:10 +00:00
}
} ,
2023-02-20 01:42:14 +00:00
embeds : {
treeRoleMenu ( guildInfo ) {
const actionRow = functions . builders . actionRows . treeRoleMenu ( guildInfo . fruitRoleId == "" ? false : true ) ;
let tempStrings = strings . embeds . treeRoleMenu ;
let description = tempStrings [ 0 ] + tempStrings [ 1 ] + ` <@& ${ guildInfo . waterRoleId } > ` + tempStrings [ 2 ] ;
if ( guildInfo . fruitRoleId != "" ) {
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 ] } ;
2023-02-26 21:11:05 +00:00
} ,
information ( content , fields ) {
const embed = new EmbedBuilder ( )
. setColor ( strings . embeds . color )
. setTitle ( 'Information' )
. setDescription ( content )
. setFooter ( { text : ` v ${ package . version } - ${ strings . embeds . footer } ` } ) ;
if ( fields ) embed . addFields ( fields ) ;
const messageContents = { embeds : [ embed ] , ephemeral : true } ;
return messageContents ;
2023-02-01 03:51:10 +00:00
}
2023-01-18 00:35:24 +00:00
} ,
2023-02-01 03:51:10 +00:00
comparisonEmbed ( content , guildInfo ) {
2023-01-21 14:58:32 +00:00
// Create the embed using the content passed to this function
const embed = new EmbedBuilder ( )
. setColor ( strings . embeds . color )
2023-02-01 03:51:10 +00:00
. setTitle ( 'Tallest Trees Comparison' )
2023-01-21 14:58:32 +00:00
. setDescription ( content )
2023-02-01 03:51:10 +00:00
. setFooter ( { text : ` v ${ package . version } - ${ strings . embeds . footer } ` } ) ;
const messageContents = { embeds : [ embed ] , components : [ this . actionRows . comparisonActionRow ( guildInfo ) ] } ;
return messageContents ;
} ,
2023-02-20 01:42:14 +00:00
waterReminderEmbed ( content , guildInfo ) {
2023-02-01 03:51:10 +00:00
// Create the embed using the content passed to this function
const embed = new EmbedBuilder ( )
2023-02-20 01:42:14 +00:00
. setColor ( strings . embeds . waterColor )
. setTitle ( strings . embeds . waterTitle )
. 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 ( ) ] } ;
return messageContents ;
} ,
fruitReminderEmbed ( content , guildInfo ) {
// Create the embed using the content passed to this function
const embed = new EmbedBuilder ( )
. setColor ( strings . embeds . fruitColor )
. setTitle ( strings . embeds . fruitTitle )
2023-02-01 03:51:10 +00:00
. 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 ( ) ] } ;
2023-01-21 14:58:32 +00:00
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 ;
} ,
2023-06-10 19:03:00 +00:00
aboutEmbed ( private ) {
const embed = new EmbedBuilder ( )
. setColor ( strings . embeds . color )
. setTitle ( strings . help . aboutTitle )
. setDescription ( ` ${ strings . help . info } \n \n ${ strings . help . longDescription } ` )
. setFooter ( { text : ` v ${ package . version } - ${ strings . embeds . footer } ` } ) ;
embed . addFields ( [
{ name : strings . help . topggTitle , value : strings . help . topggBody }
] ) ;
const privateBool = private == 'true' ;
const messageContents = { embeds : [ embed ] , ephemeral : privateBool } ;
return messageContents ;
} ,
2023-01-19 01:10:05 +00:00
errorEmbed ( content ) {
const embed = new EmbedBuilder ( )
. setColor ( 0xFF0000 )
2023-02-20 01:42:14 +00:00
. setTitle ( strings . embeds . errorTitle )
. setDescription ( ` ${ strings . embeds . errorPrefix } \n ${ 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-05-16 17:24:42 +00:00
dotCommands : {
getCommandData ( message ) {
const commandData = { } ;
// Split the message content at the final instance of a period
const finalPeriod = message . content . lastIndexOf ( '.' ) ;
2023-06-03 18:58:42 +00:00
// if(isDev) console.log(message.content);
2023-05-16 17:24:42 +00:00
// If the final period is the last character, or doesn't exist
if ( finalPeriod < 0 ) {
2023-06-03 18:58:42 +00:00
// if (isDev) console.log(finalPeriod);
2023-05-16 17:24:42 +00:00
commandData . isCommand = false ;
return commandData ;
}
commandData . isCommand = true ;
// Get the first part of the message, everything leading up to the final period
2023-06-05 21:24:05 +00:00
commandData . args = message . content . slice ( 0 , finalPeriod ) ;
2023-05-16 17:24:42 +00:00
// Get the last part of the message, everything after the final period
2023-06-03 19:40:12 +00:00
commandData . command = message . content . slice ( finalPeriod ) . replace ( '.' , '' ) . toLowerCase ( ) ;
2023-05-16 17:24:42 +00:00
commandData . author = ` ${ message . author . username } # ${ message . author . discriminator } ` ;
return this . checkCommand ( commandData ) ;
} ,
checkCommand ( commandData ) {
if ( commandData . isCommand ) {
2023-05-16 17:30:57 +00:00
const validCommands = require ( '../data/config.json' ) . validCommands ;
2023-05-16 17:24:42 +00:00
commandData . isValid = validCommands . includes ( commandData . command ) ;
// Add exceptions for messages that contain only a link
if ( commandData . args . startsWith ( 'http' ) ) commandData . isValid = false ;
}
else {
commandData . isValid = false ;
console . error ( 'Somehow a non-command made it to checkCommands()' ) ;
}
return commandData ;
2023-06-03 19:40:12 +00:00
} ,
checkPermissions ( type , userId ) {
if ( type === "devTeam" ) {
const { devTeamIds } = config ;
let matchFound = false ;
for ( let i = 0 ; i < devTeamIds . length ; i ++ ) {
if ( userId === devTeamIds [ i ] ) matchFound = true ;
}
return matchFound ;
} else if ( type === "owner" ) {
return config . ownerId === userId ;
2023-06-06 21:57:14 +00:00
} else if ( type === "everyone" ) {
return true ;
2023-06-03 19:40:12 +00:00
}
2023-05-16 17:24:42 +00:00
}
} ,
2023-01-21 14:58:32 +00:00
rankings : {
2023-02-01 03:51:10 +00:00
parse ( interaction , guildInfo ) {
return new Promise ( ( resolve , reject ) => {
if ( guildInfo . guildId == "" ) {
reject ( strings . error . noGuild ) ;
return ;
}
if ( guildInfo . leaderboardMessageId != undefined ) {
interaction . guild . channels . fetch ( guildInfo . leaderboardChannelId ) . then ( c => {
c . messages . fetch ( guildInfo . leaderboardMessageId ) . then ( leaderboardMessage => {
if ( ( leaderboardMessage . embeds . length == 0 ) || ( leaderboardMessage . embeds [ 0 ] . data . title != 'Tallest Trees' ) ) {
reject ( "This doesn't appear to be a valid ``/top trees`` message." ) ;
return ;
}
let lines = leaderboardMessage . embeds [ 0 ] . data . description . split ( '\n' ) ;
let leaderboard = {
"guildId" : interaction . guildId ,
"entries" : [ ]
} ;
for ( let i = 0 ; i < 10 ; i ++ ) {
// Breakdown each line separating it on each -
let breakdown = lines [ i ] . split ( ' - ' ) ;
// Check if the first part, the ranking, has these emojis to detect first second and third place
if ( breakdown [ 0 ] . includes ( '🥇' ) ) {
breakdown [ 0 ] = '``#1 ``'
} else if ( breakdown [ 0 ] . includes ( '🥈' ) ) {
breakdown [ 0 ] = '``#2 ``'
} else if ( breakdown [ 0 ] . includes ( '🥉' ) ) {
breakdown [ 0 ] = '``#3 ``'
2023-01-25 08:07:41 +00:00
}
2023-02-01 03:51:10 +00:00
// 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 ( '``' ) ) ) ;
2023-01-25 08:07:41 +00:00
2023-02-01 03:51:10 +00:00
// 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-02-01 03:51:10 +00:00
}
dbfn . uploadLeaderboard ( leaderboard ) . then ( res => {
resolve ( res . status ) ;
} ) . catch ( err => {
console . error ( err ) ;
reject ( err ) ;
return ;
2023-01-25 08:07:41 +00:00
} ) ;
2023-02-01 03:51:10 +00:00
} ) . catch ( err => {
reject ( strings . status . missingLeaderboardMessage ) ;
2023-05-08 13:43:41 +00:00
// console.error(err);
2023-02-01 03:51:10 +00:00
return ;
2023-01-21 14:58:32 +00:00
} ) ;
2023-02-01 03:51:10 +00:00
} ) . catch ( err => {
reject ( strings . status . missingLeaderboardChannel ) ;
console . error ( err ) ;
2023-01-25 08:07:41 +00:00
return ;
2023-02-01 03:51:10 +00:00
} ) ;
} else {
reject ( "The leaderboardMessageId is undefined somehow" ) ;
2023-01-21 14:58:32 +00:00
return ;
2023-02-01 03:51:10 +00:00
}
2023-01-21 14:58:32 +00:00
} ) ;
2023-02-01 03:51:10 +00:00
2023-01-21 14:58:32 +00:00
} ,
2023-03-11 20:12:34 +00:00
async compare ( guildInfo ) {
2023-02-01 03:51:10 +00:00
try {
2023-03-11 20:12:34 +00:00
const getLeaderboardResponse = await dbfn . getLeaderboard ( guildInfo . guildId ) ;
2023-02-01 03:51:10 +00:00
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
let statusIndicator = "" ;
if ( ( leaderboardEntry . treeHeight % 1 ) . toFixed ( 1 ) > 0 ) statusIndicator += "``[💧]``" ;
// Get the data for this tree from 24 hours ago
// const get24hTreeResponse = await dbfn.get24hTree(interaction.guildId, leaderboardEntry.treeName);
// const dayAgoTree = get24hTreeResponse.data;
// const hist24hDifference = (leaderboardEntry.treeHeight - dayAgoTree.treeHeight).toFixed(1);
// statusIndicator += `+${hist24hDifference}ft|`
// Get the 24h watering time for this tree
// const totalWaterTime = await functions.timeToHeight(dayAgoTree.treeHeight, leaderboardEntry.treeHeight);
// statusIndicator += `${totalWaterTime}]\`\``;
// Determine if this tree is the guild's tree
if ( leaderboardEntry . hasPin ) {
comparisonReplyString += ` # ${ leaderboardEntry . treeRank } - This is your tree ` ;
} 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
comparisonReplyString += ` # ${ leaderboardEntry . treeRank } - ${ currentHeightDifference . toFixed ( 1 ) } ft taller ` ;
} else {
comparisonReplyString += ` # ${ leaderboardEntry . treeRank } - ${ Math . abs ( currentHeightDifference ) . toFixed ( 1 ) } ft shorter ` ;
}
}
// Build a string using the current leaderboard entry and the historic entry from 24 hours ago
comparisonReplyString += ` ${ statusIndicator } \n ` ;
// if (process.env.isDev == 'true') comparisonReplyString += `Current Height: ${leaderboardEntry.treeHeight} 24h Ago Height: ${dayAgoTree.treeHeight}\n`;
}
return comparisonReplyString ;
} catch ( err ) {
throw err ;
}
}
} ,
tree : {
parse ( interaction , guildInfo ) {
2023-01-25 08:07:41 +00:00
return new Promise ( ( resolve , reject ) => {
2023-02-01 03:51:10 +00:00
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 ;
2023-01-25 08:07:41 +00:00
}
2023-03-11 02:16:32 +00:00
let input ;
2023-02-01 03:51:10 +00:00
input = m . embeds [ 0 ] . data . description ;
let treeName = m . embeds [ 0 ] . data . title ;
let lines = input . split ( '\n' ) ;
guildInfo . treeHeight = parseFloat ( lines [ 0 ] . slice ( lines [ 0 ] . indexOf ( 'is' ) + 3 , lines [ 0 ] . indexOf ( 'ft' ) ) ) . toFixed ( 1 ) ;
guildInfo . treeName = treeName ;
dbfn . setTreeInfo ( guildInfo ) . then ( res => {
resolve ( "The reference tree message has been saved/updated." ) ;
} ) ;
} ) . catch ( err => {
reject ( strings . status . missingTreeMessage ) ;
2023-05-08 13:43:41 +00:00
// console.error(err);
2023-02-01 03:51:10 +00:00
return ;
2023-01-25 08:07:41 +00:00
} ) ;
} ) . catch ( err => {
2023-02-01 03:51:10 +00:00
reject ( strings . status . missingTreeChannel ) ;
2023-06-17 00:01:29 +00:00
// console.error(err);
2023-02-01 03:51:10 +00:00
return ;
2023-01-25 08:07:41 +00:00
} ) ;
2023-02-01 03:51:10 +00:00
} else {
console . error ( 'treeMessageId undefined' ) ;
reject ( "There was an unknown error while setting the tree message." ) ;
2023-01-25 08:07:41 +00:00
return ;
2023-02-01 03:51:10 +00:00
}
2023-01-25 08:07:41 +00:00
} ) ;
2023-01-21 14:58:32 +00:00
}
} ,
2023-02-01 03:51:10 +00:00
messages : {
async find ( interaction , guildInfo ) {
try {
let response = { status : "Incomplete" , data : undefined , code : 0 } ;
// If the tree channel ID and leaderboard channel ID are both set
if ( guildInfo . treeChannelId != "" || guildInfo . leaderboardChannelId != "" ) {
// If one us unset, we'll set it to the current channel just to check
if ( guildInfo . treeChannelId == "" ) {
guildInfo . treeChannelId = ` ${ guildInfo . leaderboardChannelId } ` ;
} else if ( guildInfo . leaderboardChannelId == "" ) {
guildInfo . leaderboardChannelId = ` ${ guildInfo . treeChannelId } ` ;
2023-01-25 08:07:41 +00:00
}
2023-02-01 03:51:10 +00:00
let treeFound , leaderboardFound = false ;
// If these values have already been set in the database, we don't want to report that they weren't found
// they'll still get updated later if applicable.
treeFound = ( guildInfo . treeMessageId != "" ) ;
leaderboardFound = ( guildInfo . leaderboardMessageId != "" ) ;
// If the Tree and Leaderboard messages are in the same channel
if ( guildInfo . treeChannelId == guildInfo . leaderboardChannelId ) {
// Fetch the tree channel so we can get the most recent messages
const treeChannel = await interaction . guild . channels . fetch ( guildInfo . treeChannelId ) ;
// Fetch the last 20 messages in the channel
const treeChannelMessageCollection = await treeChannel . messages . fetch ( { limit : 20 } ) ;
// Create a basic array of [Message, Message, ...] from the Collection
const treeChannelMessages = Array . from ( treeChannelMessageCollection . values ( ) ) ;
// Iterate over the Messages in reverse order (newest messages first)
for ( let i = treeChannelMessages . length - 1 ; i >= 0 ; i -- ) {
let treeChannelMessage = treeChannelMessages [ i ] ;
if ( this . isTree ( treeChannelMessage ) ) { // This is a tree message
// Set the tree message ID
guildInfo . treeMessageId = treeChannelMessage . id ;
// Parse out the tree name
input = treeChannelMessage . embeds [ 0 ] . data . description ;
let treeName = treeChannelMessage . embeds [ 0 ] . data . title ;
// And tree height
2023-01-25 08:07:41 +00:00
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 ;
2023-02-01 03:51:10 +00:00
// Upload the found messages to the database
await dbfn . setTreeInfo ( guildInfo ) ;
// Let the end of the function know we found a tree message and successfully uploaded it
treeFound = true ;
} else if ( this . isLeaderboard ( treeChannelMessage ) ) { // This is a leaderboard message
// Set the leaderboard message ID
guildInfo . leaderboardMessageId = treeChannelMessage . id ;
// Upload it to the database
await dbfn . setLeaderboardInfo ( guildInfo ) ;
// Let the end of the function know we found a leaderboard message and successfully uploaded it
leaderboardFound = true ;
}
}
} else { // If the tree and leaderboard are in different channels
// Fetch the tree channel so we can get the most recent messages
const treeChannel = await interaction . guild . channels . fetch ( guildInfo . treeChannelId ) ;
// Fetch the last 20 messages in the tree channel
const treeChannelMessageCollection = await treeChannel . messages . fetch ( { limit : 20 } ) ;
// Create an Array like [Message, Message, ...] from the Collection
const treeChannelMessages = Array . from ( treeChannelMessageCollection . values ( ) ) ;
// Iterate over the Array of Messages in reverse order (newest messages first)
for ( let i = treeChannelMessages . length - 1 ; i >= 0 ; i -- ) {
let treeChannelMessage = treeChannelMessages [ i ] ;
if ( this . isTree ( treeChannelMessage ) ) { // This is a tree message
// Set the tree message ID
guildInfo . treeMessageId = treeChannelMessage . id ;
// Parse out the tree name
input = treeChannelMessage . embeds [ 0 ] . data . description ;
let treeName = treeChannelMessage . embeds [ 0 ] . data . title ;
// And tree height
let lines = input . split ( '\n' ) ;
guildInfo . treeHeight = parseFloat ( lines [ 0 ] . slice ( lines [ 0 ] . indexOf ( 'is' ) + 3 , lines [ 0 ] . indexOf ( 'ft' ) ) ) . toFixed ( 1 ) ;
guildInfo . treeName = treeName ;
// Upload the found messages to the database
await dbfn . setTreeInfo ( guildInfo ) ;
// Let the end of the function know we found a tree message and successfully uploaded it
treeFound = true ;
}
}
// Fetch the tree channel so we can get the most recent messages
const leaderboardChannel = await interaction . guild . channels . fetch ( guildInfo . leaderboardChannelId ) ;
// Fetch the last 20 messages in the leaderboard channel
const leaderboardChannelMessageCollection = await leaderboardChannel . messages . fetch ( { limit : 20 } ) ;
// Create an Array like [Message, Message, ...] from the Collection
const leaderboardChannelMessages = Array . from ( leaderboardChannelMessageCollection . values ( ) ) ;
// Iterate over the Array of Messages in reverse order (newest messages first)
for ( let i = leaderboardChannelMessages . length - 1 ; i >= 0 ; i -- ) {
let leaderboardChannelMessage = leaderboardChannelMessages [ i ] ;
if ( this . isLeaderboard ( leaderboardChannelMessage ) ) { // This is a leaderboard message
// Set the leaderboard message ID
guildInfo . leaderboardMessageId = leaderboardChannelMessage . id ;
// Upload it to the database
await dbfn . setLeaderboardInfo ( guildInfo ) ;
// Let the end of the function know we've found a leaderboard
leaderboardFound = true ;
}
}
2023-01-25 08:07:41 +00:00
}
2023-02-01 03:51:10 +00:00
// await dbfn.setGuildInfo(guildInfo);
// Bundle guildInfo into the response
2023-02-20 01:42:14 +00:00
// const getGuildInfoResponse = await dbfn.getGuildInfo(guildInfo.guildId);
await functions . collectionBuilders . guildInfos ( interaction . client ) ;
response . data = interaction . client . guildInfos . get ( guildInfo . guildId ) ;
2023-02-01 03:51:10 +00:00
// Set the response status, this is only used as a response to /setup
if ( treeFound && leaderboardFound ) { // we found both the tree and leaderboard
response . status = strings . status . treeAndLeaderboard ;
response . code = 1 ;
} else if ( treeFound || leaderboardFound ) { // Only found the tree
response . status = strings . status . missingMessage ;
response . code = 2 ;
} else { // Didn't find any
response . status = strings . status . noneFound ;
response . code = 3 ;
}
return response ;
} else { // This should only ever occur if some weird database stuff happens
response . status = "It looks like this channel doesn't contain both your ``/tree`` and ``/top trees`` messages, please run ``/setup``" ;
return response ;
}
} catch ( err ) {
throw "Problem checking messages: " + err ;
}
} ,
isTree ( message ) {
if ( message . embeds . length > 0 ) {
2023-03-11 02:16:32 +00:00
// Grab the description and title
2023-03-11 20:12:34 +00:00
const { description , title } = message . embeds [ 0 ] . data ;
2023-03-11 02:16:32 +00:00
// Make sure it's a tree message
if ( description . includes ( "Your tree is" ) ) {
// Grab the name
const treeName = title ;
// Grab the tree's height
const indices = [ description . indexOf ( "Your tree is " ) + 13 , description . indexOf ( "ft" ) ] ;
const treeHeightStr = description . slice ( indices [ 0 ] , indices [ 1 ] ) ;
const treeHeightFloat = parseFloat ( treeHeightStr ) . toFixed ( 1 ) ;
// Return the info gathered
return {
treeName : treeName ,
treeHeight : treeHeightFloat
} ;
2023-03-11 20:12:34 +00:00
} else {
return false ;
2023-03-11 02:16:32 +00:00
}
} else {
return false ;
2023-02-01 03:51:10 +00:00
}
} ,
2023-03-11 20:12:34 +00:00
// Checks if a message is a leaderboard message, and returns the entries if it is
2023-02-01 03:51:10 +00:00
isLeaderboard ( message ) {
if ( message . embeds . length > 0 ) {
2023-03-11 20:12:34 +00:00
// 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 -
2023-05-08 12:59:51 +00:00
// DO NOT USE this, it breaks when any tree name contains " - " which
// isn't uncommon apparently
// const parts = line.split(" - ");
// Instead, find the indices of the first and last instances of " - "
const hyphenIndices = [ line . indexOf ( " - " ) , line . lastIndexOf ( " - " ) ] ;
const parts = [
line . slice ( 0 , hyphenIndices [ 0 ] ) ,
line . slice ( hyphenIndices [ 0 ] + 3 , hyphenIndices [ 1 ] ) ,
line . slice ( hyphenIndices [ 1 ] + 3 , line . length )
] ;
//
2023-03-11 20:12:34 +00:00
// 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 ;
}
2023-03-11 02:16:32 +00:00
} else {
return false ;
}
} ,
async updateHandler ( message ) {
if ( message . partial ) {
2023-03-11 20:12:34 +00:00
message = await message . fetch ( ) . catch ( e => {
throw e ;
} ) ;
2023-03-11 02:16:32 +00:00
}
// Make sure the message is from Grow A Tree
if ( message . author . id != strings . ids . growATree ) return ;
// Check and store the message types
const isLeaderboard = this . isLeaderboard ( message ) ;
const isTree = this . isTree ( message ) ;
// Check if the message is a leaderboard
if ( isLeaderboard ) {
2023-03-11 20:12:34 +00:00
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 ) ;
if ( ( guildInfo . leaderboardChannelId != message . channel . id ) || ( guildInfo . leaderboardMessageId != message . id ) ) {
guildInfo . setLeaderboardMessage ( message . id , message . channel . id ) ;
doDbUpdate = true ;
}
} else {
2023-06-17 00:01:29 +00:00
guildInfo = new GuildInfo ( ) . setIds ( message . guildId , message . guild . ownerId )
2023-03-11 20:12:34 +00:00
. setLeaderboardMessage ( message . id , message . channel . id ) ;
doDbUpdate = true ;
}
if ( doDbUpdate ) {
const query = guildInfo . queryBuilder ( "setLeaderboardMessage" ) ;
await dbfn . setGuildInfo ( query ) ;
2023-06-03 18:19:34 +00:00
await functions . collectionBuilders . guildInfos ( message . client ) ;
2023-03-11 20:12:34 +00:00
}
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
2023-06-03 21:48:56 +00:00
// if (isDev) console.log(`TU: ${isTree.treeName}: ${isTree.treeHeight}ft`);
2023-03-11 02:16:32 +00:00
let guildInfo ;
2023-03-11 20:12:34 +00:00
let doDbUpdate = false ;
2023-03-11 02:16:32 +00:00
if ( message . client . guildInfos . has ( message . guildId ) ) {
guildInfo = message . client . guildInfos . get ( message . guildId ) ;
2023-03-11 20:12:34 +00:00
if ( ( guildInfo . treeName != isTree . treeName ) || ( guildInfo . treeHeight != isTree . treeHeight ) ) {
guildInfo . setTreeInfo ( isTree . treeName , isTree . treeHeight , message . channel . id , message . id ) ;
doDbUpdate = true ;
}
2023-03-11 02:16:32 +00:00
} else {
2023-06-17 00:01:29 +00:00
guildInfo = new GuildInfo ( ) . setIds ( message . guildId , message . guild . ownerId )
2023-03-11 20:12:34 +00:00
. setTreeInfo ( isTree . treeName , isTree . treeHeight , message . channel . id , message . id ) ;
doDbUpdate = true ;
}
if ( doDbUpdate ) {
const query = guildInfo . queryBuilder ( "setTreeInfo" ) ;
await dbfn . setGuildInfo ( query ) ;
2023-06-03 18:19:34 +00:00
await functions . collectionBuilders . guildInfos ( message . client ) ;
2023-03-11 02:16:32 +00:00
}
2023-02-01 03:51:10 +00:00
}
2023-01-21 14:58:32 +00:00
}
} ,
2023-02-20 01:42:14 +00:00
buttonHandlers : {
async fruitPing ( interaction ) {
if ( interaction . client . guildInfos . has ( interaction . guildId ) ) {
let guildInfo = interaction . client . guildInfos . get ( interaction . guildId ) ;
let status = "No Changes Made" ;
2023-06-03 18:42:44 +00:00
let errorFlag = false ;
const role = await functions . roles . fetchRole ( interaction . guild , guildInfo . fruitRoleId ) . catch ( e => {
errorFlag = true ;
status = strings . error . noFetchRole ;
} ) ;
2023-02-20 01:42:14 +00:00
if ( interaction . member . roles . cache . some ( role => role . id == guildInfo . fruitRoleId ) ) {
2023-06-03 18:42:44 +00:00
await functions . roles . takeRole ( interaction . member , role ) . catch ( e => {
errorFlag = true ;
status = strings . error . noTakeRole ;
} ) ;
2023-06-03 19:40:12 +00:00
if ( ! errorFlag ) status = strings . error . yesTakeRole ;
2023-02-20 01:42:14 +00:00
} else {
2023-06-01 22:31:02 +00:00
await functions . roles . giveRole ( interaction . member , role ) . catch ( e => {
2023-06-03 18:42:44 +00:00
errorFlag = true ;
status = strings . error . noGiveRole ;
2023-06-01 22:31:02 +00:00
} ) ;
2023-06-03 18:42:44 +00:00
if ( ! errorFlag ) status = strings . error . yesGiveRole ;
2023-02-20 01:42:14 +00:00
}
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" ;
2023-06-03 18:42:44 +00:00
let errorFlag = false ;
const role = await functions . roles . fetchRole ( interaction . guild , guildInfo . waterRoleId ) . catch ( e => {
errorFlag = true ;
status = strings . error . noFetchRole ;
} ) ;
2023-02-20 01:42:14 +00:00
if ( interaction . member . roles . cache . some ( role => role . id == guildInfo . waterRoleId ) ) {
2023-06-03 18:42:44 +00:00
await functions . roles . takeRole ( interaction . member , role ) . catch ( e => {
errorFlag = true ;
status = strings . error . noTakeRole ;
} ) ;
if ( ! errorFlag ) status = strings . error . yesTakeRole ;
2023-02-20 01:42:14 +00:00
} else {
2023-06-03 18:42:44 +00:00
await functions . roles . giveRole ( interaction . member , role ) . catch ( e => {
errorFlag = true ;
status = strings . error . noGiveRole ;
} ) ;
if ( ! errorFlag ) status = strings . error . yesGiveRole ;
2023-02-20 01:42:14 +00:00
}
return functions . builders . embed ( status ) ;
} else {
throw "Guild doesn't exist in database!" ;
}
}
} ,
roles : {
async fetchRole ( guild , roleId ) {
2023-06-03 18:42:44 +00:00
return await guild . roles . fetch ( roleId ) ;
2023-02-20 01:42:14 +00:00
} ,
async giveRole ( member , role ) {
2023-06-03 18:42:44 +00:00
await member . roles . add ( role ) ;
2023-02-20 01:42:14 +00:00
} ,
async takeRole ( member , role ) {
2023-06-03 18:42:44 +00:00
await member . roles . remove ( role ) ;
2023-02-20 01:42:14 +00:00
}
} ,
2023-02-20 19:07:38 +00:00
collectors : {
async create ( client , guildInfo ) {
// If a collector is already setup
if ( client . messageCollectors . has ( guildInfo . guildId ) ) {
// Close the collector
await this . end ( client , guildInfo ) ;
}
// Make sure guildInfo is what we expect, the watch channel isnt blank, and notifications are enabled
2023-06-03 20:14:00 +00:00
if ( ( guildInfo instanceof GuildInfo && guildInfo . watchChannelId != "" ) && ( guildInfo . notificationsEnabled == true ) ) {
2023-02-20 19:07:38 +00:00
// Fetch the Guild
2023-06-03 20:14:00 +00:00
const guild = await client . guilds . fetch ( guildInfo . guildId ) . catch ( e => {
throw "ERRNOGUILD"
} ) ;
2023-02-20 19:07:38 +00:00
// Fetch the Channel
const channel = await guild . channels . fetch ( guildInfo . watchChannelId ) ;
// Create the filter function
const filter = message => {
// Discard any messages sent by Silvanus
return message . author . id != process . env . BOTID ;
}
// Create the collector
const collector = channel . createMessageCollector ( { filter } ) ;
// Add the collector to the messageCollectors Collection
client . messageCollectors . set ( guildInfo . guildId , collector ) ;
2023-06-03 21:48:56 +00:00
// if (isDev) console.log("Set up a collector in " + guildInfo.guildId);
2023-02-20 19:07:38 +00:00
collector . on ( 'collect' , message => {
2023-06-03 21:48:56 +00:00
// if (isDev) console.log("Collected a message in " + message.guild.id);
2023-02-20 19:07:38 +00:00
// Check for manual relay use with "water ping" and "fruit ping"
if ( message . content . toLowerCase ( ) . includes ( "water ping" ) ) {
2023-07-10 23:52:18 +00:00
functions . sendWaterReminder ( guildInfo , guildInfo . waterMessage , guildInfo . reminderChannelId , guild ) . catch ( e => console . error ( ` [ ${ e . code } ]: ${ e . message } - ${ e . url } ` ) ) ;
2023-02-20 19:07:38 +00:00
return ;
} else if ( message . content . toLowerCase ( ) . includes ( "fruit ping" ) ) {
2023-07-10 23:52:18 +00:00
functions . sendFruitReminder ( guildInfo , guildInfo . fruitMessage , guildInfo . reminderChannelId , guild ) . catch ( e => console . error ( ` [ ${ e . code } ]: ${ e . message } - ${ e . url } ` ) ) ;
2023-02-20 19:07:38 +00:00
return ;
}
// If the message doesn't contain an embed, we can ignore it
if ( message . embeds == undefined ) return ;
if ( message . embeds . length == 0 ) return ;
2023-06-03 21:48:56 +00:00
// console.log(JSON.stringify(message.embeds[0].data));
if ( message . embeds [ 0 ] . data . description == undefined ) return ;
2023-02-20 19:07:38 +00:00
// Check the description field of the embed to determine if it matches Grow A Tree's notification texts
if ( message . embeds [ 0 ] . data . description . includes ( strings . notifications . water ) ) {
2023-07-10 23:52:18 +00:00
functions . sendWaterReminder ( guildInfo , guildInfo . waterMessage , guildInfo . reminderChannelId , guild ) . catch ( e => console . error ( ` [ ${ e . code } ]: ${ e . message } - ${ e . url } ` ) ) ;
2023-02-20 19:07:38 +00:00
} else if ( message . embeds [ 0 ] . data . description . includes ( strings . notifications . fruit ) ) {
2023-07-10 23:52:18 +00:00
functions . sendFruitReminder ( guildInfo , guildInfo . fruitMessage , guildInfo . reminderChannelId , guild ) . catch ( e => console . error ( ` [ ${ e . code } ]: ${ e . message } - ${ e . url } ` ) ) ;
2023-02-20 19:07:38 +00:00
}
} ) ;
}
} ,
async end ( client , guildInfo ) {
if ( ! client . messageCollectors ) throw "No Message Collectors" ;
if ( ! client . messageCollectors . has ( guildInfo . guildId ) ) throw "Guild doesn't have a Message Collector" ;
const collector = client . messageCollectors . get ( guildInfo . guildId ) ;
// Close the collector
await collector . stop ( ) ;
// Remove the collector from the messageCollectors Collection
client . messageCollectors . delete ( guildInfo . guildId ) ;
}
} ,
2023-02-01 03:51:10 +00:00
async refresh ( interaction ) {
2023-03-11 20:12:34 +00:00
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 ) ;
2023-02-01 03:51:10 +00:00
const embed = this . builders . comparisonEmbed ( comparedRankings , guildInfo ) ;
2023-02-20 01:42:14 +00:00
await interaction . update ( embed ) . catch ( e => console . error ( e ) ) ;
2023-02-01 03:51:10 +00:00
} else {
2023-03-11 20:12:34 +00:00
throw "Guild doesn't exist in database!" ;
2023-02-01 03:51:10 +00:00
}
2023-01-21 14:58:32 +00:00
} ,
2023-02-20 01:42:14 +00:00
reset ( interaction ) {
2023-01-25 08:07:41 +00:00
return new Promise ( ( resolve , reject ) => {
2023-02-20 01:42:14 +00:00
dbfn . deleteGuildInfo ( interaction . guildId ) . then ( res => {
functions . collectionBuilders . guildInfos ( interaction . client ) ;
2023-01-25 08:07:41 +00:00
resolve ( res ) ;
} ) . catch ( err => {
console . error ( err ) ;
reject ( err ) ;
return ;
} ) ;
} ) ;
2023-01-21 14:58:32 +00:00
} ,
2023-01-23 23:14:57 +00:00
getWaterTime ( size ) {
2023-02-01 03:51:10 +00:00
return Math . floor ( Math . pow ( size * 0.07 + 5 , 1.1 ) ) ; // Seconds
2023-01-25 09:53:49 +00:00
} ,
2023-02-20 01:42:14 +00:00
parseWaterTime ( seconds ) {
// 60 secs in min
// 3600 secs in hr
// 86400 sec in day
let waterParts = {
value : seconds ,
units : "secs"
} ;
if ( 60 < seconds && seconds <= 3600 ) { // Minutes
waterParts . value = parseFloat ( seconds / 60 ) . toFixed ( 1 ) ;
waterParts . units = "mins" ;
} else if ( 3600 < seconds && seconds <= 86400 ) {
waterParts . value = parseFloat ( seconds / 3600 ) . toFixed ( 1 ) ;
waterParts . units = "hrs" ;
} else if ( 86400 < seconds ) {
waterParts . value = parseFloat ( seconds / 86400 ) . toFixed ( 1 ) ;
waterParts . units = "days" ;
}
return ` ${ waterParts . value } ${ waterParts . units } ` ;
} ,
2023-02-26 17:09:24 +00:00
timeToHeight ( beginHeight , destHeight , efficiency , quality ) {
2023-01-25 09:53:49 +00:00
return new Promise ( ( resolve , reject ) => {
2023-02-01 03:51:10 +00:00
let time = 0 ;
2023-02-26 21:11:05 +00:00
let oldTime = 0 ;
let compostAppliedCount = 0 ;
let totalWaterCount = 0 ;
2023-02-26 17:09:24 +00:00
if ( ( efficiency ) && ( quality ) ) {
for ( let i = beginHeight ; i < destHeight ; i ++ ) {
const randNum = Math . floor ( Math . random ( ) * 100 ) ;
const compostApplied = randNum <= efficiency ;
if ( compostApplied ) {
let qualityPercent = quality / 100 ;
let waterTime = functions . getWaterTime ( i ) ;
let reductionTime = waterTime * qualityPercent ;
let finalTime = waterTime - reductionTime ;
2023-02-26 21:11:05 +00:00
compostAppliedCount ++ ;
totalWaterCount ++ ;
2023-02-26 17:09:24 +00:00
time += parseFloat ( finalTime ) ;
2023-02-26 21:11:05 +00:00
oldTime += waterTime ;
2023-02-26 17:09:24 +00:00
} else {
2023-02-26 21:11:05 +00:00
totalWaterCount ++ ;
let waterTime = parseFloat ( functions . getWaterTime ( i ) ) ;
time += waterTime ;
oldTime += waterTime ;
2023-02-26 17:09:24 +00:00
}
}
} else {
for ( let i = beginHeight ; i < destHeight ; i ++ ) {
const waterTime = parseFloat ( functions . getWaterTime ( i ) ) ;
// console.log("Height: " + i + "Time: " + waterTime);
time += waterTime ;
}
2023-02-01 03:51:10 +00:00
}
2023-02-26 21:11:05 +00:00
const readableWaterTime = this . parseWaterTime ( time ) ;
const savedTime = this . parseWaterTime ( oldTime - time ) ;
resolve ( {
time : readableWaterTime ,
totalWaterCount : totalWaterCount ? totalWaterCount : undefined ,
compostAppliedCount : compostAppliedCount ? compostAppliedCount : undefined ,
average : totalWaterCount ? parseFloat ( ( compostAppliedCount / totalWaterCount ) * 100 ) . toFixed ( 1 ) : undefined ,
savedTime : savedTime
} ) ;
2023-02-01 03:51:10 +00:00
} ) ;
} ,
sleep ( ms ) {
// console.log(`Begin Sleep: ${new Date(Date.now()).getSeconds()}`);
return new Promise ( resolve => {
setTimeout ( function ( ) {
resolve ( ) ;
// console.log(`End Sleep: ${new Date(Date.now()).getSeconds()}`);
} , ms ) ;
} ) ;
} ,
2023-02-20 01:42:14 +00:00
async sendWaterReminder ( guildInfo , message , channelId , guild ) {
const reminderChannel = await guild . channels . fetch ( channelId ) ;
2024-04-06 13:01:57 +00:00
// const reminderEmbed = functions.builders.waterReminderEmbed(message, guildInfo);
2023-03-11 20:12:34 +00:00
if ( isDev ) console . log ( ` WR: ${ guild . name } : ${ guildInfo . treeName } ` ) ;
2024-04-06 13:01:57 +00:00
await reminderChannel . send ( message ) . then ( async m => {
2023-02-20 19:14:54 +00:00
if ( ! m . deletable ) return ;
await this . sleep ( 500 ) . then ( async ( ) => {
await m . delete ( ) . catch ( e => console . error ( e ) ) ;
} ) ;
} ) . catch ( err => {
2023-07-10 23:52:18 +00:00
console . error ( ` [ ${ err . code } ]: ${ err . message } - ${ err . url } ` ) ;
2023-01-25 09:53:49 +00:00
} ) ;
2023-02-01 03:51:10 +00:00
} ,
2023-02-20 01:42:14 +00:00
async sendFruitReminder ( guildInfo , message , channelId , guild ) {
const reminderChannel = await guild . channels . fetch ( channelId ) ;
2024-04-06 13:01:57 +00:00
// const reminderEmbed = functions.builders.fruitReminderEmbed(message, guildInfo);
2023-03-11 20:12:34 +00:00
if ( isDev ) console . log ( ` FR: ${ guild . name } : ${ guildInfo . treeName } ` ) ;
2024-04-06 13:01:57 +00:00
await reminderChannel . send ( message ) . then ( async m => {
2023-02-20 02:44:46 +00:00
if ( ! m . deletable ) return ;
await this . sleep ( 500 ) . then ( async ( ) => {
await m . delete ( ) . catch ( e => console . error ( e ) ) ;
} ) ;
} ) . catch ( err => {
2023-07-10 23:52:18 +00:00
console . error ( ` [ ${ err . code } ]: ${ err . message } - ${ err . url } ` ) ;
2023-02-20 01:42:14 +00:00
} ) ;
2023-02-01 03:51:10 +00:00
} ,
2023-02-20 01:42:14 +00:00
async setupCollectors ( client ) {
let guildInfos = client . guildInfos ;
2023-02-20 19:07:38 +00:00
let collectorsArray = [ ] ;
2023-02-20 01:42:14 +00:00
await guildInfos . forEach ( async guildInfo => {
2023-02-20 19:07:38 +00:00
if ( guildInfo instanceof GuildInfo && guildInfo . watchChannelId != "" && guildInfo . notificationsEnabled ) {
2023-02-20 01:42:14 +00:00
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 ;
2023-02-01 03:51:10 +00:00
}
2023-02-20 01:42:14 +00:00
const collector = channel . createMessageCollector ( { filter } ) ;
collector . on ( 'collect' , message => {
if ( message . content . toLowerCase ( ) . includes ( "water ping" ) ) {
2023-07-10 23:52:18 +00:00
this . sendWaterReminder ( guildInfo , guildInfo . waterMessage , guildInfo . reminderChannelId , guild ) . catch ( e => console . error ( ` [ ${ e . code } ]: ${ e . message } - ${ e . url } ` ) ) ;
2023-02-20 01:42:14 +00:00
return ;
} else if ( message . content . toLowerCase ( ) . includes ( "fruit ping" ) ) {
2023-07-10 23:52:18 +00:00
this . sendFruitReminder ( guildInfo , guildInfo . fruitMessage , guildInfo . reminderChannelId , guild ) . catch ( e => console . error ( ` [ ${ e . code } ]: ${ e . message } - ${ e . url } ` ) ) ;
2023-02-20 01:42:14 +00:00
return ;
}
if ( message . embeds == undefined ) return ;
if ( message . embeds . length == 0 ) return ;
guildInfo = client . guildInfos . get ( guild . id ) ;
if ( message . embeds [ 0 ] . data . description . includes ( strings . notifications . water ) ) {
2023-07-10 23:52:18 +00:00
this . sendWaterReminder ( guildInfo , guildInfo . waterMessage , guildInfo . reminderChannelId , guild ) . catch ( e => console . error ( ` [ ${ e . code } ]: ${ e . message } - ${ e . url } ` ) ) ;
2023-02-20 01:42:14 +00:00
} else if ( message . embeds [ 0 ] . data . description . includes ( strings . notifications . fruit ) ) {
2023-07-10 23:52:18 +00:00
this . sendFruitReminder ( guildInfo , guildInfo . fruitMessage , guildInfo . reminderChannelId , guild ) . catch ( e => console . error ( ` [ ${ e . code } ]: ${ e . message } - ${ e . url } ` ) ) ;
2023-02-20 01:42:14 +00:00
}
} ) ;
2023-02-01 03:51:10 +00:00
}
2023-02-20 01:42:14 +00:00
} ) ;
2023-02-20 19:07:38 +00:00
guildInfos . set ( "collectors" , collectorsArray ) ;
2023-02-20 02:44:46 +00:00
} ,
2023-02-20 19:07:38 +00:00
async setupCollector ( channel , interaction ) {
if ( interaction . client . guildInfos . has ( interaction . guildId ) ) {
let collectors = interaction . client . guildInfos . get ( 'collectors' ) ;
let guildInfo = interaction . client . guildInfos . get ( interaction . guildId ) ;
const filter = message => {
return message . author . id != process . env . BOTID ;
2023-02-20 02:44:46 +00:00
}
2023-02-20 19:07:38 +00:00
const collector = channel . createMessageCollector ( { filter } ) ;
collectors . push ( collector ) ;
collector . on ( 'collect' , message => {
if ( message . content . toLowerCase ( ) . includes ( "water ping" ) ) {
2023-07-10 23:52:18 +00:00
this . sendWaterReminder ( guildInfo , guildInfo . waterMessage , guildInfo . reminderChannelId , guild ) . catch ( e => console . error ( ` [ ${ e . code } ]: ${ e . message } - ${ e . url } ` ) ) ;
2023-02-20 19:07:38 +00:00
return ;
} else if ( message . content . toLowerCase ( ) . includes ( "fruit ping" ) ) {
2023-07-10 23:52:18 +00:00
this . sendFruitReminder ( guildInfo , guildInfo . fruitMessage , guildInfo . reminderChannelId , guild ) . catch ( e => console . error ( ` [ ${ e . code } ]: ${ e . message } - ${ e . url } ` ) ) ;
2023-02-20 19:07:38 +00:00
return ;
}
if ( message . embeds == undefined ) return ;
if ( message . embeds . length == 0 ) return ;
if ( message . embeds [ 0 ] . data . description . includes ( strings . notifications . water ) ) {
2023-07-10 23:52:18 +00:00
this . sendWaterReminder ( guildInfo , guildInfo . waterMessage , guildInfo . reminderChannelId , guild ) . catch ( e => console . error ( ` [ ${ e . code } ]: ${ e . message } - ${ e . url } ` ) ) ;
2023-02-20 19:07:38 +00:00
} else if ( message . embeds [ 0 ] . data . description . includes ( strings . notifications . fruit ) ) {
2023-07-10 23:52:18 +00:00
this . sendFruitReminder ( guildInfo , guildInfo . fruitMessage , guildInfo . reminderChannelId , guild ) . catch ( e => console . error ( ` [ ${ e . code } ]: ${ e . message } - ${ e . url } ` ) ) ;
2023-02-20 19:07:38 +00:00
}
} ) ;
} else {
throw "Guild doesn't exist in database!" ;
}
2023-06-01 22:31:02 +00:00
} ,
generateErrorId ( ) {
const digitCount = 10 ;
const digits = [ ] ;
for ( let i = 0 ; i < digitCount ; i ++ ) {
const randBase = Math . random ( ) ;
const randNumRaw = randBase * 10 ;
const randNumRound = Math . floor ( randNumRaw ) ;
digits . push ( randNumRound ) ;
}
const errorId = digits . join ( "" ) ;
return errorId ;
2023-08-11 02:13:08 +00:00
} ,
async sendHeartbeat ( url ) {
https . get ( url , async ( response ) => {
2023-08-27 23:34:15 +00:00
if ( isDev ) console . log ( "Sent Heartbeat Request: " + url ) ;
2023-08-11 02:13:08 +00:00
let data = '' ;
response . on ( 'data' , ( chunk ) => data += chunk ) ;
response . on ( 'end' , ( ) => {
2023-08-27 23:34:15 +00:00
if ( isDev ) console . log ( "Received Heartbeat Response: " + data ) ;
2023-08-11 02:13:08 +00:00
parsedData = JSON . parse ( data ) ;
if ( ! ( parsedData . ok ) ) console . error ( "Heartbeat failed" ) ;
} ) ;
} ) . on ( "error" , ( error ) => console . error ( error ) ) ;
2023-01-19 01:10:05 +00:00
}
2023-01-18 00:35:24 +00:00
} ;
module . exports = functions ;