diff --git a/MCGalaxy/Chat/ProfanityFilter.cs b/MCGalaxy/Chat/ProfanityFilter.cs index cb216b61c..4e1863c02 100644 --- a/MCGalaxy/Chat/ProfanityFilter.cs +++ b/MCGalaxy/Chat/ProfanityFilter.cs @@ -24,7 +24,8 @@ using MCGalaxy.Util; namespace MCGalaxy { public static class ProfanityFilter { static string[] reduceKeys, reduceValues; - static List filters; + static List goodWords; + static List badWords; static bool hookedFilter; public static void Init() { @@ -37,28 +38,31 @@ namespace MCGalaxy { string[] words = text.SplitSpaces(); string[] reduced = Reduce(text).SplitSpaces(); - // Loop through each reduced word, looking for a bad word - for (int i = 0; i < reduced.Length; i++) - { - bool isFiltered = false; - foreach (string filter in filters) - { - if (reduced[i].Contains(filter)) { - isFiltered = true; break; + for (int i = 0; i < reduced.Length; i++) { + + foreach (string goodWord in goodWords) { + // If the word is a good word, skip bad word check + if (Colors.Strip(words[i].ToLower()) == goodWord) { + goto nextWord; + } + } + foreach (string badWord in badWords) { + if (reduced[i].Contains(badWord)) { + // If a bad word is found anywhere in the word, replace the word + words[i] = Censor(Colors.Strip(words[i]).Length); + goto nextWord; } } - if (!isFiltered) continue; - - // If a bad word is found anywhere in the word, replace the word - words[i] = Replace(words[i]); - } + // This is more readable than the previous implementation. Don't @ me + nextWord:; + } return String.Join(" ", words); } - static string Replace(string word) { + static string Censor(int badWordLength) { string replacement = Server.Config.ProfanityReplacement; // for * repeat to **** - return replacement.Length == 1 ? new string(replacement[0], word.Length) : replacement; + return replacement.Length == 1 ? new string(replacement[0], badWordLength) : replacement; } static void InitReduceTable() { @@ -70,23 +74,27 @@ namespace MCGalaxy { } static void LoadBadWords() { - TextFile filterFile = TextFile.Files["Profanity filter"]; - filterFile.EnsureExists(); + // Duplicated literal const values? tsk x1000 + TextFile goodWordsFile = TextFile.Files["Profanity filter exceptions"]; + TextFile badWordsFile = TextFile.Files["Profanity filter"]; + goodWordsFile.EnsureExists(); + badWordsFile.EnsureExists(); if (!hookedFilter) { hookedFilter = true; - filterFile.OnTextChanged += LoadBadWords; + badWordsFile.OnTextChanged += LoadBadWords; } - string[] lines = filterFile.GetText(); - filters = new List(); + goodWords = goodWordsFile.GetTextWithoutComments(); + badWords = badWordsFile.GetTextWithoutComments(); + + // Convert all goodwords to lowercase to make later comparisons simpler + for (int i = 0; i < goodWords.Count; i++) { + goodWords[i] = goodWords[i].ToLower(); + } // Run the badwords through the reducer to ensure things like Ls become Is and everything is lowercase - foreach (string line in lines) - { - if (line.StartsWith("#") || line.Trim().Length == 0) continue; - - string word = Reduce(line); - filters.Add(word); + for (int i = 0; i < badWords.Count; i++) { + badWords[i] = Reduce(badWords[i]); } } diff --git a/MCGalaxy/Server/Paths.cs b/MCGalaxy/Server/Paths.cs index 036774e2c..a0cc48d75 100644 --- a/MCGalaxy/Server/Paths.cs +++ b/MCGalaxy/Server/Paths.cs @@ -27,6 +27,7 @@ namespace MCGalaxy public const string TempBansFile = "text/tempbans.txt"; public const string CustomTokensFile = "text/custom$s.txt"; public const string BadWordsFile = "text/badwords.txt"; + public const string BadWordsExceptionsFile = "text/badwords_exceptions.txt"; public const string EatMessagesFile = "text/eatmessages.txt"; public const string RulesFile = "text/rules.txt"; public const string OprulesFile = "text/oprules.txt"; diff --git a/MCGalaxy/util/IO/TextFile.cs b/MCGalaxy/util/IO/TextFile.cs index 97315a014..8c69bd513 100644 --- a/MCGalaxy/util/IO/TextFile.cs +++ b/MCGalaxy/util/IO/TextFile.cs @@ -51,6 +51,19 @@ namespace MCGalaxy.Util public string[] GetText() { return File.ReadAllLines(Filename); } + /// + /// Returns all text lines in the file that do not begin with # and are not empty. + /// + /// + public List GetTextWithoutComments() { + string[] lines = GetText(); + List text = new List(); + foreach (string line in lines) { + if (line.StartsWith("#") || line.Trim().Length == 0) continue; + text.Add(line); + } + return text; + } public void SetText(string[] text) { File.WriteAllLines(Filename, text); @@ -75,6 +88,12 @@ namespace MCGalaxy.Util { "Profanity filter", new TextFile(Paths.BadWordsFile, "# This file is a list of words to remove via the profanity filter", "# Each word to remove must be on an individual line") }, + { "Profanity filter exceptions", new TextFile(Paths.BadWordsExceptionsFile, + "# This file is a list of words that the profanity filter will not filter,", + "# even when part of the word has been added to "+Paths.BadWordsFile, + "# This allows mitigation of the \"Scunthorpe problem\" on a case-by-case basis.", + "# For instance, one may want to block the word \"Thor\", but allow the word \"Scunthorpe\".", + "# Each word to allow must be on an individual line") }, { "Announcements", new TextFile(Paths.AnnouncementsFile, null) }, { "Joker", new TextFile(Paths.JokerFile, null) }, { "8ball", new TextFile(Paths.EightBallFile,