openpuzzle-captcha/module/index.js
2025-12-28 15:54:54 +03:00

118 lines
2.9 KiB
JavaScript

const fs = require("fs");
const path = require("path");
const gm = require("./gm");
const mime = require("./mimetype");
function shuffle(array) {
for (let i = array.length - 1; i > 0; i--) {
let j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
}
function readFixedBytes(pathToFile, size = 16) {
const fd = fs.openSync(pathToFile, "r");
try {
const buffer = Buffer.alloc(size);
fs.readSync(fd, buffer, 0, size, 0);
return buffer;
} catch (err) {
console.error(err);
} finally {
fs.closeSync(fd);
}
}
async function isImage (pathToFile, requiredSize=null) {
const contentType = mime.byPath(pathToFile).contentType;
const isImageMime = /^image\//m.test(contentType);
if (
!isImageMime || !requiredSize ||
!Array.isArray(requiredSize)
) return isImageMime;
const [width, height] = requiredSize;
const { width:ow, height:oh } = await gm.getSize(
fs.readFileSync(pathToFile)
);
return width === ow && height === oh;
}
async function getImageFromFolderGeneral (imagesFolder) {
// Get list of images from folder
const entries = fs.readdirSync(imagesFolder, { withFileTypes: true });
const images = [];
for (let entry of entries) {
if (!entry.isDirectory() && !!(await isImage(path.join(imagesFolder, entry.name), [150, 150]))) {
images.push(path.join(imagesFolder, entry.name));
}
}
if (images.length === 0)
throw new Error(imagesFolder, "has not required images (150x150 px)");
shuffle(images);
//console.debug(images);
return images[0];
}
function genHashGeneral(length = 3) {
return Buffer.from(
[...new Array(length)].map((x) => Math.round(Math.random() * 255)),
).toString("hex");
}
async function generateTask (imagesFolder, options = {}) {
if (!path.isAbsolute(imagesFolder))
imagesFolder = path.join(process.cwd(), imagesFolder);
// Define tools
if (!options.genHash) options.genHash = genHashGeneral;
if (!options.getImageFromFolder)
options.getImageFromFolder = getImageFromFolderGeneral;
const choosenImage = fs.readFileSync(await getImageFromFolderGeneral(imagesFolder));
// Split the image by 9 shards
const fragments = new Object();
let answer = "";
const addOps = [];
for (let row = 0; row < 3; row++)
for (let col = 0; col < 3; col++) {
let key;
do {
key = options.genHash();
} while (!!fragments[key]);
answer += key;
const shardBytes = await gm.repixelise(
await gm.cropImage(choosenImage, 50, 50, col * 50, row * 50),
);
addOps.push(
() =>
(fragments[key] =
`data:image/png;base64,${shardBytes.toString("base64")}`),
);
}
shuffle(addOps);
addOps.forEach((f) => f());
const successCode = options.genHash(8);
return {
openPuzzleCaptchaAPI: {
getTask: {
isError: false,
mode: "3x3",
fragments
},
taskSolved: {
isError: false,
isSuccess: true,
successCode
},
},
answer,
};
}
module.exports = { generateTask };