diff --git a/.gitignore b/.gitignore index b92b031..2f8128e 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ node_modules npm-debug.log *.pem package-lock.json +jsconfig.json diff --git a/config/blacklist.json b/dictionaries/profanity.json similarity index 100% rename from config/blacklist.json rename to dictionaries/profanity.json diff --git a/dictionaries/racist.json b/dictionaries/racist.json new file mode 100644 index 0000000..9c0633e --- /dev/null +++ b/dictionaries/racist.json @@ -0,0 +1,26 @@ +{ + "en": [ + "nigger", + "gook", + "ape", + "coon", + "jigaboo", + "negro", + "sambo", + "sooty", + "teapot", + "cracker", + "gringo", + "gweilo", + "gwailo", + "kwai lo", + "honky", + "redskin", + "white trash", + "slave", + "whitelist", + "white list", + "blacklist", + "black list" + ] +} diff --git a/index.js b/index.js index 7356dee..ce888c6 100644 --- a/index.js +++ b/index.js @@ -67,7 +67,7 @@ module.exports = async robot => { } async function forRepository (context) { - let config = await getConfig(context, 'profanity.yml') + let config = await getConfig(context, 'profanity.yml', null) if (!config) { scheduler.stop(context.payload.repository) diff --git a/lib/profanity.js b/lib/profanity.js index 35faebc..3901f6a 100644 --- a/lib/profanity.js +++ b/lib/profanity.js @@ -1,4 +1,3 @@ -const blacklist = require('../config/blacklist.json') const Filter = require('./filter') const schema = require('./schema') @@ -27,11 +26,12 @@ module.exports = class Profanity { getFilter () { const { language, + dictionaries, extraWords, exemptWords, placeholder } = this.config - const list = blacklist[language] + const list = this.getForbiddenWordList(dictionaries, language) return new Filter({ list: list, @@ -41,6 +41,27 @@ module.exports = class Profanity { }) } + /** + * Get the flattened forbidden words list for the specified dictionaries and language + * + * @param {string[]} dictionaries The list of dictionaries to use + * @param {string} language The language code to use + * @returns {string[]} The flattened list of forbidden words + */ + getForbiddenWordList (dictionaries, language) { + if (!dictionaries || !dictionaries.length || !language) { + return [] + } + + return Array.from(new Set(dictionaries.reduce((acc, dictName) => { + const dictionary = require('../dictionaries/' + dictName) + if (!dictionary || !dictionary[language]) { + return acc + } + return acc.concat(dictionary[language]) + }, []))) + } + async markAndSweep (type) { const {only} = this.config if (only && only !== type) { diff --git a/lib/schema.js b/lib/schema.js index 0a7a774..8aed96e 100644 --- a/lib/schema.js +++ b/lib/schema.js @@ -7,6 +7,9 @@ const fields = { censor: Joi.boolean() .description('Set to true to censor issues (defaults to false)'), + dictionaries: Joi.array().items(Joi.string().valid('profanity', 'racist')).min(1).single() + .description('The dictionaries of forbidden words to use'), + placeholder: Joi.string().max(1) .description('A letter to replace those of a forbidden word'), @@ -52,6 +55,7 @@ const fields = { const schema = Joi.object().keys({ language: fields.language.default('en'), censor: fields.censor.default(false), + dictionaries: fields.dictionaries.default(['profanity', 'racist']), placeholder: fields.placeholder.default('*'), extraWords: fields.extraWords.default([]), exemptWords: fields.exemptWords.default([]), diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 8b78852..5f36191 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -166,6 +166,7 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", + "optional": true, "requires": { "kind-of": "^3.0.2", "longest": "^1.0.1", @@ -2611,7 +2612,8 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -2632,12 +2634,14 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -2652,17 +2656,20 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -2779,7 +2786,8 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -2791,6 +2799,7 @@ "version": "1.0.0", "bundled": true, "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -2805,6 +2814,7 @@ "version": "3.0.4", "bundled": true, "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -2812,12 +2822,14 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "minipass": { "version": "2.2.4", "bundled": true, "dev": true, + "optional": true, "requires": { "safe-buffer": "^5.1.1", "yallist": "^3.0.0" @@ -2836,6 +2848,7 @@ "version": "0.5.1", "bundled": true, "dev": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -2916,7 +2929,8 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -2928,6 +2942,7 @@ "version": "1.4.0", "bundled": true, "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -3013,7 +3028,8 @@ "safe-buffer": { "version": "5.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -3049,6 +3065,7 @@ "version": "1.0.2", "bundled": true, "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -3068,6 +3085,7 @@ "version": "3.0.1", "bundled": true, "dev": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -3111,12 +3129,14 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "yallist": { "version": "3.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true } } }, @@ -5208,7 +5228,8 @@ "longest": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", - "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=" + "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=", + "optional": true }, "loose-envify": { "version": "1.3.1", diff --git a/test/schema.test.js b/test/schema.test.js index 4fbfefb..defefdc 100644 --- a/test/schema.test.js +++ b/test/schema.test.js @@ -4,6 +4,8 @@ const validConfigs = [ [{language: 'en'}], [{censor: true}], [{censor: false}], + [{dictionaries: ['profanity']}], + [{dictionaries: ['racist', 'profanity']}], [{placeholder: '*'}], [{extraWords: ['duck']}], [{extraWords: 'duck'}, {extraWords: ['duck']}], @@ -44,6 +46,8 @@ const validConfigs = [ const invalidConfigs = [ [{language: 'bananas'}, 'must be one of [de, en, es, fr, it, nl, pt, ru]'], [{censor: 'nope'}, 'must be a boolean'], + [{dictionaries: ['fake']}, 'must be one of [profanity, racist]'], + [{dictionaries: []}, 'must contain at least 1 items'], [{placeholder: ''}, 'not allowed to be empty'], [{placeholder: false}, 'must be a string'], [{placeholder: ['a', 'b']}, 'must be a string'], @@ -72,6 +76,7 @@ describe('schema', () => { expect(schema.validate({}).value).toEqual({ language: 'en', censor: false, + dictionaries: ['profanity', 'racist'], placeholder: '*', extraWords: [], exemptWords: [],