draw-captcha/index.js

150 lines
4.5 KiB
JavaScript

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 };