From 6ec6b32ed5776be4d7005a4d3817de837112960a Mon Sep 17 00:00:00 2001 From: Thomas Lynch Date: Fri, 1 Jul 2022 00:21:12 +1000 Subject: [PATCH] Change "wave" and "paint" text effect captcha options from toggle to slider Add "noise" text captcha effect slider Add font lib to get list of system fonts Add "font" text captcha option ref #469 --- configs/template.js.example | 6 ++++-- controllers/forms/globalsettings.js | 11 ++++++++--- lib/captcha/generators/text.js | 23 +++++++++++++---------- lib/misc/fonts.js | 21 +++++++++++++++++++++ migrations/0.8.0.js | 6 ++++-- models/forms/changeglobalsettings.js | 6 ++++-- models/pages/globalmanage/settings.js | 2 ++ views/pages/globalmanagesettings.pug | 19 +++++++++++++------ 8 files changed, 69 insertions(+), 25 deletions(-) create mode 100644 lib/misc/fonts.js diff --git a/configs/template.js.example b/configs/template.js.example index e342560d..3c57067b 100644 --- a/configs/template.js.example +++ b/configs/template.js.example @@ -35,9 +35,11 @@ module.exports = { type: 'text', generateLimit: 250, text: { + font: 'default', wave: false, - line: false, - paint: false, + line: 0, + paint: 2, + noise: 0, }, grid: { falses: ['○','□','♘','♢','▽','△','♖','✧','♔','♘','♕','♗','♙','♧'], diff --git a/controllers/forms/globalsettings.js b/controllers/forms/globalsettings.js index a8e81d88..6ef44640 100644 --- a/controllers/forms/globalsettings.js +++ b/controllers/forms/globalsettings.js @@ -4,6 +4,7 @@ const changeGlobalSettings = require(__dirname+'/../../models/forms/changeglobal , dynamicResponse = require(__dirname+'/../../lib/misc/dynamic.js') , themeHelper = require(__dirname+'/../../lib/misc/themes.js') , config = require(__dirname+'/../../lib/misc/config.js') + , { fontPaths } = require(__dirname+'/../../lib/misc/fonts.js') , paramConverter = require(__dirname+'/../../lib/middleware/input/paramconverter.js') , { checkSchema, lengthBody, numberBody, minmaxBody, numberBodyVariable, inArrayBody } = require(__dirname+'/../../lib/input/schema.js'); @@ -13,8 +14,8 @@ module.exports = { paramConverter: paramConverter({ timeFields: ['ban_duration', 'board_defaults_filter_ban_duration', 'default_ban_duration', 'block_bypass_expire_after_time', 'dnsbl_cache_time', 'board_defaults_delete_protection_age'], trimFields: ['captcha_options_grid_question', 'captcha_options_grid_trues', 'captcha_options_grid_falses','allowed_hosts', 'dnsbl_blacklists', 'other_mime_types', - 'highlight_options_language_subset', 'global_limits_custom_css_filters', 'board_defaults_filters', 'filters', 'archive_links', 'reverse_links'], - numberFields: ['filter_mode', 'auth_level', + 'highlight_options_language_subset', 'global_limits_custom_css_filters', 'board_defaults_filters', 'filters', 'archive_links', 'reverse_links', 'captcha_options_text_font'], + numberFields: ['filter_mode', 'auth_level', 'captcha_options_text_wave', 'captcha_options_text_paint', 'captcha_options_text_noise', 'captcha_options_generate_limit', 'captcha_options_grid_size', 'captcha_options_grid_image_size', 'captcha_options_num_distorts_min', 'captcha_options_num_distorts_max', 'captcha_options_distortion', 'captcha_options_grid_icon_y_offset', 'flood_timers_same_content_same_ip', 'flood_timers_same_content_any_ip', 'flood_timers_any_content_same_ip', 'block_bypass_expire_after_uses', 'rate_limit_cost_captcha', 'rate_limit_cost_board_settings', 'rate_limit_cost_edit_post', @@ -58,7 +59,7 @@ module.exports = { }, expected: false, error: 'Extra mime types must be like type/subtype' }, { result: () => { if (req.body.archive_links) { - /* eslint-disable no-useless-escape */ + /* eslint-disable no-useless-escape */ return /https?\:\/\/[^\s<>\[\]{}|\\^]+%s[^\s<>\[\]{}|\\^]*/i.test(req.body.archive_links); } return false; @@ -87,6 +88,10 @@ module.exports = { { result: numberBody(req.body.captcha_options_num_distorts_max, 0, 10), expected: true, error: 'Captcha options max distorts must be a number from 0-10' }, { result: minmaxBody(req.body.captcha_options_num_distorts_min, req.body.captcha_options_num_distorts_max), expected: true, error: 'Captcha options distorts min must be less than max' }, { result: numberBody(req.body.captcha_options_distortion, 0, 50), expected: true, error: 'Captcha options distortion must be a number from 0-50' }, + { result: inArrayBody(req.body.captcha_options_text_font, fontPaths), expected: true, error: 'Invalid captcha options text font' }, + { result: numberBody(req.body.captcha_options_text_wave, 0, 10), expected: true, error: 'Captcha options text wave effect strength must be a number form 0-10' }, + { result: numberBody(req.body.captcha_options_text_paint, 0, 10), expected: true, error: 'Captcha options text paint effect strength must be a number from 0-10' }, + { result: numberBody(req.body.captcha_options_text_noise, 0, 10), expected: true, error: 'Captcha options text noise effect strength must be a number from 0-10' }, { result: numberBody(req.body.dnsbl_cache_time), expected: true, error: 'Invalid dnsbl cache time' }, { result: numberBody(req.body.flood_timers_same_content_same_ip), expected: true, error: 'Invalid flood time same content same ip' }, { result: numberBody(req.body.flood_timers_same_content_any_ip), expected: true, error: 'Invalid flood time same contenet any ip' }, diff --git a/lib/captcha/generators/text.js b/lib/captcha/generators/text.js index 8e49c247..5d0775d2 100644 --- a/lib/captcha/generators/text.js +++ b/lib/captcha/generators/text.js @@ -49,6 +49,10 @@ module.exports = async () => { .fill('#000000') .fontSize(65); + if (captchaOptions.text.font !== 'default') { + captcha.font(captchaOptions.text.font); + } + //draw each character at their x based on the characterWidth() const textWidth = totalTextWidth(text); const startX = (width-textWidth)/2; @@ -58,27 +62,26 @@ module.exports = async () => { charX += characterWidth(text[i]); } - /* - TODO: possibilities for customising some of the values in these options. - Some can be converted to number inputs and 0=off? - */ //draw optional line/strikethrough - if (captchaOptions.text.line) { + if (captchaOptions.text.line === true) { const lineY = await randomRange(35,45); captcha.drawRectangle(startX, lineY, startX+textWidth, lineY+4); } //add optional wave effect - if (captchaOptions.text.wave) { - captcha.wave(5, width/4); + if (captchaOptions.text.wave > 0) { + captcha.wave(captchaOptions.text.wave, width/4); } //add optional paint effect - if (captchaOptions.text.paint) { - captcha.paint(2); + if (captchaOptions.text.paint > 0) { + captcha.paint(captchaOptions.text.paint); } - //TODO: noise effect + //add optional noise effect + if (captchaOptions.text.noise > 0) { + captcha.noise(captchaOptions.text.noise); + } //create an array of distortions and apply to the image, if distortion is enabled const { distortion, numDistorts } = captchaOptions; diff --git a/lib/misc/fonts.js b/lib/misc/fonts.js new file mode 100644 index 00000000..8062d3d0 --- /dev/null +++ b/lib/misc/fonts.js @@ -0,0 +1,21 @@ +'use strict'; + +const fontList = require('child_process') + .execSync('fc-list -f "%{file}:%{family[0]} %{style[0]}\n"') + .toString() + .split('\n') //split by newlines, like here ^ + .filter(line => line) //filter empty lines + .map(line => { + //map to an object with path and name + const [path, name] = line.split(':'); + return { path, name }; + }) + .sort((a, b) => { + //alphabetical name sort + return a.name.localeCompare(b.name); + }); + +module.exports = { + fontList, + fontPaths: new Set(['default', ...fontList.map(f => f.path)]), //memoize paths +}; diff --git a/migrations/0.8.0.js b/migrations/0.8.0.js index 7fd44e17..f502e1db 100644 --- a/migrations/0.8.0.js +++ b/migrations/0.8.0.js @@ -5,9 +5,11 @@ module.exports = async(db, redis) => { await db.collection('globalsettings').updateOne({ _id: 'globalsettings' }, { '$set': { 'captchaOptions.text': { + 'font': 'default', 'line': false, - 'wave': false, - 'paint': false, + 'wave': 0, + 'paint': 2, + 'noise': 0, }, 'captchaOptions.grid.falses': ['○','□','♘','♢','▽','△','♖','✧','♔','♘','♕','♗','♙','♧'], 'captchaOptions.grid.trues': ['●','■','♞','♦','▼','▲','♜','✦','♚','♞','♛','♝','♟','♣'], diff --git a/models/forms/changeglobalsettings.js b/models/forms/changeglobalsettings.js index aed9c7f0..28eb5aee 100644 --- a/models/forms/changeglobalsettings.js +++ b/models/forms/changeglobalsettings.js @@ -78,9 +78,11 @@ module.exports = async (req, res) => { falses: arraySetting(req.body.captcha_options_grid_falses, oldSettings.captchaOptions.grid.falses), }, text: { + font: trimSetting(req.body.captcha_options_text_font, oldSettings.captchaOptions.text.font), line: booleanSetting(req.body.captcha_options_text_line, oldSettings.captchaOptions.text.line), - wave: booleanSetting(req.body.captcha_options_text_wave, oldSettings.captchaOptions.text.wave), - paint: booleanSetting(req.body.captcha_options_text_paint, oldSettings.captchaOptions.text.paint), + wave: numberSetting(req.body.captcha_options_text_wave, oldSettings.captchaOptions.text.wave), + paint: numberSetting(req.body.captcha_options_text_paint, oldSettings.captchaOptions.text.paint), + noise: numberSetting(req.body.captcha_options_text_noise, oldSettings.captchaOptions.text.noise), }, numDistorts: { min: numberSetting(req.body.captcha_options_num_distorts_min, oldSettings.captchaOptions.numDistorts.min), diff --git a/models/pages/globalmanage/settings.js b/models/pages/globalmanage/settings.js index 0849290e..3a1067b0 100644 --- a/models/pages/globalmanage/settings.js +++ b/models/pages/globalmanage/settings.js @@ -1,6 +1,7 @@ 'use strict'; const config = require(__dirname+'/../../../lib/misc/config.js') + , { fontList } = require(__dirname+'/../../../lib/misc/fonts.js') , { themes, codeThemes } = require(__dirname+'/../../../lib/misc/themes.js') , { countryNamesMap, countryCodes } = require(__dirname+'/../../../lib/misc/countries.js'); @@ -16,6 +17,7 @@ module.exports = async (req, res) => { countryCodes, themes, codeThemes, + fontList, }); }; diff --git a/views/pages/globalmanagesettings.pug b/views/pages/globalmanagesettings.pug index 5b5146e5..6b2f6397 100644 --- a/views/pages/globalmanagesettings.pug +++ b/views/pages/globalmanagesettings.pug @@ -26,6 +26,7 @@ block content .form-wrapper.flexleft.mt-10 form.form-post(action=`/forms/global/settings`, enctype='application/x-www-form-urlencoded', method='POST') input(type='hidden' name='_csrf' value=csrf) + input.row(type='submit', value='save settings') .row.wrap.sb .col.mr-5 @@ -193,20 +194,26 @@ block content h4.mv-5 Text Captcha Options .row .label Font - select(name='captcha_options_text_font' disabled) - option(selected=true) Default + select(name='captcha_options_text_font') + option(value='default' selected=(settings.captchaOptions.text.font === 'default')) Default + each font in fontList + option(value=font.path selected=(settings.captchaOptions.text.font === font.path)) #{font.name} .row .label Strikethrough Effect label.postform-style.ph-5 input(type='checkbox', name='captcha_options_text_line', value='true' checked=settings.captchaOptions.text.line) .row - .label Wave Effect + .label Wave Effect Strength + label.postform-style.ph-5 + input(type='range' name='captcha_options_text_wave' min='0' max='10' value=settings.captchaOptions.text.wave) + .row + .label Paint Effect Strength label.postform-style.ph-5 - input(type='checkbox', name='captcha_options_text_wave', value='true' checked=settings.captchaOptions.text.wave) + input(type='range' name='captcha_options_text_paint' min='0' max='10' value=settings.captchaOptions.text.paint) .row - .label Paint Effect + .label Noise Effect Strength label.postform-style.ph-5 - input(type='checkbox', name='captcha_options_text_paint', value='true' checked=settings.captchaOptions.text.text) + input(type='range' name='captcha_options_text_noise' min='0' max='10' value=settings.captchaOptions.text.noise) .row h4.mv-5 Captcha Distortion .row