const path = require("path"); const BINARIES_OF_GM = { linux: path.join(__dirname, "bin/linux/"), win32: path.join(__dirname, "bin/win32/gm.exe"), } const MAIN_FONT = path.join(__dirname, "fonts/Helvetica.ttf"); const gmPortable = (appPath = BINARIES_OF_GM[process.platform] ?? BINARIES_OF_GM["linux"]) => require("gm").subClass({ appPath }); const gmGlobal = () => require("gm"); function shuffle(array) { let currentIndex = array.length; // While there remain elements to shuffle... while (currentIndex != 0) { // Pick a remaining element... let randomIndex = Math.floor(Math.random() * currentIndex); currentIndex--; // And swap it with the current element. [array[currentIndex], array[randomIndex]] = [ array[randomIndex], array[currentIndex]]; } return array; } function randint (min, max) { return Math.floor(Math.random() * (max - min + 1) + min); } class Captcha { constructor (isPortable = false, customPathToGM = undefined) { const gm = !isPortable ? gmGlobal() : gmPortable(customPathToGM); this.image = gm(400, 150, "#ffffff"); this.fonts = null; } addSharp () { this.image.stroke("#ffffff", 1); const curCordsA = { x: 0, y: 0 }; const curCordsB = { x: 400, y: 0 }; for (let i = 0; i <= 800; i++) { this.image.drawPoint(curCordsA.x, curCordsA.y); curCordsA.x += randint(1, 10); curCordsA.y = Math.floor(i / 2); this.image.drawPoint(curCordsB.x, curCordsB.y); curCordsB.x -= randint(1, 10); curCordsB.y = Math.floor(i / 2); } } wave () { return this.image.wave(0.25, 111); } setupRandFonts (...fonts) { if (this.fonts === null) this.fonts = fonts; else this.fonts.push(...fonts); } getRandomCaptcha () { const dictonary="qwertyuiopasdfghjklzxcvbnm1234567890"; let captchaCode = ""; for (let i = 0; i < 8; i++) { captchaCode += dictonary[randint(0, dictonary.length - 1)]; } return captchaCode; } randomiseFonts () { this.fonts = [ MAIN_FONT, path.join(__dirname, "fonts/Zametka_Parletter.otf"), path.join(__dirname, "fonts/LeonovSP.otf"), path.join(__dirname, "fonts/RuthlessSketchItalic.ttf"), //path.join(__dirname, "fonts/Pshek_PY2.ttf"), path.join(__dirname, "fonts/Daneehand Regular Cyr.ttf"), ]; } setupCode (code) { this.image.stroke("#ffffff" , 1); if (typeof code !== "string" || code.length !== 8) throw new SyntaxError("Code must be string and contains only 8 characters!"); if (this.fonts === null) return this.image.font(MAIN_FONT, 86).drawText(10, randint(80, 120), ""+code); else { const codeSize = (""+code).length; let i = 0; while (true) { for (let font of shuffle([].concat(this.fonts, this.fonts, this.fonts))) { this.image.font(font, randint(80, 86)); this.image.drawText(10 + (i * randint(46, 48)), randint(80, 120), ""+code[i]); i++; if (i >= codeSize) break; } if (i >= codeSize) break; } return this.image; } } async saveToImage (path) { const promise = new Promise((rs, rj) => { this.image.write(path, function (err) { if (!err) return rs(); rj(err); }); }); return await promise; } async saveToBuffer () { const promise = new Promise((rs, rj) => { this.image.toBuffer("JPG", function (err, buffer) { if (!err) return rs(buffer); rj(err); }); }); return await promise; } } class MasterCaptcha extends Captcha { constructor (...p) { super(...p); } async setupRandomCaptcha (savePath = null) { const code = this.getRandomCaptcha(); this.randomiseFonts(); this.setupCode(code); this.wave(); for (let _ = 0; _ < 5; _++) this.addSharp(); if (savePath === null) return { code, buffer: await this.saveToBuffer() }; await this.saveToImage(savePath); return { code }; } } module.exports = { Captcha, MasterCaptcha };