Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Profanity filter changes #665

Open
wants to merge 6 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ClientCore/CnCNet5/NameValidator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public static class NameValidator
/// what is wrong with the name.</returns>
public static string IsNameValid(string name)
{
var profanityFilter = new ProfanityFilter();
var profanityFilter = new ProfanityFilter(new[] { "admin" });

if (string.IsNullOrEmpty(name))
return "Please enter a name.".L10N("Client:ClientCore:EnterAName");
Expand Down
129 changes: 68 additions & 61 deletions ClientCore/ProfanityFilter.cs
Original file line number Diff line number Diff line change
@@ -1,90 +1,97 @@
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Linq;
using System.Text;
using System.Diagnostics;

namespace ClientCore
{
public class ProfanityFilter
{
public IList<string> CensoredWords { get; private set; }
private const char CENSOR_CHAR = '*';
private readonly Regex _combinedRegex;

/// <summary>
/// Creates a new profanity filter with a default set of censored words.
/// </summary>
public ProfanityFilter()
{
CensoredWords = new List<string>()
/// <param name="extraWords">Any extra words to be considered profane.</param>
///
public ProfanityFilter(IEnumerable<string> extraWords = null)
{
var defaultWords = new[]
{
"cunt*",
"*nigg*",
"paki*",
"shit",
"fuck*",
"admin*",
"allahu*",
"akbar",
"twat",
"cock",
"pussy",
"hitler*",
"anal"
"fuck", "shit", "cunt", "nigger", "nigga", "niggr", "cock",
"hitler", "pussy", "akbar", "allahu", "paki", "twat"
};
}
var allWords = defaultWords.Concat(extraWords ?? Enumerable.Empty<string>());

public ProfanityFilter(IEnumerable<string> censoredWords)
{
if (censoredWords == null)
throw new ArgumentNullException("censoredWords");
CensoredWords = new List<string>(censoredWords);
//for each bad word
// for each letter in the bad word,
// match l33tspeak variations (ex: sh!t)
// match repeating letters (ex: sshhhhi!iiit)
// ignore hyphens/underscores/whitespace after the letter (ex: s_h_!_t)
// join into one big, ugly pattern

var patterns = allWords
.Select(word => string.Join(
@"[-_\s]*", //ignore hyphens/underscores/whitespace after the letter (ex: s_h !_t)
word.Select(c =>
{
switch (char.ToLower(c))
{
case 'a': return "[aA4@]+"; //in between [] are leetspeak variations.
case 'b': return "[bB8]+"; //and a + matches repeating letters
case 'e': return "[eE3]+";
case 'g': return "[gG69]+";
case 'i': return "[iI1!]+";
case 'o': return "[oO0]+";
case 's': return "[sS5$]+";
case 't': return "[tT7]+";
case 'z': return "[zZ2]+";
default: return $"[{char.ToLower(c)}{char.ToUpper(c)}]+";
}
})
));

_combinedRegex = new Regex(
$@"(?i)((?:{string.Join("|", patterns)}))",
RegexOptions.Compiled
);
}

/// <summary>
/// Checks if the text contains profanities.
/// </summary>
/// <param name="text">The text to be checked for profanities.</param>
///
public bool IsOffensive(string text)
{
string censoredText = text;
foreach (string censoredWord in CensoredWords)
{
string regularExpression = ToRegexPattern(censoredWord);
censoredText = Regex.Replace(censoredText, regularExpression, "",
RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
if (string.IsNullOrWhiteSpace(text))
return false;

if(string.IsNullOrEmpty(censoredText))
return true;
}
return false;
return _combinedRegex.IsMatch(text);
}

public string CensorText(string text)
/// <summary>
/// Censors profane words with asterisks in the provided text.
/// </summary>
/// <param name="text">The text to be censored.</param>
/// <param name="respectSetting">Whether or not to abide by the user's chosen setting for censoring profanity.</param>
///
public string CensorText(string text, bool respectSetting)
{
if (text == null)
throw new ArgumentNullException("text");
string censoredText = text;
foreach (string censoredWord in CensoredWords)
if (respectSetting && !UserINISettings.Instance.FilterProfanity)
{
string regularExpression = ToRegexPattern(censoredWord);
censoredText = Regex.Replace(censoredText, regularExpression, StarCensoredMatch,
RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
return text;
}
return censoredText;
}

private static string StarCensoredMatch(Match m)
{
string word = m.Captures[0].Value;
return new string('*', word.Length);
}
if (string.IsNullOrWhiteSpace(text))
return text;

private string ToRegexPattern(string wildcardSearch)
{
string regexPattern = Regex.Escape(wildcardSearch);
regexPattern = regexPattern.Replace(@"\*", ".*?");
regexPattern = regexPattern.Replace(@"\?", ".");
if (regexPattern.StartsWith(".*?"))
{
regexPattern = regexPattern.Substring(3);
regexPattern = @"(^\b)*?" + regexPattern;
}
regexPattern = @"\b" + regexPattern + @"\b";
return regexPattern;
return _combinedRegex.Replace(text, match =>
new string(CENSOR_CHAR, match.Groups[1].Length)
);
}
}
}
}
2 changes: 2 additions & 0 deletions ClientCore/Settings/UserINISettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ protected UserINISettings(IniFile iniFile)
AutomaticCnCNetLogin = new BoolSetting(iniFile, MULTIPLAYER, "AutomaticCnCNetLogin", false);
DiscordIntegration = new BoolSetting(iniFile, MULTIPLAYER, "DiscordIntegration", true);
AllowGameInvitesFromFriendsOnly = new BoolSetting(iniFile, MULTIPLAYER, "AllowGameInvitesFromFriendsOnly", false);
FilterProfanity = new BoolSetting(iniFile, MULTIPLAYER, "FilterProfanity", true);
NotifyOnUserListChange = new BoolSetting(iniFile, MULTIPLAYER, "NotifyOnUserListChange", true);
DisablePrivateMessagePopups = new BoolSetting(iniFile, MULTIPLAYER, "DisablePrivateMessagePopups", false);
AllowPrivateMessagesFromState = new IntSetting(iniFile, MULTIPLAYER, "AllowPrivateMessagesFromState", (int)AllowPrivateMessagesFromEnum.All);
Expand Down Expand Up @@ -195,6 +196,7 @@ protected UserINISettings(IniFile iniFile)
public BoolSetting AutomaticCnCNetLogin { get; private set; }
public BoolSetting DiscordIntegration { get; private set; }
public BoolSetting AllowGameInvitesFromFriendsOnly { get; private set; }
public BoolSetting FilterProfanity { get; private set; }

public BoolSetting NotifyOnUserListChange { get; private set; }

Expand Down
Loading
Loading