diff --git a/Roseau-Server/src/main/java/org/alexdev/roseau/game/GameVariables.java b/Roseau-Server/src/main/java/org/alexdev/roseau/game/GameVariables.java index 4ef5d33..b3951e9 100644 --- a/Roseau-Server/src/main/java/org/alexdev/roseau/game/GameVariables.java +++ b/Roseau-Server/src/main/java/org/alexdev/roseau/game/GameVariables.java @@ -18,6 +18,8 @@ public class GameVariables { public static String MESSENGER_GREETING; public static boolean DEBUG_ENABLE = false; public static int AFK_ROOM_KICK; + public static String CHAT_FILTER_WORDS; + public static String CHAT_FILTER_REPLACEMENT; public static void setVariables() { @@ -33,5 +35,8 @@ public static void setVariables() { TALK_DISTANCE = Util.getHabboConfig().get("Player", "talking.lookat.distance", Integer.class); USER_DEFAULT_CREDITS = Util.getHabboConfig().get("Register", "user.default.credits", int.class); DEBUG_ENABLE = Util.getHabboConfig().get("Debug", "debug.enable", Boolean.class); + CHAT_FILTER_WORDS = Util.getHabboConfig().get("Chat", "filter.words", String.class); + CHAT_FILTER_REPLACEMENT = Util.getHabboConfig().get("Chat", "filter.replacement", String.class); + } } diff --git a/Roseau-Server/src/main/java/org/alexdev/roseau/game/room/entity/ChatUtility.java b/Roseau-Server/src/main/java/org/alexdev/roseau/game/room/entity/ChatUtility.java index be65526..d3bc2ea 100644 --- a/Roseau-Server/src/main/java/org/alexdev/roseau/game/room/entity/ChatUtility.java +++ b/Roseau-Server/src/main/java/org/alexdev/roseau/game/room/entity/ChatUtility.java @@ -3,6 +3,7 @@ import java.util.Hashtable; import java.util.Map; import java.util.Random; +import org.alexdev.roseau.game.GameVariables; /* * Written by office.boy (Mike) and Nillus from Blunk v5 @@ -53,7 +54,27 @@ public static void setupEmotes(boolean classicOnly) public static String[] filterWords(String[] words) { - // TODO: crappy word filter for swearwords + String filterList = GameVariables.CHAT_FILTER_WORDS; + String replacement = GameVariables.CHAT_FILTER_REPLACEMENT; + + if (filterList == null || filterList.isEmpty() || replacement == null || replacement.isEmpty()) { + return words; + } + + String[] bannedWords = filterList.split(","); + + for (int i = 0; i < words.length; i++) { + // try to prevent bypasses like f.uck + String normalized = words[i].replaceAll("[^a-zA-Z]", ""); + + for (String banned : bannedWords) { + if (normalized.equalsIgnoreCase(banned.trim())) { + words[i] = replacement; + break; + } + } + } + return words; } diff --git a/Roseau-Server/src/main/java/org/alexdev/roseau/game/room/entity/ChatUtilityTest.java b/Roseau-Server/src/main/java/org/alexdev/roseau/game/room/entity/ChatUtilityTest.java new file mode 100644 index 0000000..02d48ad --- /dev/null +++ b/Roseau-Server/src/main/java/org/alexdev/roseau/game/room/entity/ChatUtilityTest.java @@ -0,0 +1,162 @@ +package org.alexdev.roseau.game.room.entity; + +import org.alexdev.roseau.game.GameVariables; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class ChatUtilityTest { + + @BeforeEach + void setUp() { + GameVariables.CHAT_FILTER_WORDS = "fuck,sex,damn"; + GameVariables.CHAT_FILTER_REPLACEMENT = "bobba"; + } + + @Test + void noFilterConfigured_returnsWordsUnchanged() { + GameVariables.CHAT_FILTER_WORDS = null; + + String[] input = {"hello", "world"}; + String[] result = ChatUtility.filterWords(input); + + assertArrayEquals(new String[]{"hello", "world"}, result); + } + + @Test + void emptyFilterList_returnsWordsUnchanged() { + GameVariables.CHAT_FILTER_WORDS = ""; + + String[] input = {"hello", "world"}; + String[] result = ChatUtility.filterWords(input); + + assertArrayEquals(new String[]{"hello", "world"}, result); + } + + @Test + void noReplacementConfigured_returnsWordsUnchanged() { + GameVariables.CHAT_FILTER_REPLACEMENT = null; + + String[] input = {"fuck", "world"}; + String[] result = ChatUtility.filterWords(input); + + assertArrayEquals(new String[]{"fuck", "world"}, result); + } + + @Test + void exactMatch_isReplaced() { + String[] input = {"fuck", "hello"}; + String[] result = ChatUtility.filterWords(input); + + assertArrayEquals(new String[]{"bobba", "hello"}, result); + } + + @Test + void caseInsensitiveMatch_isReplaced() { + String[] input = {"FUCK", "Fuck"}; + String[] result = ChatUtility.filterWords(input); + + assertArrayEquals(new String[]{"bobba", "bobba"}, result); + } + + @Test + void dots_areStrippedBeforeMatch() { + String[] input = {"f.uck"}; + String[] result = ChatUtility.filterWords(input); + + assertArrayEquals(new String[]{"bobba"}, result); + } + + @Test + void dashes_areStrippedBeforeMatch() { + String[] input = {"f-u-c-k"}; + String[] result = ChatUtility.filterWords(input); + + assertArrayEquals(new String[]{"bobba"}, result); + } + + @Test + void underscores_areStrippedBeforeMatch() { + String[] input = {"f_u_c_k"}; + String[] result = ChatUtility.filterWords(input); + + assertArrayEquals(new String[]{"bobba"}, result); + } + + @Test + void numbers_areStrippedBeforeMatch() { + String[] input = {"f0uck"}; + String[] result = ChatUtility.filterWords(input); + + assertArrayEquals(new String[]{"bobba"}, result); + } + + @Test + void mixedSeparators_areStrippedBeforeMatch() { + String[] input = {"f...u...c...k"}; + String[] result = ChatUtility.filterWords(input); + + assertArrayEquals(new String[]{"bobba"}, result); + } + + @Test + void multipleSeparatorsTogether_areStrippedBeforeMatch() { + String[] input = {"f.u-c_k"}; + String[] result = ChatUtility.filterWords(input); + + assertArrayEquals(new String[]{"bobba"}, result); + } + + @Test + void nonBannedWord_isUnchanged() { + String[] input = {"hello"}; + String[] result = ChatUtility.filterWords(input); + + assertArrayEquals(new String[]{"hello"}, result); + } + + @Test + void multipleWords_someFiltered() { + String[] input = {"hello", "fuck", "world", "sex"}; + String[] result = ChatUtility.filterWords(input); + + assertArrayEquals(new String[]{"hello", "bobba", "world", "bobba"}, result); + } + + @Test + void multipleBannedWords_allReplaced() { + GameVariables.CHAT_FILTER_WORDS = "fuck,sex,damn"; + + String[] input = {"fuck", "sex", "damn"}; + String[] result = ChatUtility.filterWords(input); + + assertArrayEquals(new String[]{"bobba", "bobba", "bobba"}, result); + } + + @Test + void whitespaceInFilterList_isTrimmed() { + GameVariables.CHAT_FILTER_WORDS = " fuck , sex "; + + String[] input = {"fuck", "sex"}; + String[] result = ChatUtility.filterWords(input); + + assertArrayEquals(new String[]{"bobba", "bobba"}, result); + } + + @Test + void singleBannedWordInMultiWordSentence() { + String[] input = {"this", "is", "a", "fuck", "sentence"}; + String[] result = ChatUtility.filterWords(input); + + assertArrayEquals(new String[]{"this", "is", "a", "bobba", "sentence"}, result); + } + + @Test + void asterisks_areStrippedBeforeMatch() { + String[] input = {"f*ck"}; + String[] result = ChatUtility.filterWords(input); + + assertArrayEquals(new String[]{"bobba"}, result); + } +} diff --git a/Roseau-Server/src/main/java/org/alexdev/roseau/messages/incoming/TALK.java b/Roseau-Server/src/main/java/org/alexdev/roseau/messages/incoming/TALK.java index b85793e..3574516 100644 --- a/Roseau-Server/src/main/java/org/alexdev/roseau/messages/incoming/TALK.java +++ b/Roseau-Server/src/main/java/org/alexdev/roseau/messages/incoming/TALK.java @@ -28,6 +28,7 @@ public void handle(Player player, ClientMessage reader) { } String talkMessage = Util.filterInput(reader.getMessageBody()); + talkMessage = String.join(" ", ChatUtility.filterWords(talkMessage.split(" "))); if (!reader.getHeader().equals("CHAT") && !reader.getHeader().equals("SHOUT") && !reader.getHeader().equals("WHISPER")) { return; diff --git a/roseau.properties b/roseau.properties index 5d1bd25..f04b5e5 100644 --- a/roseau.properties +++ b/roseau.properties @@ -19,3 +19,7 @@ log.errors=true log.output=true log.connections=true log.packets=true + +[Chat] +filter.words=fuck,sex,porn +filter.replacement=bobba \ No newline at end of file