From c6a5dd2701aed2dad0b9bb7aeae65b7cb5ca108e Mon Sep 17 00:00:00 2001 From: Ayden Jahola Date: Sat, 31 Aug 2024 02:34:09 +0100 Subject: [PATCH] initial commit --- .gitignore | 17 +++ Procfile | 1 + README.md | 60 +++++++++++ bot.js | 206 +++++++++++++++++++++++++++++++++++++ models/VerificationCode.js | 10 ++ package.json | 19 ++++ 6 files changed, 313 insertions(+) create mode 100644 .gitignore create mode 100644 Procfile create mode 100644 README.md create mode 100644 bot.js create mode 100644 models/VerificationCode.js create mode 100644 package.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3034591 --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ +# Node +node_modules/ +package-lock.json + +# .env +.env +.env.local +.env.development.local +.env.test.local +.env.production.local + +# yarn stuff +yarn-error.log +yarn.lock + +# db +db-data \ No newline at end of file diff --git a/Procfile b/Procfile new file mode 100644 index 0000000..8753747 --- /dev/null +++ b/Procfile @@ -0,0 +1 @@ +worker: node bot.js \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..57abd1d --- /dev/null +++ b/README.md @@ -0,0 +1,60 @@ +# Esports Verification Bot + +Welcome to the **Esports Verification Bot**! This bot is designed to handle user verification for Discord servers, specifically for esports communities. It verifies users through their student email addresses and manages roles based on their verification status. + +## Features + +- **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. +- **Customizable**: Easy to configure email domains, roles, and channels for different needs. +- **Expiration Handling**: Verification codes expire after 10 minutes for added security. + +## 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 + +1. **Clone the Repository** + +```sh + git clone git@github.com:aydenjahola/esports-verification-bot.git + cd esports-verification-bot +``` + +2. **Install Dependencies** + +```sh +npm install +``` + +3. **Set Up Environment Variables** + +Create a `.env` file in the root directory and add the following: + +```env +BOT_TOKEN=your_discord_bot_token +MONGODB_URI=your_mongodb_connection_string +EMAIL_USER=your_email@gmail.com +EMAIL_PASS=your_email_password +VERIFIED_ROLE_NAME=YourVerifiedRoleName +EMAIL_DOMAIN=mail.dcu.ie +GUILD_ID=your_discord_guild_id +``` + +4. **Run the Bot** + +```sh +node bot.js +``` + +### Usage + +- **!verify your_email@mail.dcu.ie**: Sends a verification code to the provided email. +- **!code your_code**: Validates the provided verification code. diff --git a/bot.js b/bot.js new file mode 100644 index 0000000..fcfac28 --- /dev/null +++ b/bot.js @@ -0,0 +1,206 @@ +require("dotenv").config(); +const { Client, GatewayIntentBits } = require("discord.js"); +const nodemailer = require("nodemailer"); +const mongoose = require("mongoose"); +const VerificationCode = require("./models/VerificationCode"); + +const client = new Client({ + intents: [ + GatewayIntentBits.Guilds, + GatewayIntentBits.GuildMessages, + GatewayIntentBits.MessageContent, + ], +}); + +const VERIFIED_ROLE_NAME = process.env.VERIFIED_ROLE_NAME; // Role name to assign after verification +const EMAIL_DOMAIN = process.env.EMAIL_DOMAIN; // Domain to verify against +const GUILD_ID = process.env.GUILD_ID; // Guild ID to restrict the bot +const VERIFICATION_CHANNEL_NAME = "verification"; // Channel name to restrict the bot + +// Connect to MongoDB +mongoose + .connect(process.env.MONGODB_URI) + .then(() => console.log("Connected to MongoDB")) + .catch((err) => console.error("Failed to connect to MongoDB", err)); + +// Setup Nodemailer +const transporter = nodemailer.createTransport({ + service: "Gmail", + auth: { + user: process.env.EMAIL_USER, + pass: process.env.EMAIL_PASS, + }, +}); + +client.once("ready", () => { + console.log(`Logged in as ${client.user.tag}`); +}); + +client.on("messageCreate", async (message) => { + try { + // Ensure bot only operates in the designated channel + const verificationChannel = message.guild.channels.cache.find( + (channel) => channel.name === VERIFICATION_CHANNEL_NAME + ); + + if (!message.guild || !verificationChannel) { + console.error("Guild or verification channel not found."); + return; + } + + if (message.author.bot || message.channel.id !== verificationChannel.id) + return; + + if (message.content.startsWith("!verify")) { + const email = message.content.split(" ")[1]; + + if (!email) { + return verificationChannel.send( + "Please provide your email address. Usage: `!verify your_email@mail.dcu.ie`" + ); + } + + const emailDomain = email.split("@")[1]; + if (emailDomain !== EMAIL_DOMAIN) { + return verificationChannel.send( + "You must use a valid DCU student email address." + ); + } + + const guild = client.guilds.cache.get(GUILD_ID); + if (!guild) { + console.error("Guild not found."); + return; + } + + const member = guild.members.cache.get(message.author.id); + if (!member) { + console.error("Member not found."); + return; + } + + const role = guild.roles.cache.find((r) => r.name === VERIFIED_ROLE_NAME); + if (!role) { + console.error("Role not found."); + return; + } + + if (member.roles.cache.has(role.id)) { + return verificationChannel.send("You are already verified!"); + } + + const verificationCode = Math.floor( + 100000 + Math.random() * 900000 + ).toString(); + + const emailHtml = ` + + +

Your Verification Code

+

Hi there,

+

Thank you for requesting verification. Your verification code is:

+

+ ${verificationCode} +

+

This code is valid for 10 minutes. Please enter it in the Discord channel using the command !code your_code.

+

If you did not request this code, please ignore this email.

+

Best regards,
Esports Committee

+ + + `; + + try { + await transporter.sendMail({ + from: process.env.EMAIL_USER, + to: email, + subject: "Esports Verification Code", + html: emailHtml, // Use HTML content + }); + + await VerificationCode.create({ + userId: message.author.id, + email: email, + code: verificationCode, + }); + + verificationChannel.send( + `**A verification code has been sent to your email.**\n` + + `Please reply with \`!code your_code\` to verify your account.\n` + + `**Note:** The code is only valid for **10 minutes**.` + ); + } catch (err) { + console.error("Error sending email or saving verification code:", err); + verificationChannel.send( + "There was an error sending the verification email." + ); + } + } + + if (message.content.startsWith("!code")) { + const code = message.content.split(" ")[1]; + + if (!code) { + return verificationChannel.send( + "Please provide the verification code. Usage: `!code your_code`" + ); + } + + try { + const verificationEntry = await VerificationCode.findOne({ + userId: message.author.id, + code, + }); + + if (!verificationEntry) { + return verificationChannel.send( + "Invalid or expired verification code. Please try again." + ); + } + + const guild = client.guilds.cache.get(GUILD_ID); + if (!guild) { + console.error("Guild not found."); + return; + } + + const member = guild.members.cache.get(message.author.id); + if (!member) { + console.error("Member not found."); + return; + } + + const role = guild.roles.cache.find( + (r) => r.name === VERIFIED_ROLE_NAME + ); + if (!role) { + console.error("Role not found."); + return; + } + + if (member.roles.cache.has(role.id)) { + return verificationChannel.send("You are already verified!"); + } + + await member.roles.add(role); + verificationChannel.send( + `Congratulations ${message.author}, you have been verified!` + ); + + // No need to manually delete the verification entry, as it will expire automatically in 10 minutes + } catch (err) { + console.error("Error processing verification code:", err); + verificationChannel.send( + "There was an error processing your verification. Please try again later." + ); + } + } + } catch (err) { + console.error("Unhandled error in messageCreate event:", err); + } +}); + +client.on("error", (err) => { + console.error("Client error:", err); +}); + +client.login(process.env.BOT_TOKEN); diff --git a/models/VerificationCode.js b/models/VerificationCode.js new file mode 100644 index 0000000..a613839 --- /dev/null +++ b/models/VerificationCode.js @@ -0,0 +1,10 @@ +const mongoose = require("mongoose"); + +const VerificationCodeSchema = new mongoose.Schema({ + userId: { type: String, required: true }, + email: { type: String, required: true }, + code: { type: String, required: true }, + createdAt: { type: Date, expires: "10m", default: Date.now }, // Code expires in 10 minutes +}); + +module.exports = mongoose.model("VerificationCode", VerificationCodeSchema); diff --git a/package.json b/package.json new file mode 100644 index 0000000..27d9b39 --- /dev/null +++ b/package.json @@ -0,0 +1,19 @@ +{ + "name": "esports-bot", + "version": "1.0.0", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "description": "", + "dependencies": { + "discord.js": "^14.15.3", + "dotenv": "^16.4.5", + "express": "^4.19.2", + "mongoose": "^8.6.0", + "nodemailer": "^6.9.14" + } +}