bad-words-next
Version:
JavaScript/TypeScript filter and checker for bad words aka profanity
241 lines (238 loc) • 7.65 kB
JavaScript
import { createClass as _createClass, createForOfIteratorHelper as _createForOfIteratorHelper, objectSpread2 as _objectSpread2, classCallCheck as _classCallCheck } from './_virtual/_rollupPluginBabelHelpers.mjs';
import { remove } from 'confusables';
import moize from 'moize';
/**
* @license bad-words-next
* Copyright (c) 2022, Void Resort. (MIT License)
* https://github.com/voidresort/bad-words-next
*/
function escapeRegexpWord(word) {
return word.replace(/[.?^${}()|[\]\\]/g, '\\$&').replace(/\b\*\b/, '');
}
function escapeRegexpString(str) {
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
function regexpToString(re) {
var str = re.toString();
var i = 0;
var j = str.length - 1;
for (;;) {
var left = str[i] === '/';
var right = str[j] === '/';
if (i >= j || left && right) {
break;
}
i += Number(!left);
j -= Number(!right);
}
i++;
j--;
if (str[i] === '^') i++;
if (str[j] === '$' && str[j - 1] !== '\\') j--;
return str.slice(i, j + 1);
}
var DEFAULT_OPTIONS = {
placeholder: '***',
placeholderMode: 'replace',
specialChars: /\d|[!@#$%^&*()[\];:'",.?\-_=+~`|]|a|(?:the)|(?:el)|(?:la)/,
spaceChars: ['', '.', '-', ';', '|'],
confusables: ['en', 'es', 'de', 'ru_lat'],
maxCacheSize: 100,
exclusions: []
};
var BadWordsNext = function () {
function BadWordsNext(opts) {
_classCallCheck(this, BadWordsNext);
this.opts = opts !== undefined ? _objectSpread2(_objectSpread2({}, DEFAULT_OPTIONS), opts) : DEFAULT_OPTIONS;
this.specialChars = regexpToString(this.opts.specialChars);
this.exclusionsRegexps = this.opts.exclusions.map(this.regexp.bind(this));
this.data = {};
this.ids = [];
var preCheckMaxCacheSize = Math.max(0, Math.floor(this.opts.maxCacheSize / 3));
var moizedPreCheck = moize(this.preCheck, {
maxSize: preCheckMaxCacheSize
});
this.preCheck = moizedPreCheck;
var moizedCheck = moize(this.check, {
maxSize: this.opts.maxCacheSize - preCheckMaxCacheSize
});
this.check = moizedCheck;
this.clear = function () {
moizedPreCheck.clear();
moizedCheck.clear();
};
if (this.opts.data !== undefined) {
this.add(this.opts.data);
}
}
return _createClass(BadWordsNext, [{
key: "add",
value: function add(data) {
this.clear();
var regexp = '';
var lookalike = '';
var _iterator = _createForOfIteratorHelper(data.words),
_step;
try {
for (_iterator.s(); !(_step = _iterator.n()).done;) {
var word = _step.value;
var exp = escapeRegexpWord(word.trim().replace(/\s/g, '_'));
if (exp === '') continue;
if (exp.startsWith('*')) {
exp = "[^\\s\\b^]*".concat(exp.slice(1));
}
if (exp.endsWith('*')) {
exp = "".concat(exp.slice(0, -1), "[^\\s\\b$]*");
}
regexp += regexp !== '' ? "|".concat(exp) : exp;
if (exp.includes('_')) {
var _iterator2 = _createForOfIteratorHelper(this.opts.spaceChars),
_step2;
try {
for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
var ch = _step2.value;
regexp += "|".concat(exp.replace(/_/g, escapeRegexpString(ch)));
}
} catch (err) {
_iterator2.e(err);
} finally {
_iterator2.f();
}
}
}
} catch (err) {
_iterator.e(err);
} finally {
_iterator.f();
}
for (var key in data.lookalike) {
var esc = escapeRegexpString(key);
lookalike += lookalike !== '' ? "|".concat(esc) : esc;
}
this.data[data.id] = _objectSpread2(_objectSpread2({}, data), {}, {
wordsRegexp: this.regexp(regexp)
});
if (lookalike !== '') {
this.data[data.id].lookalikeRegexp = new RegExp(lookalike, 'ig');
}
this.ids.push(data.id);
}
}, {
key: "prepare",
value: function prepare(str, id) {
var _this = this;
var s = str;
if (this.data[id].lookalikeRegexp !== undefined) {
s = str.replace(this.data[id].lookalikeRegexp, function (m) {
if (_this.data[id].lookalike[m] !== undefined) {
return _this.data[id].lookalike[m];
}
var ml = m.toLowerCase();
if (_this.data[id].lookalike[ml] !== undefined) {
return _this.data[id].lookalike[ml];
}
return m;
});
}
return this.opts.confusables.includes(id) ? remove(s) : s;
}
}, {
key: "regexp",
value: function regexp(expr) {
return new RegExp("(?:^|\\b|\\s)(?:".concat(this.specialChars, ")*(?:").concat(expr, ")(?:").concat(this.specialChars, ")*(?:$|\\b|\\s)"), 'i');
}
}, {
key: "preCheck",
value: function preCheck(str) {
var _iterator3 = _createForOfIteratorHelper(this.ids),
_step3;
try {
for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
var id = _step3.value;
if (this.data[id].wordsRegexp.test(str) || this.data[id].wordsRegexp.test(this.prepare(str, id))) {
return true;
}
}
} catch (err) {
_iterator3.e(err);
} finally {
_iterator3.f();
}
return false;
}
}, {
key: "check",
value: function check(word) {
var _iterator4 = _createForOfIteratorHelper(this.ids),
_step4;
try {
for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
var id = _step4.value;
var preparedWord = null;
var _iterator5 = _createForOfIteratorHelper(this.exclusionsRegexps),
_step5;
try {
for (_iterator5.s(); !(_step5 = _iterator5.n()).done;) {
var exclusionRegexp = _step5.value;
if (exclusionRegexp.test(word)) {
return false;
}
if (preparedWord === null) {
preparedWord = this.prepare(word, id);
}
if (exclusionRegexp.test(preparedWord)) {
return false;
}
}
} catch (err) {
_iterator5.e(err);
} finally {
_iterator5.f();
}
if (this.data[id].wordsRegexp.test(word)) {
return true;
}
if (preparedWord === null) {
preparedWord = this.prepare(word, id);
}
if (this.data[id].wordsRegexp.test(preparedWord)) {
return true;
}
}
} catch (err) {
_iterator4.e(err);
} finally {
_iterator4.f();
}
return false;
}
}, {
key: "filter",
value: function filter(str, onCatch) {
var _this2 = this;
if (str === '' || !this.preCheck(str)) return str;
var delims = [];
var re = /([\b\s])/g;
var match;
while ((match = re.exec(str)) !== null) {
delims.push(match[0]);
}
var repeat = this.opts.placeholderMode === 'repeat';
return str.split(/[\b\s]/).map(function (word) {
if (_this2.check(word)) {
if (onCatch !== undefined) {
onCatch(word);
}
if (repeat) {
return _this2.opts.placeholder.repeat(word.length);
}
return _this2.opts.placeholder;
}
return word;
}).reduce(function (acc, word, i) {
return acc + (i > 0 ? delims[i - 1] === undefined ? ' ' : delims[i - 1] : '') + word;
}, '');
}
}]);
}();
export { BadWordsNext as default };