code:
'use strict';

//use simple string replacement and filter to remove dead quotes, instead of running the whole messagehandler again
module.exports = (deletedPosts, updateQuotePosts) => {
	const bulkWrites = [];
	updateQuotePosts.forEach(post => {
		deletedPosts.forEach(ap => {
			const quotesBefore = post.quotes.length;
			post.quotes = post.quotes.filter(q => q.postId !== ap.postId);
			if (quotesBefore !== post.quotes.length) { //optimization, probably
				post.message = post.message.replace(
					`<a class="quote" href="/${ap.board}/thread/${ap.thread}.html#${ap.postId}">>>${ap.postId}</a>`,
					`<span class="invalid-quote">>>${ap.postId}</span>`
				);
			}
		});
		bulkWrites.push({
			'updateOne': {
				'filter': {
					'_id': post._id
				},
				'update': {
					'$set': {
						'quotes': post.quotes,
						'message': post.message,
					}
				}
			}
		});
	});
	return bulkWrites;
};


code:
'use strict';

const { Bans } = require(__dirname+'/../../db/')
	, dynamicResponse = require(__dirname+'/../misc/dynamic.js')
	, FIELDS_TO_REPLACE = ['email', 'subject', 'message'];

module.exports = async (req, res, globalFilter, hit, filter, redirect) => {

	const { __ } = res.locals;

	const blockMessage = __('Your post was blocked by a word filter') + (filter.filterMessage ? ': ' + filter.filterMessage : '');

	if (filter.filterMode === 1) {
		return dynamicResponse(req, res, 400, 'message', {
			'title': __('Bad request'),
			'message': blockMessage,
			'redirect': redirect
		});
	} else if (filter.filterMode === 2) {
		const banBoard = globalFilter ? [null, res.locals.board._id] : res.locals.board._id;
		const banDate = new Date();
		const banExpiry = new Date(filter.filterBanDuration + banDate.getTime());
		const ban = {
			'global': globalFilter ? true : false,
			'ip': {
				'cloak': res.locals.ip.cloak,
				'raw': res.locals.ip.raw,
				'type': res.locals.ip.type,
			},
			'range': 0,
			'reason': __(`${globalFilter ? 'global ' :''}word filter auto ban`) + (filter.filterMessage ? ': ' + filter.filterMessage : ''),
			'board': banBoard,
			'posts': null,
			'issuer': 'system', //todo: make a "system" property instead?
			'date': banDate,
			'expireAt': banExpiry,
			'allowAppeal': filter.filterBanAppealable,
			'showUser': true,
			'note': __(`${globalFilter ? 'global ' :''}filter hit: "%s"`, (hit)),
			'seen': true,
		};
		const insertedResult = await Bans.insertOne(ban);
		//add and delete some props for the dynamic response
		ban._id = insertedResult.insertedId;
		ban.ip.raw = null;
		ban.note = null;
		return dynamicResponse(req, res, 403, 'ban', {
			bans: [ban]
		});
	} else {
		//the filter could have hit any part of the combinedstring
		for (const field of FIELDS_TO_REPLACE) {
			if (req.body[field]) {
				for (const filterInput of filter.filters) {
					req.body[field] = req.body[field].replaceAll(filterInput, filter.replaceText);
				}
			}
		}
		return;
	}

};

repo_name: Marzisoft/jschan

license: mit


code:
'use strict';

module.exports = (req, res) => {

	//combines a bunch of parts of the post (name, subject, message, filenames+phashes)
	const fileStrings = res.locals.numFiles ? req.files.file.map(f => `${f.name}|${f.phash || ''}|${f.sha256 || ''}`).join('|') : 0;
	const combinedString = [req.body.name, req.body.message, req.body.subject, req.body.email, fileStrings].join('|').toLowerCase();

	//"strict" filtering adds a bunch of permutations to also compare filters with;
	let strictCombinedString = combinedString;

	//diacritics and "zalgo" removed
	strictCombinedString += combinedString.normalize('NFD').replace(/[\u0300-\u036f]/g, '');

	//zero width spaces removed
	strictCombinedString += combinedString.replace(/[\u200B-\u200D\uFEFF]/g, '');

	//just a-z, 0-9, . and -
	strictCombinedString += combinedString.replace(/[^a-zA-Z0-9.-]+/gm, '');

	//urlendoded characters in URLs replaced (todo: remove this if/when the url regex gets updated to no longer match these)
	strictCombinedString += combinedString.split(/(%[^%]+)/).map(part => {
		try {
			return decodeURIComponent(part);
		} catch (e) {
			return '';
		}
	}).join('');

	return  { combinedString, strictCombinedString };

};


code:
const simpleEscape = require('./escape.js');

describe('simpleEscape() - convert some characters to html entities', () => {
	const cases = [
		{ in: '\'', out: ''' },
		{ in: '/', out: '/' },
		{ in: '`', out: '`' },
		{ in: '=', out: '=' },
		{ in: '&', out: '&' },
		{ in: '<', out: '<' },
		{ in: '>', out: '>' },
		{ in: '"', out: '"' },
	];
	for (let i in cases) {
		test(`should output ${cases[i].out} for an input of ${cases[i].in}`, () => {
			expect(simpleEscape(cases[i].in)).toBe(cases[i].out);
		});
	}
});


code:
'use strict';

module.exports = {

	regexPrepare: /##(?<numdice>[1-9][0-9]{0,1})%(?<numsides>1[0-9]{1,8}|[2-9][0-9]{0,8})(?:(?<operator>[+-])(?<modifier>[1-9][0-9]{0,8}))?(?<value>=[1-9][0-9]{0,8})?/gmi,
	regexMarkdown: /##(?<numdice>[1-9][0-9]{0,1})%(?<numsides>1[0-9]{1,8}|[2-9][0-9]{0,8})(?:(?<operator>[+-])(?<modifier>[1-9][0-9]{0,8}))?=(?<value>[1-9][0-9]{0,8})/gmi,

	prepare: (force, match, numdice, numsides, operator, modifier, value) => {
		if (!force && value) {
			return match;
		}

		numdice = parseInt(numdice);
		numsides = parseInt(numsides);
		let matchWithoutValue = match.replace(/=.*/, '');

		let sum = 0;
		for (let i = 0; i < numdice; ++i) {
			sum += Math.floor(Math.random() * numsides) + 1;
		}
		if (modifier && operator) {
			modifier = parseInt(modifier);
			//do i need to make sure it doesnt go negative or maybe give absolute value?
			if (operator === '+') {
				sum += modifier;
			} else {
				sum -= modifier;
			}
		}
		return `${matchWithoutValue}=${sum}${value ? value : ''}`;
	},

	markdown: (permissions, match, numdice, numsides, operator, modifier, value) => {
		numdice = parseInt(numdice);
		numsides = parseInt(numsides);
		value = parseInt(value);
		return `<img src='/file/dice.png' height='16' width='16' /><span class='dice'>(##${numdice}%${numsides}${modifier ? operator+modifier : '' }) = ${value}</span>`;
	},
};


code:
const diceroll = require('./diceroll.js');

describe('diceroll markdown', () => {

	const prepareCases = [
		{ in: '##3%6', out: '##3%6=' },
		{ in: '##99%99', out: '##99%99=' },
		{ in: '##999%999', out: '##999%999' },
		{ in: '##3%8+5', out: '##3%8+5=' },
		{ in: '##3%8-5', out: '##3%8-5=' },
		{ in: '##0%0', out: '##0%0' },
	];
	for (let i in prepareCases) {
		test(`should contain ${prepareCases[i].out} for an input of ${prepareCases[i].in}`, () => {
			const output = prepareCases[i].in.replace(diceroll.regexPrepare, diceroll.prepare.bind(null, false));
			expect(output).toContain(prepareCases[i].out);
		});
	}

	const markdownCases = [
		{ in: '##3%6=10', out: '(##3%6)' },
		{ in: '##99%99=5138', out: '(##99%99)' },
		{ in: '##999%999=10000', out: '##999%999=' },
		{ in: '##0%0=10', out: '##0%0=' },
		{ in: '##0%0', out: '##0%0' },
	];
	for (let i in markdownCases) {
		test(`should contain ${markdownCases[i].out} for an input of ${markdownCases[i].in}`, () => {
			const output = markdownCases[i].in.replace(diceroll.regexMarkdown, diceroll.markdown.bind(null, false));
			expect(output).toContain(markdownCases[i].out);
		});
	}

});


code:
'use strict';

const fortunes = ['example1', 'example2', 'example3'];

module.exports = {

	fortunes,

	regex: /##fortune/gmi,

	markdown: () => {
		const randomFortune = fortunes[Math.floor(Math.random()*fortunes.length)];
		return `<span class='title'>${randomFortune}</span>`;
	},

};


code:
const fortune = require('./fortune.js');

describe('fortune markdown', () => {
	test('should contain a random fortune for an input of ##fortune', () => {
		const output = '##fortune'.replace(fortune.regex, fortune.markdown.bind(null, false));
		const hasFortuneText = fortune.fortunes.some(f => output.includes(f));
		expect(hasFortuneText).toBe(true);
	});
});


code:
'use strict';

const { Permissions } = require(__dirname+'/../../../permission/permissions.js');

module.exports = (permissions, match, p1, p2, p3, offset, string, groups) => {

	let { url, label, urlOnly } = groups;

	url = url || urlOnly;
	if (!permissions.get(Permissions.MANAGE_BOARD_GENERAL)) {
		label = url
			.replace(/\(/g, '(')
			.replace(/\)/g, ')');
	}
	url = url.replace(/\(/g, '%28')
		.replace(/\)/g, '%29');

/*
	//todo: Something to revisit later
	const href = url;
	if (urlOnly == null) {
		try {
			const urlObject = new URL(url);
			if (config.get.allowedHosts.includes(urlObject.hostname)) {
				href = `${urlObject.pathname}${urlObject.search}${urlObject.hash}`;
			}
		} catch (e) { }
	}
*/

	return `<a rel='nofollow' referrerpolicy='same-origin' target='_blank' href='${url}'>${label || url}</a>`;

};


code:
const linkmatch = require('./linkmatch.js');
/* eslint-disable no-useless-escape */
const linkRegex = /\[(?[^\[][^\]]*?)\]\((?<url>(?:/[^\s<>\[\]{}|\\^)]+|https?\://[^\s<>\[\]{}|\\^)]+))\)|(?<urlOnly>https?\://[^\s<>\[\]{}|\\^]+)/g;
const Permission = require('../../../permission/permission.js');
const ROOT = new Permission();
ROOT.setAll(Permission.allPermissions);
const NO_PERMISSION = new Permission();

describe('link markdown', () => {

	const rootCases = [
		{ in: 'http://something.com', out: 'href=' },
		{ in: 'https://something.com', out: 'href=' },
		{ in: '[test](http://something.com)', out: '>test' },
		{ in: '[test](https://something.com)', out: '>test' },
		{ in: 'http://', out: 'http://' },
		{ in: 'https://', out: 'https://' },
	];

	for (let i in rootCases) {
		test(`should contain ${rootCases[i].out} for an input of ${rootCases[i].in}`, () => {
			expect(rootCases[i].in.replace(linkRegex, linkmatch.bind(null, ROOT))).toContain(rootCases[i].out);
		});
	}

	const cases = [
		{ in: 'http://something.com', out: 'href=' },
		{ in: 'https://something.com', out: 'href=' },
		{ in: '[http://something.com](test)', out: '>http://something.com' },
		{ in: '[https://something.com](test)', out: '>https://something.com' },
		{ in: 'http://', out: 'http://' },
		{ in: 'https://', out: 'https://' },
	];

	for (let i in cases) {
		test(`should contain ${cases[i].out} for an input of ${cases[i].in}`, () => {
			expect(cases[i].in.replace(linkRegex, linkmatch.bind(null, NO_PERMISSION))).toContain(cases[i].out);
		});
	}

});


code:
'use strict';

const greentextRegex = /^>((?!>\d+|>>/\w+(/\d*)?|>>#/).*)/gm
	, pinktextRegex = /^<(.+)/gm
	, boldRegex = /''(.+?)''/gm
	, titleRegex = /==(.+?)==/gm
	, monoRegex = /`(.+?)`/gm
	, underlineRegex = /__(.+?)__/gm
	, strikeRegex = /~~(.+?)~~/gm
	, italicRegex = /\*\*(.+?)\*\*/gm
	, spoilerRegex = /\|\|([\s\S]+?)\|\|/gm
	, detectedRegex = /\(\(\((.+?)\)\)\)/gm
	/* eslint-disable no-useless-escape */
	, linkRegex = /(?:[^!])\[(?<label>[^\[][^\]]*?)\]\((?<url>(?:/[^\s<>\[\]{}|\\^)]+|https?\://[^\s<>\[\]{}|\\^)]+|mailto:[\w.@]+))\)|(?<urlOnly>(?:(?<!='))https?\://[^\s<>\[\]{}|\\^]+)/g
	, imageRegex = /!\[(?<alt>[^\]]*)\]\((?<url>(?:/[^\s<>\[\]{}|\\^]+|https?\://[^\s<>\[\]{}|\\^]+))\)/g
	, codeRegex = /(?:(?<language>[a-z+]{1,14})\r?\n)?(?<code>[\s\S]+)/i
	, includeSplitRegex = /(\[code\][\s\S]+?\[\/code\])/gm
	, splitRegex = /\[code\]([\s\S]+?)\[\/code\]/gm
	, trimNewlineRegex = /^(\s*\r?\n)*/g
	, simpleEscape = require(__dirname+'/escape.js')
	, { highlight, highlightAuto, listLanguages } = require('highlight.js')
	, validLanguages = listLanguages() //precompute
	, { addCallback } = require(__dirname+'/../../redis/redis.js')
	, config = require(__dirname+'/../../misc/config.js')
	, diceroll = require(__dirname+'/handler/diceroll.js')
	, fortune = require(__dirname+'/handler/fortune.js')
	, linkmatch = require(__dirname+'/handler/linkmatch.js')
	, { Permissions } = require(__dirname+'/../../permission/permissions.js');

let replacements = [];

const updateMarkdownPerms = () => {
	replacements = [
		{ permission: Permissions.USE_MARKDOWN_PINKTEXT, regex: pinktextRegex, cb: (permissions, match, pinktext) => `<span class='pinktext'><${pinktext}</span>` },
		{ permission: Permissions.USE_MARKDOWN_GREENTEXT, regex: greentextRegex, cb: (permissions, match, greentext) => `<span class='greentext'>>${greentext}</span>` },
		{ permission: Permissions.USE_MARKDOWN_BOLD, regex: boldRegex, cb: (permissions, match, bold) => `<span class='bold'>${bold}</span>` },
		{ permission: Permissions.USE_MARKDOWN_UNDERLINE, regex: underlineRegex, cb: (permissions, match, underline) => `<span class='underline'>${underline}</span>` },
		{ permission: Permissions.USE_MARKDOWN_STRIKETHROUGH, regex: strikeRegex, cb: (permissions, match, strike) => `<span class='strike'>${strike}</span>` },
		{ permission: Permissions.USE_MARKDOWN_TITLE, regex: titleRegex, cb: (permissions, match, title) => `<span class='title'>${title}</span>` },
		{ permission: Permissions.USE_MARKDOWN_ITALIC, regex: italicRegex, cb: (permissions, match, italic) => `<span class='em'>${italic}</span>` },
		{ permission: Permissions.USE_MARKDOWN_SPOILER, regex: spoilerRegex, cb: (permissions, match, spoiler) => `<span class='spoiler'>${spoiler}</span>` },
		{ permission: Permissions.USE_MARKDOWN_MONO, regex: monoRegex, cb: (permissions, match, mono) => `<span class='mono'>${mono}</span>` },
		{ permission: Permissions.USE_MARKDOWN_DETECTED, regex: detectedRegex, cb: (permissions, match, detected) => `<span class='detected'>((( ${detected} )))</span>` },
		{ permission: Permissions.USE_MARKDOWN_IMAGE, regex: imageRegex, cb: (permissions, match, altText, imageSrc) => `<img class='embimg' title='${altText}' alt='${altText}' src='${imageSrc}' />` },
		{ permission: Permissions.USE_MARKDOWN_LINK, regex: linkRegex, cb: linkmatch },
		{ permission: Permissions.USE_MARKDOWN_DICE, regex: diceroll.regexMarkdown, cb: diceroll.markdown },
		{ permission: Permissions.USE_MARKDOWN_FORTUNE, regex: fortune.regex, cb: fortune.markdown },
	];
};

updateMarkdownPerms();
addCallback('config', updateMarkdownPerms);

module.exports = {

	prepareMarkdown: (text, force) => {
		if (!text || text.length === 0) {
			return text;
		}
		const chunks = text.split(includeSplitRegex);
		for (let i = 0; i < chunks.length; i++) {
			//every other chunk will be a code block
			if (i % 2 === 0) {
				chunks[i] = chunks[i].replace(
					diceroll.regexPrepare, diceroll.prepare.bind(null, force));
			}
		}
		return chunks.join('');
	},

	markdown: (text, permissions) => {
		const chunks = text.split(splitRegex);
		const { highlightOptions } = config.get;
		for (let i = 0; i < chunks.length; i++) {
			//every other chunk will be a code block
			if (i % 2 === 0) {
				const escaped = simpleEscape(chunks[i]);
				const newlineFix = escaped.replace(/^\r?\n/,''); //fix ending newline because of codeblock
				chunks[i] = module.exports.processRegularChunk(newlineFix, permissions);
			} else if (permissions.get(Permissions.USE_MARKDOWN_CODE)) {
				chunks[i] = module.exports.processCodeChunk(chunks[i], highlightOptions);
			}
		}
		return chunks.join('');
	},

	processCodeChunk: (text, highlightOptions) => {
		const matches = text.match(codeRegex);
		const trimFix = matches.groups.code.replace(trimNewlineRegex, '');
		let lang;
		if (matches.groups.language && matches.groups.language.length > 0) {
			lang = matches.groups.language.toLowerCase();
		}
		if (!lang) {
			//no language specified, try automatic syntax highlighting
			const { relevance, value } = highlightAuto(trimFix, highlightOptions.languageSubset);
			if (relevance > highlightOptions.threshold) {
				return `<span class='code hljs'>${value}</span>`;
			}
		} else if (lang === 'aa') {
			return `<span class='aa'>${simpleEscape(matches.groups.code)}</span>`;
		} else if (validLanguages.includes(lang)) {
			const { value } = highlight(trimFix, { language: lang, ignoreIllegals: true });
			return `<span class='code hljs'>${value}</span>`;
		}
		//else, auto highlight relevance threshold was too low, lang was not a valid language, or lang was 'plain'
		return `<span class='code'>${simpleEscape(trimFix)}</span>`;
	},

	processRegularChunk: (text, permissions) => {
		//filter replacements based on their permissions
		const allowedReplacements = replacements.filter(r => permissions.get(r.permission));
		for (let i = 0; i < allowedReplacements.length; i++) {
			//could bind more variables here and make them available as additional arguments. would pass more args -> markdown -> procesRegularChunk, etc.
			text = text.replace(allowedReplacements[i].regex, allowedReplacements[i].cb.bind(null, permissions));
		}
		return text;
	},

};


code:
'use strict';

module.exports = {

	before: {
		allowedTags: [],
		allowedAttributes: {}
	},

	after: {
		allowedTags: [ 'span', 'a', 'img', 'small'],
		allowedAttributes: {
			'a': [ 'href', 'rel', 'class', 'referrerpolicy', 'target' ],
			'span': [ 'class' ],
			'img': ['src', 'height', 'width', 'alt', 'title', 'class']
		}
	}

};


code:
'use strict';

const quoteHandler = require(__dirname+'/quotes.js')
	, { markdown } = require(__dirname+'/markdown/markdown.js')
	, sanitizeOptions = require(__dirname+'/markdown/sanitizeoptions.js')
	, Permission = require(__dirname+'/../permission/permission.js')
	, roleManager = require(__dirname+'/../permission/rolemanager.js')
	, sanitize = require('sanitize-html');

module.exports = async (inputMessage, boardName, threadId=null, permissions=null) => {

	let message = inputMessage;
	let quotes = [];
	let crossquotes = [];

	if (permissions === null) {
		//technically there has for a long time been a bug here, but it can be fixed later. permissions unknown for old msgs
		permissions = new Permission(roleManager.roles.ANON.base64);
	}

	//markdown a post, link the quotes, sanitize and return message and quote arrays
	if (message && message.length > 0) {
		message = markdown(message, permissions);
		const { quotedMessage, threadQuotes, crossQuotes } = await quoteHandler.process(boardName, message, threadId);
		message = quotedMessage;
		quotes = threadQuotes;
		crossquotes = crossQuotes;
		message = sanitize(message, sanitizeOptions.after);
	}

	return { message, quotes, crossquotes };

};


code:
'use strict';

const { getInsecureTrip, getSecureTrip } = require(__dirname+'/tripcode.js')
	, { Permissions } = require(__dirname+'/../permission/permissions.js')
	, nameRegex = /^(?<name>[^#].*?)?(?:(?<tripcode>##(?<strip>[^ ].*?)|#(?<itrip>[^#].*?)))?(?<capcode>##(?<capcodetext> .*?)?)?$/
	, staffLevels = ['Admin', 'Global Staff', 'Board Owner', 'Board Staff'];

module.exports = async (inputName, permissions, boardSettings, boardOwner, boardStaff, username, __) => {

	const { forceAnon, defaultName } = boardSettings;
	const isBoardOwner = username === boardOwner; //why not just check staffboards and ownedboards?
	const staffUsernames = Object.keys(boardStaff);
	const isBoardMod = staffUsernames.includes(username);
	const staffPermissions = [permissions.get(Permissions.ROOT), permissions.get(Permissions.MANAGE_GLOBAL_GENERAL), isBoardOwner, isBoardMod];
	const langStaffLevels = staffLevels.map(l => __(l));
	const staffLevelsRegex = new RegExp(`(${langStaffLevels.join('|')})+`, 'igm');

	let name = defaultName;
	let tripcode = null;
	let capcode = null;
	if ((permissions.get(Permissions.MANAGE_BOARD_GENERAL) || !forceAnon) && inputName && inputName.length > 0) {
		// get matches with named groups for name, trip and capcode in 1 regex
		const matches = inputName.match(nameRegex);
		if (matches && matches.groups) {

			const groups = matches.groups;

			//name
			if (groups.name) {
				name = groups.name;
			}

			//tripcode
			if (groups.tripcode) {
				let tripcodeText = groups.strip || groups.itrip;
				if (!permissions.get(Permissions.MANAGE_BOARD_GENERAL) && groups.capcode === '##' && !groups.capcodetext) {
					//for the complaining non-staff troglodyte who puts the name as all #s
					tripcodeText = tripcodeText.concat('##');
				}
				if (groups.strip) {
					tripcode = `!!${(await getSecureTrip(tripcodeText))}`;
				} else if (groups.itrip) {
					tripcode = `!${getInsecureTrip(tripcodeText)}`;
				}
			}

			//capcode
			if (permissions.get(Permissions.MANAGE_BOARD_GENERAL) && groups.capcode) {
				let capcodeInput = groups.capcodetext ? groups.capcodetext.trim() : '';
				const { staffLevelDirect, staffLevelFallback } = staffPermissions.reduce((acc, sp, i) => {
					if (sp === true) {
						if (!acc.staffLevelFallback) {
							acc.staffLevelFallback = langStaffLevels[i];
						}
						if (!acc.staffLevelDirect && capcodeInput.toLowerCase().startsWith(staffLevels[i].toLowerCase())) {
							acc.staffLevelDirect = langStaffLevels[i];
						}
					}
					return acc;
				}, { 'staffLevelDirect': null, 'staffLevelFallback': null });
				//we still get the same fallbacks as before, its just more annoying without the direct permlevel mapping
				const staffLevel = staffLevelDirect ||
						(isBoardOwner ? langStaffLevels[2]
							: isBoardMod ? langStaffLevels[3]
								: staffLevelFallback);
				capcode = staffLevel;
				if (capcodeInput && capcodeInput.toLowerCase() !== staffLevel.toLowerCase()) {
					capcode = `${staffLevel} ${capcodeInput.replace(staffLevelsRegex, '').trim()}`;
				}
				capcode = `## ${capcode.trim()}`;
			}
		}
	}

	return { name, tripcode, capcode };

};


code:
const name = require('./name.js');
const Permission = require('../permission/permission.js');
const i18n = require('../locale/locale.js');
const ROOT = new Permission();
ROOT.setAll(Permission.allPermissions);
const NO_PERMISSION = new Permission();

describe('name/trip/capcode handler', () => {

	const cases = [
		{ in: '## Admin', out: { name: 'Anon', tripcode: null, capcode: '## Admin' } },
		{ in: '## Global Staff', out: { name: 'Anon', tripcode: null, capcode: '## Global Staff' } },
		{ in: '## Board Owner', out: { name: 'Anon', tripcode: null, capcode: '## Admin' } },
		{ in: '## Board Staff', out: { name: 'Anon', tripcode: null, capcode: '## Admin' } },
		{ in: '##', out: { name: 'Anon', tripcode: null, capcode: '## Admin' } },
		{ in: '', out: { name: 'Anon', tripcode: null, capcode: null } },
		{ in: 'test', out: { name: 'test', tripcode: null, capcode: null } },
		{ in: 'test#12345', out: { name: 'test', tripcode: '!CSZ6G0yP9Q', capcode: null } },
		{ in: '#12345', out: { name: 'Anon', tripcode: '!CSZ6G0yP9Q', capcode: null } },
		{ in: '#12345## Admin', out: { name: 'Anon', tripcode: '!CSZ6G0yP9Q', capcode: '## Admin' } },
		{ in: '###########', perm: NO_PERMISSION, out: { name: 'Anon', tripcode: '!!AR2Fv1lg4=', capcode: null } },
		{ in: '##test', out: { name: 'Anon', tripcode: '!!4XzKAGoCE=', capcode: null } },
		{ in: 'test#12345## Admin', out: { name: 'test', tripcode: '!CSZ6G0yP9Q', capcode: '## Admin' } },
	];

	for (let i in cases) {
		test(`should contain ${cases[i].out.capcode} for an input of ${cases[i].in}`, async () => {
			const output = await name(cases[i].in, (cases[i].perm || ROOT), {forceAnon: false, defaultName: 'Anon'}, 'a', {a:ROOT}, 'b', i18n.__);
			expect(output).toStrictEqual(cases[i].out);
		});
	}

});


code:
'use strict';

const Posts = require(__dirname+'/../../db/posts.js')
	, Boards = require(__dirname+'/../../db/boards.js')
	, quoteRegex = />>(?<quotenum>\d+)/g
	, crossQuoteRegex = />>>/(?<board>\w+)(?:/(?<quotenum>\d*))?/gm
	, catalogSearchQuoteRegex = />>>#/(?<search>\w+)//gm;

module.exports = {

	quoteRegex, crossQuoteRegex, catalogSearchQuoteRegex,

	process: async (board, text, thread) => {

		//get the matches
		const quotes = text.match(quoteRegex);
		const crossQuotes = text.match(crossQuoteRegex);
		const catalogSearchQuotes = text.match(catalogSearchQuoteRegex);
		if (!quotes && !crossQuotes && !catalogSearchQuotes) {
			return { quotedMessage: text, threadQuotes: [], crossQuotes: [] };
		}

		//make query for db including crossquotes
		const postQueryOrs = [];
		const boardQueryIns = [];
		const crossQuoteMap = {};
		if (quotes && board) {
			const quoteIds = [...new Set(quotes.map(q => { return Number(q.substring(8)); }))];
			postQueryOrs.push({
				'board': board,
				'postId': {
					'$in': quoteIds
				}
			});
		}

		if (crossQuotes) {
			for (let i = 0; i < crossQuotes.length; i++) {
				const crossQuote = crossQuotes[i].split('/');
				const crossQuoteBoard = crossQuote[1];
				const crossQuotePostId = +crossQuote[2];
				if (!crossQuoteMap[crossQuoteBoard]) {
					crossQuoteMap[crossQuoteBoard] = [];
				}
				if (!isNaN(crossQuotePostId) && crossQuotePostId > 0) {
					crossQuoteMap[crossQuoteBoard].push(crossQuotePostId);
				}
			}
			const crossQuoteBoards = Object.keys(crossQuoteMap);
			for (let i = 0; i < crossQuoteBoards.length; i++) {
				const crossQuoteBoard = crossQuoteBoards[i];
				boardQueryIns.push(crossQuoteBoard);
				const crossQuoteBoardPostIds = crossQuoteMap[crossQuoteBoard];
				if (crossQuoteBoardPostIds.length > 0) {
					postQueryOrs.push({
						'board': crossQuoteBoard,
						'postId': {
							'$in': crossQuoteBoardPostIds
						}
					});
				}
			}
		}

		//get all the posts from quotes
		const postThreadIdMap = {};
		if (crossQuotes || quotes) {
			const [ posts, boards ] = await Promise.all([
				postQueryOrs.length > 0 ? Posts.getPostsForQuotes(postQueryOrs) : [],
				boardQueryIns.length > 0 ? Boards.db.find({ '_id': { '$in': boardQueryIns } }, { projection: { '_id': 1 } }).toArray() : []
			]);

			//turn the result into a map of postId => threadId/postId
			for (let i = 0; i < posts.length; i++) {
				const post = posts[i];
				if (!postThreadIdMap[post.board]) {
					postThreadIdMap[post.board] = {};
				}
				postThreadIdMap[post.board][post.postId] = {
					'_id': post._id,
					'thread': post.thread || post.postId,
					'postId': post.postId
				};
			}
			for (let i = 0; i < boards.length; i++) {
				const boardName = boards[i]._id;
				if (!postThreadIdMap[boardName]) {
					postThreadIdMap[boardName] = {};
				}
			}
		}

		//then replace the quotes with only ones that exist
		const threadQuotes = new Set();
		const nonThreadQuotes = new Set();
		if (quotes) {
			text = text.replace(quoteRegex, (match, quotenum) => {
				if (postThreadIdMap[board] && postThreadIdMap[board][quotenum]) {
					if (postThreadIdMap[board][quotenum].thread === thread) {
						threadQuotes.add(postThreadIdMap[board][quotenum]);
					} else {
						nonThreadQuotes.add(postThreadIdMap[board][quotenum]);
					}
					return `>>${quotenum}${postThreadIdMap[board][quotenum].postId == thread ? ' (OP) ' : ''}`;
				}
				return `<span class='invalid-quote'>>>${quotenum}</span>`;
			});
		}
		if (crossQuotes) {
			text = text.replace(crossQuoteRegex, (match, quoteboard, quotenum) => {
				if (postThreadIdMap[quoteboard]) {
					if (!quotenum) {
						return `<a class='quote' href='/${quoteboard}/index.html'>>>>/${quoteboard}/</a>`;
					} else if (!isNaN(quotenum) && quotenum > 0 && postThreadIdMap[quoteboard][quotenum]) {
						return `<a class='quote' href='/${quoteboard}/thread/${postThreadIdMap[quoteboard][quotenum].thread}.html#${quotenum}'>>>>/${quoteboard}/${quotenum}</a>`;
					}
				}
				return `<span class='invalid-quote'>>>>/${quoteboard}/${quotenum || ''}</span>`;
			});
		}
		if (catalogSearchQuotes) {
			text = text.replace(catalogSearchQuoteRegex, (match, search) => {
				return `<a href='/${board}/catalog.html#${board}-/${search}/'>>>>#/${search}/</a>`;
			});
		}

		return { quotedMessage: text, threadQuotes: [...threadQuotes], crossQuotes: [...nonThreadQuotes] };

	},

};


code:
'use strict';

const { tripcodeSecret } = require(__dirname+'/../../configs/secrets.js')
	, { createHash } = require('crypto')
	, { encode } = require('iconv-lite')
	, crypt = require('unix-crypt-td-js')
	, replace = {
		':': 'A',
		';': 'B',
		'<': 'C',
		'=': 'D',
		'>': 'E',
		'?': 'F',
		'@': 'G',
		'[': 'a',
		'\\': 'b',
		']': 'c',
		'^': 'd',
		'_': 'e',
		'`': 'f',
	};

module.exports = {

	getSecureTrip: async (password) => {
		const tripcodeHash = createHash('sha256').update(password + tripcodeSecret).digest('base64');
		const tripcode = tripcodeHash.substring(tripcodeHash.length-10);
		return tripcode;
	},

	getInsecureTrip: (password) => {
		const encoded = encode(password, 'SHIFT_JIS')
			.toString('latin1');
		let salt = `${encoded}H..`
			.substring(1, 3)
			.replace(/[^.-z]/g, '.');
		for (let find in replace) {
			salt = salt.split(find).join(replace[find]);
		}
		const hashed = crypt(encoded, salt);
		return hashed.slice(-10);
	},

};


code:
const { getSecureTrip, getInsecureTrip } = require('./tripcode.js');

describe('getSecureTrip() - "secure" tripcodes', () => {
	const cases = [
		{ in: '' },
		{ in: null },
		{ in: '13245' },
		{ in: '1324512345123451234512345123451234512345' },
	];
	for (let i in cases) {
		test(`should not error for an input of ${cases[i].in}`, async () => {
			expect((await getSecureTrip(cases[i].in)));
		});
	}
});

describe('getInsecureTrip() - "insecure" tripcodes', () => {
	const cases = [
		{ in: '', out: '8NBuQ4l6uQ' },
		{ in: null, out: '8NBuQ4l6uQ' },
		{ in: '13245', out: 'VPkdFNhOGY' },
		{ in: '1324512345123451234512345123451234512345', out: '9ovLU2O1wk' },
	];
	for (let i in cases) {
		test(`should contain ${cases[i].out} for an input of ${cases[i].in}`, () => {
			expect(getInsecureTrip(cases[i].in)).toBe(cases[i].out);
		});
	}
});


code:
'use strict';

const Redis = require('ioredis')
	, { redis, debugLogs } = require(__dirname+'/../../configs/secrets.js')
	, sharedClient = new Redis(redis)
	, subscriber = new Redis(redis)
	, publisher = new Redis(redis)
	, messageCallbacks = {
		'config': [],
		'roles': [],
	};

module.exports = {

	redisClient: sharedClient,
	redisSubscriber: subscriber,
	redisPublisher: publisher,

	close: () => {
		sharedClient.quit();
		publisher.quit();
		subscriber.quit();
	},

	addCallback: (channel, cb) => {
		if (messageCallbacks[channel].length === 0) {
			subscriber.subscribe('config', (err) => {
				if (err) {
					return console.error(err);
				}
			});
			subscriber.subscribe('roles', (err) => {
				if (err) {
					return console.error(err);
				}
			});
			subscriber.on('message', (channel, message) => {
				debugLogs && console.log(`Subscriber message from channel ${channel}`);
				let data;
				if (message) {
					data = JSON.parse(message);
				}
				messageCallbacks[channel].forEach(cb => {
					cb(data);
				});
			});
		}
		messageCallbacks[channel].push(cb);
	},

	//get a value with key
	get: (key) => {
		return sharedClient.get(key).then(res => { return JSON.parse(res); });
	},

	//set a value on key
	set: (key, value, ttl) => {
		if (ttl) {
			return sharedClient.set(key, JSON.stringify(value), 'EX', ttl);
		} else {
			return sharedClient.set(key, JSON.stringify(value));
		}
	},

	incr: (key) => {
		return sharedClient.incr(key);
	},

	expire: (key, ttl) => {
		return sharedClient.expire(key, ttl);
	},

	//set a value on key if not exist
	setnx: (key, value) => {
		return sharedClient.setnx(key, JSON.stringify(value));
	},

	//add items to a set
	sadd: (key, value) => {
		return sharedClient.sadd(key, value);
	},

	//get all members of a set
	sgetall: (key) => {
		return sharedClient.smembers(key);
	},

	//remove item from a set
	srem: (key, values) => {
		return sharedClient.srem(key, values);
	},

	//get random item from set
	srand: (key) => {
		return sharedClient.srandmember(key);
	},

	//delete value with key
	del: (keyOrKeys) => {
		debugLogs && console.log('redis del():', keyOrKeys);
		if (Array.isArray(keyOrKeys)) {
			return sharedClient.del(...keyOrKeys);
		} else {
			return sharedClient.del(keyOrKeys);
		}
	},

	getPattern: (pattern) => {
		return new Promise((resolve, reject) => {
			const stream = sharedClient.scanStream({
				match: pattern
			});
			let allKeys = [];
			stream.on('data', (keys) => {
				allKeys = allKeys.concat(keys);
			});
			stream.on('end', async () => {
				const pipeline = sharedClient.pipeline();
				for (let i = 0; i < allKeys.length; i++) {
					pipeline.get(allKeys[i]);
				}
				let results;
				try {
					results = await pipeline.exec();
				} catch (e) {
					return reject(e);
				}
				const data = {};
				for (let i = 0; i < results.length; i++) {
					data[allKeys[i]] = JSON.parse(results[i][1]);
				}
				resolve(data);
			});
			stream.on('error', (err) => {
				reject(err);
			});
		});
	},

	deletePattern: (pattern) => {
		return new Promise((resolve, reject) => {
			let totalDeleted = 0;
			const stream = sharedClient.scanStream({
				match: pattern
			});
			stream.on('data', (keys) => {
				if (keys.length > 0) {
					totalDeleted += keys.length;
					const pipeline = sharedClient.pipeline();
					for (let i = 0; i < keys.length; i++) {
						pipeline.del(keys[i]);
					}
					pipeline.exec();
				}
			});
			stream.on('end', () => {
				resolve(totalDeleted);
			});
			stream.on('error', (err) => {
				reject(err);
			});
		});
	},

};


code:
'use strict';

const Redlock = require('redlock')
	, { redisClient } = require(__dirname+'/redis.js')
	, redlock = new Redlock([redisClient]);

redlock.on('clientError', console.error);

module.exports = redlock;


code:
'use strict';

//NOTE: unused (for now)
const { Web3 } = require('web3')
	, config = require(__dirname+'/../misc/config.js')
	, { addCallback } = require(__dirname+'/../redis/redis.js')
	, web3 = new Web3(config.get.ethereumNode);

const updateWeb3Provider = () => {
	web3.setProvider(config.get.ethereumNode);
};

addCallback('config', updateWeb3Provider);

module.exports = web3;


Name

.html name: .html name

.html name must be 50 characters or less: .html name must be 50 characters or less

.html name must be unique: .html name must be unique

.html name must contain a-z 0-9 _ - only: .html name must contain a-z 0-9 _ - only

(You): (You)

(You)s: (You)s

one: %s character

other: %s characters

one: %s day ago

other: %s days ago

one: %s day from now

other: %s days from now

%s earlier: %s earlier

one: %s file

other: %s files

%s files are being served, totaling %s.: %s files are being served, totaling %s.

one: %s file selected

other: %s files selected

one: %s hour ago

other: %s hours ago

one: %s hour from now

other: %s hours from now

one: %s minute ago

other: %s minutes ago

one: %s minute from now

other: %s minutes from now

one: %s month ago

other: %s months ago

one: %s month from now

other: %s months from now

one: %s reply

other: %s replies

%s replies and %s files omitted.: %s replies and %s files omitted.

%s replies and 1 file omitted.: %s replies and 1 file omitted.

%s replies omitted.: %s replies omitted.

one: %s UID

other: %s UIDs

one: %s week ago

other: %s weeks ago

one: %s week from now

other: %s weeks from now

one: %s year ago

other: %s years ago

one: %s year from now

other: %s years from now

⊖ Thread has reached reply limit.: ⊖ Thread has reached reply limit.

1 reply and %s files omitted.: 1 reply and %s files omitted.

1 reply and 1 file omitted.: %s reply and 1 file omitted.

1 reply omitted.: 1 reply omitted.

24 Hour Time: 24 Hour Time

24h time: 24h time

2FA Code: 2FA Code

2FA code must be 6 characters: 2FA code must be 6 characters

2FA QR code expired, try again: 2FA QR code expired, try again

404 Not Found: 404 Not Found

500 Internal Server Error: 500 Internal Server Error

502 Bad Gateway: 502 Bad Gateway

503 Service Unavailable: 503 Service Unavailable

504 Gateway Timeout: 504 Gateway Timeout

6 digits: 6 digits

a-zA-Z0-9-_ only: a-zA-Z0-9-_ only

Abandoned Board Action: Abandoned Board Action

Abandoned board action must be a number from 0-3: Abandoned board action must be a number from 0-3

Ability to create new boards.: Ability to create new boards.

Ability to register an account.: Ability to register an account.

Ability to see raw IPs in moderation interfaces.: Ability to see raw IPs in moderation interfaces.

Access board bans. Ability to unban, edit, or deny appeals.: Access board bans. Ability to unban, edit, or deny appeals.

Access board logs. Ability to search/filter.: Access board logs. Ability to search/filter.

Access board settings. Ability to change any settings. Settings page will show transfer/delete forms for those with "Board Owner" permission.

Access global bans. Ability to unban, edit, or deny appeals.: Access global bans. Ability to unban, edit, or deny appeals.

Access global logs. Ability to search/filter: Access global logs. Ability to search/filter

Access global settings. Ability to change any settings.: Access global settings. Ability to change any settings.

Access news posting. Ability to add, edit, or delete newsposts.: Access news posting. Ability to add, edit, or delete newsposts.

Access roles list. Ability to edit roles: Access roles list. Ability to edit roles

Access the accounts list. Ability to search/sort. Ability to edit permissions of any user.

Access the global board list. Ability to search/filter. Also grants the ability to transfer or delete any board.

Access to board assets and custompages. Ability to upload, create, edit, delete.

Access to staff management, and ability to add or remove permissions from others. Can only be given by somebody else with "Board Owner" permission. Use with caution!

Account: Account

Account deleted: Account deleted

Account does not exist: Account does not exist

Account permissions: Account permissions

Account with that username already exists: Account with that username already exists

Accounts: Accounts

Actions: Actions

Active sessions: Active sessions

Active Sessions: Active Sessions

Add: Add

one: Add Asset (Max %s)

other: Add Assets (Max %s)

one: Add Banner (Max %s)

other: Add Banners (Max %s)

Add Boards: Add Boards

Add Custom Page: Add Custom Page

Add Filter: Add Filter

one: Add Flag (Max %s)

other: Add Flags (Max %s)

Add layer: Add layer

Add News: Add News

Add Staff: Add Staff

one: Added %s ban

other: Added %s bans

Added custom page: Added custom page

Added filter: Added filter

Added newspost: Added newspost

Added staff: Added staff

Admin: Admin

Airbrush: Airbrush

Allow Animated Image Files: Allow Animated Image Files

Allow Audio Files: Allow Audio Files

Allow Custom Overboard: Allow Custom Overboard

Allow Image Files: Allow Image Files

Allow Other Files: Allow Other Files

Allow Video Files: Allow Video Files

Allowed Code Themes: Allowed Code Themes

Allowed Hosts: Allowed Hosts

Allowed hosts must not exceed 10000 characters: Allowed hosts must not exceed 10000 characters

Allowed Themes: Allowed Themes

Another role already exists with the same permissions: "%s": Another role already exists with the same permissions: "%s"

Appeal message must be %s characters or less: Appeal message must be %s characters or less

Arbitrary assets that can be used for other purposes e.g. custom CSS

Are you sure? Changing the canvas will clear all layers and history and disable replay recording.

At least the following file is not unique: %s: At least the following file is not unique: %s

At least the following files are not unique: %s: At least the following files are not unique: %s

Ban reason must be %s characters or less: Ban reason must be %s characters or less

Banner files height must be a number > 0: Banner files height must be a number > 0

Block bypass expire after uses must be a number > 0: Block bypass expire after uses must be a number > 0

Block bypass expired or exceeded max uses: Block bypass expired or exceeded max uses

Board announcements must be 5000 characters or less: Board announcements must be 5000 characters or less

Board default captcha mode must be a number from 0-2: Board default captcha mode must be a number from 0-2

Board default file r9k mode must be a number from 0-2: Board default file r9k mode must be a number from 0-2

Board default lock mode must be a number from 0-2: Board default lock mode must be a number from 0-2

Board default message r9k mode must be a number from 0-2: Board default message r9k mode must be a number from 0-2

Board default pph trigger action must be a number from 0-4: Board default pph trigger action must be a number from 0-4

Board default pph trigger must be a number: Board default pph trigger must be a number

Board default tph trigger action must be a number from 0-4: Board default tph trigger action must be a number from 0-4

Board default tph trigger must be a number: Board default tph trigger must be a number

Board defaults bump limit must be within global limits: Board defaults bump limit must be within global limits

Board defaults captcha reset must be a number from 0-2: Board defaults captcha reset must be a number from 0-2

Board defaults filter ban duration must be a number: Board defaults filter ban duration must be a number

Board defaults filter mode must be a number from 0-2: Board defaults filter mode must be a number from 0-2

Board defaults lock reset must be a number from 0-2: Board defaults lock reset must be a number from 0-2

Board defaults max files must be within global limits: Board defaults max files must be within global limits

Board defaults max reply message length must be within global limits

Board defaults max thread message length must be within global limits

Board defaults min reply message length must be a number: Board defaults min reply message length must be a number

Board defaults min thread message length must be a number: Board defaults min thread message length must be a number

Board defaults reply limit must be within global limits: Board defaults reply limit must be within global limits

Board defaults reply message length min must be less than max: Board defaults reply message length min must be less than max

Board defaults thread limit must be within global limits: Board defaults thread limit must be within global limits

Board defaults thread message length min must be less than max: Board defaults thread message length min must be less than max

Board description must be %s characters or less: Board description must be %s characters or less

Bypass anonymizer restrictions e.g. disabled file posting.: Bypass anonymizer restrictions e.g. disabled file posting.

Bypass ratelimits for getting new captchas, editing posts, editing board settings, etc.

Bypass the basic anti-flood spamcheck for too frequent similar posting.

Cannot transfer to account that does not exist: Cannot transfer to account that does not exist

Captcha options distortion must be a number from 0-50: Captcha options distortion must be a number from 0-50

Captcha options distorts min must be less than max: Captcha options distorts min must be less than max

Captcha options generate limit must be a number > 0: Captcha options generate limit must be a number > 0

Captcha options grid edge effect strength must be a number from 0-50

Captcha options grid image size must be a number from 50-500: Captcha options grid image size must be a number from 50-500

Captcha options grid noise effect strength must be a number from 0-10

Captcha options grid size must be a number from 2-6: Captcha options grid size must be a number from 2-6

Captcha options icon y offset must be a number from 0-50: Captcha options icon y offset must be a number from 0-50

Captcha options max distorts must be a number from 0-10: Captcha options max distorts must be a number from 0-10

Captcha options min distorts must be a number from 0-10: Captcha options min distorts must be a number from 0-10

Captcha options text noise effect strength must be a number from 0-10

Captcha options text paint effect strength must be a number from 0-10

Captcha options text wave effect strength must be a number form 0-10

Click here if you are not redirected automatically.: Click here if you are not redirected automatically.

Code Highlight Language Detect Threshold: Code Highlight Language Detect Threshold

Completed block bypass, you may go back and make your post.: Completed block bypass, you may go back and make your post.

Country code header length must not exceed 100 characters: Country code header length must not exceed 100 characters

Custom css filters must not exceed 10000 characters: Custom css filters must not exceed 10000 characters

Custom CSS must be %s characters or less: Custom CSS must be %s characters or less

Custom CSS strict mode is enabled and does not allow the following: "%s"

Custom pages max length must be a number: Custom pages max length must be a number

Default volume must be a number from 0-100: Default volume must be a number from 0-100

Description: Description

Description must be %s characters or less: Description must be %s characters or less

Destination for move does not exist, or you do not have permission

Enabling 2FA will invalidate all your existing sessions and you will have to login again.

Exceeded max asset uploads in one request of %s: Exceeded max asset uploads in one request of %s

Exceeded max banner uploads in one request of %s: Exceeded max banner uploads in one request of %s

Exceeded max flag uploads in one request of %s: Exceeded max flag uploads in one request of %s

Export does not include post password or (You)'s: Export does not include post password or (You)'s

Extra mime types must be like type/subtype: Extra mime types must be like type/subtype

Filter text cannot exceed 50000 characters: Filter text cannot exceed 50000 characters

Filters length must be 20000 characters or less: Filters length must be 20000 characters or less

Flags that can be applied to posts if custom flags are enabled in board settings

Full control of the board, equivalent to the BO. Can delete and/or transfer the board. Can only be given by somebody else with "Board Owner" permission. Use with caution!

General board staff permission. Access mod index, catalog, recent posts and reports. Ability to submit mod actions. Bypass board-specific bans and post filters.

General global staff permission. Access to recent posts and reports. Ability to submit global actions.

Global announcement must not exceed 10000 characters: Global announcement must not exceed 10000 characters

Global bump limit min must be less than max: Global bump limit min must be less than max

Global bump limit minimum must be a number: Global bump limit minimum must be a number

Global limit ban reason field length must be a number: Global limit ban reason field length must be a number

Global limit board description field length must be a number: Global limit board description field length must be a number

Global limit board name field length must be a number: Global limit board name field length must be a number

Global limit board uri field length must be a number: Global limit board uri field length must be a number

Global limit email field length must be a number: Global limit email field length must be a number

Global limit log message field length must be a number: Global limit log message field length must be a number

Global limit message field length must be a number: Global limit message field length must be a number

Global limit name field length must be a number: Global limit name field length must be a number

Global limit postpassword field length must be a number >=20: Global limit postpassword field length must be a number >=20

Global limit report reason field length must be a number: Global limit report reason field length must be a number

Global limit subject field length must be a number: Global limit subject field length must be a number

Global reply limit maximum must be a number: Global reply limit maximum must be a number

Global reply limit min must be less than max: Global reply limit min must be less than max

Global reply limit minimum must be a number: Global reply limit minimum must be a number

Global thread limit maximum must be a number: Global thread limit maximum must be a number

Global thread limit min must be less than max: Global thread limit min must be less than max

Global thread limit minimum must be a number: Global thread limit minimum must be a number

Highlight options language subset must not exceed 10000 characters

Highlight options threshold must be a number: Highlight options threshold must be a number

Images randomly chosen and displayed at the top of most pages on the board

Inactive account action must be a number from 0-2: Inactive account action must be a number from 0-2

Invalid archive links URL format, must be a link containing %s where the url param belongs.

Invalid board defaults OP thread age delete protection: Invalid board defaults OP thread age delete protection

Invalid board defaults OP thread reply count delete protection: Invalid board defaults OP thread reply count delete protection

Invalid ethereum links URL format, must be a link containing %s where the url param belongs.

Invalid file "%s". Max banner dimensions are %sx%s and must be a 3:1 aspect ratio.

Invalid file "%s". Max banner dimensions are %sx%s.: Invalid file "%s". Max banner dimensions are %sx%s.

Invalid file type for %s. Mimetype %s not allowed.: Invalid file type for %s. Mimetype %s not allowed.

Invalid OP thread reply count delete protection: Invalid OP thread reply count delete protection

Invalid or missing "Referer" header. Are you posting from the correct URL?

Invalid reverse image search links URL format, must be a link containing %s where the url param belongs.

Invalid selected posts or destination thread: Invalid selected posts or destination thread

IP header length must not exceed 100 characters: IP header length must not exceed 100 characters

Max reply message length must be 0-%s and not less than "Min Reply Message Length" (currently %s)

Max thread message length must be 0-%s and not less than "Min Thread Message Length" (currently %s)

Message: Message

Message must be 10000 characters or less: Message must be 10000 characters or less

Messages must be unique in this thread. Your message is not unique.

Messages must be unique on this board. Your message is not unique.

Meta site name must not exceed 100 characters: Meta site name must not exceed 100 characters

Min and max reply message lengths must not violate eachother: Min and max reply message lengths must not violate eachother

Min and max thread message lengths must not violate eachother: Min and max thread message lengths must not violate eachother

Min reply message length must be 0-%s and not more than "Max Reply Message Length" (currently %s)

Min thread message length must be 0-%s and not more than "Max Thread Message Length" (currently %s)

Modlog message must be %s characters or less: Modlog message must be %s characters or less

Multi input staff limit must be a number: Multi input staff limit must be a number

Must check parent post if checking reports for report action: Must check parent post if checking reports for report action

Must input destinaton thread number or board to move posts: Must input destinaton thread number or board to move posts

Must select a report action if checked reports: Must select a report action if checked reports

Must select at least one asset to delete: Must select at least one asset to delete

Must select at least one banner to delete: Must select at least one banner to delete

Must select at least one custom page to delete: Must select at least one custom page to delete

Must select at least one filter to delete: Must select at least one filter to delete

Must select at least one newspost to delete: Must select at least one newspost to delete

Must select at least one session to delete: Must select at least one session to delete

Must select at least one staff to delete: Must select at least one staff to delete

Must select post and reports to ban reporter: Must select post and reports to ban reporter

New owner must be different from current owner: New owner must be different from current owner

New owner username must be 50 characters or less: New owner username must be 50 characters or less

New owner username must contain a-z 0-9 only: New owner username must contain a-z 0-9 only

New password and password confirmation must match: New password and password confirmation must match

New password confirmation must be 100 characters or less: New password confirmation must be 100 characters or less

New pasword must be 100 characters or less: New pasword must be 100 characters or less

No camera? Use this secret in your authenticator app instead: No camera? Use this secret in your authenticator app instead

Note: Setting board management permissions on an account/role level will grant them globally i.e for all boards.
To make somebody a normal board owner/staff, transfer them the board or give them the appropriate permissions in the board staff permission editing interface.

Password and password confirmation must match: Password and password confirmation must match

Password confirmation must be 100 characters or less: Password confirmation must be 100 characters or less

Password did not match any selected posts: Password did not match any selected posts

Please complete a block bypass to continue: Please complete a block bypass to continue

Please enable JavaScript to solve the captcha.: Please enable JavaScript to solve the captcha.

Please enter at least one allowed host in the "Allowed Hosts" field when the "Referer Check" option is selected.

Please wait before generating another 2FA QR code.: Please wait before generating another 2FA QR code.

Please wait before making another post, or a post similar to another user

Posting files through anonymizers has been disabled globally: Posting files through anonymizers has been disabled globally

Posting files through anonymizers has been disabled on this board

Rate limit cost board settings must be a number from 1-100: Rate limit cost board settings must be a number from 1-100

Rate limit cost captcha must be a number from 1-100: Rate limit cost captcha must be a number from 1-100

Rate limit cost edit post must be a number from 1-100: Rate limit cost edit post must be a number from 1-100

Recently bumped threads from multiple boards: Recently bumped threads from multiple boards

Right click to replace with the current color: Right click to replace with the current color

Scan the QR Code in an authenticator app, and submit the code: Scan the QR Code in an authenticator app, and submit the code

Space file name replacement must be 1 character: Space file name replacement must be 1 character

Subject: Subject

Tags length must be 2000 characters or less: Tags length must be 2000 characters or less

Test first in top-right settings if you have javascript enabled: Test first in top-right settings if you have javascript enabled

Text: Text

The server acting as a gateway or proxy did not get a response in time from the upstream server.

The server acting as a gateway or proxy received an invalid response from the upstream server.

The server encountered an error and was unable to fulfill your request.

The server failed to process "%s". Possible unsupported or corrupt file.

The server is currently unable to handle the request due to a temporary overload or scheduled maintenance.

There are currently %s public boards, %s in total. Sitewide, %s posts have been made in the last day, %s in the last hour, %s in total.

There are currently %s sites and %s boards in the known webring. Across all webring boards, %s posts have been made in the last hour, %s in total.

Thread messages must be %s-%s characters: Thread messages must be %s-%s characters

Thumbnail extension must be at least 1 character: Thumbnail extension must be at least 1 character

Title: Title

Too many files. Max files per post is %s: Too many files. Max files per post is %s

Too many files. Max files per post on this board is %s: Too many files. Max files per post on this board is %s

Total number of banners would exceed global limit of %s: Total number of banners would exceed global limit of %s

Total number of filters would exceed global limit of %s: Total number of filters would exceed global limit of %s

Total number of flags would exceed global limit of %s: Total number of flags would exceed global limit of %s

Two factor authentication enabled successfully: Two factor authentication enabled successfully

Uploaded files must be unique in this thread.: Uploaded files must be unique in this thread.

Uploaded files must be unique on this board.: Uploaded files must be unique on this board.

User file spoiling is disabled on this board: User file spoiling is disabled on this board

User file unlinking is disabled on this board: User file unlinking is disabled on this board

User post deletion is disabled on this board: User post deletion is disabled on this board

Username must not be an ethereum address: Username must not be an ethereum address

Video thumbnail percentage must be a number from 1-100: Video thumbnail percentage must be a number from 1-100

Webring blacklist must not exceed 10000 characters: Webring blacklist must not exceed 10000 characters

Webring following list must not exceed 10000 characters: Webring following list must not exceed 10000 characters

Webring logos list must not exceed 10000 characters: Webring logos list must not exceed 10000 characters

You are changing settings too quickly, please wait a minute and try again

You are editing posts too quickly, please wait a minute and try again

You can't apply template permissions to a ROOT user.: You can't apply template permissions to a ROOT user.

You can't edit the permissions of the board owner: You can't edit the permissions of the board owner

You cannot delete your account while you hold staff position on any board

Your country "%s" is not allowed to post on this board: Your country "%s" is not allowed to post on this board

Your request was blocked because your IP address is listed on a blacklist.

Replace text cannot exceed 500 characters: Replace text cannot exceed 500 characters


Nombre

.html name: nombre .html

.html name must be 50 characters or less: el nombre .html debe tener 50 caracteres o menos

.html name must be unique: el nombre .html debe ser único

.html name must contain a-z 0-9 _ - only: el nombre .html solo puede contener a-z 0-9 _ -

(You): (Tú)

(You)s: (Tú)s

one: %s carácter

other: %s caracteres

one: hace %s día

other: hace %s días

one: %s día desde ahora

other: %s días desde ahora

%s earlier: %s antes

one: %s archivo

other: %s archivos

%s files are being served, totaling %s.: %s archivos están siendo servidos, con un total de %s.

one: %s archivo seleccionado

other: %s archivos seleccionados

one: hace %s hora

other: hace %s horas

one: %s hora desde ahora

other: %s horas desde ahora

one: hace %s minuto

other: hace %s minutos

one: %s minuto desde ahora

other: %s minutos desde ahora

one: hace %s mes

other: hace %s meses

one: %s mes desde ahora

other: %s meses desde ahora

one: %s respuesta

other: %s respuestas

%s replies and %s files omitted.: %s respuestas y %s archivos omitidos.

%s replies and 1 file omitted.: %s respuestas y 1 archivo omitido.

%s replies omitted.: %s respuestas omitidas.

one: hace %s semana

other: hace %s semanas

one: %s semana desde ahora

other: %s semanas desde ahora

one: hace %s año

other: hace %s años

one: %s año desde ahora

other: %s años desde ahora

⊖ Thread has reached reply limit.: ⊖ El hilo ha alcanzado el límite de respuestas.

1 reply and %s files omitted.: 1 respuesta y %s archivos omitidos.

1 reply and 1 file omitted.: 1 respuesta y 1 archivo omitido.

1 reply omitted.: 1 respuesta omitida.

24 Hour Time: Hora 24

24h time: Hora 24

2FA Code: Código 2FA

2FA code must be 6 characters: El código 2FA debe tener 6 caracteres

2FA QR code expired, try again: El código QR 2FA ha expirado, inténtalo de nuevo

404 Not Found: 404 No encontrado

500 Internal Server Error: 500 Error interno del servidor

502 Bad Gateway: 502 Puerta de enlace incorrecta

503 Service Unavailable: 503 Servicio no disponible

504 Gateway Timeout: 504 Tiempo de espera de la puerta de enlace

6 digits: 6 dígitos

a-zA-Z0-9-_ only: solo a-zA-Z0-9-_

Abandoned Board Action: Acción de Tablón Abandonado

Abandoned board action must be a number from 0-3: La acción de tablón abandonado debe ser un número del 0 al 3

Ability to create new boards.: Capacidad para crear nuevos tablóns.

Ability to register an account.: Capacidad para registrar una cuenta.

Ability to see raw IPs in moderation interfaces.: Capacidad de ver IPs en bruto en interfaces de moderación.

Access board bans. Ability to unban, edit, or deny appeals.: Acceso a baneos de tablón. Capacidad para desbanear, editar o denegar apelaciones.

Access board logs. Ability to search/filter.: Acceso a registros de tablón. Capacidad para buscar/filtrar.

Acceso a configuraciones de tablón. Capacidad para cambiar cualquier configuración. La página de configuraciones mostrará formularios de transferencia/eliminación para aquellos con permiso de "Propietario del tablón".

Access global bans. Ability to unban, edit, or deny appeals.: Acceso a baneos globales. Capacidad para desbanear, editar o denegar apelaciones.

Access global logs. Ability to search/filter: Acceso a registros globales. Capacidad para buscar/filtrar.

Access global settings. Ability to change any settings.: Acceso a configuraciones globales. Capacidad para cambiar cualquier configuración.

Access news posting. Ability to add, edit, or delete newsposts.: Acceso a publicaciones de noticias. Capacidad para agregar, editar o eliminar noticias.

Access roles list. Ability to edit roles: Acceso a lista de roles. Capacidad para editar roles.

Acceso a lista de cuentas. Capacidad para buscar/ordenar. Capacidad para editar permisos de cualquier usuario.

Acceso a lista global de tablóns. Capacidad para buscar/filtrar. También permite transferir o eliminar cualquier tablón.

Acceso a activos de tablón y páginas personalizadas. Capacidad para cargar, crear, editar, eliminar.

Acceso a gestión de personal, y capacidad para agregar o eliminar permisos de otros. Solo puede ser otorgado por alguien con permiso de "Propietario del tablón". ¡Usar con precaución!

Account: Cuenta

Account deleted: Cuenta eliminada

Account does not exist: La cuenta no existe

Account permissions: Permisos de cuenta

Account with that username already exists: Ya existe una cuenta con ese nombre de usuario

Accounts: Cuentas

Actions: Acciones

Active sessions: Sesiones activas

Active Sessions: Sesiones Activas

Add: Agregar

one: Agregar Recurso (Máx %s)

other: Agregar Recursos (Máx %s)

one: Agregar Banner (Máx %s)

other: Agregar Banners (Máx %s)

Add Boards: Agregar Tablóns

Add Custom Page: Agregar Página Personalizada

Add Filter: Agregar Filtro

one: Agregar Bandera (Máx %s)

other: Agregar Banderas (Máx %s)

Add layer: Agregar capa

Add News: Agregar Noticia

Add Staff: Agregar Personal

one: Se añadió %s ban

other: Se añadieron %s bans

Added custom page: Se agregó página personalizada

Added filter: Filtro agregado

Added newspost: Se agregó noticia

Added staff: Se agregó personal

Airbrush: Aerógrafo

Allow Animated Image Files: Permitir Archivos de Imágenes Animadas

Allow Audio Files: Permitir Archivos de Audio

Allow Custom Overboard: Permitir Overboard Personalizado

Allow Image Files: Permitir Archivos de Imagen

Allow Other Files: Permitir Otros Archivos

Allow Video Files: Permitir Archivos de Video

Allowed Code Themes: Temas de Código Permitidos

Allowed Hosts: Hosts Permitidos

Allowed hosts must not exceed 10000 characters: Los hosts permitidos no pueden exceder los 10000 caracteres

Allowed Themes: Temas Permitidos

alphanumeric only: Solo alfanumérico

Always Reveal Spoiler Markdown: Mostrar Siempre Spoilers Markdown

Always reveal text spoilers: Mostrar siempre spoilers de texto

Animated .gif Thumbnails: Miniaturas Animadas .gif

Anon name must be 50 characters or less: El nombre anónimo debe tener 50 caracteres o menos

Another role already exists with the same permissions: "%s": Ya existe otro rol con los mismos permisos: "%s"

Appeal message must be %s characters or less: El mensaje de apelación debe tener %s caracteres o menos

Appeals must include a message: Las apelaciones deben incluir un mensaje

Activos arbitrarios que pueden ser utilizados para otros propósitos como CSS personalizado

¿Estás seguro? Cambiar el lienzo borrará todas las capas e historial y desactivará la grabación de repetición.

Asset files max must be a number: El máximo de archivos de recurso debe ser un número

Asset files size must be a number: El tamaño de los archivos de recurso debe ser un número

Asset files total must be a number: El total de archivos de recurso debe ser un número

At least the following file is not unique: %s: Al menos el siguiente archivo no es único: %s

At least the following files are not unique: %s: Al menos los siguientes archivos no son únicos: %s

Ban note must be %s characters or less: La nota de ban debe tener %s caracteres o menos

Ban reason must be %s characters or less: El motivo de ban debe tener %s caracteres o menos

Banner files height must be a number > 0: La altura máxima de archivos de banner debe ser un número > 0

Banner files max must be a number: El máximo de archivos de banner debe ser un número

Banner files size must be a number: El tamaño de archivos de banner debe ser un número

Banner files total must be a number: El total de archivos de banner debe ser un número

Banner files width must be a number > 0: El ancho máximo de archivos de banner debe ser un número > 0

Block bypass expire after uses must be a number > 0: El bypass de bloqueo que expira después de usos debe ser un número > 0

Block bypass expired or exceeded max uses: El bypass expiró o excedió el número máximo de usos

Board announcements must be 5000 characters or less: Los anuncios del tablón deben ser de 5000 caracteres o menos

Board default captcha mode must be a number from 0-2: El modo captcha predeterminado del tablón debe ser un número de 0 a 2

Board default file r9k mode must be a number from 0-2: El modo r9k predeterminado para archivos del tablón debe ser un número de 0 a 2

Board default lock mode must be a number from 0-2: El modo de bloqueo predeterminado del tablón debe ser un número de 0 a 2

Board default message r9k mode must be a number from 0-2: El modo r9k predeterminado para mensajes del tablón debe ser un número de 0 a 2

Board default pph trigger action must be a number from 0-4: La acción de desencadenar PPH predeterminada del tablón debe ser un número de 0 a 4

Board default pph trigger must be a number: El desencadenador PPH predeterminado del tablón debe ser un número

Board default tph trigger action must be a number from 0-4: La acción de desencadenar HPH predeterminada del tablón debe ser un número de 0 a 4

Board default tph trigger must be a number: El desencadenador HPH predeterminado del tablón debe ser un número

Board Defaults: Configuraciones Predeterminadas del Tablón

Board defaults bump limit must be within global limits: El límite predeterminado de bumps del tablón debe estar dentro de los límites globales

Board defaults captcha reset must be a number from 0-2: El reinicio predeterminado de captcha del tablón debe ser un número de 0 a 2

Board defaults filter ban duration must be a number: La duración predeterminada de ban por filtro del tablón debe ser un número

Board defaults filter mode must be a number from 0-2: El modo de filtro predeterminado del tablón debe ser un número de 0 a 2

Board defaults lock reset must be a number from 0-2: El reinicio predeterminado de bloqueo del tablón debe ser un número de 0 a 2

Board defaults max files must be within global limits: El límite máximo de archivos predeterminado del tablón debe estar dentro de los límites globales

La longitud máxima predeterminada de mensaje de respuesta del tablón debe estar dentro de los límites globales

La longitud máxima predeterminada de mensaje de hilo del tablón debe estar dentro de los límites globales

Board defaults min reply message length must be a number: La longitud mínima predeterminada de mensaje de respuesta del tablón debe ser un número

Board defaults min thread message length must be a number: La longitud mínima predeterminada de mensaje de hilo del tablón debe ser un número

Board defaults reply limit must be within global limits: El límite de respuestas predeterminado del tablón debe estar dentro de los límites globales

Board defaults reply message length min must be less than max: La longitud mínima predeterminada de mensaje de respuesta debe ser menor que la máxima

Board defaults thread limit must be within global limits: El límite de hilos predeterminado del tablón debe estar dentro de los límites globales

Board defaults thread message length min must be less than max: La longitud mínima predeterminada de mensaje de hilo debe ser menor que la máxima

Board description must be %s characters or less: La descripción del tablón debe tener %s caracteres o menos

Board has no custom banners.: El tablón no tiene banners personalizados.

Board name must be 1-%s characters: El nombre del tablón debe tener de 1 a %s caracteres

Bypass all post filters.: Bypass a todos los filtros de publicación.

Bypass anonymizer restrictions e.g. disabled file posting.: Saltar restricciones del anonimizador, por ejemplo, la publicación de archivos desactivada.

Saltar límites de tasa para obtener nuevos captchas, editar publicaciones, editar configuraciones de tablón, etc.

Saltar el spamcheck básico anti-flood para publicaciones frecuentes o similares.

Can only create %s pages per board: Solo se pueden crear %s páginas por tablón

Cannot transfer to account that does not exist: No se puede transferir a una cuenta que no existe

Captcha options distortion must be a number from 0-50: Las opciones de distorsión de Captcha deben ser un número de 0 a 50

Captcha options distorts min must be less than max: El mínimo de distorsiones de las opciones de Captcha debe ser menor que el máximo

Captcha options generate limit must be a number > 0: El límite de generación de opciones de Captcha debe ser un número > 0

La fuerza del efecto de borde de la cuadrícula de las opciones de Captcha debe ser un número de 0 a 50

Captcha options grid image size must be a number from 50-500: El tamaño de imagen de la cuadrícula de las opciones de Captcha debe ser un número de 50 a 500

La fuerza del efecto de ruido de la cuadrícula de las opciones de Captcha debe ser un número de 0 a 10

Captcha options grid size must be a number from 2-6: El tamaño de la cuadrícula de las opciones de Captcha debe ser un número de 2 a 6

Captcha options icon y offset must be a number from 0-50: El desplazamiento de icono y de las opciones de Captcha debe ser un número de 0 a 50

Captcha options max distorts must be a number from 0-10: El máximo de distorsiones de las opciones de Captcha debe ser un número de 0 a 10

Captcha options min distorts must be a number from 0-10: El mínimo de distorsiones de las opciones de Captcha debe ser un número de 0 a 10

La fuerza del efecto de ruido de texto de las opciones de Captcha debe ser un número de 0 a 10

La fuerza del efecto de pintura de texto de las opciones de Captcha debe ser un número de 0 a 10

La fuerza del efecto de onda de texto de las opciones de Captcha debe ser un número de 0 a 10

Click here if you are not redirected automatically.: Haz clic aquí si no te redirigen automáticamente.

Code Highlight Language Detect Threshold: Umbral para Detectar Lenguajes de Resaltado de Código

Code Highlighting Auto-Detect Languages: Lenguajes de Resaltado de Código Detectados Automáticamente

Completed block bypass, you may go back and make your post.: Bypass de bloqueo completado, puedes volver y hacer tu publicación.

Connected for live posts (%sms): Conectado para publicaciones en vivo (%sms)

Country code header length must not exceed 100 characters: La longitud del encabezado de código de país no debe exceder los 100 caracteres

Custom css filters must not exceed 10000 characters: Los filtros personalizados de CSS no deben exceder los 10000 caracteres

Custom css max must be a number: El CSS personalizado máximo debe ser un número

Custom CSS must be %s characters or less: El CSS personalizado debe tener %s caracteres o menos

El modo estricto de CSS personalizado está activado y no permite lo siguiente: "%s"

Custom Pages Max Characters: Máximo de Caracteres en Páginas Personalizadas

Custom pages max length must be a number: La longitud máxima de páginas personalizadas debe ser un número

Custom pages max must be a number: El máximo de páginas personalizadas debe ser un número

Default volume must be a number from 0-100: El volumen predeterminado debe ser un número de 0 a 100

Description: Descripción

Description must be %s characters or less: La descripción debe tener %s caracteres o menos

El destino para mover no existe o no tienes permiso

Disable anonymizer file posting: Desactivar publicación de archivos de anonimizadores

Disable Anonymizer File Posting: Desactivar Publicación de Archivos de Anonimizadores

Do NOT import untrusted settings data!: ¡NO importar datos de configuraciones no confiables!

Early 404 fraction must be a number: La fracción temprana de 404 debe ser un número

Email must be %s characters or less: El correo electrónico debe tener %s caracteres o menos

Enable captcha for all posts: Habilitar captcha para todas las publicaciones

Activar 2FA invalidará todas tus sesiones activas y tendrás que iniciar sesión nuevamente.

Exceeded max asset uploads in one request of %s: Se superó el máximo de cargas de activos en una solicitud de %s

Exceeded max banner uploads in one request of %s: Se superó el máximo de cargas de banners en una solicitud de %s

Exceeded max flag uploads in one request of %s: Se superó el máximo de cargas de banderas en una solicitud de %s

Export does not include post password or (You)'s: La exportación no incluye la contraseña de la publicación ni (Tu)s

Extra mime types must be like type/subtype: Los tipos MIME adicionales deben ser como tipo/subtipo

Filter mode must be a number from 0-2: El modo de filtro debe ser un número de 0 a 2

Filter text cannot exceed 50000 characters: El texto del filtro no puede superar los 50000 caracteres

Filters length must be 20000 characters or less: La longitud de los filtros debe ser de 20000 caracteres o menos

Flag files max must be a number: El máximo de archivos de bandera debe ser un número

Flag files size must be a number: El tamaño de los archivos de bandera debe ser un número

Flag files total must be a number: El total de archivos de bandera debe ser un número

Banderas que se pueden aplicar a las publicaciones si las banderas personalizadas están habilitadas en la configuración del tablón

Frontend Script Defaults: Valores Predeterminados del Script Frontend

Control total del tablón, equivalente al dueño del tablón. Puede eliminar y/o transferir la tablón. Solo puede ser otorgado por alguien con permisos de "Dueño del Tablón". ¡Usar con precaución!

Permiso general del staff del tablón. Acceso al índice de mod, catálogo, publicaciones recientes e informes. Capacidad para realizar acciones de mod. Bypass de bans y filtros específicos del tablón.

Permiso general del staff global. Acceso a publicaciones recientes e informes. Capacidad para realizar acciones globales.

Global announcement must not exceed 10000 characters: El anuncio global no debe exceder los 10000 caracteres

Global bump limit min must be less than max: El límite mínimo global de bumps debe ser menor que el máximo

Global bump limit minimum must be a number: El límite mínimo global de bumps debe ser un número

Global limit ban reason field length must be a number: La longitud del motivo de ban global debe ser un número

Global limit board description field length must be a number: La longitud de la descripción del tablón global debe ser un número

Global limit board name field length must be a number: La longitud del nombre del tablón global debe ser un número

Global limit board uri field length must be a number: La longitud del campo URI del tablón global debe ser un número

Global limit email field length must be a number: La longitud del campo de correo electrónico global debe ser un número

Global limit log message field length must be a number: La longitud del campo de mensaje de registro global debe ser un número

Global limit message field length must be a number: La longitud del campo de mensaje global debe ser un número

Global limit name field length must be a number: La longitud del campo de nombre global debe ser un número

Global limit postpassword field length must be a number >=20: La longitud del campo de contraseña de publicación global debe ser un número >=20

Global limit report reason field length must be a number: La longitud del campo de motivo de denuncia global debe ser un número

Global limit subject field length must be a number: La longitud del campo de asunto global debe ser un número

Global post history for %s: Historial global de publicaciones para %s

Global reply limit maximum must be a number: El límite máximo de respuestas globales debe ser un número

Global reply limit min must be less than max: El límite mínimo global de respuestas debe ser menor que el máximo

Global reply limit minimum must be a number: El límite mínimo global de respuestas debe ser un número

Global thread limit maximum must be a number: El límite máximo de hilos globales debe ser un número

Global thread limit min must be less than max: El límite mínimo de hilos globales debe ser menor que el máximo

Global thread limit minimum must be a number: El límite mínimo de hilos globales debe ser un número

Hide Username In Modlog: Ocultar Nombre de Usuario en el Registro de Moderación

El subconjunto de idioma de las opciones de resaltado no debe exceder los 10000 caracteres

Highlight options threshold must be a number: El umbral de opciones de resaltado debe ser un número

Hover expands follow cursor: El cursor flotante se expande siguiendo el cursor

Imágenes elegidas al azar y mostradas en la parte superior de la mayoría de las páginas del tablón

Inactive account action must be a number from 0-2: La acción para cuentas inactivas debe ser un número de 0 a 2

Formato de URL de archivo inválido, debe ser un enlace que contenga %s donde pertenece el parámetro de URL.

Invalid block bypass expire after time: Tiempo de expiración de bypass de bloqueo inválido

Invalid board default code theme: Tema de código predeterminado del tablón inválido

Invalid board defaults OP thread age delete protection: Protección de eliminación de edad de hilo OP predeterminada del tablón inválida

Invalid board defaults OP thread reply count delete protection: Protección de eliminación de recuento de respuestas de hilo OP predeterminada del tablón inválida

Formato de URL de Ethereum inválido, debe ser un enlace que contenga %s donde pertenece el parámetro de URL.

Archivo "%s" inválido. Las dimensiones máximas del banner son %sx%s y deben tener una relación de aspecto 3:1.

Invalid file "%s". Max banner dimensions are %sx%s.: Archivo "%s" inválido. Las dimensiones máximas del banner son %sx%s.

Invalid file type for %s. Mimetype %s not allowed.: Tipo de archivo inválido para %s. Tipo %s no permitido.

Invalid flood time any content same ip: Tiempo de inundación de cualquier contenido con la misma IP inválido

Invalid flood time same contenet any ip: Tiempo de inundación del mismo contenido con cualquier IP inválido

Invalid flood time same content same ip: Tiempo de inundación del mismo contenido con la misma IP inválido

Invalid number of reports checked: Número inválido de denuncias verificadas

Invalid OP thread age delete protection: Protección de eliminación de edad de hilo OP inválida

Invalid OP thread reply count delete protection: Protección de eliminación de recuento de respuestas de hilo OP inválida

Encabezado "Referer" inválido o ausente. ¿Está publicando desde la URL correcta?

Invalid post move destination: Destino de movimiento de publicación inválido

Formato de URL de búsqueda de imagen inversa inválido, debe ser un enlace que contenga %s donde pertenece el parámetro de URL.

Invalid selected posts or destination thread: Publicaciones seleccionadas o hilo de destino inválidos

Invalid trigger reset captcha: Restablecimiento de activación de captcha inválido

Invalid trigger reset lock: Restablecimiento de activación de bloqueo inválido

IP header length must not exceed 100 characters: La longitud del encabezado IP no debe exceder los 100 caracteres

Max files per post is %s: Máximo de archivos por publicación es %s

Max files per post on this board is %s: Máximo de archivos por publicación en este tablón es %s

Max Homepage News Entries: Máximo de Entradas de Noticias en la Página de Inicio

Max recent news must be a number: Las últimas noticias máximas deben ser un número

Max Reply Message Length: Longitud Máxima del Mensaje de Respuesta

Max reply message length must be 0-%s: La longitud máxima del mensaje de respuesta debe ser de 0 a %s

La longitud máxima del mensaje de respuesta debe ser de 0 a %s y no menos de "Longitud Mínima del Mensaje de Respuesta" (actualmente %s)

Max thread message length must be 0-%s: La longitud máxima del mensaje del hilo debe ser de 0 a %s

La longitud máxima del mensaje del hilo debe ser de 0 a %s y no menos de "Longitud Mínima del Mensaje del Hilo" (actualmente %s)

Message: Mensaje

Message must be %s characters or less: El mensaje debe tener %s caracteres o menos

Message must be 10000 characters or less: El mensaje debe tener 10000 caracteres o menos

Los mensajes deben ser únicos en este hilo. Tu mensaje no es único.

Los mensajes deben ser únicos en este tablón. Tu mensaje no es único.

Meta site name must not exceed 100 characters: El nombre del sitio meta no debe exceder los 100 caracteres

Meta url must not exceed 100 characters: La URL meta no debe exceder los 100 caracteres

Mime type "%s" invalid for file "%s": Tipo MIME "%s" inválido para el archivo "%s"

Mime type mismatch for file "%s": Discrepancia de tipo MIME para el archivo "%s"

Min and max reply message lengths must not violate eachother: Las longitudes mínima y máxima del mensaje de respuesta no deben violarse mutuamente

Min and max thread message lengths must not violate eachother: Las longitudes mínima y máxima del mensaje del hilo no deben violarse mutuamente

Min Reply Message Length: Longitud Mínima del Mensaje de Respuesta

Min reply message length must be 0-%s: La longitud mínima del mensaje de respuesta debe ser de 0 a %s

La longitud mínima del mensaje de respuesta debe ser de 0 a %s y no más de "Longitud Máxima del Mensaje de Respuesta" (actualmente %s)

Min thread message length must be 0-%s: La longitud mínima del mensaje del hilo debe ser de 0 a %s

La longitud mínima del mensaje del hilo debe ser de 0 a %s y no más de "Longitud Máxima del Mensaje del Hilo" (actualmente %s)

Missing new owner username: Nombre de usuario del nuevo propietario faltante

Missing new password confirmation: Confirmación de nueva contraseña faltante

Modlog message must be %s characters or less: El mensaje del registro de moderación debe tener %s caracteres o menos

Multi input anon limit must be a number: El límite de entradas anónimas múltiples debe ser un número

Multi Input Form Anon: Formulario de Entradas Múltiples Anónimo

Multi Input Form Staff: Formulario de Entradas Múltiples del Personal

Multi input staff limit must be a number: El límite de entradas múltiples del personal debe ser un número

Must check parent post if checking reports for report action: Debe verificar la publicación principal si está revisando informes para la acción de informe

Must input destinaton thread number or board to move posts: Debe ingresar el número del hilo de destino o la tabla para mover las publicaciones

Must not select >%s posts per request: No se deben seleccionar más de >%s publicaciones por solicitud

Must select a report action if checked reports: Debe seleccionar una acción de informe si ha marcado informes

Must select at least one asset to delete: Debe seleccionar al menos un activo para eliminar

Must select at least one ban: Debe seleccionar al menos una prohibición

Must select at least one ban to appeal: Debe seleccionar al menos una prohibición para apelar

Must select at least one banner to delete: Debe seleccionar al menos un banner para eliminar

Must select at least one custom page to delete: Debe seleccionar al menos una página personalizada para eliminar

Must select at least one filter to delete: Debe seleccionar al menos un filtro para eliminar

Must select at least one flag to delete: Debe seleccionar al menos una bandera para eliminar

Must select at least one newspost to delete: Debe seleccionar al menos una noticia para eliminar

Must select at least one post: Debe seleccionar al menos una publicación

Must select at least one session to delete: Debe seleccionar al menos una sesión para eliminar

Must select at least one staff to delete: Debe seleccionar al menos un miembro del personal para eliminar

Must select only 1 post for edit action: Debe seleccionar solo 1 publicación para la acción de edición

Must select post and reports to ban reporter: Debe seleccionar la publicación y los informes para prohibir al denunciante

Name must be %s characters or less: El nombre debe tener %s caracteres o menos

New owner must be different from current owner: El nuevo propietario debe ser diferente del propietario actual

New owner username must be 50 characters or less: El nombre de usuario del nuevo propietario debe tener 50 caracteres o menos

New owner username must contain a-z 0-9 only: El nombre de usuario del nuevo propietario solo puede contener a-z 0-9

New password and password confirmation must match: La nueva contraseña y la confirmación de la contraseña deben coincidir

New password confirmation must be 100 characters or less: La confirmación de la nueva contraseña debe tener 100 caracteres o menos

New pasword must be 100 characters or less: La nueva contraseña debe tener 100 caracteres o menos

No camera? Use this secret in your authenticator app instead: ¿Sin cámara? Usa este secreto en tu aplicación de autenticación en su lugar

No threads selected to Bumplock: No se han seleccionado hilos para Bumplock

No threads selected to Lock: No se han seleccionado hilos para Bloquear

No threads selected to make Cyclical: No se han seleccionado hilos para hacer Cíclicos

No threads selected to Sticky: No se han seleccionado hilos para Destacar

No video/audio files in this thread.: No hay archivos de vídeo/audio en este hilo.

Nota: Establecer permisos de gestión de tablón a nivel de cuenta/cargo les otorgará globalmente, es decir, para todos los tablóns.
Para convertir a alguien en dueño/staff normal de un tablón, transfiera el tablón o brinde los permisos apropiados en la interfaz de edición de permisos de staff del tablón.

OP Reply Count Delete Protection: Protección de cantidad de respuestas del OP para eliminar hilo

OP Thread Age Delete Protection: Protección de antigüedad del hilo del OP para eliminar

Password and password confirmation must match: La contraseña y la confirmación de la contraseña deben coincidir

Password confirmation must be 100 characters or less: La confirmación de la contraseña debe tener 100 caracteres o menos

Password did not match any selected posts: La contraseña no coincide con ninguna de las publicaciones seleccionadas

Password must be %s characters or less: La contraseña debe tener %s caracteres o menos

Password must be 1-100 characters: La contraseña debe tener entre 1 y 100 caracteres

Password must be 50 characters or less: La contraseña debe tener 50 caracteres o menos

Please complete a block bypass to continue: Complete un bypass de bloqueo para continuar

Please enable JavaScript to solve the captcha.: Por favor, habilite JavaScript para resolver el captcha.

Por favor, ingrese al menos un host permitido en el campo "Hosts Permitidos" cuando la opción de "Verificar Referer" esté seleccionada.

Please wait before generating another 2FA QR code.: Espere antes de generar otro código QR 2FA.

Espere antes de hacer otra publicación o una similar a la de otro usuario

Post files max must be a number: El máximo de archivos debe ser un número

Post files size must be a number: El tamaño de archivos debe ser un número

Posting files through anonymizers has been disabled globally: La publicación de archivos a través de anonimizadores ha sido desactivada globalmente

La publicación de archivos a través de anonimizadores ha sido desactivada en este tablón

Posts must include a message or file: Las publicaciones deben incluir un mensaje o archivo

Preview replies must be a number: La vista previa de respuestas debe ser un número

Prune Modlogs Days: Eliminar registros de moderación en días

Prune modlogs must be a number of days: Eliminar registros de moderación debe ser un número de días

Rate limit cost board settings must be a number from 1-100: El costo límite de la configuración del tablón debe ser un número de 1 a 100

Rate limit cost captcha must be a number from 1-100: El costo límite de captcha debe ser un número de 1 a 100

Rate limit cost edit post must be a number from 1-100: El costo límite de edición de publicación debe ser un número de 1 a 100

Recently bumped threads from multiple boards: Hilos recientemente movidos de múltiples tablóns

Reply Limit must be %s-%s: El límite de respuestas debe ser de %s a %s

Reply messages must be %s-%s characters: Los mensajes de respuesta deben tener entre %s y %s caracteres

Report must be %s characters or less: El reporte debe tener %s caracteres o menos

Reverse Image Search Links URL: URL de Enlaces de Búsqueda Inversa de Imágenes

Right click to replace with the current color: Haga clic derecho para reemplazar con el color actual

Scan the QR Code in an authenticator app, and submit the code: Escanea el código QR en una aplicación de autenticación y envía el código

Selected posts not found: Publicaciones seleccionadas no encontradas

one: Establecer nivel pegajoso para %s hilo a %s

other: Establecer nivel pegajoso para %s hilos a %s

Space File Name Replacement: Reemplazo de Nombre de Archivo con Espacios

Space file name replacement must be 1 character: El reemplazo de nombre de archivo con espacios debe ser de 1 caracter

Sticky preview replies must be a number: La vista previa de respuestas fijas debe ser un número

Subject: Asunto

Subject must be %s characters or less: El asunto debe tener %s caracteres o menos

Tags length must be 2000 characters or less: Las etiquetas deben tener 2000 caracteres o menos

Template Generation Lock Time: Tiempo de Bloqueo de Generación de Plantillas

Test first in top-right settings if you have javascript enabled: Pruebe primero en la configuración en la esquina superior derecha si tiene JavaScript habilitado

Text: Texto

El servidor actuando como gateway o proxy no recibió una respuesta a tiempo del servidor upstream.

El servidor actuando como gateway o proxy recibió una respuesta inválida del servidor upstream.

El servidor encontró un error y no pudo cumplir con su solicitud.

El servidor no pudo procesar "%s". Archivo no compatible o corrupto.

El servidor actualmente no puede manejar la solicitud debido a una sobrecarga temporal o mantenimiento programado.

Actualmente hay %s tablóns públicos, %s en total. En todo el sitio, se han realizado %s publicaciones en el último día, %s en la última hora, %s en total.

Actualmente hay %s sitios y %s tablóns en el webring conocido. En todos los tablóns de webring, se han realizado %s publicaciones en la última hora, %s en total.

Thread messages must be %s-%s characters: Los mensajes de hilo deben tener entre %s y %s caracteres

Thumb extension must be like .xxx: La extensión del thumbnail debe ser como .xxx

Thumbnail extension must be at least 1 character: La extensión del thumbnail debe tener al menos 1 caracter

Title: Título

Title must be 50 characters or less: El título debe tener 50 caracteres o menos

one: Se ha alternado el modo Cíclico para %s hilo

other: Se ha alternado el modo Cíclico para %s hilos

other: Se ha alternado el bloqueo para %s hilos

Too many files. Max files per post is %s: Demasiados archivos. Máximo de archivos por publicación es %s

Too many files. Max files per post on this board is %s: Demasiados archivos. Máximo de archivos por publicación en este tablón es %s

Total Custom Pages Per Board: Total de Páginas Personalizadas Por Tablón

Total number of banners would exceed global limit of %s: El total de banners excedería el límite global de %s

Total number of filters would exceed global limit of %s: El total de filtros excedería el límite global de %s

Total number of flags would exceed global limit of %s: El total de etiquetas excedería el límite global de %s

Two factor authentication enabled successfully: Autenticación de dos factores activada con éxito

Two Factor Authentication Setup: Configuración de Autenticación de Dos Factores

Uploaded files must be unique in this thread.: Los archivos cargados deben ser únicos en este hilo.

Uploaded files must be unique on this board.: Los archivos cargados deben ser únicos en este tablón.

User file spoiling is disabled on this board: El estropear archivos por usuarios está deshabilitado en este tablón

User file unlinking is disabled on this board: El desvincular archivos por usuarios está deshabilitado en este tablón

User post deletion is disabled on this board: El borrado de publicaciones por usuarios está deshabilitado en este tablón

USER WAS BANNED FOR THIS POST: EL USUARIO FUE BANEADO POR ESTA PUBLICACIÓN

Username must be 1-50 characters: El nombre de usuario debe tener entre 1 y 50 caracteres

Username must be 50 characters or less: El nombre de usuario debe tener 50 caracteres o menos

Username must contain a-z 0-9 only: El nombre de usuario solo puede contener a-z 0-9

Username must not be an ethereum address: El nombre de usuario no puede ser una dirección de Ethereum

Video thumbnail percentage must be a number from 1-100: El porcentaje de miniatura de video debe ser un número del 1 al 100

Webring blacklist must not exceed 10000 characters: La lista negra de Webring no debe superar los 10000 caracteres

Webring following list must not exceed 10000 characters: La lista de seguimiento de Webring no debe superar los 10000 caracteres

Webring logos list must not exceed 10000 characters: La lista de logotipos de Webring no debe superar los 10000 caracteres

You already have 2FA setup: Ya tienes configurada la autenticación de dos factores (2FA)

Estás cambiando las opciones demasiado rápido, por favor espera un minuto e intenta de nuevo

Estás editando las publicaciones demasiado rápido, por favor espera un minuto e intenta de nuevo

You can't apply template permissions to a ROOT user.: No puedes aplicar plantillas de permisos a un usuario ROOT.

You can't edit the permissions of the board owner: No puedes editar los permisos del dueño del tabla

No puedes eliminar tu cuenta mientras ocupas un cargo de staff en alguna tabla

Your country "%s" is not allowed to post on this board: Tu país "%s" no está permitido para publicar en esta tabla

Your post was blocked by a word filter: Tu publicación fue bloqueada por un filtro de palabras

Tu solicitud fue bloqueada porque tu dirección IP está en una lista negra

Replace text cannot exceed 500 characters: El texto de reemplazo no puede superar los 500 caracteres