mirror of
https://github.com/aydenjahola/discord-multipurpose-bot.git
synced 2024-11-22 08:45:55 +00:00
add trivia command as well as leaderboard command, and update README
This commit is contained in:
parent
2ec7c73709
commit
77e5489269
7 changed files with 292 additions and 19 deletions
|
@ -13,7 +13,8 @@ EMAIL_DOMAINS=example@example.com // or it can be a list, example: example.com,e
|
||||||
GUILD_ID=YOUR_GUILD_ID
|
GUILD_ID=YOUR_GUILD_ID
|
||||||
VERIFICATION_CHANNEL_NAME=YOUR_VERIFICATION_CHANNEL_NAME
|
VERIFICATION_CHANNEL_NAME=YOUR_VERIFICATION_CHANNEL_NAME
|
||||||
VERIFIED_ROLE_NAME=YOUR_VERIFIED_ROLE_NAME
|
VERIFIED_ROLE_NAME=YOUR_VERIFIED_ROLE_NAME
|
||||||
ADMIN_LOG_CHANNEL_ID=YOUR_ADMIN_LOG_CHANNEL_ID
|
LOG_CHANNEL_ID=YOUR_LOG_CHANNEL_ID
|
||||||
|
MOD_ROLE_ID=YOUR_MOD_ROLE_ID
|
||||||
|
|
||||||
# Database
|
# Database
|
||||||
MONGODB_URI=YOUR_MONGODB_URI
|
MONGODB_URI=YOUR_MONGODB_URI
|
61
README.md
61
README.md
|
@ -1,23 +1,14 @@
|
||||||
# Discord Verification Bot
|
# Discord Multipurpose Bot
|
||||||
|
|
||||||
Welcome to the **Discord Verification Bot**! This bot is designed to handle user verification for Discord servers. It verifies users through their student email addresses and manages roles based on their verification status.
|
Welcome to the **Discord Multipurpose Bot**! This bot manages user verification for Discord servers through email authentication, includes a fun trivia game feature, and provides role management and leaderboard tracking functionalities.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- **Email Verification**: Users receive a verification code via email and must enter it in Discord to verify their account.
|
- **Email Verification**: Users receive a verification code via email and must enter it in Discord to verify their account.
|
||||||
- **Role Management**: Automatically assigns a specific role to users once they have been verified.
|
- **Role Management**: Automatically assigns a specific role to users once they have been verified.
|
||||||
- **Customizable**: Easy to configure email domains, roles, and channels for different needs.
|
- **Trivia Game**: Play a video game-themed trivia game and compete with others in the server.
|
||||||
- **Expiration Handling**: Verification codes expire after 10 minutes for added security.
|
- **Leaderboard**: Displays the top players based on correct trivia answers.
|
||||||
|
- **Customizable**: Configure email domains, roles, trivia settings, and more to suit your server.
|
||||||
## Getting Started
|
|
||||||
|
|
||||||
### Prerequisites
|
|
||||||
|
|
||||||
Before you begin, ensure you have:
|
|
||||||
|
|
||||||
- A Discord bot token (create a bot on the [Discord Developer Portal](https://discord.com/developers/applications)).
|
|
||||||
- Access to a MongoDB database (you can use [MongoDB Atlas](https://www.mongodb.com/cloud/atlas) for a free tier).
|
|
||||||
- A Gmail account for sending emails (you can use any email service, but make sure to adjust the Nodemailer configuration).
|
|
||||||
|
|
||||||
### Installation
|
### Installation
|
||||||
|
|
||||||
|
@ -25,6 +16,9 @@ Before you begin, ensure you have:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
git clone git@github.com:aydenjahola/esports-verification-bot.git
|
git clone git@github.com:aydenjahola/esports-verification-bot.git
|
||||||
|
```
|
||||||
|
|
||||||
|
```sh
|
||||||
cd esports-verification-bot
|
cd esports-verification-bot
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -48,13 +42,14 @@ EMAIL_USER=example@example.com
|
||||||
EMAIL_PASS=YOUR_EMAIL_PASS
|
EMAIL_PASS=YOUR_EMAIL_PASS
|
||||||
|
|
||||||
# Allowed domains for email verification
|
# Allowed domains for email verification
|
||||||
EMAIL_DOMAINS=example@example.com // or it can be a list, example: "example.com,example2.com"
|
EMAIL_DOMAINS=example@example.com // or it can be a list, example: example.com,example2.com
|
||||||
|
|
||||||
# Discord
|
# Discord
|
||||||
GUILD_ID=YOUR_GUILD_ID
|
GUILD_ID=YOUR_GUILD_ID
|
||||||
VERIFICATION_CHANNEL_NAME=YOUR_VERIFICATION_CHANNEL_NAME
|
VERIFICATION_CHANNEL_NAME=YOUR_VERIFICATION_CHANNEL_NAME
|
||||||
VERIFIED_ROLE_NAME=YOUR_VERIFIED_ROLE_NAME
|
VERIFIED_ROLE_NAME=YOUR_VERIFIED_ROLE_NAME
|
||||||
ADMIN_LOG_CHANNEL_ID=YOUR_ADMIN_LOG_CHANNEL_ID
|
LOG_CHANNEL_ID=YOUR_LOG_CHANNEL_ID
|
||||||
|
MOD_ROLE_ID=YOUR_MOD_ROLE_ID
|
||||||
|
|
||||||
# Database
|
# Database
|
||||||
MONGODB_URI=YOUR_MONGODB_URI
|
MONGODB_URI=YOUR_MONGODB_URI
|
||||||
|
@ -68,7 +63,37 @@ this can also be seen in in the [.env.example](./.env.example)
|
||||||
node bot.js
|
node bot.js
|
||||||
```
|
```
|
||||||
|
|
||||||
### Usage
|
## Usage
|
||||||
|
|
||||||
|
### Information Commands
|
||||||
|
|
||||||
|
- **/botinfo**: Displays information about the bot
|
||||||
|
- **/serverinfo**: Displays information about the server
|
||||||
|
|
||||||
|
### Utility Commands
|
||||||
|
|
||||||
|
- **/help**: Lists all available commands
|
||||||
|
- **/ping**: Replies with Pong! and bot latency
|
||||||
|
- **/uptime**: Shows how long the bot has been running
|
||||||
|
|
||||||
|
### Email Verification Commands
|
||||||
|
|
||||||
- **/verify your_email@example.com**: Sends a verification code to the provided email.
|
- **/verify your_email@example.com**: Sends a verification code to the provided email.
|
||||||
- **/code your_code**: Validates the provided verification code.
|
- **/code your_code**: Validates the provided verification code and completes the verification process.
|
||||||
|
|
||||||
|
### Moderation Commands
|
||||||
|
|
||||||
|
- **/purge**: Deletes messages from the channel
|
||||||
|
- **/userinfo**: Displays information about a user
|
||||||
|
- **/warn**: Issue a warning to a user
|
||||||
|
|
||||||
|
### Fun Commands
|
||||||
|
|
||||||
|
- **/trivia**: Starts a trivia game with video game-themed questions. Players have 30 seconds to answer.
|
||||||
|
- Accepts both number answers (1-4) **and** the correct answer
|
||||||
|
- **/leaderboard**: Displays the top 10 players on the trivia leaderboard based on their correct answers.
|
||||||
|
|
||||||
|
### Other Functionalities
|
||||||
|
|
||||||
|
- **Role Management**: Once a user is verified, they are automatically assigned a predefined role.
|
||||||
|
- **Admin Log**: Admins can review logs of verification attempts and trivia games in a designated channel.
|
||||||
|
|
52
commands/fun/leaderboard.js
Normal file
52
commands/fun/leaderboard.js
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
const { SlashCommandBuilder, EmbedBuilder } = require("discord.js");
|
||||||
|
const Leaderboard = require("../../models/Leaderboard");
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
data: new SlashCommandBuilder()
|
||||||
|
.setName("leaderboard")
|
||||||
|
.setDescription("Displays the trivia leaderboard"),
|
||||||
|
|
||||||
|
async execute(interaction, client) {
|
||||||
|
const guild = interaction.guild;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const scores = await Leaderboard.find()
|
||||||
|
.sort({ correctAnswers: -1 })
|
||||||
|
.limit(10);
|
||||||
|
|
||||||
|
const leaderboardEmbed = new EmbedBuilder()
|
||||||
|
.setColor("#0099ff")
|
||||||
|
.setTitle("Trivia Leaderboard")
|
||||||
|
.setDescription(
|
||||||
|
scores
|
||||||
|
.map(
|
||||||
|
(entry, index) =>
|
||||||
|
`${index + 1}. ${entry.username}: ${
|
||||||
|
entry.correctAnswers
|
||||||
|
} correct answers in ${entry.gamesPlayed} games`
|
||||||
|
)
|
||||||
|
.join("\n")
|
||||||
|
)
|
||||||
|
.setTimestamp();
|
||||||
|
|
||||||
|
if (guild.iconURL()) {
|
||||||
|
leaderboardEmbed.setFooter({
|
||||||
|
text: guild.name,
|
||||||
|
iconURL: guild.iconURL(),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
leaderboardEmbed.setFooter({
|
||||||
|
text: guild.name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await interaction.reply({ embeds: [leaderboardEmbed] });
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error executing leaderboard command:", error);
|
||||||
|
await interaction.reply({
|
||||||
|
content: "There was an error while executing this command!",
|
||||||
|
ephemeral: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
170
commands/fun/trivia.js
Normal file
170
commands/fun/trivia.js
Normal file
|
@ -0,0 +1,170 @@
|
||||||
|
const { SlashCommandBuilder, EmbedBuilder } = require("discord.js");
|
||||||
|
const axios = require("axios");
|
||||||
|
const TriviaQuestion = require("../../models/TriviaQuestion");
|
||||||
|
const Leaderboard = require("../../models/Leaderboard");
|
||||||
|
const { decode } = require("html-entities");
|
||||||
|
|
||||||
|
const API_INTERVAL = 5000; // 5 seconds
|
||||||
|
const QUESTION_EXPIRY = 30 * 24 * 60 * 60 * 1000; // 1 month
|
||||||
|
|
||||||
|
let lastApiCall = 0;
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
data: new SlashCommandBuilder()
|
||||||
|
.setName("trivia")
|
||||||
|
.setDescription("Play a trivia game about video games"),
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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 });
|
||||||
|
|
||||||
|
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&type=multiple" // Category 15 is for Video Games
|
||||||
|
);
|
||||||
|
|
||||||
|
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
|
||||||
|
);
|
||||||
|
|
||||||
|
// 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({
|
||||||
|
content: `<@${userId}>`,
|
||||||
|
embeds: [triviaEmbed],
|
||||||
|
});
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
// 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 collector = interaction.channel.createMessageCollector({
|
||||||
|
filter,
|
||||||
|
max: 1,
|
||||||
|
time: timeLimit,
|
||||||
|
});
|
||||||
|
|
||||||
|
collector.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.`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
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 again later.`,
|
||||||
|
ephemeral: true,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await interaction.reply({
|
||||||
|
content: `<@${userId}> There was an error while executing this command!`,
|
||||||
|
ephemeral: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
12
models/Leaderboard.js
Normal file
12
models/Leaderboard.js
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
const mongoose = require("mongoose");
|
||||||
|
|
||||||
|
const leaderboardSchema = new mongoose.Schema({
|
||||||
|
userId: { type: String, required: true },
|
||||||
|
username: { type: String, required: true },
|
||||||
|
gamesPlayed: { type: Number, default: 0 },
|
||||||
|
correctAnswers: { type: Number, default: 0 },
|
||||||
|
});
|
||||||
|
|
||||||
|
const Leaderboard = mongoose.model("Leaderboard", leaderboardSchema);
|
||||||
|
|
||||||
|
module.exports = Leaderboard;
|
11
models/TriviaQuestion.js
Normal file
11
models/TriviaQuestion.js
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
const mongoose = require("mongoose");
|
||||||
|
|
||||||
|
const triviaQuestionSchema = new mongoose.Schema({
|
||||||
|
question: String,
|
||||||
|
correct_answer: String,
|
||||||
|
incorrect_answers: [String],
|
||||||
|
last_served: Date, // Track when the question was last served
|
||||||
|
timestamp: { type: Date, default: Date.now },
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = mongoose.model("TriviaQuestion", triviaQuestionSchema);
|
|
@ -10,9 +10,11 @@
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"description": "",
|
"description": "",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"axios": "^1.7.7",
|
||||||
"discord.js": "^14.15.3",
|
"discord.js": "^14.15.3",
|
||||||
"dotenv": "^16.4.5",
|
"dotenv": "^16.4.5",
|
||||||
"express": "^4.19.2",
|
"express": "^4.19.2",
|
||||||
|
"html-entities": "^2.5.2",
|
||||||
"mongoose": "^8.6.0",
|
"mongoose": "^8.6.0",
|
||||||
"nodemailer": "^6.9.14"
|
"nodemailer": "^6.9.14"
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue