diff --git a/commands/fun/trivia.js b/commands/fun/trivia.js index bfb7bad..d854388 100644 --- a/commands/fun/trivia.js +++ b/commands/fun/trivia.js @@ -12,7 +12,17 @@ let lastApiCall = 0; module.exports = { data: new SlashCommandBuilder() .setName("trivia") - .setDescription("Play a trivia game about video games"), + .setDescription("Play a trivia game") + .addStringOption((option) => + option + .setName("category") + .setDescription("Choose a trivia category") + .setRequired(true) + .addChoices( + { name: "Video Games", value: "15" }, + { name: "Anime & Manga", value: "31" } + ) + ), async execute(interaction, client) { const userId = interaction.user.id; @@ -20,151 +30,146 @@ module.exports = { const guild = interaction.guild; const timeLimit = 30000; // Time limit for answering in milliseconds - try { - // 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 - }).sort({ last_served: 1 }); + const categoryId = interaction.options.getString("category"); + const categoryName = categoryId === "15" ? "Video Games" : "Anime & Manga"; - 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=15" // Category 15 is for Video Games - ); + // 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 }); - 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), - last_served: null, // Initially not served - }); - - // Fetch the newly created question - triviaQuestion = await TriviaQuestion.findOne({ - question: decode(triviaQuestion.question), - }); - } - - 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); - const allAnswers = [...incorrectAnswers, correctAnswer].sort( - () => Math.random() - 0.5 + 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}` ); - // Create a mapping of numbers to answers - const answerMap = allAnswers.reduce((map, answer, index) => { - map[index + 1] = answer; - return map; - }, {}); + triviaQuestion = response.data.results[0]; + lastApiCall = Date.now(); - // 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({ - content: `<@${userId}>`, - embeds: [triviaEmbed], + // 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 }); - // Create a message collector specific to the user - const filter = (response) => { - const userInput = response.content.trim(); - const userAnswerNumber = parseInt(userInput, 10); - const userAnswerText = - allAnswers.includes(userInput) || - (answerMap[userAnswerNumber] && - answerMap[userAnswerNumber] === correctAnswer); + // Fetch the newly created question + triviaQuestion = await TriviaQuestion.findOne({ + question: decode(triviaQuestion.question), + category: categoryName, // Filter by category + }); + } - // 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)) - ); - }; + if (triviaQuestion) { + triviaQuestion.last_served = new Date(); + await triviaQuestion.save(); + } - const collector = interaction.channel.createMessageCollector({ - filter, - max: 1, - time: timeLimit, + 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(), }); - collector.on("collect", async (message) => { - const userInput = message.content.trim(); - const userAnswerNumber = parseInt(userInput, 10); - const userAnswer = answerMap[userAnswerNumber] || userInput; + await interaction.reply({ + embeds: [triviaEmbed], + }); - let resultMessage = "Incorrect! Better luck next time."; + // 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); - if (userAnswer === correctAnswer) { - resultMessage = "Correct!"; - } + // 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)) + ); + }; - // 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(); + const answerCollector = interaction.channel.createMessageCollector({ + filter: answerFilter, + max: 1, + time: timeLimit, + }); - await interaction.followUp( - `${resultMessage} <@${userId}> You've answered ${userScore.correctAnswers} questions correctly out of ${userScore.gamesPlayed} games.` - ); - }); + answerCollector.on("collect", async (message) => { + const userInput = message.content.trim(); + const userAnswerNumber = parseInt(userInput, 10); + const userAnswer = answerMap[userAnswerNumber] || userInput; - collector.on("end", (collected, reason) => { - if (reason === "time") { - interaction.followUp( - `<@${userId}> Time's up! You didn't answer in time.` - ); - } - }); - } catch (error) { - console.error("Error executing trivia command:", error); - if (error.response && error.response.status === 429) { - await interaction.reply({ - content: `<@${userId}> The trivia API rate limit has been exceeded. Please try in 5 seconds.`, - ephemeral: true, + 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 { - await interaction.reply({ - content: `<@${userId}> There was an error while executing this command!`, - ephemeral: true, - }); + 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.` + ); + }); + + answerCollector.on("end", (collected, reason) => { + if (reason === "time") { + interaction.followUp( + `<@${userId}> Time's up! You didn't answer in time.` + ); + } + }); }, }; diff --git a/commands/moderation/clearLeaderboard.js b/commands/moderation/clearLeaderboard.js new file mode 100644 index 0000000..54980d4 --- /dev/null +++ b/commands/moderation/clearLeaderboard.js @@ -0,0 +1,37 @@ +const { SlashCommandBuilder } = require("discord.js"); +const Leaderboard = require("../../models/Leaderboard"); + +module.exports = { + data: new SlashCommandBuilder() + .setName("clearleaderboard") + .setDescription("Clears all entries in the trivia leaderboard"), + isModOnly: true, + + async execute(interaction) { + try { + const requiredRoleId = process.env.MOD_ROLE_ID; + if (!interaction.member.roles.cache.has(requiredRoleId)) { + await interaction.reply({ + content: "You do not have the required role to use this command!", + ephemeral: true, + }); + return; + } + + // Clear the leaderboard + await Leaderboard.deleteMany({}); + + // Notify the mod who executed the command + await interaction.reply({ + content: "The leaderboard has been cleared successfully.", + ephemeral: true, + }); + } catch (error) { + console.error("Error executing clearleaderboard command:", error); + await interaction.reply({ + content: "There was an error while executing this command!", + ephemeral: true, + }); + } + }, +}; diff --git a/models/TriviaQuestion.js b/models/TriviaQuestion.js index efe588c..18641e1 100644 --- a/models/TriviaQuestion.js +++ b/models/TriviaQuestion.js @@ -4,6 +4,7 @@ const triviaQuestionSchema = new mongoose.Schema({ question: String, correct_answer: String, incorrect_answers: [String], + category: String, last_served: Date, // Track when the question was last served timestamp: { type: Date, default: Date.now }, });