@2toad/profanity
Advanced tools
Comparing version 2.3.1 to 2.4.0
@@ -1,2 +0,1 @@ | ||
declare const _default: string[]; | ||
export default _default; | ||
export declare const profaneWords: readonly string[]; |
@@ -5,3 +5,4 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.default = [ | ||
exports.profaneWords = void 0; | ||
exports.profaneWords = [ | ||
"4r5e", | ||
@@ -8,0 +9,0 @@ "5h1t", |
@@ -1,3 +0,4 @@ | ||
export * from "./profanity"; | ||
export * from "./profanity-options"; | ||
export { Profanity, profanity } from "./profanity"; | ||
export { ProfanityOptions } from "./profanity-options"; | ||
export { CensorType } from "./models"; | ||
export { profaneWords } from "./data"; |
"use strict"; | ||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
var desc = Object.getOwnPropertyDescriptor(m, k); | ||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { | ||
desc = { enumerable: true, get: function() { return m[k]; } }; | ||
} | ||
Object.defineProperty(o, k2, desc); | ||
}) : (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
o[k2] = m[k]; | ||
})); | ||
var __exportStar = (this && this.__exportStar) || function(m, exports) { | ||
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.CensorType = void 0; | ||
__exportStar(require("./profanity"), exports); | ||
__exportStar(require("./profanity-options"), exports); | ||
exports.profaneWords = exports.CensorType = exports.ProfanityOptions = exports.profanity = exports.Profanity = void 0; | ||
var profanity_1 = require("./profanity"); | ||
Object.defineProperty(exports, "Profanity", { enumerable: true, get: function () { return profanity_1.Profanity; } }); | ||
Object.defineProperty(exports, "profanity", { enumerable: true, get: function () { return profanity_1.profanity; } }); | ||
var profanity_options_1 = require("./profanity-options"); | ||
Object.defineProperty(exports, "ProfanityOptions", { enumerable: true, get: function () { return profanity_options_1.ProfanityOptions; } }); | ||
var models_1 = require("./models"); | ||
Object.defineProperty(exports, "CensorType", { enumerable: true, get: function () { return models_1.CensorType; } }); | ||
var data_1 = require("./data"); | ||
Object.defineProperty(exports, "profaneWords", { enumerable: true, get: function () { return data_1.profaneWords; } }); | ||
//# sourceMappingURL=index.js.map |
@@ -1,2 +0,2 @@ | ||
export * from "./censor-type"; | ||
export * from "./list"; | ||
export { CensorType } from "./censor-type"; | ||
export { List } from "./list"; |
"use strict"; | ||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
var desc = Object.getOwnPropertyDescriptor(m, k); | ||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { | ||
desc = { enumerable: true, get: function() { return m[k]; } }; | ||
} | ||
Object.defineProperty(o, k2, desc); | ||
}) : (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
o[k2] = m[k]; | ||
})); | ||
var __exportStar = (this && this.__exportStar) || function(m, exports) { | ||
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
__exportStar(require("./censor-type"), exports); | ||
__exportStar(require("./list"), exports); | ||
exports.List = exports.CensorType = void 0; | ||
var censor_type_1 = require("./censor-type"); | ||
Object.defineProperty(exports, "CensorType", { enumerable: true, get: function () { return censor_type_1.CensorType; } }); | ||
var list_1 = require("./list"); | ||
Object.defineProperty(exports, "List", { enumerable: true, get: function () { return list_1.List; } }); | ||
//# sourceMappingURL=index.js.map |
@@ -7,3 +7,3 @@ export declare class List { | ||
removeWords(words: string[]): void; | ||
addWords(words: string[]): void; | ||
addWords(words: readonly string[] | string[]): void; | ||
} |
@@ -17,3 +17,3 @@ "use strict"; | ||
addWords(words) { | ||
this.words = this.words.concat(words); | ||
this.words = this.words.concat(words.map((word) => word.toLowerCase())); | ||
this.onListChanged(); | ||
@@ -20,0 +20,0 @@ } |
@@ -5,3 +5,3 @@ export declare class ProfanityOptions { | ||
grawlixChar: string; | ||
constructor(); | ||
constructor(options?: Partial<ProfanityOptions>); | ||
} |
@@ -5,6 +5,7 @@ "use strict"; | ||
class ProfanityOptions { | ||
constructor() { | ||
this.wholeWord = true; | ||
this.grawlix = "@#$%&!"; | ||
this.grawlixChar = "*"; | ||
constructor(options = {}) { | ||
var _a, _b, _c; | ||
this.wholeWord = (_a = options.wholeWord) !== null && _a !== void 0 ? _a : true; | ||
this.grawlix = (_b = options.grawlix) !== null && _b !== void 0 ? _b : "@#$%&!"; | ||
this.grawlixChar = (_c = options.grawlixChar) !== null && _c !== void 0 ? _c : "*"; | ||
} | ||
@@ -11,0 +12,0 @@ } |
@@ -8,5 +8,6 @@ import { ProfanityOptions } from "./profanity-options"; | ||
private regex; | ||
constructor(options?: ProfanityOptions); | ||
constructor(options?: ProfanityOptions | Partial<ProfanityOptions>); | ||
exists(text: string): boolean; | ||
censor(text: string, censorType?: CensorType): string; | ||
private replaceProfanity; | ||
addWords(words: string[]): void; | ||
@@ -17,2 +18,1 @@ removeWords(words: string[]): void; | ||
export declare const profanity: Profanity; | ||
export default profanity; |
"use strict"; | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
@@ -9,38 +6,59 @@ exports.profanity = exports.Profanity = void 0; | ||
const models_1 = require("./models"); | ||
const misc_1 = require("./utils/misc"); | ||
const profane_words_1 = __importDefault(require("./data/profane-words")); | ||
const utils_1 = require("./utils"); | ||
const data_1 = require("./data"); | ||
class Profanity { | ||
constructor(options) { | ||
this.options = options || new profanity_options_1.ProfanityOptions(); | ||
this.options = options ? { ...new profanity_options_1.ProfanityOptions(), ...options } : new profanity_options_1.ProfanityOptions(); | ||
this.whitelist = new models_1.List(() => this.buildRegex()); | ||
this.blacklist = new models_1.List(() => this.buildRegex()); | ||
this.blacklist.addWords(profane_words_1.default); | ||
this.blacklist.addWords(data_1.profaneWords); | ||
} | ||
exists(text) { | ||
this.regex.lastIndex = 0; | ||
return this.regex.test(text); | ||
const lowercaseText = text.toLowerCase(); | ||
let match; | ||
do { | ||
match = this.regex.exec(lowercaseText); | ||
if (match !== null) { | ||
const matchStart = match.index; | ||
const matchEnd = matchStart + match[0].length; | ||
// Check if the matched word is part of a whitelisted word | ||
const isWhitelisted = this.whitelist.words.some((whitelistedWord) => { | ||
const whitelistedIndex = lowercaseText.indexOf(whitelistedWord, Math.max(0, matchStart - whitelistedWord.length + 1)); | ||
if (whitelistedIndex === -1) | ||
return false; | ||
const whitelistedEnd = whitelistedIndex + whitelistedWord.length; | ||
if (this.options.wholeWord) { | ||
// For whole word matching, ensure the whitelisted word exactly matches the profane word | ||
// and is not part of a hyphenated or underscore-separated word | ||
return (matchStart === whitelistedIndex && | ||
matchEnd === whitelistedEnd && | ||
(matchStart === 0 || !/[\w-_]/.test(lowercaseText[matchStart - 1])) && | ||
(matchEnd === lowercaseText.length || !/[\w-_]/.test(lowercaseText[matchEnd]))); | ||
} | ||
// For partial matching, check if the profane word is contained within the whitelisted word | ||
return (matchStart >= whitelistedIndex && matchStart < whitelistedEnd) || (matchEnd > whitelistedIndex && matchEnd <= whitelistedEnd); | ||
}); | ||
if (!isWhitelisted) { | ||
return true; | ||
} | ||
} | ||
} while (match !== null); | ||
return false; | ||
} | ||
censor(text, censorType = models_1.CensorType.Word) { | ||
const lowercaseText = text.toLowerCase(); | ||
switch (censorType) { | ||
case models_1.CensorType.Word: | ||
return text.replace(this.regex, this.options.grawlix); | ||
return text.replace(this.regex, (match) => { | ||
const underscore = match.includes("_") ? "_" : ""; | ||
return this.options.grawlix + underscore; | ||
}); | ||
case models_1.CensorType.FirstChar: { | ||
let output = text; | ||
Array.from(text.matchAll(this.regex)).forEach((match) => { | ||
const word = match[0]; | ||
const grawlix = this.options.grawlixChar + word.slice(1, word.length); | ||
output = output.replace(word, grawlix); | ||
}); | ||
return output; | ||
return this.replaceProfanity(text, lowercaseText, (word) => this.options.grawlixChar + word.slice(1)); | ||
} | ||
case models_1.CensorType.FirstVowel: | ||
case models_1.CensorType.AllVowels: { | ||
const regex = new RegExp("[aeiou]", censorType === models_1.CensorType.FirstVowel ? "i" : "ig"); | ||
let output = text; | ||
Array.from(text.matchAll(this.regex)).forEach((match) => { | ||
const word = match[0]; | ||
const grawlix = word.replace(regex, this.options.grawlixChar); | ||
output = output.replace(word, grawlix); | ||
}); | ||
return output; | ||
const vowelRegex = new RegExp("[aeiou]", censorType === models_1.CensorType.FirstVowel ? "i" : "ig"); | ||
return this.replaceProfanity(text, lowercaseText, (word) => word.replace(vowelRegex, this.options.grawlixChar)); | ||
} | ||
@@ -51,2 +69,20 @@ default: | ||
} | ||
replaceProfanity(text, lowercaseText, replacer) { | ||
let result = text; | ||
let offset = 0; | ||
this.regex.lastIndex = 0; | ||
let match; | ||
do { | ||
match = this.regex.exec(lowercaseText); | ||
if (match !== null) { | ||
const matchStart = match.index; | ||
const matchEnd = matchStart + match[0].length; | ||
const originalWord = text.slice(matchStart + offset, matchEnd + offset); | ||
const censoredWord = replacer(originalWord); | ||
result = result.slice(0, matchStart + offset) + censoredWord + result.slice(matchEnd + offset); | ||
offset += censoredWord.length - originalWord.length; | ||
} | ||
} while (match !== null); | ||
return result; | ||
} | ||
addWords(words) { | ||
@@ -56,10 +92,8 @@ this.blacklist.addWords(words); | ||
removeWords(words) { | ||
this.blacklist.removeWords(words); | ||
this.blacklist.removeWords(words.map((word) => word.toLowerCase())); | ||
} | ||
buildRegex() { | ||
const escapedBlacklistWords = this.blacklist.words.map(misc_1.escapeRegExp); | ||
const escapedWhitelistWords = this.whitelist.words.map(misc_1.escapeRegExp); | ||
const blacklistPattern = `${this.options.wholeWord ? "\\b" : ""}(${escapedBlacklistWords.join("|")})${this.options.wholeWord ? "\\b" : ""}`; | ||
const whitelistPattern = this.whitelist.empty ? "" : `(?!${escapedWhitelistWords.join("|")})`; | ||
this.regex = new RegExp(whitelistPattern + blacklistPattern, "ig"); | ||
const escapedBlacklistWords = this.blacklist.words.map(utils_1.escapeRegExp); | ||
const profanityPattern = `${this.options.wholeWord ? "(?:\\b|_)" : ""}(${escapedBlacklistWords.join("|")})${this.options.wholeWord ? "(?:\\b|_)" : ""}`; | ||
this.regex = new RegExp(profanityPattern, "gi"); | ||
} | ||
@@ -69,3 +103,2 @@ } | ||
exports.profanity = new Profanity(); | ||
exports.default = exports.profanity; | ||
//# sourceMappingURL=profanity.js.map |
{ | ||
"name": "@2toad/profanity", | ||
"version": "2.3.1", | ||
"version": "2.4.0", | ||
"description": "A JavaScript profanity filter", | ||
@@ -17,3 +17,5 @@ "homepage": "https://github.com/2Toad/Profanity", | ||
"local": "npm run build && concurrently -p \"none\" \"npx tsc --watch\" \"nodemon -q dist/index.js\"", | ||
"test": "mocha -r ts-node/register -r mocha-suppress-logs tests/**/*.spec.ts", | ||
"pretest": "npm run build", | ||
"test": "mocha -r ts-node/register tests/**/*.spec.ts", | ||
"test:watch": "npm run test -- --watch", | ||
"lint": "eslint . --cache", | ||
@@ -23,3 +25,3 @@ "lint:fix": "eslint . --cache --fix", | ||
"prettier:fix": "prettier --write **/*.ts", | ||
"prepublishOnly": "npm run lint && npm run prettier && npm run build && npm test", | ||
"prepublishOnly": "npm run lint && npm run prettier && npm test", | ||
"prepare": "husky" | ||
@@ -34,14 +36,19 @@ }, | ||
"profane", | ||
"obscenity", | ||
"obscene", | ||
"obscenity", | ||
"obscenities", | ||
"cussing", | ||
"curse", | ||
"cursing", | ||
"swearing", | ||
"swearwords", | ||
"vulgar", | ||
"swear-words", | ||
"vulgarity", | ||
"bad words", | ||
"bad language", | ||
"dirty words" | ||
"badwords", | ||
"bad-words", | ||
"badlanguage", | ||
"bad-language", | ||
"dirtywords", | ||
"dirty-words", | ||
"censor", | ||
"filter" | ||
], | ||
@@ -48,0 +55,0 @@ "devDependencies": { |
@@ -17,3 +17,3 @@ # Profanity ๐งผ | ||
>If you're using Node 11.x or older, you'll need to install [Profanity 1.x](https://github.com/2Toad/Profanity/releases) (e.g., `npm i @2toad/profanity@1.4.0`) | ||
>If you're using Node 11.x or older, you'll need to install [Profanity 1.x](https://github.com/2Toad/Profanity/releases) (e.g., `npm i @2toad/profanity@1.4.1`) | ||
@@ -23,7 +23,8 @@ ## Usage ๐ | ||
```JavaScript | ||
import { profanity } from '@2toad/profanity'; | ||
import { profanity, CensorType } from '@2toad/profanity'; | ||
// or | ||
var profanity = require('@2toad/profanity').profanity; | ||
const { profanity, CensorType } = require('@2toad/profanity'); | ||
``` | ||
```JavaScript | ||
profanity.exists('I like big butts and I cannot lie'); | ||
@@ -46,10 +47,9 @@ // true | ||
```JavaScript | ||
import { Profanity, ProfanityOptions } from '@2toad/profanity'; | ||
import { Profanity } from '@2toad/profanity'; | ||
const options = new ProfanityOptions(); | ||
options.wholeWord = false; | ||
options.grawlix = '*****'; | ||
options.grawlixChar = '$'; | ||
const profanity = new Profanity(options); | ||
const profanity = new Profanity({ | ||
wholeWord: false, | ||
grawlix: '*****', | ||
grawlixChar: '$', | ||
}); | ||
``` | ||
@@ -71,2 +71,19 @@ | ||
#### Compound Words | ||
Profanity detection works on parts of compound words, rather than treating hyphenated or underscore-separated words as indivisible. | ||
When `wholeWord` is `true`, each portion of a compound word is analyzed for a match: | ||
```JavaScript | ||
profanity.exists("Don't be an arsenic-monster"); | ||
// false | ||
profanity.exists("Don't be an arse-monster"); | ||
// true (matched on arse) | ||
``` | ||
Setting `wholeWord` to `false`, results in partial word matches on each portion of a compound word: | ||
```JavaScript | ||
profanity.exists("Don't be an arsenic-monster"); | ||
// true (matched on arse) | ||
``` | ||
### grawlix ๐ฅ | ||
@@ -73,0 +90,0 @@ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
41036
34
690
145