Converted the base CurseFilter to actionscript.
git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@4474 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
@@ -0,0 +1,218 @@
|
||||
//
|
||||
// $Id$
|
||||
|
||||
package com.threerings.crowd.chat.client {
|
||||
|
||||
import com.threerings.util.Name;
|
||||
import com.threerings.util.StringUtil;
|
||||
|
||||
/**
|
||||
* A chat filter that can filter out curse words from user chat.
|
||||
*/
|
||||
public /*abstract*/ class CurseFilter implements ChatFilter
|
||||
{
|
||||
/** Filtration constants. */
|
||||
public static const DROP :int = 0;
|
||||
public static const COMIC :int = 1;
|
||||
public static const VERNACULAR :int = 2;
|
||||
public static const UNFILTERED :int = 3;
|
||||
|
||||
/**
|
||||
* Creates a curse filter. The curse words should be a string in the
|
||||
* following format:
|
||||
*
|
||||
* <pre>
|
||||
* *penis*=John_Thomas shit*=barnacle muff=britches
|
||||
* </pre>
|
||||
*
|
||||
* The key/value pairs are separated by spaces, * matches word characters
|
||||
* and the value after the = is the string into which to convert the text
|
||||
* when converting to the vernacular. Underscores in the target string will
|
||||
* be turned into spaces.
|
||||
*
|
||||
* <p> And stopWords should be in the following format:
|
||||
*
|
||||
* <pre>
|
||||
* *faggot* rape rapes raped raping
|
||||
* </pre>
|
||||
*
|
||||
* Words are separated by spaces and * matches any other word characters.
|
||||
*/
|
||||
public function CurseFilter (curseWords :String, stopWords :String)
|
||||
{
|
||||
configureCurseWords(curseWords);
|
||||
configureStopWords(stopWords);
|
||||
}
|
||||
|
||||
/**
|
||||
* The client will need to provide a way to look up our current chat filter
|
||||
* mode.
|
||||
*/
|
||||
public function getFilterMode () :int
|
||||
{
|
||||
throw new Error("abstract");
|
||||
}
|
||||
|
||||
// from interface ChatFilter
|
||||
public function filter (
|
||||
msg :String, otherUser :Name, outgoing :Boolean) :String
|
||||
{
|
||||
// first, check against the drop-always list
|
||||
if (_stopExp.test(msg)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// then see what kind of curse filtering the user has configured
|
||||
var level :int = getFilterMode();
|
||||
if (level == UNFILTERED) {
|
||||
return msg;
|
||||
}
|
||||
|
||||
var numStops :int = _curseExps.length;
|
||||
for (var ii :int = 0; ii < numStops; ii++) {
|
||||
var regexp :RegExp = (_curseExps[ii] as RegExp);
|
||||
regexp.lastIndex = 0; // reset the matcher
|
||||
|
||||
var result :Object;
|
||||
var replacement :String;
|
||||
while (null != (result = regexp.exec(msg))) {
|
||||
switch (level) {
|
||||
case DROP:
|
||||
return null;
|
||||
|
||||
case COMIC: default:
|
||||
replacement = String(_replacements[ii]).replace(
|
||||
/\ /g, comicChars(int(_comicLength[ii])));
|
||||
break;
|
||||
|
||||
case VERNACULAR:
|
||||
var vern :String = String(_vernacular[ii]);
|
||||
var firstChar :String = String(result[2]).charAt(0);
|
||||
if (StringUtil.isUpperCase(String(result[2]))) {
|
||||
vern = vern.charAt(0).toUpperCase() + vern.substring(1);
|
||||
}
|
||||
replacement = String(_replacements[ii]).replace(
|
||||
/\ /g, vern);
|
||||
break;
|
||||
}
|
||||
replacement = replacement.replace("$1", String(result[1]));
|
||||
replacement = replacement.replace("$3", String(result[3]));
|
||||
|
||||
// do the replacement. Note the jimmying of lastIndex:
|
||||
// abjectscript does not make this smooth!
|
||||
var matchLength :int = String(result[0]).length;
|
||||
var matchIndex :int = int(result.index);
|
||||
msg = msg.substring(0, matchIndex) + replacement +
|
||||
msg.substring(matchIndex + matchLength);
|
||||
regexp.lastIndex += (replacement.length - matchLength);
|
||||
}
|
||||
}
|
||||
|
||||
return msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the curse word portion of our filtering.
|
||||
*/
|
||||
protected function configureCurseWords (curseWords :String) :void
|
||||
{
|
||||
var tokens :Array = curseWords.split(" ");
|
||||
for each (var token :String in tokens) {
|
||||
token = StringUtil.trim(token);
|
||||
if (token == "") {
|
||||
continue;
|
||||
}
|
||||
var bits :Array = token.split("=");
|
||||
if (bits.length != 2) {
|
||||
Log.getLog(this).warning("Something looks wrong with " +
|
||||
"your cursewords (" + token + "), skipping.");
|
||||
continue;
|
||||
}
|
||||
|
||||
var curse :String = String(bits[0]);
|
||||
var s :String = "";
|
||||
var p :String = "";
|
||||
if (curse.charAt(0) == "*") {
|
||||
curse = curse.substring(1);
|
||||
p += "([a-zA-Z]*)";
|
||||
s += "$1";
|
||||
} else {
|
||||
p += "()";
|
||||
}
|
||||
s += " ";
|
||||
p += " ";
|
||||
if (curse.charAt(curse.length - 1) == "*") {
|
||||
curse = curse.substring(0, curse.length - 1);
|
||||
p += "([a-zA-Z]*)";
|
||||
s += "$3";
|
||||
}
|
||||
|
||||
var regexp :RegExp = new RegExp(
|
||||
"\\b" + p.replace(" ", "(" + curse + ")") + "\\b", "gi");
|
||||
_curseExps.push(regexp);
|
||||
_replacements.push(s);
|
||||
|
||||
var slang :String = StringUtil.trim(String(bits[1]));
|
||||
slang = slang.replace(/_/g, " ");
|
||||
_vernacular.push(slang);
|
||||
_comicLength.push(curse.length);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the words that will stop.
|
||||
*/
|
||||
protected function configureStopWords (stopWords :String) :void
|
||||
{
|
||||
var tokens :Array = stopWords.split(" ");
|
||||
var pattern :String = "";
|
||||
for each (var token :String in tokens) {
|
||||
token = StringUtil.trim(token);
|
||||
if (token == "") {
|
||||
continue;
|
||||
}
|
||||
if ("" == pattern) {
|
||||
pattern += ".*(";
|
||||
} else {
|
||||
pattern += "|";
|
||||
}
|
||||
pattern += "\\b" +
|
||||
token.replace(/\*/g, "[A-Za-z]*") + "\\b";
|
||||
}
|
||||
pattern += ").*";
|
||||
|
||||
_stopExp = new RegExp(pattern, "i");
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a comicy replacement of the specified length;
|
||||
*/
|
||||
protected function comicChars (length :int) :String
|
||||
{
|
||||
var str :String = "";
|
||||
while (length-- > 0) {
|
||||
str += COMIC_CHARS.charAt(int(Math.random() * COMIC_CHARS.length));
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
/** A regexp that will always cause a message to be dropped if it
|
||||
* matches. */
|
||||
protected var _stopExp :RegExp;
|
||||
|
||||
/** Regexps for each curseword. */
|
||||
protected var _curseExps :Array = [];
|
||||
|
||||
/** Length of comic-y replacements for each curseword. */
|
||||
protected var _comicLength :Array = [];
|
||||
|
||||
/** Replacements. */
|
||||
protected var _replacements :Array = [];
|
||||
|
||||
/** Replacements for each curseword "in the vernacular". */
|
||||
protected var _vernacular :Array = [];
|
||||
|
||||
/** Comic replacement characters. */
|
||||
protected const COMIC_CHARS :String = "!@#%&*";
|
||||
}
|
||||
}
|
||||
@@ -45,6 +45,26 @@ public class StringUtil
|
||||
return (str.lastIndexOf(substr, 0) == 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true iff the first character is a lower-case character.
|
||||
*/
|
||||
public static function isLowerCase (str :String) :Boolean
|
||||
{
|
||||
var firstChar :String = str.charAt(0);
|
||||
return (firstChar.toUpperCase() != firstChar) &&
|
||||
(firstChar.toLowerCase() == firstChar);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true iff the first character is an upper-case character.
|
||||
*/
|
||||
public static function isUpperCase (str :String) :Boolean
|
||||
{
|
||||
var firstChar :String = str.charAt(0);
|
||||
return (firstChar.toUpperCase() == firstChar) &&
|
||||
(firstChar.toLowerCase() != firstChar);
|
||||
}
|
||||
|
||||
/**
|
||||
* Append 0 or more copies of the padChar String to the input String
|
||||
* until it is at least the specified length.
|
||||
|
||||
Reference in New Issue
Block a user