Merge pull request #4 from Fretuks/main

Fix quiz command
This commit is contained in:
2026-01-27 09:04:27 +01:00
committed by GitHub
6 changed files with 252 additions and 25 deletions
+2 -2
View File
@@ -40,8 +40,8 @@ Im going to make a To-do List for the features i want to implement for my Bot.
3. **Minigames** 3. **Minigames**
- [ ] Implement a Quiz Game: - [ ] Implement a Quiz Game:
- [x] Add a `/quiz` command to start the game. - [x] Add a `/quiz` command to start the game.
- [ ] Provide multiple-choice or true/false questions. - [x] Provide multiple-choice or true/false questions.
- [ ] Award virtual currency for correct answers. - [x] Award virtual currency for correct answers.
- [ ] Add a simple number-guessing game (e.g., `!guess [number]`). - [ ] Add a simple number-guessing game (e.g., `!guess [number]`).
- [ ] Create a leaderboard for game winners. - [ ] Create a leaderboard for game winners.
+124 -22
View File
@@ -1,43 +1,145 @@
const quiz = require('./quiz.json'); const quiz = require('./quiz.json');
const {SlashCommandBuilder} = require("discord.js"); const {
SlashCommandBuilder,
ActionRowBuilder,
ButtonBuilder,
ButtonStyle,
EmbedBuilder,
} = require('discord.js');
const { connectToDatabase } = require('../../db.js');
module.exports = { const QUIZ_REWARD = 50;
data: new SlashCommandBuilder() const QUIZ_TIMEOUT_MS = 30000;
.setName("quiz")
.setDescription("Start a cool quiz"), const normalizeAnswer = (answer) => answer.trim().toLowerCase();
category: 'Games',
async execute(interaction) { const buildRows = (choices) => {
const item = quiz[Math.floor(Math.random() * quiz.length)]; const rows = [];
await interaction.reply({ for (let i = 0; i < choices.length; i += 5) {
content: `**Quiz Time!**\n${item.question}\n\n_You have 30 seconds to answer!_`, const slice = choices.slice(i, i + 5);
const row = new ActionRowBuilder().addComponents(
slice.map((choice, index) =>
new ButtonBuilder()
.setCustomId(`quiz_choice_${i + index}`)
.setLabel(choice)
.setStyle(ButtonStyle.Primary)
)
);
rows.push(row);
}
return rows;
};
const disableRows = (rows) =>
rows.map((row) => {
const disabledRow = ActionRowBuilder.from(row);
disabledRow.components = disabledRow.components.map((component) =>
ButtonBuilder.from(component).setDisabled(true)
);
return disabledRow;
}); });
// Define a filter to check the correct answer const awardReward = async (userId) => {
const collectorFilter = (response) => { const dbClient = await connectToDatabase();
return ( const db = dbClient.db('discord');
response.author.id === interaction.user.id && // Ensure it's the same user const collection = db.collection('currency');
item.answers.some(
(answer) => answer.toLowerCase() === response.content.toLowerCase() await collection.updateOne(
) { userID: userId },
{ $inc: { balance: QUIZ_REWARD } },
{ upsert: true }
); );
}; };
module.exports = {
data: new SlashCommandBuilder()
.setName('quiz')
.setDescription('Start a cool quiz'),
category: 'Games',
async execute(interaction) {
const item = quiz[Math.floor(Math.random() * quiz.length)];
const answers = (item.answers ?? [])
.map(normalizeAnswer)
.filter(Boolean);
const hasChoices = Array.isArray(item.choices) && item.choices.length > 0;
const embed = new EmbedBuilder()
.setColor(0xf7b731)
.setTitle('Quiz Time!')
.setDescription(item.question)
.setFooter({ text: 'You have 30 seconds to answer.' });
if (!interaction.channel) {
return interaction.reply({
content: 'I need a channel to run the quiz.',
ephemeral: true,
});
}
if (hasChoices) {
const choices = item.choices;
const rows = buildRows(choices);
const reply = await interaction.reply({
embeds: [embed],
components: rows,
fetchReply: true,
});
try {
const selection = await reply.awaitMessageComponent({
filter: (component) => component.user.id === interaction.user.id,
time: QUIZ_TIMEOUT_MS,
});
const choiceIndex = Number(selection.customId.split('_').pop());
const selected = choices[choiceIndex];
const isCorrect = answers.includes(normalizeAnswer(selected));
const resultText = isCorrect
? `${selection.user} nailed it! **${selected}** is correct. +${QUIZ_REWARD} coins! 🎉`
: `Nice try! The correct answer was **${item.answers[0]}**.`;
if (isCorrect) {
await awardReward(selection.user.id);
}
await selection.update({
content: resultText,
embeds: [],
components: disableRows(rows),
});
} catch (err) {
await interaction.editReply({
content: `⏳ Time's up! The correct answer was **${item.answers[0]}**.`,
embeds: [],
components: disableRows(rows),
});
}
return;
}
await interaction.reply({ embeds: [embed] });
const collectorFilter = (response) =>
response.author.id === interaction.user.id &&
answers.includes(normalizeAnswer(response.content));
try { try {
// Await messages in the same channel
const collected = await interaction.channel.awaitMessages({ const collected = await interaction.channel.awaitMessages({
filter: collectorFilter, filter: collectorFilter,
max: 1, max: 1,
time: 30000, // 30 seconds time: QUIZ_TIMEOUT_MS,
errors: ['time'], errors: ['time'],
}); });
// If a message is collected, reply with the correct response
const correctAnswer = collected.first(); const correctAnswer = collected.first();
await awardReward(correctAnswer.author.id);
await interaction.followUp( await interaction.followUp(
`${correctAnswer.author} got the correct answer: **${correctAnswer.content}**! 🎉` `${correctAnswer.author} got the correct answer: **${correctAnswer.content}**! +${QUIZ_REWARD} coins! 🎉`
); );
} catch (err) { } catch (err) {
// If no one answers in time
await interaction.followUp( await interaction.followUp(
`⏳ Time's up! Nobody got the correct answer. The correct answers were: **${item.answers.join(', ')}**.` `⏳ Time's up! Nobody got the correct answer. The correct answers were: **${item.answers.join(', ')}**.`
); );
+15 -3
View File
@@ -1,10 +1,22 @@
[ [
{ {
"question": "What color is the sky?", "question": "What color is the clear daytime sky?",
"choices": ["Blue", "Green", "Red", "Yellow"],
"answers": ["blue"] "answers": ["blue"]
}, },
{ {
"question": "How many letters are there in the alphabet?", "question": "How many letters are there in the English alphabet?",
"answers": ["26", "twenty-six", "twenty six", "twentysix"] "choices": ["24", "25", "26", "27"],
"answers": ["26"]
},
{
"question": "True or False: The Earth orbits the Sun.",
"choices": ["True", "False"],
"answers": ["true"]
},
{
"question": "Which planet is known as the Red Planet?",
"choices": ["Mars", "Venus", "Jupiter", "Mercury"],
"answers": ["mars"]
} }
] ]
+25
View File
@@ -0,0 +1,25 @@
const { SlashCommandBuilder, EmbedBuilder } = require('discord.js');
module.exports = {
data: new SlashCommandBuilder()
.setName('avatar')
.setDescription('Display a user\'s avatar in full size.')
.addUserOption((option) =>
option
.setName('user')
.setDescription('The user to fetch the avatar for.')
),
category: 'Utility',
async execute(interaction) {
const targetUser = interaction.options.getUser('user') ?? interaction.user;
const avatarUrl = targetUser.displayAvatarURL({ size: 1024, extension: 'png' });
const embed = new EmbedBuilder()
.setColor(0xa55eea)
.setTitle(`${targetUser.username}'s Avatar`)
.setImage(avatarUrl)
.setTimestamp();
await interaction.reply({ embeds: [embed] });
},
};
+54
View File
@@ -0,0 +1,54 @@
const { SlashCommandBuilder, EmbedBuilder } = require('discord.js');
const clamp = (value, min, max) => Math.min(Math.max(value, min), max);
const rollDice = (count, sides) => {
const rolls = [];
for (let i = 0; i < count; i += 1) {
rolls.push(1 + Math.floor(Math.random() * sides));
}
return rolls;
};
module.exports = {
data: new SlashCommandBuilder()
.setName('roll')
.setDescription('Roll one or more dice with a custom number of sides.')
.addIntegerOption((option) =>
option
.setName('sides')
.setDescription('Number of sides on each die (2-100).')
.setMinValue(2)
.setMaxValue(100)
)
.addIntegerOption((option) =>
option
.setName('count')
.setDescription('How many dice to roll (1-6).')
.setMinValue(1)
.setMaxValue(6)
),
category: 'Utility',
async execute(interaction) {
const sidesInput = interaction.options.getInteger('sides') ?? 6;
const countInput = interaction.options.getInteger('count') ?? 1;
const sides = clamp(sidesInput, 2, 100);
const count = clamp(countInput, 1, 6);
const rolls = rollDice(count, sides);
const total = rolls.reduce((sum, roll) => sum + roll, 0);
const embed = new EmbedBuilder()
.setColor(0x20bf6b)
.setTitle('Dice Roll')
.addFields(
{ name: 'Sides', value: `${sides}`, inline: true },
{ name: 'Dice', value: `${count}`, inline: true },
{ name: 'Total', value: `${total}`, inline: true },
{ name: 'Rolls', value: rolls.join(', ') }
)
.setTimestamp();
await interaction.reply({ embeds: [embed] });
},
};
+34
View File
@@ -0,0 +1,34 @@
const { SlashCommandBuilder, EmbedBuilder } = require('discord.js');
const formatDuration = (totalSeconds) => {
const seconds = Math.floor(totalSeconds % 60);
const minutes = Math.floor((totalSeconds / 60) % 60);
const hours = Math.floor((totalSeconds / 3600) % 24);
const days = Math.floor(totalSeconds / 86400);
const parts = [
days ? `${days}d` : null,
hours ? `${hours}h` : null,
minutes ? `${minutes}m` : null,
`${seconds}s`,
].filter(Boolean);
return parts.join(' ');
};
module.exports = {
data: new SlashCommandBuilder()
.setName('uptime')
.setDescription('Display how long the bot has been running.'),
category: 'Utility',
async execute(interaction) {
const uptimeSeconds = process.uptime();
const embed = new EmbedBuilder()
.setColor(0x45aaf2)
.setTitle('Bot Uptime')
.setDescription(`Online for **${formatDuration(uptimeSeconds)}**.`)
.setTimestamp();
await interaction.reply({ embeds: [embed], ephemeral: true });
},
};