const { EmbedBuilder, ApplicationCommandType, Collection, AttachmentBuilder } = require('discord.js'); const { dangercordcheckVerification } = require('../../functions/dangercord_verificationUtils'); const { createCanvas, loadImage } = require('canvas'); const cfg = require('../../config/config.js'); const captchaUser = new Collection(); module.exports = { name: 'verify', description: 'To verify yourself.', type: ApplicationCommandType.ChatInput, run: async (client, interaction) => { const guildId = interaction.guild.id; async function generateCaptcha(count) { // Setting up the variables for generation var upchars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; var lowchars = "abcdefghijklmnopqrstuvwxyz"; var nums = "0123456789"; //get a random letter/number from upchars, lowchars, and nums var chars = upchars + lowchars + nums; var captcha = ""; for (var i = 0; i < count; i++) { var c = Math.floor(Math.random() * 3); if (c === 0) { captcha += upchars.charAt(Math.floor(Math.random() * upchars.length)); } else if (c === 1) { captcha += lowchars.charAt(Math.floor(Math.random() * lowchars.length)); } else { captcha += nums.charAt(Math.floor(Math.random() * nums.length)); } } return captcha; } async function createCaptcha(captcha) { const canvas = createCanvas(200, 75); const ctx = canvas.getContext('2d'); // Background color ctx.fillStyle = '#000000'; ctx.fillRect(0, 0, canvas.width, canvas.height); // Text ctx.font = '30px Arial'; ctx.textBaseline = 'middle'; ctx.textAlign = 'center'; ctx.fillStyle = '#ffffff'; // Calculate spacing between characters to fit within canvas width const charWidth = 30; const totalWidth = captcha.length * charWidth; const startX = (canvas.width - totalWidth) / 2; // Draw each character with random y-offset for (let i = 0; i < captcha.length; i++) { const x = startX + i * charWidth + charWidth / 2; // Adjust x-position for each character const y = 37.5 + Math.random() * 30 - 15; // Random y-offset ctx.fillText(captcha[i], x, y); } // Adding noise for (let i = 0; i < 50; i++) { ctx.fillStyle = `rgba(255,255,255,${Math.random() * 0.9})`; ctx.fillRect(Math.random() * canvas.width, Math.random() * canvas.height, 1, 1); } // Adding white lines for (let i = 0; i < 30; i++) { ctx.strokeStyle = `rgba(255,255,255,${Math.random() * 1.8})`; ctx.beginPath(); ctx.moveTo(Math.random() * canvas.width, Math.random() * canvas.height); ctx.lineTo(Math.random() * canvas.width, Math.random() * canvas.height); ctx.stroke(); } // Adding blue lines for (let i = 0; i < 30; i++) { ctx.strokeStyle = `rgba(0, 0, 255, ${Math.random() * 0.9})`; ctx.beginPath(); ctx.moveTo(Math.random() * canvas.width, Math.random() * canvas.height); ctx.lineTo(Math.random() * canvas.width, Math.random() * canvas.height); ctx.stroke(); } // Rotation const rotation = Math.random() * 0.2 - 0.1; // Random rotation between -0.1 and 0.1 radians ctx.translate(canvas.width / 2, canvas.height / 2); ctx.rotate(rotation); ctx.translate(-canvas.width / 2, -canvas.height / 2); const buffer = canvas.toBuffer('image/png'); return buffer; } if (interaction.channel.id !== cfg.guilds[guildId].channelId) { return interaction.reply({ content: cfg.messages.wrongChannel, ephemeral: true }) } if (interaction.member.roles.cache.has(cfg.guilds[guildId].roleToAdd)) { return interaction.reply({ content: cfg.messages.alreadyVerified, ephemeral: true }) } let attempts = 0; let blacklisted = false; const maxAttempts = cfg.maxAttempts; let validated = false; // Flag to track if user is already validated async function verifyUser(captcha) { attempts++; if (attempts > maxAttempts) { await interaction.user.send(cfg.messages.maxAttemptsExceeded); return; } if (attempts == 1) { // Check the Dangercord API using our custom library function await dangercordcheckVerification(interaction.user.id) .then(result => { console.log('Verification result:', result); if (result === 1) { console.log(`${interaction.user.id} is not blacklisted at dangercord, lets continue with the verification process`) // Proceed with further actions } else { console.log(`${interaction.user.id} is blacklisted at dangercord, we will not continue with the verification process`); blacklisted = true; // Set blacklisted to true // Handle the case when user is blacklisted interaction.editReply({ content: cfg.messages.cannotContinue, ephemeral: true }).then(() => { // Stop execution here return; }).catch(error => { console.error('Error:', error); }); } }) .catch(error => { console.error('Error:', error); }); } if (blacklisted == true) return const buff = await createCaptcha(captcha); const attachment = new AttachmentBuilder(buff, { name: 'captcha.png' }); const embed = new EmbedBuilder() .setTitle("Captcha Verification") .setDescription(`Please type the captcha below. You have ${cfg.captchaTimeout / 1000} seconds to respond. Attempt ${attempts}/${maxAttempts}`) .setColor('Green') .setImage('attachment://captcha.png') .setTimestamp(); if (attempts > 1) { await interaction.user.send(cfg.messages.fail); } await interaction.user.send({ embeds: [embed], files: [attachment] }).then(async (msg) => { const filter = m => !m.author.bot && m.author.id === interaction.user.id && m.channel.id === msg.channel.id; const collector = msg.channel.createMessageCollector({ filter, time: cfg.captchaTimeout }); collector.on('collect', async (m) => { //if member left the server before the verification is done if (!interaction.guild.members.cache.has(interaction.user.id)) { await interaction.user.send(cfg.messages.userLeft); collector.stop(); } else { if (m.content === captcha) { validated = true; await interaction.member.roles.add(cfg.guilds[guildId].roleToAdd); await interaction.member.roles.remove(cfg.guilds[guildId].firstRole); await interaction.user.send(cfg.messages.success); return } else { // If the content doesn't match captcha collector.stop(); } } }); collector.on('end', async (collected, reason) => { if (reason === 'time' && !validated) { // Send timeout message only if not already validated await interaction.user.send(cfg.messages.tookTooLong); } else if (!validated) { // Only generate a new captcha and attempt verification if attempts < maxAttempts const newCaptcha = await generateCaptcha(cfg.captchaCount); await verifyUser(newCaptcha); } }); }).catch(async (err) => { await interaction.reply({ content: cfg.messages.unableToDM, ephemeral: true }); }); } const captcha = await generateCaptcha(cfg.captchaCount); await interaction.reply({ content: cfg.messages.toVerify, ephemeral: true }); verifyUser(captcha); } } /* Note from Naoki: This looks simple enough but damn. Canvas is driving me nuts due to how hard to work with it. Had to StackOverFlow-ed it to get it to work. I'm not sure if this is the best way to do it but it works. Big shoutout to StackOverFlow and the people I got the code from. */