All Articles

badwords 필터링

profane words & bad words 관련 오픈소스

  1. https://github.com/web-mech/badwords
  2. https://github.com/ChaseFlorell/jQuery.ProfanityFilter.git
  3. https://github.com/dariusk/wordfilter

1, 2, 3번 오픈소스의 코드를 살펴본 결과,

2번 코드는 old style 코드라고 판단이 들었다. 3번은 javascript와 python 버젼 중 하나를 선택해야 한다. 요즘 javascript를 연습하는 김에, 그리고 회원가입 입력값 관련 validation은 프론트 단에서 처리하는 것이 낫다고 생각하여 이 작업은 javascript로 하기로 결정.

그렇다면 1번과 3번 중 하나를 선택해야 했다.

1번 코드

const localList = require('./lang.json').words;
const baseList = require('badwords-list').array;

class Filter {

  /**
   * Filter constructor.
   * @constructor
   * @param {object} options - Filter instance options
   * @param {boolean} options.emptyList - Instantiate filter with no blacklist
   * @param {array} options.list - Instantiate filter with custom list
   * @param {string} options.placeHolder - Character used to replace profane words.
   * @param {string} options.regex - Regular expression used to sanitize words before comparing them to blacklist.
   * @param {string} options.replaceRegex - Regular expression used to replace profane words with placeHolder.
   * @param {string} options.splitRegex - Regular expression used to split a string into words.
   */
  constructor(options = {}) {
    Object.assign(this, {
      list: options.emptyList && [] || Array.prototype.concat.apply(localList, [baseList, options.list || []]),
      exclude: options.exclude || [],
      splitRegex: options.splitRegex || /\b/,
      placeHolder: options.placeHolder || '*',
      regex: options.regex || /[^a-zA-Z0-9|\$|\@]|\^/g,
      replaceRegex: options.replaceRegex || /\w/g
    })
  }

  /**
   * Determine if a string contains profane language.
   * @param {string} string - String to evaluate for profanity.
   */
  isProfane(string) {
    return this.list
      .filter((word) => {
        const wordExp = new RegExp(`\\b${word.replace(/(\W)/g, '\\$1')}\\b`, 'gi');
        return !this.exclude.includes(word.toLowerCase()) && wordExp.test(string);
      })
      .length > 0 || false;
  }

아직 es6 자바스크립트가 잘 읽히지 않는 내게.. 1번 코드는 심플하지 않다.

3번 코드

/*
 * wordfilter
 * https://github.com/dariusk/wordfilter
 *
 * Copyright (c) 2013 Darius Kazemi
 * Licensed under the MIT license.
 */

'use strict';

var blacklist, regex;

function rebuild() {
  regex = new RegExp(blacklist.join('|'), 'i');
}

blacklist = require('./badwords.json');
rebuild();

module.exports = {
  blacklisted: function(string) {
    return !!blacklist.length && regex.test(string);
  },
  addWords: function(array) {
    blacklist = blacklist.concat(array);
    rebuild();
  },
  removeWord: function(word) {
    var index = blacklist.indexOf(word);
    if (index > -1) {
      blacklist.splice(index, 1);
      rebuild();
    }
  },
  clearList: function() {
    blacklist = [];
    rebuild();
  },
};

누가 봐도 1번 보단 심플한 3번 코드를 사용하기로 결정했다.

regex = new RegExp(blacklist.join('|'), 'i');

join()은 배열의 모든 요소를 연결해 하나의 문자열로 만든다. 참고 : https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Array/join join의 결과값이 어떻게 나오는 지 확인하기 위해 codesandbox 에서 테스트를 해보았다.

// badwords.json
["weport", "admin"]
$.getJSON("src/badwords.json", function() {})
  .done(function(response) {
    var regex = new RegExp(response.join('|'), 'i');
    console.log(regex)
  })

// /weport|admin/i

위 결과값으로 만들어진 정규표현식에 test() 메소드로 badword인지 아닌지 여부를 확인한다.

function test(blackList, regex, string) {
  console.log(!!blackList.length && regex.test(string));
}

$.getJSON("src/badwords.json", function() {})
  .done(function(response) {
    var regex = new RegExp(response.join("|"), "i");
    test(response, regex, "admin");
  })
// true

// 만약 string에 다른 값을 주면
// test(response, regex, "other value");
// false