diff --git a/commands/fun/trivia.js b/commands/fun/trivia.js index 0419a3d..34df497 100644 --- a/commands/fun/trivia.js +++ b/commands/fun/trivia.js @@ -43,152 +43,167 @@ module.exports = { // Add the user to the set of active trivia players ongoingTrivia.add(userId); - const categoryId = interaction.options.getString("category"); - const categoryName = categoryId === "15" ? "Video Games" : "Anime & Manga"; + try { + const categoryId = interaction.options.getString("category"); + const categoryName = + categoryId === "15" ? "Video Games" : "Anime & Manga"; - // Fetch a trivia question from the cache or the API - let triviaQuestion = await TriviaQuestion.findOne({ - last_served: { $lt: new Date(Date.now() - QUESTION_EXPIRY) }, // Fetch questions not served recently - category: categoryName, // Filter by category - }).sort({ last_served: 1 }); - - if (!triviaQuestion || Date.now() - lastApiCall >= API_INTERVAL) { - // Fetch a new trivia question from OTDB - const response = await axios.get( - `https://opentdb.com/api.php?amount=1&category=${categoryId}` - ); - - triviaQuestion = response.data.results[0]; - lastApiCall = Date.now(); - - // Save the new trivia question to MongoDB - await TriviaQuestion.create({ - question: decode(triviaQuestion.question), - correct_answer: decode(triviaQuestion.correct_answer), - incorrect_answers: triviaQuestion.incorrect_answers.map(decode), - category: categoryName, // Include the category - last_served: null, // Initially not served - }); - - // Fetch the newly created question - triviaQuestion = await TriviaQuestion.findOne({ - question: decode(triviaQuestion.question), + // Fetch a trivia question from the cache or the API + let triviaQuestion = await TriviaQuestion.findOne({ + last_served: { $lt: new Date(Date.now() - QUESTION_EXPIRY) }, // Fetch questions not served recently category: categoryName, // Filter by category - }); - } + }).sort({ last_served: 1 }); - if (triviaQuestion) { - triviaQuestion.last_served = new Date(); - await triviaQuestion.save(); - } - - const question = decode(triviaQuestion.question); - const correctAnswer = decode(triviaQuestion.correct_answer); - const incorrectAnswers = triviaQuestion.incorrect_answers.map(decode); - let allAnswers = [...incorrectAnswers, correctAnswer]; - - // Handle True/False questions specifically - if (triviaQuestion.type === "boolean") { - allAnswers = ["True", "False"]; - } - - allAnswers = allAnswers.sort(() => Math.random() - 0.5); // Shuffle answers - - // Create a mapping of numbers to answers - const answerMap = allAnswers.reduce((map, answer, index) => { - map[index + 1] = answer; - return map; - }, {}); - - // Create an embed with the trivia question and numbered options - const triviaEmbed = new EmbedBuilder() - .setColor("#0099ff") - .setTitle("Trivia Question") - .setDescription(question) - .addFields( - Object.entries(answerMap).map(([number, answer]) => ({ - name: `Option ${number}`, - value: answer, - inline: true, - })) - ) - .setTimestamp() - .setFooter({ - text: `${guild.name} | Answer within ${timeLimit / 1000} seconds`, - iconURL: guild.iconURL(), - }); - - await interaction.reply({ - embeds: [triviaEmbed], - }); - - // Create a message collector specific to the user - const answerFilter = (response) => { - const userInput = response.content.trim(); - const userAnswerNumber = parseInt(userInput, 10); - const userAnswerText = - allAnswers.includes(userInput) || - (answerMap[userAnswerNumber] && - answerMap[userAnswerNumber] === correctAnswer); - - // Check if the input is a number within valid range or a text that matches one of the options - return ( - response.author.id === userId && - (userAnswerText || (userAnswerNumber >= 1 && userAnswerNumber <= 4)) - ); - }; - - const answerCollector = interaction.channel.createMessageCollector({ - filter: answerFilter, - max: 1, - time: timeLimit, - }); - - answerCollector.on("collect", async (message) => { - const userInput = message.content.trim(); - const userAnswerNumber = parseInt(userInput, 10); - const userAnswer = answerMap[userAnswerNumber] || userInput; - - let resultMessage = "Incorrect! Better luck next time."; - - if (userAnswer === correctAnswer) { - resultMessage = "Correct!"; - } - - // Update leaderboard - let userScore = await Leaderboard.findOne({ userId }); - if (!userScore) { - userScore = new Leaderboard({ - userId, - username, - gamesPlayed: 1, - correctAnswers: userAnswer === correctAnswer ? 1 : 0, - }); - } else { - userScore.gamesPlayed += 1; - if (userAnswer === correctAnswer) { - userScore.correctAnswers += 1; - } - } - await userScore.save(); - - await interaction.followUp( - `${resultMessage} <@${userId}> You've answered ${userScore.correctAnswers} questions correctly out of ${userScore.gamesPlayed} games.` - ); - - // Remove user from the ongoing trivia set once the game is finished - ongoingTrivia.delete(userId); - }); - - answerCollector.on("end", (collected, reason) => { - if (reason === "time") { - interaction.followUp( - `<@${userId}> Time's up! You didn't answer in time.` + if (!triviaQuestion || Date.now() - lastApiCall >= API_INTERVAL) { + // Fetch a new trivia question from OTDB + const response = await axios.get( + `https://opentdb.com/api.php?amount=1&category=${categoryId}` ); - // Remove user from the ongoing trivia set when the game times out - ongoingTrivia.delete(userId); + triviaQuestion = response.data.results[0]; + lastApiCall = Date.now(); + + // Save the new trivia question to MongoDB + await TriviaQuestion.create({ + question: decode(triviaQuestion.question), + correct_answer: decode(triviaQuestion.correct_answer), + incorrect_answers: triviaQuestion.incorrect_answers.map(decode), + category: categoryName, // Include the category + last_served: null, // Initially not served + }); + + // Fetch the newly created question + triviaQuestion = await TriviaQuestion.findOne({ + question: decode(triviaQuestion.question), + category: categoryName, // Filter by category + }); } - }); + + if (triviaQuestion) { + triviaQuestion.last_served = new Date(); + await triviaQuestion.save(); + } + + const question = decode(triviaQuestion.question); + const correctAnswer = decode(triviaQuestion.correct_answer); + const incorrectAnswers = triviaQuestion.incorrect_answers.map(decode); + let allAnswers = [...incorrectAnswers, correctAnswer]; + + // Handle True/False questions specifically + if (triviaQuestion.type === "boolean") { + allAnswers = ["True", "False"]; + } + + allAnswers = allAnswers.sort(() => Math.random() - 0.5); // Shuffle answers + + // Create a mapping of numbers to answers + const answerMap = allAnswers.reduce((map, answer, index) => { + map[index + 1] = answer; + return map; + }, {}); + + // Create an embed with the trivia question and numbered options + const triviaEmbed = new EmbedBuilder() + .setColor("#0099ff") + .setTitle("Trivia Question") + .setDescription(question) + .addFields( + Object.entries(answerMap).map(([number, answer]) => ({ + name: `Option ${number}`, + value: answer, + inline: true, + })) + ) + .setTimestamp() + .setFooter({ + text: `${guild.name} | Answer within ${timeLimit / 1000} seconds`, + iconURL: guild.iconURL(), + }); + + await interaction.reply({ + embeds: [triviaEmbed], + }); + + // Create a message collector specific to the user + const answerFilter = (response) => { + const userInput = response.content.trim(); + const userAnswerNumber = parseInt(userInput, 10); + const userAnswerText = + allAnswers.includes(userInput) || + (answerMap[userAnswerNumber] && + answerMap[userAnswerNumber] === correctAnswer); + + // Check if the input is a number within valid range or a text that matches one of the options + return ( + response.author.id === userId && + (userAnswerText || (userAnswerNumber >= 1 && userAnswerNumber <= 4)) + ); + }; + + const answerCollector = interaction.channel.createMessageCollector({ + filter: answerFilter, + max: 1, + time: timeLimit, + }); + + answerCollector.on("collect", async (message) => { + const userInput = message.content.trim(); + const userAnswerNumber = parseInt(userInput, 10); + const userAnswer = answerMap[userAnswerNumber] || userInput; + + let resultMessage = "Incorrect! Better luck next time."; + + if (userAnswer === correctAnswer) { + resultMessage = "Correct!"; + } + + // Update leaderboard + let userScore = await Leaderboard.findOne({ userId }); + if (!userScore) { + userScore = new Leaderboard({ + userId, + username, + gamesPlayed: 1, + correctAnswers: userAnswer === correctAnswer ? 1 : 0, + }); + } else { + userScore.gamesPlayed += 1; + if (userAnswer === correctAnswer) { + userScore.correctAnswers += 1; + } + } + await userScore.save(); + + await interaction.followUp( + `${resultMessage} <@${userId}> You've answered ${userScore.correctAnswers} questions correctly out of ${userScore.gamesPlayed} games.` + ); + + // Remove user from the ongoing trivia set once the game is finished + ongoingTrivia.delete(userId); + }); + + answerCollector.on("end", (collected, reason) => { + if (reason === "time") { + interaction.followUp( + `<@${userId}> Time's up! You didn't answer in time.` + ); + + // Remove user from the ongoing trivia set when the game times out + ongoingTrivia.delete(userId); + } + }); + } catch (error) { + console.error("Error fetching trivia question:", error); + + // Inform the user about the error and let them retry + await interaction.reply({ + content: + "There was an error fetching your trivia question. Please try again later.", + ephemeral: true, + }); + + // Remove the user from the ongoing trivia set in case of an error + ongoingTrivia.delete(userId); + } }, };