mirror of
https://github.com/aydenjahola/discord-multipurpose-bot.git
synced 2024-11-22 16:55:55 +00:00
trivia: quick refactor
This commit is contained in:
parent
04254e660c
commit
d38b9e531a
1 changed files with 214 additions and 198 deletions
|
@ -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);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in a new issue