diff --git a/commands/legacy/quotes/quoteadd.js b/commands/legacy/quotes/quoteadd.js index ff6b70d..4291ddd 100644 --- a/commands/legacy/quotes/quoteadd.js +++ b/commands/legacy/quotes/quoteadd.js @@ -1,76 +1,130 @@ -const { EmbedBuilder, MessageFlags } = require("discord.js") -const { client, mClient } = require("../../../index") +const { EmbedBuilder, MessageFlags } = require("discord.js"); +const { client, mClient } = require("../../../index"); module.exports = { - name: 'quotesadd', - description: 'adds a quote to the database by replying to the message', - category: 'Quotes', - aliases: ['qadd', 'qa'], - async execute(message, args) { - const refMessage = message.reference // message to be quoted has been replied to - const refMessageId = refMessage.messageId - const refChannelId = refMessage.channelId - const refGuildId = refMessage.guildId - var msgData = {} - var refData = {} - const quoteGuild = await client.guilds.fetch(refGuildId) - const quoteChannel = await quoteGuild.channels.fetch(refChannelId) - const quoteMessage = await quoteChannel.messages.fetch(refMessageId) + name: "quotesadd", + description: "Adds a quote to the database by replying to a message.", + aliases: ["qadd", "qa"], + async execute(message) { + // Ensure the command is used as a reply. + if (!message.reference?.messageId) { + return message.reply({ + content: "Please reply to the message you want to quote.", + flags: MessageFlags.Ephemeral + }); + } - msgData = { + const { messageId, channelId, guildId } = message.reference; + + let quoteMessage; + let quoteChannel; + let quoteGuild; + + try { + quoteGuild = await client.guilds.fetch(guildId); + quoteChannel = await quoteGuild.channels.fetch(channelId); + quoteMessage = await quoteChannel.messages.fetch(messageId); + } catch (err) { + console.error(err); + return message.reply({ + content: "Unable to fetch the quoted message.", + flags: MessageFlags.Ephemeral + }); + } + + const msgData = { content: quoteMessage.content, - author: await quoteMessage.author, - reference: await quoteMessage.reference, - embed: await quoteMessage.embeds ? 'embed' : null - } // debug - if (msgData.reference) { - const quoteRefGuild = await client.guilds.fetch(msgData.reference.guildId) - const quoteRefChannel = await quoteRefGuild.channels.fetch(msgData.reference.channelId) - const quoteRefMessage = await quoteRefChannel.messages.fetch(msgData.reference.messageId) + author: quoteMessage.author, + hasEmbed: quoteMessage.embeds.length > 0, + reference: quoteMessage.reference + }; + // Only populated if the quoted message is itself a reply. + let refData = null; - refData = { - content: quoteRefMessage.content, - author: await quoteRefMessage.author, - embed: await quoteRefMessage.embeds.length > 0 ? 'embed' : null + if (msgData.reference?.messageId) { + try { + const refGuild = await client.guilds.fetch(msgData.reference.guildId); + const refChannel = await refGuild.channels.fetch(msgData.reference.channelId); + const refMessage = await refChannel.messages.fetch(msgData.reference.messageId); + + refData = { + content: refMessage.content, + author: refMessage.author, + hasEmbed: refMessage.embeds.length > 0 + }; + } catch (err) { + // Referenced message couldn't be fetched (deleted, inaccessible, etc.) + refData = null; } } - const db = mClient.db(process.env.M_DB) - const quotesColl = db.collection('items_quotes') - const found = await quotesColl.findOne({ messageID: quoteMessage.id }) - if (found) { return await message.reply({ content: 'Quote Already in Database', flags: MessageFlags.Ephemeral }) } - await quotesColl.findOneAndUpdate({ messageID: quoteMessage.id }, { - $set: { - messageID: quoteMessage.id, - channelID: quoteChannel.id, - guildID: quoteGuild.id, - userID: msgData.author.id, - count: 0 - } - }, { upsert: true }) + const db = mClient.db(process.env.M_DB); + const quotesColl = db.collection("items_quotes"); - var description = '' + const existing = await quotesColl.findOne({ + messageID: quoteMessage.id + }); + + if (existing) { + return message.reply({ + content: "Quote already exists in the database.", + flags: MessageFlags.Ephemeral + }); + } + + await quotesColl.findOneAndUpdate( + { messageID: quoteMessage.id }, + { + $set: { + messageID: quoteMessage.id, + channelID: quoteChannel.id, + guildID: quoteGuild.id, + userID: msgData.author.id, + count: 0 + } + }, + { upsert: true } + ); + + const messageLink = `https://discord.com/channels/${quoteGuild.id}/${quoteChannel.id}/${quoteMessage.id}`; + + let description = ""; + + // Only show the replied-to message if one actually exists. if (refData) { - description += `> ${refData.author}` - if (refData.embed) { - description += '*an embed / a command* ' + description += `> ${refData.author} `; + + if (refData.hasEmbed) { + description += "*an embed / a command*"; + } else if (refData.content) { + description += refData.content; } else { - description += refData.content + description += "*no text*"; } - description += `\r\n↳` + + description += "\n↳ "; } - const messageLink = `https://discord.com/channels/${quoteGuild.id}/${quoteChannel.id}/${quoteMessage.id}/` - description += `${msgData.author} ${msgData.content}\r\n\r\n${messageLink}` - let timestamp = new Date(message.createdTimestamp) - let footer = timestamp.toDateString().toString() + + description += `${msgData.author} `; + + if (msgData.hasEmbed && !msgData.content) { + description += "*an embed / a command*"; + } else { + description += msgData.content || "*no text*"; + } + + description += `\n\n${messageLink}`; + + const footer = `${new Date(quoteMessage.createdTimestamp).toDateString()} #${quoteMessage.id}`; const embed = new EmbedBuilder() - .setThumbnail(msgData.author.displayAvatarURL()) + .setTitle("- Quote Added! -") .setURL(messageLink) - .setTitle(`- Quote Added! -`) + .setThumbnail(msgData.author.displayAvatarURL()) .setDescription(description) - .setFooter({ text: (footer + ' #' + quoteMessage.id) }) - await message.reply({ embeds: [embed] }) + .setFooter({ text: footer }); + + return message.reply({ embeds: [embed] }); } -} +}; \ No newline at end of file diff --git a/commands/slash/applications/quotes.js b/commands/slash/applications/quotes.js index 31664ec..752006b 100644 --- a/commands/slash/applications/quotes.js +++ b/commands/slash/applications/quotes.js @@ -3,12 +3,12 @@ Random By Year? Counter (Leaderboard) */ const { SlashCommandBuilder, EmbedBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle, UserSelectMenuBuilder, MessageFlags } = require('discord.js') -const { mClient } = require('../../../index') +const { mClient, client } = require('../../../index') + require('dotenv').config() function delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } async function quotesAdd(interaction) { - const messageLink = await interaction.options.getString('link') //regex for message link const regex = /^https:\/\/discord\.com\/channels\/\d+\/\d+\/\d+$/ @@ -25,23 +25,26 @@ async function quotesAdd(interaction) { let channelID = temp[temp.length - 2] let guildID = temp[temp.length - 3] - const guild = await interaction.client.guilds.fetch(guildID) + const guild = await client.guilds.fetch(guildID) const channel = await guild.channels.fetch(channelID) const message = await channel.messages.fetch(messageID) - var msgData = { + let refData = null + + let msgData = { content: message.content, - author: await message.author, - reference: await message.reference, - embed: await message.embeds ? 'embed' : null - } // debug + author: message.author, + reference: message.reference, + embed: message.embeds.length > 0 ? 'embed' : null + } + if (msgData.reference) { - let refGuild = await interaction.client.guilds.fetch(msgData.reference.guildId) + let refGuild = await client.guilds.fetch(msgData.reference.guildId) let refChannel = await refGuild.channels.fetch(msgData.reference.channelId) let refMessage = await refChannel.messages.fetch(msgData.reference.messageId) - var refData = { + refData = { content: refMessage.content, author: await refMessage.author, embed: await refMessage.embeds.length > 0 ? 'embed' : null @@ -104,16 +107,18 @@ async function quotesSearch(interaction) { if (!found) { return await interaction.reply({ content: 'ID not found!', flags: MessageFlags.Ephemeral }) } - const guild = await interaction.client.guilds.fetch(found.guildID) + const guild = await client.guilds.fetch(found.guildID) const channel = await guild.channels.fetch(found.channelID) const message = await channel.messages.fetch(found.messageID) - var msgData = { + let refData = null + + let msgData = { content: message.content, - author: await message.author, - reference: await message.reference, - embed: await message.embeds ? 'embed' : null - } // debug + author: message.author, + reference: message.reference, + embed: message.embeds.length > 0 ? 'embed' : null + } quotesColl.findOneAndUpdate({ messageID: found.messageID @@ -123,9 +128,12 @@ async function quotesSearch(interaction) { upsert: true }) - if (msgData.reference) { - let refMessage = await channel.messages.fetch(msgData.reference.messageId) - var refData = { + if (msgData.reference?.messageId) { + let refGuild = await client.guilds.fetch(msgData.reference.guildId) + let refChannel = await refGuild.channels.fetch(msgData.reference.channelId) + let refMessage = await refChannel.messages.fetch(msgData.reference.messageId) + + refData = { content: refMessage.content, author: await refMessage.author, embed: await refMessage.embeds.length > 0 ? 'embed' : null @@ -178,16 +186,18 @@ async function quotesRandom(interaction) { } const found = rdm[0] if (!found) { return await interaction.reply({ content: 'No Entry Found.', flags: MessageFlags.Ephemeral }) } - const guild = await interaction.client.guilds.fetch(found.guildID) + const guild = await client.guilds.fetch(found.guildID) const channel = await guild.channels.fetch(found.channelID) const message = await channel.messages.fetch(found.messageID) - var msgData = { + let refData = null + + let msgData = { content: message.content, - author: await message.author, - reference: await message.reference, - embed: await message.embeds ? 'embed' : null - } // debug + author: message.author, + reference: message.reference, + embed: message.embeds.length > 0 ? 'embed' : null + } quotesColl.findOneAndUpdate({ messageID: found.messageID @@ -197,9 +207,11 @@ async function quotesRandom(interaction) { upsert: true }) - if (msgData.reference) { - let refMessage = await channel.messages.fetch(msgData.reference.messageId) - var refData = { + if (msgData.reference?.messageId) { + let refGuild = await client.guilds.fetch(msgData.reference.guildId) + let refChannel = await refGuild.channels.fetch(msgData.reference.channelId) + let refMessage = await refChannel.messages.fetch(msgData.reference.messageId) + refData = { content: refMessage.content, author: await refMessage.author, embed: await refMessage.embeds.length > 0 ? 'embed' : null @@ -295,7 +307,7 @@ async function quotesLeaderboard(interaction, page = 1, limit = 5) { return interaction.editReply({ content: 'No quotes found for this server yet!', components: [] }); } - const guild = await interaction.client.guilds.fetch(interaction.guild.id); + const guild = await client.guilds.fetch(interaction.guild.id); // Fetch all messages concurrently const fields = await Promise.all( @@ -387,23 +399,23 @@ module.exports = { switch (interaction.options._subcommand) { case 'add': await interaction.deferReply() - quotesAdd(interaction) + await quotesAdd(interaction) break; case 'remove': - quotesRemove(interaction) + awaitquotesRemove(interaction) break; case 'search': - quotesSearch(interaction) + awaitquotesSearch(interaction) break; case 'random': - quotesRandom(interaction) + awaitquotesRandom(interaction) break; case 'leaderboard': await interaction.deferReply() - quotesLeaderboard(interaction) + awaitquotesLeaderboard(interaction) break; case 'list': - quotesList(interaction) + await quotesList(interaction) break; default: break; @@ -411,4 +423,4 @@ module.exports = { } }, -module.exports.quotesLeaderboard = quotesLeaderboard \ No newline at end of file + module.exports.quotesLeaderboard = quotesLeaderboard \ No newline at end of file diff --git a/events/InteractionCreate.js b/events/InteractionCreate.js index 1bb6815..34f1827 100644 --- a/events/InteractionCreate.js +++ b/events/InteractionCreate.js @@ -59,99 +59,120 @@ async function selectMenuHandler(interaction) { } } } + async function modalSubmitHandler(interaction) { - if (interaction.customId === 'wgeBaseModal') { - const db = mClient.db('neo-db') - const wgeColl = db.collection('timer_wge') - const participateColl = db.collection('items_wge') + if (interaction.customId !== 'wgeBaseModal') return; - // Get the data entered by the user + const db = mClient.db('neo-db'); + const wgeColl = db.collection('timer_wge'); + const participateColl = db.collection('items_wge'); + const historyWgeColl = db.collection('history_wge'); - var wgeAssignedUser = {} - wgeAssignedUser.id = interaction.components[0].component.values[0] + const assigningUser = interaction.user; + const selectedUserId = interaction.components[0].component.values[0]; + const topic = interaction.fields.getTextInputValue('wgeTopicInput'); + const defaultAssignedTime = 7 * 24 * 60 * 60 * 1000; // 7 days - const wgeAssigningUser = interaction.user - if (wgeAssignedUser.id === process.env.D_ID) { - // Randomizer Function - const randomParticipant = await participateColl.aggregate([ - { $match: { status: true } }, - { $sample: { size: 1 } } - ]).toArray() + // Get current running WGE so we can exclude the current expert + const prevWge = await wgeColl.findOne({ + debug: false, + state: 'running' + }); - if (!randomParticipant) { return interaction.followUp({ content: 'No User is participating!' }) } + let assignedUser; - random = await interaction.guild.members.fetch(randomParticipant[0].userID) - wgeAssignedUser = random.user - } else { - // validate non-random user - const foundParticipant = await participateColl.findOne({ - $and: [ - { assignedUser: wgeAssignedUser.id }, - { status: true} - ] - }) - - if(!foundParticipant || foundParticipant == null){ return interaction.reply({ - content: `<@${wgeAssignedUser.id}> is not participating!`, - flags: MessageFlags.Ephemeral - })} - } - - - const prevWge = await wgeColl.findOne({ - $and: [ - { debug: false }, - { state: 'running' } - ] - }) - - if (prevWge != null) { - // Save previous run in history_wge - const historyWgeColl = db.collection('history_wge') - await historyWgeColl.insertOne({ - assignedUser: prevWge.assignedUser, - assigningUser: prevWge.assigningUser, - timeRemaining: prevWge.timeRemaining, - topic: prevWge.topic, - debug: prevWge.debug, - createdAt: prevWge.createdAt - }); - - console.log({ - message: 'saved previous wge in history_wge', - data: prevWge - }) - - await wgeColl.deleteOne({ - _id: prevWge._id - }) - } - - const wgeAssignedTopic = interaction.fields.getTextInputValue('wgeTopicInput'); - - const wgeDefaultAssignedTime = 7 * 24 * 60 * 60 * 1000; // 7 days in ms - - await wgeColl.insertOne({ - assignedUser: wgeAssignedUser.id, - assigningUser: wgeAssigningUser.id, - - startedAt: Date.now(), - timeRemaining: wgeDefaultAssignedTime, - - state: 'running', - - notifications: { - half: false, - day24: false, - complete: false + // Random assignment + if (selectedUserId === process.env.D_ID) { + const randomParticipant = await participateColl.aggregate([ + { + $match: { + status: true, + ...(prevWge && { + userID: { $ne: prevWge.assignedUser } + }) + } }, + { $sample: { size: 1 } } + ]).toArray(); - topic: wgeAssignedTopic, - debug: false, + if (randomParticipant.length === 0) { + return interaction.reply({ + content: 'There are no eligible participants available.', + flags: MessageFlags.Ephemeral + }); + } - createdAt: new Date() + const member = await interaction.guild.members.fetch(randomParticipant[0].userID); + assignedUser = member.user; + } + // Manual assignment + else { + const participant = await participateColl.findOne({ + userID: selectedUserId, + status: true }); - return interaction.reply({ content: `<@${wgeAssignedUser.id}> is the next expert! Their topic is **${wgeAssignedTopic}**` }) + if (!participant) { + return interaction.reply({ + content: `<@${selectedUserId}> is not participating!`, + flags: MessageFlags.Ephemeral + }); + } + + if (prevWge && selectedUserId === prevWge.assignedUser) { + return interaction.reply({ + content: `<@${selectedUserId}> was the previous expert and cannot be selected again this round.`, + flags: MessageFlags.Ephemeral + }); + } + + assignedUser = { id: selectedUserId }; } + + // Archive previous WGE + if (prevWge) { + await historyWgeColl.insertOne({ + assignedUser: prevWge.assignedUser, + assigningUser: prevWge.assigningUser, + timeRemaining: prevWge.timeRemaining, + topic: prevWge.topic, + debug: prevWge.debug, + createdAt: prevWge.createdAt + }); + + console.log({ + message: 'Saved previous WGE to history_wge', + data: prevWge + }); + + await wgeColl.deleteOne({ + _id: prevWge._id + }); + } + + // Create new WGE + await wgeColl.insertOne({ + assignedUser: assignedUser.id, + assigningUser: assigningUser.id, + + startedAt: Date.now(), + timeRemaining: defaultAssignedTime, + + state: 'running', + + notifications: { + half: false, + day24: false, + complete: false + }, + + topic, + debug: false, + + createdAt: new Date() + }); + + return interaction.reply({ + content: `<@${assignedUser.id}> is the next expert! Their topic is **${topic}**` + }); } \ No newline at end of file diff --git a/events/MessageCreate.js b/events/MessageCreate.js index fdf8c6a..dc665bb 100644 --- a/events/MessageCreate.js +++ b/events/MessageCreate.js @@ -1,17 +1,23 @@ -const { Events, MessageFlags } = require('discord.js') -require('dotenv').config() -const { client, mClient } = require('../index') -const prefix = process.env.D_Prefix -const db = mClient.db(process.env.M_DB) +const { Events, MessageFlags } = require("discord.js"); +require("dotenv").config(); + +const { client, mClient } = require("../index"); + +const prefix = process.env.D_Prefix; +const db = mClient.db(process.env.M_DB); module.exports = { name: Events.MessageCreate, once: false, - async execute(message) { - if (message.author.bot) { return } - // Erster/Zweiter/Dritter Handler - if (message.channelId === '353902391021535244' || message.channelId === '471058991481487361') { + async execute(message) { + if (message.author.bot) return; + + /* + * Daily number handler + * Only does something if Ersti/Zweiti/Dritti is detected. + */ + if (message.channelId === "353902391021535244") { const numbered = { Erst: "Ersti", Zweit: "Zweiti", @@ -22,82 +28,93 @@ module.exports = { new RegExp(`\\b\\w*${stem}\\w*\\b`, "i").test(message.content) )?.[1]; - if (!found) return; + if (found) { + const numberedColl = db.collection("items_daily_numbers"); - const numberedColl = db.collection('items_daily_numbers'); - const today = new Date().toLocaleDateString("en-CA", { - timeZone: "Europe/Berlin" - }); - - // Tages-Dokument sicherstellen - await numberedColl.findOneAndUpdate( - { date: today }, - { - $setOnInsert: { - date: today, - positions: { - Ersti: null, - Zweiti: null, - Dritti: null - } - } - }, - { upsert: true } - ); - - // šŸ”’ Atomarer Claim: - // - Position muss frei sein - // - User darf noch keine Position heute haben - const result = await numberedColl.findOneAndUpdate( - { - date: today, - [`positions.${found}`]: null, - - "positions.Ersti.userId": { $ne: message.author.id }, - "positions.Zweiti.userId": { $ne: message.author.id }, - "positions.Dritti.userId": { $ne: message.author.id } - }, - { - $set: { - [`positions.${found}`]: { - userId: message.author.id, - timestamp: new Date() - } - } - }, - { returnDocument: "after" } - ); - - // āŒ bereits vergeben oder schon eine Position - if (!result) { - return message.reply({ - content: `āŒ Du kannst pro Tag nur eine Position beanspruchen, und sie muss noch frei sein.`, - flags: MessageFlags.Ephemeral + const today = new Date().toLocaleDateString("en-CA", { + timeZone: "Europe/Berlin" }); - } - // āœ… Erfolg - return message.reply({ - content: `āœ… Du bist ${found}! :point_right: :point_left: <@${message.author.id}>`, - flags: MessageFlags.Ephemeral - }); + await numberedColl.findOneAndUpdate( + { date: today }, + { + $setOnInsert: { + date: today, + positions: { + Ersti: null, + Zweiti: null, + Dritti: null + } + } + }, + { upsert: true } + ); + + const result = await numberedColl.findOneAndUpdate( + { + date: today, + [`positions.${found}`]: null, + "positions.Ersti.userId": { $ne: message.author.id }, + "positions.Zweiti.userId": { $ne: message.author.id }, + "positions.Dritti.userId": { $ne: message.author.id } + }, + { + $set: { + [`positions.${found}`]: { + userId: message.author.id, + timestamp: new Date() + } + } + }, + { + returnDocument: "after" + } + ); + + if (!result) { + await message.reply({ + content: + "āŒ Du kannst pro Tag nur eine Position beanspruchen, und sie muss noch frei sein.", + flags: MessageFlags.Ephemeral + }); + } else { + await message.reply({ + content: `āœ… Du bist ${found}! :point_right: :point_left: <@${message.author.id}>`, + flags: MessageFlags.Ephemeral + }); + } + + // Only stop here if this was actually a number claim. + return; + } } - // Command Handler - const args = message.content.slice(prefix.length).trim().split(/ +/); + /* + * Command handler + */ if (!message.content.startsWith(prefix)) return; + const args = message.content + .slice(prefix.length) + .trim() + .split(/ +/); + const cmd = args.shift().toLowerCase(); - let command = client.legacyCommands.get(cmd) + let command = client.legacyCommands.get(cmd); - if (!command) command = client.legacyCommands.get(client.aliases.get(cmd)) - if (!command) return - try { - client.legacyCommands.get(command.name).execute(message, args) - } catch (error) { - console.error(error) + if (!command) { + command = client.aliases.get(cmd) + ? client.legacyCommands.get(client.aliases.get(cmd)) + : null; } + if (!command) return; + + try { + await command.execute(message, args); + } catch (error) { + console.error(error); + } } -} +}; \ No newline at end of file diff --git a/events/timer24h.js b/events/timer24h.js index 5c60d37..ab5fa81 100644 --- a/events/timer24h.js +++ b/events/timer24h.js @@ -20,8 +20,15 @@ module.exports = { const member = await getMember(client, timer.assignedUser); if (!member) return; - await member.send( - `āš ļø Less than 24 hours remaining for: **${timer.topic}**` - ).catch(() => {}); + console.log(`āš ļø Less than 24 hours remaining for ${member.user.username}, notifying...`) + + try { + await member.send( + `āš ļø Less than 24 hours remaining for: **${timer.topic}**` + ) + console.log('notified successfully!') + } catch (e) { + console.error(`notification failed, errorr ${e}`) + } } }; \ No newline at end of file diff --git a/events/timerHalf.js b/events/timerHalf.js index 0c3eaa8..2daee3b 100644 --- a/events/timerHalf.js +++ b/events/timerHalf.js @@ -20,8 +20,14 @@ module.exports = { const member = await getMember(client, timer.assignedUser); if (!member) return; - await member.send( + console.log(`ā³ Half time reached for: ${member.user.username}, notifying...`) + try { + await member.send( `ā³ Half time reached for: **${timer.topic}**` - ).catch(() => {}); + ) + console.log('notified successfully!') + } catch (e) { + console.error(`notification failed, errorr ${e}`) + } } }; \ No newline at end of file