trivia: quick refactor

This commit is contained in:
Ayden Jahola 2024-09-06 02:11:06 +01:00
parent 04254e660c
commit d38b9e531a
No known key found for this signature in database
GPG key ID: 71DD90AE4AE92742

View file

@ -10,205 +10,130 @@ const ongoingTrivia = new Set(); // Track users with ongoing trivia
let lastApiCall = 0; let lastApiCall = 0;
module.exports = { const CATEGORY_MAP = {
data: new SlashCommandBuilder() 15: "Video Games",
.setName("trivia") 31: "Anime & Manga",
.setDescription("Play a trivia game") 18: "Computers",
.addStringOption((option) => 16: "Board Games",
option 29: "Comics",
.setName("category") 32: "Cartoons & Animations",
.setDescription("Choose a trivia category") 11: "Film",
.setRequired(true) 9: "General Knowledge",
.addChoices( 17: "Science & Nature",
{ name: "General Knowledge", value: "9" }, 27: "Animals",
{ name: "Video Games", value: "15" }, 12: "Music",
{ name: "Anime & Manga", value: "31" }, 23: "History",
{ name: "Computers", value: "18" }, 22: "Geography",
{ name: "Board Games", value: "16" }, 20: "Mythology",
{ name: "Comics", value: "29" }, };
{ name: "Cartoons & Animations", value: "32" },
{ name: "Film", value: "11" },
{ name: "Science & Nature", value: "17" },
{ name: "Animals", value: "27" },
{ name: "Music", value: "12" },
{ name: "History", value: "23" },
{ name: "Geography", value: "22" },
{ name: "Mythology", value: "20" }
)
),
async execute(interaction, client) { const fetchTriviaQuestion = async (categoryId, categoryName) => {
const userId = interaction.user.id; try {
const username = interaction.user.username; let triviaQuestion = await TriviaQuestion.findOne({
const guild = interaction.guild; last_served: { $lt: new Date(Date.now() - QUESTION_EXPIRY) },
const timeLimit = 30000; // Time limit for answering in milliseconds category: categoryName,
}).sort({ last_served: 1 });
// Check if the user already has an ongoing trivia game if (!triviaQuestion || Date.now() - lastApiCall >= API_INTERVAL) {
if (ongoingTrivia.has(userId)) { const response = await axios.get(
return interaction.reply({ `https://opentdb.com/api.php?amount=1&category=${categoryId}`
content: );
"You already have an ongoing trivia game. Please finish it before starting a new one.", triviaQuestion = response.data.results[0];
ephemeral: true, // Only visible to the user lastApiCall = Date.now();
await TriviaQuestion.create({
question: decode(triviaQuestion.question),
correct_answer: decode(triviaQuestion.correct_answer),
incorrect_answers: triviaQuestion.incorrect_answers.map(decode),
category: categoryName,
last_served: null,
});
triviaQuestion = await TriviaQuestion.findOne({
question: decode(triviaQuestion.question),
category: categoryName,
}); });
} }
// Add the user to the set of active trivia players if (triviaQuestion) {
ongoingTrivia.add(userId); triviaQuestion.last_served = new Date();
await triviaQuestion.save();
}
try { return triviaQuestion;
const categoryId = interaction.options.getString("category"); } catch (error) {
const categoryName = (() => { console.error("Error fetching or saving trivia question:", error);
switch (categoryId) { throw new Error("Error fetching trivia question");
case "15": }
return "Video Games"; };
case "31":
return "Anime & Manga";
case "18":
return "Computers";
case "16":
return "Board Games";
case "29":
return "Comics";
case "32":
return "Cartoons & Animations";
case "11":
return "Film";
case "9":
return "General Knowledge";
case "17":
return "Science & Nature";
case "27":
return "Animals";
case "12":
return "Music";
case "23":
return "History";
case "22":
return "Geography";
case "20":
return "Mythology";
default:
return "Video Games";
}
})()
.replace(/[^a-zA-Z0-9 &]/g, "")
.trim(); // Remove special characters and trim the category name for MongoDB query purposes in the Emebed title
// Fetch a trivia question from the cache or the API const createTriviaEmbed = (
let triviaQuestion = await TriviaQuestion.findOne({ categoryName,
last_served: { $lt: new Date(Date.now() - QUESTION_EXPIRY) }, // Fetch questions not served recently question,
category: categoryName, // Filter by category answerMap,
}).sort({ last_served: 1 }); guild,
timeLimit
) => {
return new EmbedBuilder()
.setColor("#0099ff")
.setTitle(`${categoryName} 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(),
});
};
if (!triviaQuestion || Date.now() - lastApiCall >= API_INTERVAL) { const handleAnswerCollection = async (
// Fetch a new trivia question from OTDB interaction,
const response = await axios.get( triviaQuestion,
`https://opentdb.com/api.php?amount=1&category=${categoryId}` answerMap,
); correctAnswer,
allAnswers,
timeLimit,
userId,
username
) => {
try {
const answerFilter = (response) => {
const userInput = response.content.trim();
const userAnswerNumber = parseInt(userInput, 10);
const userAnswerText =
allAnswers.includes(userInput) ||
(answerMap[userAnswerNumber] &&
answerMap[userAnswerNumber] === correctAnswer);
triviaQuestion = response.data.results[0]; return (
lastApiCall = Date.now(); response.author.id === userId &&
(userAnswerText || (userAnswerNumber >= 1 && userAnswerNumber <= 4))
);
};
// Save the new trivia question to MongoDB const answerCollector = interaction.channel.createMessageCollector({
await TriviaQuestion.create({ filter: answerFilter,
question: decode(triviaQuestion.question), max: 1,
correct_answer: decode(triviaQuestion.correct_answer), time: timeLimit,
incorrect_answers: triviaQuestion.incorrect_answers.map(decode), });
category: categoryName, // Include the category
last_served: null, // Initially not served
});
// Fetch the newly created question answerCollector.on("collect", async (message) => {
triviaQuestion = await TriviaQuestion.findOne({ try {
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];
// Declare answerMap before any conditions
let answerMap = {};
// Handle True/False questions specifically
if (triviaQuestion.type === "boolean") {
// Always keep "True" as option 1 and "False" as option 2
answerMap = { 1: "True", 2: "False" };
} else {
// Shuffle answers for other types of questions
allAnswers = allAnswers.sort(() => Math.random() - 0.5);
// Assign the map without redeclaring
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(`${categoryName} 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 userInput = message.content.trim();
const userAnswerNumber = parseInt(userInput, 10); const userAnswerNumber = parseInt(userInput, 10);
const userAnswer = answerMap[userAnswerNumber] || userInput; const userAnswer = answerMap[userAnswerNumber] || userInput;
let resultMessage = "Incorrect! Better luck next time."; let resultMessage =
userAnswer === correctAnswer
? "Correct!"
: "Incorrect! Better luck next time.";
if (userAnswer === correctAnswer) {
resultMessage = "Correct!";
}
// Update leaderboard
let userScore = await Leaderboard.findOne({ userId }); let userScore = await Leaderboard.findOne({ userId });
if (!userScore) { if (!userScore) {
userScore = new Leaderboard({ userScore = new Leaderboard({
@ -229,31 +154,122 @@ module.exports = {
`${resultMessage} <@${userId}> You've answered ${userScore.correctAnswers} questions correctly out of ${userScore.gamesPlayed} games.` `${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); ongoingTrivia.delete(userId);
}); } catch (error) {
console.error("Error processing collected answer:", error);
await interaction.followUp({
content: "There was an error processing your answer.",
ephemeral: true,
});
ongoingTrivia.delete(userId);
}
});
answerCollector.on("end", (collected, reason) => { answerCollector.on("end", (collected, reason) => {
if (reason === "time") { if (reason === "time") {
interaction.followUp( interaction.followUp(
`<@${userId}> Time's up! You didn't answer in time.` `<@${userId}> Time's up! You didn't answer in time.`
); );
ongoingTrivia.delete(userId);
}
});
} catch (error) {
console.error("Error handling answer collection:", error);
await interaction.followUp({
content: "There was an error handling your response.",
ephemeral: true,
});
ongoingTrivia.delete(userId);
}
};
// Remove user from the ongoing trivia set when the game times out module.exports = {
ongoingTrivia.delete(userId); data: new SlashCommandBuilder()
} .setName("trivia")
.setDescription("Play a trivia game")
.addStringOption((option) =>
option
.setName("category")
.setDescription("Choose a trivia category")
.setRequired(true)
.addChoices(
...Object.entries(CATEGORY_MAP).map(([value, name]) => ({
name,
value,
}))
)
),
async execute(interaction, client) {
const userId = interaction.user.id;
const username = interaction.user.username;
const guild = interaction.guild;
const timeLimit = 30000; // Time limit for answering in milliseconds
if (ongoingTrivia.has(userId)) {
return interaction.reply({
content:
"You already have an ongoing trivia game. Please finish it before starting a new one.",
ephemeral: true,
}); });
}
ongoingTrivia.add(userId);
try {
const categoryId = interaction.options.getString("category");
const categoryName = CATEGORY_MAP[categoryId] || "Video Games";
const triviaQuestion = await fetchTriviaQuestion(
categoryId,
categoryName
);
if (!triviaQuestion) throw new Error("Failed to fetch trivia question");
const question = decode(triviaQuestion.question);
const correctAnswer = decode(triviaQuestion.correct_answer);
const incorrectAnswers = triviaQuestion.incorrect_answers.map(decode);
let allAnswers = [...incorrectAnswers, correctAnswer];
let answerMap = {};
if (triviaQuestion.type === "boolean") {
answerMap = { 1: "True", 2: "False" };
} else {
allAnswers = allAnswers.sort(() => Math.random() - 0.5);
answerMap = allAnswers.reduce((map, answer, index) => {
map[index + 1] = answer;
return map;
}, {});
}
const triviaEmbed = createTriviaEmbed(
categoryName,
question,
answerMap,
guild,
timeLimit
);
await interaction.reply({ embeds: [triviaEmbed] });
await handleAnswerCollection(
interaction,
triviaQuestion,
answerMap,
correctAnswer,
allAnswers,
timeLimit,
userId,
username
);
} catch (error) { } catch (error) {
console.error("Error fetching trivia question:", error); console.error("Error executing trivia command:", error);
// Inform the user about the error and let them retry
await interaction.reply({ await interaction.reply({
content: content:
"Trivia API hit the rate limit. Please try again in 5 seconds.", "Trivia API hit the rate limit. Please try again in 5 seconds.",
ephemeral: true, ephemeral: true,
}); });
// Remove the user from the ongoing trivia set in case of an error
ongoingTrivia.delete(userId); ongoingTrivia.delete(userId);
} }
}, },