jschan - Anonymous imageboard software. Classic look, modern features and feel. Works without JavaScript and supports Tor, I2P, Lokinet, etc.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

127 lines
4.7 KiB

'use strict';
const gm = require('@fatchan/gm').subClass({ imageMagick: true })
, { promisify } = require('util')
, getDistorts = require(__dirname+'/../getdistorts.js')
, randomRange = promisify(require('crypto').randomInt)
, randomBytes = promisify(require('crypto').randomBytes)
, padding = 30 //pad edge of image to account for character size + distortion
, nArrows = ['↑', '↟', '↥', '↾', '↿', '⇑', '⇡']
, eArrows = ['➸', '→', '➳', '➵', '→', '↛', '↠', '↣', '↦', '↪', '↬', '↱', '↳', '⇉', '⇏', '⇒', '⇛', '⇝', '⇢']
, wArrows = [ '←', '↚', '↞', '↜', '↢', '↩', '↤', '↫', '↰', '↲', '↵', '⇇', '⇍', '⇐', '⇚', '⇜', '⇠']
, sArrows = ['↓', '↡', '↧', '↴', '⇂', '⇃', '⇊', '⇓', '⇣']
, allArrows = [...nArrows, ...eArrows, ...wArrows, ...sArrows]
, randomBool = async (p) => { return ((await randomBytes(1))[0] > p); }
, randomOf = async (arr) => { return arr[(await randomRange(0, arr.length))]; };
//TODO: last two could belong in lib/misc/(random?)
module.exports = async (captchaOptions) => {
const { size, trues, falses, imageSize, noise, edge } = captchaOptions.grid;
const width = imageSize+padding; //TODO: these will never be different, right?
const height = imageSize+padding;
const charMatrix = new Array(size).fill(false)
.map(() => new Array(size).fill(false));
const answerMatrix = new Array(size).fill(false)
.map(() => new Array(size).fill(false));
//put the icon arrows should point at
const correctRow = await randomRange(0, size);
const correctCol = await randomRange(0, size);
charMatrix[correctRow][correctCol] = await randomOf(trues);
//put correct and incorrect arrows in the row/column
const numArray = [...new Array(size).keys()];
const perpendicularRows = numArray.filter(x => x !== correctRow);
for (let row of perpendicularRows) {
/*TODO: necessary to memoize these "inverse" sets of arrows? or maybe instead of even doing a 50/50
random, it should just pick a random from allArrows then set the isCorrect based on if its in the correct set?*/
let arrows;
const isCorrect = await randomBool(127);
if (row < correctRow) {
arrows = isCorrect ? sArrows : [...nArrows, ...eArrows, ...wArrows];
} else if (row > correctRow) {
arrows = isCorrect ? nArrows : [...sArrows, ...eArrows, ...wArrows];
}
charMatrix[row][correctCol] = await randomOf(arrows);
answerMatrix[row][correctCol] = isCorrect;
}
const perpendicularCols = numArray.filter(x => x !== correctCol);
for (let col of perpendicularCols) {
let arrows;
const isCorrect = await randomBool(127);
if (col < correctCol) {
arrows = isCorrect ? eArrows : [...sArrows, ...nArrows, ...wArrows];
} else if (col > correctCol) {
arrows = isCorrect ? wArrows : [...sArrows, ...nArrows, ...eArrows];
}
charMatrix[correctRow][col] = await randomOf(arrows);
answerMatrix[correctRow][col] = isCorrect;
}
//TODO: diagonals? need more arrows
//this sucks
if (!answerMatrix.flat().some(x => x === true)) {
if ((await randomBool(127)) === true) {
const randomRow = await randomOf(perpendicularRows);
const arrows = randomRow < correctRow ? sArrows : nArrows;
charMatrix[randomRow][correctCol] = await randomOf(arrows);
answerMatrix[randomRow][correctCol] = true;
} else {
const randomCol = await randomOf(perpendicularCols);
const arrows = randomCol < correctCol ? eArrows : wArrows;
charMatrix[correctRow][randomCol] = await randomOf(arrows);
answerMatrix[correctRow][randomCol] = true;
}
}
//fill the rest with junk arrows/falses
for (let row = 0; row < size; row++) {
for (let col = 0; col < size; col++) {
if (charMatrix[row][col] === false) {
charMatrix[row][col] = (await randomBool(80))
? (await randomOf(allArrows))
: (await randomOf(falses));
}
}
}
const captcha = gm(width, height, '#ffffff')
.fill('#000000')
.font(__dirname+'/../font.ttf');
const spaceSize = (width-padding)/size;
for (let row = 0; row < size; row++) {
let cxOffset = Math.floor(spaceSize * 1.5);
for (let col = 0; col < size; col++) {
const cyOffset = captchaOptions.grid.iconYOffset;
captcha.fontSize((await randomRange(20, 30)));
captcha.drawText(
(spaceSize * col) + cyOffset,
(spaceSize * row) + cxOffset,
charMatrix[row][col]
);
}
}
//create an array of distortions and apply to the image, if distortion is enabled
const { distortion, numDistorts } = captchaOptions;
if (distortion > 0) {
const distorts = await getDistorts(width, height, numDistorts, distortion);
captcha.distort(distorts, 'Shepards');
}
//add optional edge effect
if (edge > 0) {
captcha.edge(edge);
}
//add optional noise effect
if (noise > 0) {
captcha.noise(noise);
}
return { captcha, solution: answerMatrix.flat() };
};