144 lines
4.9 KiB
JavaScript
144 lines
4.9 KiB
JavaScript
const sharp = require('sharp');
|
|
const canvas = require('canvas');
|
|
const crypto = require('crypto');
|
|
const path = require('path');
|
|
const randint = (min, max) => { return crypto.randomInt(min, max) };
|
|
|
|
canvas.registerFont(path.join(__dirname, 'asset/lobster.ttf'), { family: 'Lobster' });
|
|
|
|
/** @param {import('fastify').FastifyRequest} req */
|
|
const getSettings = (req) => {
|
|
const q = req.query;
|
|
const settings = {
|
|
// Width and height are ratio numbers; actual width & height is calculated from text length & padding
|
|
width: q.width ?? 3,
|
|
height: q.height ?? 1,
|
|
padding: q.padding ?? 20,
|
|
format: q.format ?? 'png',
|
|
dpi: q.dpi ?? 250
|
|
};
|
|
for (const key in settings) {
|
|
if (['format'].indexOf(key) !== -1) continue;
|
|
settings[key] = parseInt(settings[key]);
|
|
}
|
|
return settings;
|
|
}
|
|
|
|
const get_size = (ratio, dpi, txtlen, padding) => {
|
|
return {
|
|
width: Math.floor((ratio[0] * dpi * 0.12) + (txtlen * dpi * 0.08)) + (padding * 2),
|
|
height: Math.floor(ratio[1] * dpi * 0.2) + (padding * 2)
|
|
}
|
|
}
|
|
|
|
const createImage = async (width, height, bg = 'white', options = {}) => {
|
|
return sharp({
|
|
create: {
|
|
width: width,
|
|
height: height,
|
|
channels: 3,
|
|
background: bg,
|
|
...options
|
|
}
|
|
})
|
|
}
|
|
const drawText = (pic, text, dpi, blend, options = {}) => {
|
|
return pic.composite([{
|
|
input: {
|
|
text: {
|
|
text,
|
|
font: 'Open Sans',
|
|
dpi,
|
|
rgba: true,
|
|
...options
|
|
}
|
|
},
|
|
blend
|
|
}])
|
|
}
|
|
|
|
/** @param {import('fastify').FastifyInstance} fastify */
|
|
module.exports = (fastify) => {
|
|
fastify.get('/captcha/v1/:text', async (req, res) => {
|
|
|
|
const text = req.params.text;
|
|
|
|
const settings = getSettings(req);
|
|
const size = get_size([settings.width, settings.height], settings.dpi, text.length, settings.padding);
|
|
|
|
console.log(size)
|
|
console.log(settings)
|
|
|
|
let pic = await createImage(size.width, size.height);
|
|
|
|
drawText(pic, text, settings.dpi, 'over');
|
|
|
|
const textpic = await pic
|
|
.ensureAlpha()
|
|
.toFormat('png', { compressionLevel: 0 })
|
|
.toBuffer();
|
|
|
|
const draw = canvas.createCanvas(size.width, size.height);
|
|
const ctx = draw.getContext('2d');
|
|
ctx.drawImage(await canvas.loadImage(textpic), 0, 0, size.width, size.height);
|
|
|
|
const strokes = randint(25, 40);
|
|
for (let i = 0; i < strokes; i++) {
|
|
ctx.strokeStyle = `rgba(0,0,0,${10})`;
|
|
ctx.beginPath();
|
|
ctx.bezierCurveTo(randint(0, size.width), randint(0, size.height), randint(0, size.width), randint(0, size.height), randint(0, size.width), randint(0, size.height));
|
|
ctx.stroke();
|
|
}
|
|
|
|
res.header('Content-Type', 'png');
|
|
return draw.toBuffer();
|
|
});
|
|
|
|
fastify.get('/captcha/v2/:text', async (req, res) => {
|
|
const text = req.params.text;
|
|
const settings = getSettings(req);
|
|
const size = get_size([settings.width, settings.height], settings.dpi, text.length, settings.padding);
|
|
size.height *= 0.5
|
|
|
|
const draw = canvas.createCanvas(size.width, size.height);
|
|
const ctx = draw.getContext('2d');
|
|
|
|
const arcs = randint(5, 32);
|
|
for (let i = 0; i != arcs; i++) {
|
|
ctx.beginPath();
|
|
ctx.strokeStyle = `rgba(${randint(0,255)},${randint(0,255)},${randint(0,255)},${randint(0,255)})`;
|
|
ctx.arc(randint(0, size.width), randint(0, size.height), randint(10, 25), 0, 360);
|
|
ctx.stroke();
|
|
ctx.closePath();
|
|
}
|
|
|
|
// let i = 0;
|
|
ctx.textDrawingMode = 'path';
|
|
|
|
const textStyles = ['#f00', '#2a4', '#00f', '#9a0', '#f0f'];
|
|
|
|
ctx.strokeStyle = textStyles[randint(0, textStyles.length)];
|
|
ctx.font = `${settings.dpi * 0.2}px sans-serif`;
|
|
|
|
textStyles.splice(textStyles.indexOf(ctx.strokeStyle), 1);
|
|
const gap = randint(50, 64);
|
|
for (const i in text) {
|
|
ctx.fillText(text[i], settings.padding + (i * randint(40,48)), randint(Math.floor(settings.padding + settings.dpi * 0.05), size.height - settings.padding + Math.floor(settings.dpi * 0.1)), 200);
|
|
ctx.stroke()
|
|
}
|
|
|
|
const fauxletters = randint(20,40);
|
|
let alphabet = 'abcdefghijklmnopqrstuvwxyz';
|
|
alphabet += alphabet.toUpperCase();
|
|
alphabet += '0123456789';
|
|
for (let i = 0; i !== fauxletters; i++) {
|
|
ctx.strokeStyle = textStyles[randint(0,textStyles.length)] + '2';
|
|
ctx.font = `${settings.dpi * 0.2}px serif`
|
|
ctx.strokeText(alphabet[randint(0, alphabet.length)], randint(0, size.width), randint(0, size.height), 200);
|
|
ctx.stroke()
|
|
}
|
|
|
|
res.header('Content-Type', 'image/png');
|
|
return draw.toBuffer();
|
|
})
|
|
} |