'use strict';
const Canvas = require('canvas');
const { DisGroupDevError, Messages } = require('../errors/DisGroupDevError');
/**
* @typedef {Object} CaptchaOptions
* @property {?Number} height The height of the captcha (default: 250)
* @property {?Number} length The length of the captcha (default: 6)
* @property {?String} value The value of the captcha if not provided it will be generated randomly
* @property {?Number} width The width of the captcha (default: 400) ⚠️ Do only change if you know what you do! ⚠️
*/
/**
* The captcha class
* @class
*/
class Captcha {
/**
* The constructor of the captcha class
* @param {CaptchaOptions} options The options of the captcha
*/
constructor(options = {}) {
if (!options.height || typeof options.height !== 'number') options.height = 250;
if (!options.length || typeof options.length !== 'number') options.length = 6;
if (!options.width || typeof options.width !== 'number') options.width = 400;
/**
* The canvas
* @type {Canvas.Canvas}
* @private
*/
this._canvas = Canvas.createCanvas(options.width, options.height);
/**
* The options of the captcha
* @type {CaptchaOptions}
* @public
*/
this.options = options;
/**
* The value of the captcha
* @type {String}
* @public
*/
this.value = options.value ?? Captcha.randomText(options.length);
this._generateCaptcha();
}
/**
* Generates the captcha
* @private
*/
_generateCaptcha() {
const ctx = this._canvas.getContext('2d');
ctx.globalAlpha = 1;
ctx.fillStyle = '#ffffff';
ctx.beginPath();
ctx.fillRect(0, 0, this.options.width, this.options.height);
ctx.save();
ctx.strokeStyle = '#000000';
ctx.lineWidth = 4;
ctx.beginPath();
const coords = [];
for (let i = 0; i < 4; i++) {
if (!coords[i]) coords[i] = [];
for (let j = 0; j < 5; j++) coords[i][j] = Math.round(Math.random() * 80) + j * 80;
if (!(i % 2)) coords[i] = Captcha.shuffleArray(coords[i]);
}
for (let i = 0; i < coords.length; i++) {
if (!(i % 2)) {
for (let j = 0; j < coords[i].length; j++) {
if (!i) {
ctx.moveTo(coords[i][j], 0);
ctx.lineTo(coords[i + 1][j], this.options.width);
} else {
ctx.moveTo(0, coords[i][j]);
ctx.lineTo(this.options.width, coords[i + 1][j]);
}
}
}
}
ctx.stroke();
ctx.fillStyle = '#000000';
ctx.lineWidth = 0;
for (let i = 0; i < 200; i++) {
ctx.beginPath();
ctx.arc(Math.round(Math.random() * 360) + 20, Math.round(Math.random() * 360) + 20, Math.round(Math.random() * 7) + 1, 0, Math.PI * 2);
ctx.fill();
}
ctx.font = 'bold 75px sans-serif';
ctx.fillStyle = '#000000';
ctx.textAlign = 'center';
ctx.textBaseline = 'top';
ctx.translate(0, this.options.height);
ctx.translate(
Math.round(Math.random() * 100 - 50) + 200,
-1 * Math.round(Math.random() * (this.options.height / 4) - this.options.height / 8) - this.options.height / 2,
);
ctx.rotate(Math.random() - 0.5);
ctx.beginPath();
ctx.fillText(this.value, 0, 0);
ctx.restore();
for (let i = 0; i < 5000; i++) {
ctx.beginPath();
let color = '#';
while (color.length < 7) color += Math.round(Math.random() * 16).toString(16);
color += 'a0';
ctx.fillStyle = color;
ctx.arc(Math.round(Math.random() * this.options.width), Math.round(Math.random() * this.options.height), Math.random() * 2, 0, Math.PI * 2);
ctx.fill();
}
}
/**
* The JPG stream of the captcha
* @returns {Canvas.JPEGStream}
* @public
*/
get jpeg() {
return this._canvas.createJPEGStream();
}
/**
* The PDF stream of the captcha
* @returns {Canvas.PDFStream}
* @public
*/
get pdf() {
return this._canvas.createPDFStream();
}
/**
* The PNG stream of the captcha
* @returns {Canvas.PNGStream}
* @public
*/
get png() {
return this._canvas.createPNGStream();
}
/**
* Generates a random text
* @param {Number} length The length of the text (default: 6)
* @returns {String}
* @public
*/
static randomText(length = 6) {
if (typeof length !== 'number') throw new DisGroupDevError(Messages.NOT_A_NUMBER(length));
return Math.random().toString(36).replace(/[^a-z]|[gkqr]+/gi, '')
.substring(0, length)
.toUpperCase();
}
/**
* Shuffles an array with numbers
* @param {Array<Number>} arr The array to shuffle
* @returns {Array<Number>}
* @public
*/
static shuffleArray(arr) {
let i = arr.length;
let temp;
let rndIndex;
while (i !== 0) {
rndIndex = Math.floor(Math.random() * i);
i--;
temp = arr[i];
arr[i] = arr[rndIndex];
arr[rndIndex] = temp;
}
return arr;
}
}
module.exports = Captcha;