From 6ee0b874b5bd9a2b12a4a5ac8df3a76a8d856142 Mon Sep 17 00:00:00 2001 From: Ayden Jahola Date: Mon, 2 Sep 2024 18:32:03 +0100 Subject: [PATCH] rewrite the bot to use slash commands and use empheral messages for privacy --- Procfile | 2 +- README.md | 4 +- bot.js | 206 --------------------------------------------- commands/code.js | 95 +++++++++++++++++++++ commands/verify.js | 121 ++++++++++++++++++++++++++ index.js | 79 +++++++++++++++++ 6 files changed, 298 insertions(+), 209 deletions(-) delete mode 100644 bot.js create mode 100644 commands/code.js create mode 100644 commands/verify.js create mode 100644 index.js diff --git a/Procfile b/Procfile index 8753747..973094b 100644 --- a/Procfile +++ b/Procfile @@ -1 +1 @@ -worker: node bot.js \ No newline at end of file +worker: node index.js \ No newline at end of file diff --git a/README.md b/README.md index 05979be..8375619 100644 --- a/README.md +++ b/README.md @@ -69,5 +69,5 @@ node bot.js ### Usage -- **!verify your_email@example.com**: Sends a verification code to the provided email. -- **!code your_code**: Validates the provided verification code. +- **/verify your_email@example.com**: Sends a verification code to the provided email. +- **/code your_code**: Validates the provided verification code. diff --git a/bot.js b/bot.js deleted file mode 100644 index 8150c79..0000000 --- a/bot.js +++ /dev/null @@ -1,206 +0,0 @@ -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_DOMAINS = process.env.EMAIL_DOMAINS.split(","); // Domains to verify against, converted to an array -const GUILD_ID = process.env.GUILD_ID; // Guild ID to restrict the bot -const VERIFICATION_CHANNEL_NAME = process.env.VERIFICATION_CHANNEL_NAME; // 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 (!EMAIL_DOMAINS.includes(emailDomain)) { - return verificationChannel.send( - "You must use a valid DCU 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 Esports Verification Code

-

Hi there,

-

Thank you for requesting verification. Your Esports verification code is:

-

- ${verificationCode} -

-

This code is valid for 10 minutes. Please enter it in the verification 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_NAME}" <${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/commands/code.js b/commands/code.js new file mode 100644 index 0000000..440ff92 --- /dev/null +++ b/commands/code.js @@ -0,0 +1,95 @@ +const { SlashCommandBuilder } = require("discord.js"); +const VerificationCode = require("../models/VerificationCode"); + +module.exports = { + data: new SlashCommandBuilder() + .setName("code") + .setDescription("Verify your account with a verification code") + .addStringOption((option) => + option + .setName("code") + .setDescription("Your verification code") + .setRequired(true) + ), + + async execute(interaction, client) { + const code = interaction.options.getString("code"); + + if (!code) { + return interaction.reply({ + content: "Please provide the verification code.", + ephemeral: true, + }); + } + + try { + const verificationEntry = await VerificationCode.findOne({ + userId: interaction.user.id, + code, + }); + + if (!verificationEntry) { + return interaction.reply({ + content: "Invalid or expired verification code. Please try again.", + ephemeral: true, + }); + } + + const guild = client.guilds.cache.get(process.env.GUILD_ID); + + if (!guild) { + console.error("Guild not found."); + return interaction.reply({ + content: "Guild not found.", + ephemeral: true, + }); + } + + const member = guild.members.cache.get(interaction.user.id); + + if (!member) { + console.error("Member not found in the guild."); + return interaction.reply({ + content: "Member not found in the guild.", + ephemeral: true, + }); + } + + const role = guild.roles.cache.find( + (r) => r.name === process.env.VERIFIED_ROLE_NAME + ); + + if (!role) { + console.error(`Role "${process.env.VERIFIED_ROLE_NAME}" not found.`); + return interaction.reply({ + content: `Role "${process.env.VERIFIED_ROLE_NAME}" not found.`, + ephemeral: true, + }); + } + + if (member.roles.cache.has(role.id)) { + return interaction.reply({ + content: "You are already verified!", + ephemeral: true, + }); + } + + await member.roles.add(role); + + interaction.reply({ + content: `Congratulations ${interaction.user.username}, you have been verified!`, + ephemeral: true, + }); + + // the verification code is no longer needed, but it will automatically be deleted after 10 minutes + await VerificationCode.deleteOne({ userId: interaction.user.id, code }); + } catch (err) { + console.error("Error processing verification code:", err); + interaction.reply({ + content: + "There was an error processing your verification. Please try again later.", + ephemeral: true, + }); + } + }, +}; diff --git a/commands/verify.js b/commands/verify.js new file mode 100644 index 0000000..f313a59 --- /dev/null +++ b/commands/verify.js @@ -0,0 +1,121 @@ +const { SlashCommandBuilder } = require("discord.js"); +const nodemailer = require("nodemailer"); +const VerificationCode = require("../models/VerificationCode"); + +const transporter = nodemailer.createTransport({ + service: "Gmail", + auth: { + user: process.env.EMAIL_USER, + pass: process.env.EMAIL_PASS, + }, +}); + +module.exports = { + data: new SlashCommandBuilder() + .setName("verify") + .setDescription("Verify your account with an email address") + .addStringOption((option) => + option + .setName("email") + .setDescription("Your DCU email address") + .setRequired(true) + ), + + async execute(interaction, client) { + const email = interaction.options.getString("email"); + const emailDomain = email.split("@")[1]; + const EMAIL_DOMAINS = process.env.EMAIL_DOMAINS.split(","); + + if (!EMAIL_DOMAINS.includes(emailDomain)) { + return interaction.reply({ + content: "You must use a valid DCU email address.", + ephemeral: true, + }); + } + + const guild = client.guilds.cache.get(process.env.GUILD_ID); + + if (!guild) { + console.error("Guild not found."); + return interaction.reply({ + content: "Guild not found.", + ephemeral: true, + }); + } + + const member = guild.members.cache.get(interaction.user.id); + + if (!member) { + console.error("Member not found in the guild."); + return interaction.reply({ + content: "Member not found in the guild.", + ephemeral: true, + }); + } + + const role = guild.roles.cache.find( + (r) => r.name === process.env.VERIFIED_ROLE_NAME + ); + + if (!role) { + console.error(`Role "${process.env.VERIFIED_ROLE_NAME}" not found.`); + return interaction.reply({ + content: `Role "${process.env.VERIFIED_ROLE_NAME}" not found.`, + ephemeral: true, + }); + } + + if (member.roles.cache.has(role.id)) { + return interaction.reply({ + content: "You are already verified!", + ephemeral: true, + }); + } + + const verificationCode = Math.floor( + 100000 + Math.random() * 900000 + ).toString(); + + const emailHtml = ` + + +

Your Esports Verification Code

+

Hi there,

+

Your Esports verification code is:

+

+ ${verificationCode} +

+

This code is valid for 10 minutes. Use it with 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_NAME}" <${process.env.EMAIL_USER}>`, + to: email, + subject: "Esports Verification Code", + html: emailHtml, + }); + + await VerificationCode.create({ + userId: interaction.user.id, + email: email, + code: verificationCode, + }); + + interaction.reply({ + content: `A verification code has been sent to your email. Use \`/code your_code\` to verify your account. The code is valid for 10 minutes.`, + ephemeral: true, + }); + } catch (err) { + console.error("Error sending email or saving verification code:", err); + interaction.reply({ + content: "There was an error sending the verification email.", + ephemeral: true, + }); + } + }, +}; diff --git a/index.js b/index.js new file mode 100644 index 0000000..38c64be --- /dev/null +++ b/index.js @@ -0,0 +1,79 @@ +require("dotenv").config(); +const { + Client, + GatewayIntentBits, + Collection, + REST, + Routes, +} = require("discord.js"); +const mongoose = require("mongoose"); +const fs = require("fs"); +const path = require("path"); + +const client = new Client({ + intents: [ + GatewayIntentBits.Guilds, + GatewayIntentBits.GuildMessages, + GatewayIntentBits.MessageContent, + ], +}); + +const GUIlD_ID = process.env.GUILD_ID; + +client.commands = new Collection(); + +const commandFiles = fs + .readdirSync(path.join(__dirname, "commands")) + .filter((file) => file.endsWith(".js")); + +for (const file of commandFiles) { + const command = require(`./commands/${file}`); + client.commands.set(command.data.name, command); +} + +client.once("ready", async () => { + console.log(`Logges in as ${client.user.tag}`); + + const commands = client.commands.map((cmd) => cmd.data.toJSON()); + + const rest = new REST({ version: "10" }).setToken(process.env.BOT_TOKEN); + + try { + await rest.put(Routes.applicationGuildCommands(client.user.id, GUIlD_ID), { + body: commands, + }); + console.log("Successfully registered application commands."); + } catch (err) { + console.error("Error registering application commands:", err); + } +}); + +// MongoDB connection +mongoose + .connect(process.env.MONGODB_URI) + .then(() => console.log("Connected to MongoDB")) + .catch((err) => console.error("Failed to connect to MongoDB", err)); + +client.on("interactionCreate", async (interaction) => { + if (!interaction.isCommand()) return; + + const command = client.commands.get(interaction.commandName); + + if (!command) return; + + try { + await command.execute(interaction, client); + } catch (err) { + console.error(err); + await interaction.reply({ + content: "There was an error while executing this command!", + ephemeral: true, + }); + } +}); + +client.on("Error", (err) => { + console.error("Client error:", err); +}); + +client.login(process.env.BOT_TOKEN);