Repository: nates/ward Branch: master Commit: 86c853710d11 Files: 19 Total size: 23.6 KB Directory structure: gitextract_fwq65eg7/ ├── LICENSE ├── README.md ├── assets/ │ ├── css/ │ │ └── style.css │ └── js/ │ └── particles.js ├── config-example.js ├── events/ │ ├── guildMemberAdd.js │ ├── interactionCreate.js │ ├── onReady.js │ └── slashCreate.js ├── html/ │ ├── invalidCaptcha.html │ ├── invalidLink.html │ ├── valid.html │ └── verify.html ├── index.js ├── package.json ├── pool.js └── public/ ├── slash/ │ ├── misc/ │ │ └── ping.js │ └── verify/ │ └── verify.js └── util.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2020 nate Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================

🔑 ward

A Discord verification bot using reCAPTCHA v2.

## Requirements - [Node.js v16.9.0 or higher](https://nodejs.org/en/) - [Google reCaptcha Key](https://www.google.com/recaptcha/admin/create) ## Setup - Rename your `config-example.js` file to `config.js` - Register your site with [reCaptcha](https://www.google.com/recaptcha/admin/create) with the domain you are currently using. If running locally, only put localhost on the domain area. ChoosereCAPTCHA v2 "I'm not a robot" for the reCaptcha Type, and copy the secret and public key into the config.js file. If you are using HTTPS, enable it in the config add your certificate and private key file with the names: `certificate.pem` and `private.pem`. - To run your own version on Repl.it, create a new project and click the `Import from Github` button and copy this repository url and paste it on the Repl.it site. - Here is what the configuration file looks like and the things that are required for the bot to run. ```js module.exports = { server: { domain: "localhost", https: false, httpPort: 8080, }, Discord: { // —— Things that are required for the whole project to work. token: "", // —— Your bot's token. botId: "", // —— The bot's ID. guildId: "", // —— The server ID on where the commands will be deployed. verifiedRole: "", // —— Role that will be added to the user when they verify their account. // —— For users that want to have a role removed upon verification, if you want this, set remove-role to true, and set your remove role ID. removeRole: false, removeRoleId: "", // —— Set the bot's presence, for statusType see: https://discord-api-types.dev/api/discord-api-types-v10/enum/ActivityType statusType: 3, // 1 (STREAMING), 2 (LISTENING), 3 (WATCHING), 5 (COMPETING). Default is 0 (PLAYING). statusMsg: "unverified users!", // —— By default, rules are set to disabled, this means rules will be hidden. If you want to use the rules function, change disabled to your rules. Please ensure you use \n for each line break and do not use any symbols that could interfear with JSON. rulesEnabled: true, rules: "Type your rules here if rulesEnabled is enabled, ensure to use \n for new lines" }, reCAPTCHA: { secretKey: "", publicKey: "" } } ``` - Finished editing the files and ready to turn on your bot? run `npm start` in the bot folder. ## Issues **Not receiving a DM when joining my server** - If you are not receiving a DM when joining your server, Go to your Discord bot dashboard and enable both intents. Note: If your bot is more than 100 servers, you will have to verify your bot. **Bot failing to login** - You must go to your Discord bot dashboard and enable both intents. Note: If your bot is more than 100 servers, you will have to verify your bot. ## Preview ![Embed](https://i.imgur.com/zomEnpw.png) ![Website](https://i.imgur.com/tmrcyjF.png) ================================================ FILE: assets/css/style.css ================================================ @import url('https://fonts.googleapis.com/css2?family=Roboto&display=swap'); body { background-color: #2b2b2b; width: 100%; height: 100%; overflow: hidden; font-family: 'Roboto', sans-serif; } #particles { width: 100%; height: 100%; overflow: hidden; z-index: -1; position: fixed; } #app { z-index: 1; position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); } span { font-size: 28px; font-weight: 400; color: white; } ================================================ FILE: assets/js/particles.js ================================================ $(document).ready(function() { $('#particles').particleground({ dotColor: '#b3afa8', lineColor: '#b3afa8', density: 50000 }); }) ================================================ FILE: config-example.js ================================================ module.exports = { server: { domain: "localhost", https: false, httpPort: 8080, }, Discord: { // —— Things that are required for the whole project to work. token: "", // —— Your bot's token. botId: "", // —— The bot's ID. guildId: "", // —— The server ID on where the commands will be deployed. verifiedRole: "", // —— Role that will be added to the user when they verify their account. // —— For users that want to have a role removed upon verification, if you want this, set remove-role to true, and set your remove role ID. removeRole: false, removeRoleId: "", // —— Set the bot's presence, for statusType see: https://discord-api-types.dev/api/discord-api-types-v10/enum/ActivityType statusType: 3, // 1 (STREAMING), 2 (LISTENING), 3 (WATCHING), 5 (COMPETING). Default is 0 (PLAYING). statusMsg: "unverified users!", // —— By default, rules are set to disabled, this means rules will be hidden. If you want to use the rules function, change disabled to your rules. Please ensure you use \n for each line break and do not use any symbols that could interfear with JSON. rulesEnabled: true, rules: "Type your rules here if rulesEnabled is enabled, ensure to use \n for new lines" }, reCAPTCHA: { secretKey: "", publicKey: "" } } ================================================ FILE: events/guildMemberAdd.js ================================================ // —— dot. const { Signale } = require('signale'); const { EmbedBuilder, ActionRowBuilder, ButtonBuilder } = require('discord.js'); const logger = new Signale({ scope: 'Discord' }); const config = require('../config.js'); const pool = require('../pool.js'); module.exports = { name: "guildMemberAdd", async execute(member) { const domain = config.server.domain === 'localhost' ? `${config.server.domain}:${config.server.httpPort}` : `${config.server.domain}`; if(config.Discord.rulesEnabled) { const linkID = pool.createLink(member.id); const captchaEmbed = new EmbedBuilder() .setColor('#0099ff') .setTitle('reCAPTCHA Verification') .setDescription(`To gain access to this server you must solve a captcha. The link will expire in 15 minutes.\n${config.server.https ? 'https://' : 'http://'}${domain}/verify/${linkID}`) member.send({ embeds: [captchaEmbed] }).catch(() => { logger.error(`Failed to send captcha to user! (Maybe they have DMs turned off?)`); }); } else { const linkID = pool.createLink(member.id); const captchaEmbed = new EmbedBuilder() .setColor('#0099ff') .setTitle('reCAPTCHA Verification') .setDescription(`To gain access to this server you must solve a captcha. The link will expire in 15 minutes.\n${config.server.https ? 'https://' : 'http://'}${domain}/verify/${linkID}`) member.send({ embeds: [captchaEmbed] }).catch(() => { logger.error(`Failed to send captcha to user! (Maybe they have DMs turned off?)`); }); } }, }; ================================================ FILE: events/interactionCreate.js ================================================ const { EmbedBuilder } = require('discord.js'); const pool = require('../pool.js'); module.exports = { name: "interactionCreate", async execute(interaction) { if(!interaction.isButton()) return; if(interaction.customId === 'rules') { logger.info('User agreed to the rules!'); interaction.user.createDM().then(dm => { dm.send("You have sucessfully agreed to rules.").catch(console.error); const linkID = pool.createLink(interaction.user.id); const captchaEmbed = new EmbedBuilder() .setColor('#0099ff') .setTitle('reCAPTCHA Verification') .setDescription(`To gain access to this server you must solve a captcha. The link will expire in 15 minutes.\n${config.https ? 'https://' : 'http://'}${config.domain}/verify/${linkID}`) dm.send({ embeds: [captchaEmbed] }).catch(() => { logger.error(`Failed to send captcha to user! (Maybe they have DMs turned off?)`); }); }) } }, }; ================================================ FILE: events/onReady.js ================================================ // —— dot. const { Signale } = require('signale'); const config = require('../config.js'); const logger = new Signale({ scope: 'Discord' }); module.exports = { name: "ready", once: true, async execute(client) { logger.success('Bot online!'); client.user.setPresence({ activities: [{ type: config.Discord.statusType, name: `${config.Discord.statusMsg}` }], status: 'idle' }) logger.success('Status Set!'); }, }; ================================================ FILE: events/slashCreate.js ================================================ const { Signale } = require('signale'); const logger = new Signale({ scope: 'Pool' }); module.exports = { name: "interactionCreate", async execute(interaction) { const { client } = interaction; if (!interaction.isChatInputCommand()) return; const command = client.slashCommands.get(interaction.commandName); if (!command) return; try { await command.execute(interaction); } catch (err) { logger.error(err); await interaction.reply({ content: "There was an issue while executing that command!", ephemeral: true, }); } }, }; ================================================ FILE: html/invalidCaptcha.html ================================================ Discord Verification
Invalid reCAPTCHA!
================================================ FILE: html/invalidLink.html ================================================ Discord Verification
Invalid link!
================================================ FILE: html/valid.html ================================================ Discord Verification
Verified!
================================================ FILE: html/verify.html ================================================ Discord Verification
================================================ FILE: index.js ================================================ // —— Requiring the packages the we need. const fs = require("fs"); const { Client, Collection, Partials } = require("discord.js"); const { Signale } = require('signale'); const { REST } = require("@discordjs/rest"); const { Routes } = require("discord-api-types/v9"); const express = require('express'); const path = require('path'); const axios = require('axios'); const https = require('https'); const pool = require('./pool'); const config = require("./config.js"); const logger = new Signale({ scope: 'Discord' }); // —— Initializing the client. const client = new Client({ intents: [ 131071 ], // Basically for (most?) of the intents. partials: [ Partials.Channel ] }); // —— All event files of the event handler. const eventFiles = fs .readdirSync("./events") .filter((file) => file.endsWith(".js")); for (const file of eventFiles) { const event = require(`./events/${file}`); if (event.once) { client.once(event.name, (...args) => event.execute(...args, client)); } else { client.on(event.name, async (...args) => await event.execute(...args, client)); } } client.slashCommands = new Collection(); // —— Registration of Slash-Command Interactions. const slashCommands = fs.readdirSync("./public/slash"); for (const module of slashCommands) { const commandFiles = fs .readdirSync(`./public/slash/${module}`) .filter((file) => file.endsWith(".js")); for (const commandFile of commandFiles) { const command = require(`./public/slash/${module}/${commandFile}`); client.slashCommands.set(command.data.name, command); } } // —— Registration of Slash-Commands in Discord API const rest = new REST({ version: "9" }).setToken(config.Discord.token); const commandJsonData = [ ...Array.from(client.slashCommands.values()).map((c) => c.data.toJSON()), ]; (async () => { try { logger.success("Started refreshing application (/) commands."); await rest.put(Routes.applicationGuildCommands(config.Discord.botId, config.Discord.guildId), { body: commandJsonData }); logger.success("Successfully reloaded application (/) commands."); } catch (error) { console.error(error); } })(); async function addRole(userID) { try { const guild = await client.guilds.fetch(config.Discord.guildId), role = await guild.roles.fetch(config.Discord.verifiedRole), member = await guild.members.fetch(userID); member.roles.add(role) .catch(() => { logger.error(`Failed to add role to user ${member.user.tag}! (Maybe verified role is above bot role?)`); return; }) .then(() => { logger.info(`Added verified role to user ${member.user.tag}.`); }) } catch (e) { console.log(e) logger.error(`Failed to add role to user ${userID}!`); } } async function removeRole(userID) { const removeRole = config.Discord.removeRole if(removeRole) { try { const guild = await client.guilds.fetch(config.Discord.guildId), removeRoleId = await guild.roles.fetch(config.Discord.removeRoleId), member = await guild.members.fetch(userID); member.roles.remove(removeRoleId) .catch(() => { logger.error(`Failed to remove role from user ${member.user.tag}! (Maybe role is above bot role?)`); return; }) .then(() => { logger.info(`Removed role from user ${member.user.tag}.`); }) } catch(e) { logger.error(`Failed to remove role from user ${userID}!`); } } else { logger.info(`Remove role is set to false, step skipped.`) } } // —— Login into your client application with bot's token. client.login(config.Discord.token) .catch(() => { logger.fatal('Failed to login! Is your intents enabled?'); process.exit(0); }) // —— And another thingy. const app = express(), port = config.server.https ? 443 : config.server.httpPort; // —— Define render engine and assets path app.engine('html', require('ejs').renderFile); app.use(express.static(path.join(__dirname, '/assets'))); app.use(express.json()); app.use(express.urlencoded({ extended: true })); // GET /verify/id app.get('/verify/:verifyId?', (req, res) => { if (!req.params.verifyId) return res.sendFile(path.join(__dirname, '/html/invalidLink.html')); if (!pool.isValidLink(req.params.verifyId)) return res.sendFile(path.join(__dirname, '/html/invalidLink.html')); res.render(path.join(__dirname, '/html/verify.html'), { publicKey: config.reCAPTCHA.publicKey }); }); // POST /verify/id app.post('/verify/:verifyId?', async (req, res) => { if (!req.body || !req.body['g-recaptcha-response']) return res.sendFile(path.join(__dirname, '/html/invalidLink.html')); const response = await axios({ method: 'post', url: `https://www.google.com/recaptcha/api/siteverify?secret=${config.reCAPTCHA.secretKey}&response=${req.body['g-recaptcha-response']}`, headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }); if (!response.data.success) return res.sendFile(path.join(__dirname, '/html/invalidCaptcha.html')); if (!pool.isValidLink(req.params.verifyId)) return res.sendFile(path.join(__dirname, '/html/invalidLink.html')); await addRole(pool.getDiscordId(req.params.verifyId)); await removeRole(pool.getDiscordId(req.params.verifyId)); pool.removeLink(req.params.verifyId); res.sendFile(path.join(__dirname, '/html/valid.html')); }); const start = () => { if (config.https) { https.createServer({ key: fs.readFileSync('private.pem'), cert: fs.readFileSync('certificate.pem') }, app).listen(port, () => logger.info(`Listening on port ${port}.`)); } else { app.listen(port, () => logger.info(`Listening on port ${port}.`)); } } // —— Start the server start(); ================================================ FILE: package.json ================================================ { "name": "ward", "version": "4.0.0", "description": "A Discord verification bot using reCAPTCHA v2.", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "start": "node --no-warnings ./index.js" }, "repository": { "type": "git", "url": "git+https://github.com/nates/ward.git" }, "author": "nates", "license": "MIT", "bugs": { "url": "https://github.com/nates/ward/issues" }, "homepage": "https://github.com/nates/ward#readme", "dependencies": { "@discordjs/rest": "^1.0.1", "axios": "^0.21.1", "body-parser": "^1.19.0", "discord.js": "^14.0.3", "ejs": "^3.1.8", "express": "^4.18.1", "https": "^1.0.0", "signale": "^1.4.0" } } ================================================ FILE: pool.js ================================================ // —— Requiring the packages that we need for this file. const { Signale } = require('signale'); const createCode = require('./public/util.js').createCode; const logger = new Signale({ scope: 'Pool' }); // —— Making the functions!! let linkPool = []; function createLink(discordID) { const linkID = createCode(8); linkPool.push({ discordID: discordID, linkID: linkID }); setTimeout(function() { if (isValidLink(linkID)) removeLink(linkID); }, 900000); logger.info('Created new link ID:', linkID); return linkID; } function isValidLink(linkID) { for (let i = 0; i < linkPool.length; i++) if (linkPool[i].linkID == linkID) return true; return false; } function removeLink(linkID) { for (let i = 0; i < linkPool.length; i++) if (linkPool[i].linkID == linkID) delete linkPool[i]; linkPool = linkPool.filter(n => n); } function getDiscordId(linkID) { for (let i = 0; i < linkPool.length; i++) if (linkPool[i].linkID == linkID) return linkPool[i].discordID; return false; } module.exports = { isValidLink, removeLink, createLink, getDiscordId }; ================================================ FILE: public/slash/misc/ping.js ================================================ const { SlashCommandBuilder } = require("discord.js"); module.exports = { data: new SlashCommandBuilder() .setName("ping") .setDescription( "Test to see if bot is online." ), async execute(interaction) { interaction.reply("Pong!"); }, }; ================================================ FILE: public/slash/verify/verify.js ================================================ const { SlashCommandBuilder, EmbedBuilder, ActionRowBuilder, ButtonBuilder } = require("discord.js"); const config = require("../../../config.js"); const pool = require("../../../pool.js") module.exports = { data: new SlashCommandBuilder() .setName("verify") .setDescription( "Verify yourself in the server!" ), async execute(interaction) { const domain = config.server.domain === 'localhost' ? `${config.server.domain}:${config.server.httpPort}` : `${config.server.domain}`; if(interaction.member.roles.cache.some(r => r.id === config.Discord.verifiedRole)) { await interaction.reply("Whoops, you are already verified!"); return; } const button = new ActionRowBuilder() .addComponents( new ButtonBuilder() .setCustomId("rules") .setLabel('Agree') .setEmoji('✅') .setStyle(1) ) const embed = new EmbedBuilder() .setColor('#0099ff') .setTitle('Rules') .setDescription(config.Discord.rules); if(config.Discord.rulesEnabled) { await interaction.reply('Please check your DMS!') const linkID = pool.createLink(interaction.user.id); const captchaEmbed = new EmbedBuilder() .setColor('#0099ff') .setTitle('reCAPTCHA Verification') .setDescription(`To gain access to this server you must solve a captcha. The link will expire in 15 minutes.\n${config.server.https ? 'https://' : 'http://'}${domain}/verify/${linkID}`) await interaction.user.createDM().then(async (dm) => { await dm.send({ embeds: [captchaEmbed] }).catch(() => { logger.error(`Failed to send captcha to user! (Maybe they have DMs turned off?)`); }); }); } else { await interaction.reply('Please check your DMS!') const linkID = pool.createLink(interaction.user.id); const captchaEmbed = new EmbedBuilder() .setColor('#0099ff') .setTitle('reCAPTCHA Verification') .setDescription(`To gain access to this server you must solve a captcha. The link will expire in 15 minutes.\n${config.server.https ? 'https://' : 'http://'}${domain}/verify/${linkID}`) await interaction.user.createDM().then(async (dm) => { await dm.send({ embeds: [captchaEmbed] }).catch(() => { logger.error(`Failed to send captcha to user! (Maybe they have DMs turned off?)`); }) }); } }, }; ================================================ FILE: public/util.js ================================================ // uwu. function createCode(length) { let characters = 'abcdefghijkmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; let result = ''; for (let i = 0; i < length; i++) { result += characters.charAt(Math.floor(Math.random() * characters.length)); } return result; } module.exports = { createCode };