diff --git a/main.js b/main.js index 2753916..481fbc3 100755 --- a/main.js +++ b/main.js @@ -28,9 +28,9 @@ const dbfn = require('./modules/dbfn.js'); const isDev = process.env.DEBUG; client.once('ready', async () => { - await fn.collectionBuilders.slashCommands(client); + fn.collectionBuilders.slashCommands(client); await fn.collectionBuilders.guildInfos(client); - await fn.setupCollectors(client); + await fn.collectionBuilders.messageCollectors(client); console.log('Ready!'); client.user.setActivity({ name: strings.activity.name, type: ActivityType.Watching }); if (isDev == 'false') { @@ -85,6 +85,11 @@ client.on('interactionCreate', async interaction => { } }); +client.on('messageUpdate', (oldMessage, newMessage) => { + if (process.env.DEBUG) console.log(`Message updated`); +}); + + process.on('unhandledRejection', error => { console.error('Unhandled promise rejection:', error); }); diff --git a/modules/functions.js b/modules/functions.js index ab0fc66..055f428 100755 --- a/modules/functions.js +++ b/modules/functions.js @@ -43,6 +43,17 @@ const functions = { client.guildInfos.set(guildInfo.guildId, guildInfo); } return 'guildInfos Collection Built'; + }, + async messageCollectors(client) { + // Create an empty collection for MessageCollectors + if (!client.messageCollectors) client.messageCollectors = new Discord.Collection(); + client.messageCollectors.clear(); + // Get all of the guild infos from the client + const { guildInfos, messageCollectors } = client; + // Iterate over each guild info + await guildInfos.forEach(async guildInfo => { + await functions.collectors.create(client, guildInfo); + }); } }, builders: { @@ -528,6 +539,59 @@ const functions = { await member.roles.remove(role).catch(err => console.error("Error taking the role: " + err + "\n" + JSON.stringify(role))); } }, + collectors: { + async create(client, guildInfo) { + // If a collector is already setup + if (client.messageCollectors.has(guildInfo.guildId)) { + // Close the collector + await this.end(client, guildInfo); + } + // Make sure guildInfo is what we expect, the watch channel isnt blank, and notifications are enabled + if (guildInfo instanceof GuildInfo && guildInfo.watchChannelId != "" && guildInfo.notificationsEnabled) { + // Fetch the Guild + const guild = await client.guilds.fetch(guildInfo.guildId); + // Fetch the Channel + const channel = await guild.channels.fetch(guildInfo.watchChannelId); + // Create the filter function + const filter = message => { + // Discard any messages sent by Silvanus + return message.author.id != process.env.BOTID; + } + // Create the collector + const collector = channel.createMessageCollector({ filter }); + // Add the collector to the messageCollectors Collection + client.messageCollectors.set(guildInfo.guildId, collector); + collector.on('collect', message => { + // Check for manual relay use with "water ping" and "fruit ping" + if (message.content.toLowerCase().includes("water ping")) { + functions.sendWaterReminder(guildInfo, guildInfo.waterMessage, guildInfo.reminderChannelId, guild); + return; + } else if (message.content.toLowerCase().includes("fruit ping")) { + functions.sendFruitReminder(guildInfo, guildInfo.fruitMessage, guildInfo.reminderChannelId, guild); + return; + } + // If the message doesn't contain an embed, we can ignore it + if (message.embeds == undefined) return; + if (message.embeds.length == 0) return; + // Check the description field of the embed to determine if it matches Grow A Tree's notification texts + if (message.embeds[0].data.description.includes(strings.notifications.water)) { + functions.sendWaterReminder(guildInfo, guildInfo.waterMessage, guildInfo.reminderChannelId, guild); + } else if (message.embeds[0].data.description.includes(strings.notifications.fruit)) { + functions.sendFruitReminder(guildInfo, guildInfo.fruitMessage, guildInfo.reminderChannelId, guild); + } + }); + } + }, + async end(client, guildInfo) { + if (!client.messageCollectors) throw "No Message Collectors"; + if (!client.messageCollectors.has(guildInfo.guildId)) throw "Guild doesn't have a Message Collector"; + const collector = client.messageCollectors.get(guildInfo.guildId); + // Close the collector + await collector.stop(); + // Remove the collector from the messageCollectors Collection + client.messageCollectors.delete(guildInfo.guildId); + } + }, async refresh(interaction) { // const getGuildInfoResponse = await dbfn.getGuildInfo(interaction.guildId); // let guildInfo = getGuildInfoResponse.data; @@ -642,9 +706,9 @@ const functions = { }, async setupCollectors(client) { let guildInfos = client.guildInfos; - guildInfos.set("collectors", []); + let collectorsArray = []; await guildInfos.forEach(async guildInfo => { - if ( guildInfo instanceof GuildInfo && guildInfo.watchChannelId != "" && guildInfo.notificationsEnabled) { + if (guildInfo instanceof GuildInfo && guildInfo.watchChannelId != "" && guildInfo.notificationsEnabled) { const guild = await client.guilds.fetch(guildInfo.guildId); // console.log(guildInfo instanceof GuildInfo); const channel = await guild.channels.fetch(guildInfo.watchChannelId); @@ -671,29 +735,36 @@ const functions = { }); } }); + guildInfos.set("collectors", collectorsArray); }, - async setupCollector(channel, guildInfo) { - const filter = message => { - return message.author.id != process.env.BOTID; + async setupCollector(channel, interaction) { + if (interaction.client.guildInfos.has(interaction.guildId)) { + let collectors = interaction.client.guildInfos.get('collectors'); + let guildInfo = interaction.client.guildInfos.get(interaction.guildId); + const filter = message => { + return message.author.id != process.env.BOTID; + } + const collector = channel.createMessageCollector({ filter }); + collectors.push(collector); + collector.on('collect', message => { + if (message.content.toLowerCase().includes("water ping")) { + this.sendWaterReminder(guildInfo, guildInfo.waterMessage, guildInfo.reminderChannelId, guild); + return; + } else if (message.content.toLowerCase().includes("fruit ping")) { + this.sendFruitReminder(guildInfo, guildInfo.fruitMessage, guildInfo.reminderChannelId, guild); + return; + } + if (message.embeds == undefined) return; + if (message.embeds.length == 0) return; + if (message.embeds[0].data.description.includes(strings.notifications.water)) { + this.sendWaterReminder(guildInfo, guildInfo.waterMessage, guildInfo.reminderChannelId, guild); + } else if (message.embeds[0].data.description.includes(strings.notifications.fruit)) { + this.sendFruitReminder(guildInfo, guildInfo.fruitMessage, guildInfo.reminderChannelId, guild); + } + }); + } else { + throw "Guild doesn't exist in database!"; } - const collector = channel.createMessageCollector({ filter }); - collector.on('collect', message => { - if (message.content.toLowerCase().includes("water ping")) { - this.sendWaterReminder(guildInfo, guildInfo.waterMessage, guildInfo.reminderChannelId, guild); - return; - } else if (message.content.toLowerCase().includes("fruit ping")) { - this.sendFruitReminder(guildInfo, guildInfo.fruitMessage, guildInfo.reminderChannelId, guild); - return; - } - if (message.embeds == undefined) return; - if (message.embeds.length == 0) return; - guildInfo = client.guildInfos.get(guild.id); - if (message.embeds[0].data.description.includes(strings.notifications.water)) { - this.sendWaterReminder(guildInfo, guildInfo.waterMessage, guildInfo.reminderChannelId, guild); - } else if (message.embeds[0].data.description.includes(strings.notifications.fruit)) { - this.sendFruitReminder(guildInfo, guildInfo.fruitMessage, guildInfo.reminderChannelId, guild); - } - }); } }; diff --git a/slash-commands/notifications.js b/slash-commands/notifications.js index cd45dbc..0283a43 100755 --- a/slash-commands/notifications.js +++ b/slash-commands/notifications.js @@ -6,7 +6,7 @@ const strings = require('../data/strings.json'); module.exports = { data: new SlashCommandBuilder() - .setName('notifications') + .setName('relay') .setDescription('A notification relay for improved water and fruit notifications') .addSubcommand(sc => sc.setName('set') @@ -67,78 +67,115 @@ module.exports = { const subcommand = interaction.options.getSubcommand(); // if (process.env.DEBUG) console.log(`${typeof subcommand}: ${subcommand}`); switch (subcommand) { + // Set all components for the first time case "set": + // If there is already a guildInfo object for this server if (interaction.client.guildInfos.has(interaction.guildId)) { - const watchChannel = interaction.options.getChannel('watchchannel'); - const waterMessage = interaction.options.getString('watermessage'); - const fruitMessage = interaction.options.getString('fruitmessage') ? interaction.options.getString('fruitmessage') : interaction.options.getString('watermessage'); - const reminderChannel = interaction.options.getChannel('pingchannel'); let guildInfo = interaction.client.guildInfos.get(interaction.guildId); - guildInfo.setReminders(waterMessage, fruitMessage, reminderChannel.id, watchChannel.id, true); - let query = guildInfo.queryBuilder("setReminders"); - await dbfn.setGuildInfo(query); - const replyParts = [ - `I'll watch <#${watchChannel.id}> for Grow A Tree Notifications and relay them to <#${reminderChannel.id}>.`, - `Water Message: ${waterMessage}` - ]; - if (fruitMessage != "") replyParts.push(`Fruit Message: ${fruitMessage}`); - await interaction.editReply(replyParts.join("\n")).catch(e => console.error(e)); - fn.collectionBuilders.guildInfos(interaction.client); - fn.setupCollector(interaction.channel, guildInfo); - } else { + // Get options from the interaction const watchChannel = interaction.options.getChannel('watchchannel'); const waterMessage = interaction.options.getString('watermessage'); + // If the fruit message is set, use it, otherwise default to the water message. const fruitMessage = interaction.options.getString('fruitmessage') ? interaction.options.getString('fruitmessage') : interaction.options.getString('watermessage'); const reminderChannel = interaction.options.getChannel('pingchannel'); + // Set the reminder configuration in the GuildInfo object + guildInfo.setReminders(waterMessage, fruitMessage, reminderChannel.id, watchChannel.id, true); + // Update the guildInfos Collection + interaction.client.guildInfos.set(interaction.guildId, guildInfo); + // Build a query to update the database + let query = guildInfo.queryBuilder("setReminders"); + // Run the query + await dbfn.setGuildInfo(query); + // Set up a collector on the watch channel + fn.collectors.create(interaction.client, guildInfo); + // Compose a reply + const reply = [ + `I'll watch <#${watchChannel.id}> for Grow A Tree Notifications and relay them to <#${reminderChannel.id}>.`, + `Water Message: ${waterMessage}`, + `Fruit Message: ${fruitMessage}` + ].join("\n"); + // Send the reply + await interaction.editReply(fn.builders.embed(reply)).catch(e => console.error(e)); + } else { + // Get options from the interaction + const watchChannel = interaction.options.getChannel('watchchannel'); + const waterMessage = interaction.options.getString('watermessage'); + // If the fruit message is set, use it. Otherwise default to the water message + const fruitMessage = interaction.options.getString('fruitmessage') ? interaction.options.getString('fruitmessage') : interaction.options.getString('watermessage'); + const reminderChannel = interaction.options.getChannel('pingchannel'); + // Create a new GuildInfo object let guildInfo = new GuildInfo() .setId(interaction.guildId) + // Set the reminder configuration .setReminders(waterMessage, fruitMessage, reminderChannel.id, watchChannel.id, true); + // Update the guildInfos Collection + interaction.client.guildInfos.set(interaction.guildId, guildInfo); + // Build a query to update the database let query = guildInfo.queryBuilder("setReminders"); + // Run the query await dbfn.setGuildInfo(query); - const replyParts = [ + // Create a messageCollector on the watch channel + fn.collectors.create(interaction.client, guildInfo); + // Compose a reply + const reply = [ `I'll watch <#${watchChannel.id}> for Grow A Tree Notifications and relay them to <#${reminderChannel.id}>.`, - `Water Message: ${waterMessage}` - ]; - if (fruitMessage != "") replyParts.push(`Fruit Message: ${fruitMessage}`); - await interaction.editReply(replyParts.join("\n")).catch(e => console.error(e)); - fn.collectionBuilders.guildInfos(interaction.client); - fn.setupCollector(watchChannel, guildInfo); + `Water Message: ${waterMessage}`, + `Fruit Message: ${fruitMessage}` + ].join("\n"); + // Send the reply + await interaction.editReply(reply).catch(e => console.error(e)); } break; - case "update": + case "update": // Update the relay configuration piecemeal if (interaction.client.guildInfos.has(interaction.guildId)) { let guildInfo = interaction.client.guildInfos.get(interaction.guildId); + + // Get all possible options from the interaction const inWatchChannel = interaction.options.getChannel('watchchannel'); const inWaterMessage = interaction.options.getString('watermessage'); const inFruitMessage = interaction.options.getString('fruitmessage'); const inReminderChannel = interaction.options.getChannel('pingchannel'); + // Check if each option is set, if it is, use it. Otherwise use what was already set const outWatchChannelId = inWatchChannel ? inWatchChannel.id : guildInfo.watchChannelId; const outWaterMessage = inWaterMessage ? inWaterMessage : guildInfo.waterMessage; const outFruitMessage = inFruitMessage ? inFruitMessage : guildInfo.fruitMessage; const outReminderChannelId = inReminderChannel ? inReminderChannel.id : guildInfo.reminderChannelId; - + + // Update the relay configuration guildInfo.setReminders(outWaterMessage, outFruitMessage, outReminderChannelId, outWatchChannelId, true); + // Update the guildInfos Collection + interaction.client.guildInfos.set(interaction.guildId, guildInfo); + // Build a query to update the database let query = guildInfo.queryBuilder("setReminders"); + // Run the query await dbfn.setGuildInfo(query); - const replyParts = [ + // Create a messageCollector on the watch channel + fn.collectors.create(interaction.client, guildInfo); + // Compose a reply + const reply = [ `I'll watch <#${outWatchChannelId}> for Grow A Tree Notifications and relay them to <#${outReminderChannelId}>.`, - `Water Message: ${outWaterMessage}` - ]; - if (outFruitMessage != "") replyParts.push(`Fruit Message: ${outFruitMessage}`); - await interaction.editReply(replyParts.join("\n")).catch(e => console.error(e)); - fn.collectionBuilders.guildInfos(interaction.client); - fn.setupCollector(inWatchChannel, guildInfo); + `Water Message: ${outWaterMessage}`, + `Fruit Message: ${outFruitMessage}` + ].join("\n"); + // Send the reply + await interaction.editReply(reply).catch(e => console.error(e)); } else { await interaction.editReply(fn.builders.errorEmbed("There is no existing notification relay to update!")).catch(e => console.error(e)); } break; - case 'disable': + case 'disable': // Disable the relay if (interaction.client.guildInfos.has(interaction.guildId)) { let guildInfo = interaction.client.guildInfos.get(interaction.guildId); + // Update the relay config with all undefined except for `enabled` which is false guildInfo.setReminders(undefined, undefined, undefined, undefined, false); + // Update the guildInfos Collection + interaction.client.guildInfos.set(interaction.guildId, guildInfo); + // Update the database await dbfn.setGuildInfo(guildInfo.queryBuilder("setReminders")).catch(e => console.error(e)); - await fn.collectionBuilders.guildInfos(interaction.client); + // Close the collector + await fn.collectors.end(interaction.client, guildInfo).catch(e => console.error(e)); + // Reply confirming disabling of relay await interaction.editReply(fn.builders.embed(strings.status.optout)).catch(e => console.error(e)); } else { await interaction.editReply(fn.builders.errorEmbed("A notification relay has not been set up yet!")).catch(e => console.error(e));