From 285e4767065045aa3054adce676a01acb8bcb82e Mon Sep 17 00:00:00 2001 From: SyntaxDevTeam Date: Fri, 27 Mar 2026 08:49:25 +0100 Subject: [PATCH 1/3] Add optional remote banned-words synchronization --- .../syntaxdevteam/cleanerx/base/WordFilter.kt | 20 ++- .../common/BannedWordsSynchronizer.kt | 139 +++++++++++++++--- CleanerX-Paper/src/main/resources/config.yml | 10 +- .../cleanerx/base/WordFilterTest.kt | 8 + .../common/BannedWordsSynchronizerTest.kt | 39 +++++ .../syntaxdevteam/cleanerx/base/WordFilter.kt | 22 ++- .../common/BannedWordsSynchronizer.kt | 124 +++++++++++++--- CleanerX-Spigot/src/main/resources/config.yml | 10 +- 8 files changed, 312 insertions(+), 60 deletions(-) create mode 100644 CleanerX-Paper/src/test/kotlin/pl/syntaxdevteam/cleanerx/common/BannedWordsSynchronizerTest.kt diff --git a/CleanerX-Paper/src/main/kotlin/pl/syntaxdevteam/cleanerx/base/WordFilter.kt b/CleanerX-Paper/src/main/kotlin/pl/syntaxdevteam/cleanerx/base/WordFilter.kt index 39d8787..378d5f1 100644 --- a/CleanerX-Paper/src/main/kotlin/pl/syntaxdevteam/cleanerx/base/WordFilter.kt +++ b/CleanerX-Paper/src/main/kotlin/pl/syntaxdevteam/cleanerx/base/WordFilter.kt @@ -52,7 +52,7 @@ class WordFilter(private val plugin: CleanerX) { whitelistWords = config.getStringList("whitelistWords") .map { it.lowercase(Locale.ROOT) } .toMutableList() - whitelistPatterns = whitelistWords.mapNotNull { toFilterPattern(it) } + whitelistPatterns = whitelistWords.mapNotNull { toFilterPattern(it, allowSuffix = false) } } /** @@ -149,7 +149,7 @@ class WordFilter(private val plugin: CleanerX) { val lowerWord = word.lowercase(Locale.ROOT) if (!bannedWords.contains(lowerWord)) { bannedWords.add(lowerWord) - bannedPatterns = bannedWords.mapNotNull { toFilterPattern(it) } + bannedPatterns = bannedWords.mapNotNull { toFilterPattern(it, allowSuffix = true) } saveBannedWords() plugin.logger.info("Word $word added to banned words list.") } @@ -182,7 +182,7 @@ class WordFilter(private val plugin: CleanerX) { val lowerWord = word.lowercase(Locale.ROOT) if (!whitelistWords.contains(lowerWord)) { whitelistWords.add(lowerWord) - whitelistPatterns = whitelistWords.mapNotNull { toFilterPattern(it) } + whitelistPatterns = whitelistWords.mapNotNull { toFilterPattern(it, allowSuffix = false) } saveWhitelistWords() plugin.logger.info("Word $word added to whitelist.") } @@ -224,7 +224,7 @@ class WordFilter(private val plugin: CleanerX) { bannedWords = words .map { it.lowercase(Locale.ROOT) } .toMutableList() - bannedPatterns = bannedWords.mapNotNull { toFilterPattern(it) } + bannedPatterns = bannedWords.mapNotNull { toFilterPattern(it, allowSuffix = true) } } /** @@ -257,16 +257,16 @@ class WordFilter(private val plugin: CleanerX) { } - private fun toFilterPattern(word: String): FilterPattern? { + private fun toFilterPattern(word: String, allowSuffix: Boolean): FilterPattern? { val normalized = normalizeWord(word) if (normalized.isEmpty()) { return null } - return FilterPattern(word, normalized, buildFuzzyRegex(normalized)) + return FilterPattern(word, normalized, buildFuzzyRegex(normalized, allowSuffix)) } - private fun buildFuzzyRegex(normalizedWord: String): Regex { + private fun buildFuzzyRegex(normalizedWord: String, allowSuffix: Boolean): Regex { val builder = StringBuilder() builder.append("(? @@ -275,6 +275,11 @@ class WordFilter(private val plugin: CleanerX) { builder.append("[^a-z]*") } } + if (allowSuffix) { + builder.append("(?:[^a-z]*[a-z]{1,") + builder.append(MAX_SUFFIX_LENGTH) + builder.append("})?") + } builder.append("(?![a-z])") return Regex(builder.toString()) } @@ -401,6 +406,7 @@ class WordFilter(private val plugin: CleanerX) { } companion object { + private const val MAX_SUFFIX_LENGTH = 3 private val NON_SPACING_MARKS_REGEX = "\\p{Mn}+".toRegex() private val CHARACTER_REPLACEMENTS: Map = mapOf( '4' to "a", diff --git a/CleanerX-Paper/src/main/kotlin/pl/syntaxdevteam/cleanerx/common/BannedWordsSynchronizer.kt b/CleanerX-Paper/src/main/kotlin/pl/syntaxdevteam/cleanerx/common/BannedWordsSynchronizer.kt index 20907b2..840a8ea 100644 --- a/CleanerX-Paper/src/main/kotlin/pl/syntaxdevteam/cleanerx/common/BannedWordsSynchronizer.kt +++ b/CleanerX-Paper/src/main/kotlin/pl/syntaxdevteam/cleanerx/common/BannedWordsSynchronizer.kt @@ -5,7 +5,13 @@ import pl.syntaxdevteam.cleanerx.CleanerX import pl.syntaxdevteam.cleanerx.util.SchedulerAdapter import java.io.File import java.io.InputStreamReader +import java.io.StringReader +import java.net.URI +import java.net.http.HttpClient +import java.net.http.HttpRequest +import java.net.http.HttpResponse import java.nio.charset.StandardCharsets +import java.time.Duration import java.util.Locale /** @@ -33,41 +39,50 @@ class BannedWordsSynchronizer(private val plugin: CleanerX) { val serverConfig = YamlConfiguration.loadConfiguration(serverFile) val serverWords = serverConfig.getStringList(BANNED_WORDS_KEY).toMutableList() - val resourceWords = loadResourceWords() + val syncEnabled = plugin.config.getBoolean(REMOTE_SYNC_ENABLED_KEY, false) + val includeResourceWords = plugin.config.getBoolean(INCLUDE_RESOURCE_WORDS_KEY, true) + val timeoutMillis = plugin.config.getLong(REMOTE_SYNC_TIMEOUT_KEY, DEFAULT_REMOTE_TIMEOUT_MS).coerceAtLeast(500L) + val remoteSources = plugin.config.getStringList(REMOTE_SYNC_SOURCES_KEY).filter { it.isNotBlank() } - if (resourceWords.isEmpty()) { - plugin.logger.warning("The resource banned_words.yml file is empty or missing the bannedWords section.") - complete(onComplete, serverWords.toList()) - return@runAsync - } - - val normalizedExisting = serverWords + val mergedWords = serverWords.toMutableList() + val normalizedExisting = mergedWords .map { it.lowercase(Locale.ROOT) } .toMutableSet() - var modified = false - for (word in resourceWords) { - val normalized = word.lowercase(Locale.ROOT) - if (!normalizedExisting.contains(normalized)) { - serverWords.add(word) - normalizedExisting.add(normalized) - modified = true - } + if (includeResourceWords) { + mergeWords(mergedWords, normalizedExisting, loadResourceWords()) + } + + if (syncEnabled && remoteSources.isNotEmpty()) { + val remoteWords = loadRemoteWords(remoteSources, timeoutMillis) + mergeWords(mergedWords, normalizedExisting, remoteWords) } + val modified = mergedWords.size != serverWords.size + if (modified) { - serverConfig.set(BANNED_WORDS_KEY, serverWords) + serverConfig.set(BANNED_WORDS_KEY, mergedWords) serverConfig.save(serverFile) - plugin.logger.info("Synchronized banned_words.yml with plugin resources.") + plugin.logger.info("Synchronized banned_words.yml with configured sources.") } - complete(onComplete, serverWords.toList()) + complete(onComplete, mergedWords.toList()) } catch (exception: Exception) { plugin.logger.err("Failed to synchronize banned words: ${exception.message}") } } } + private fun mergeWords(target: MutableList, normalized: MutableSet, words: List) { + for (word in words) { + val normalizedWord = word.lowercase(Locale.ROOT) + if (!normalized.contains(normalizedWord)) { + target.add(word) + normalized.add(normalizedWord) + } + } + } + private fun loadResourceWords(): List { val resourceStream = plugin.getResource(BANNED_WORDS_FILE) if (resourceStream == null) { @@ -78,11 +93,50 @@ class BannedWordsSynchronizer(private val plugin: CleanerX) { resourceStream.use { stream -> InputStreamReader(stream, StandardCharsets.UTF_8).use { reader -> val config = YamlConfiguration.loadConfiguration(reader) - return config.getStringList(BANNED_WORDS_KEY) + return sanitizeWords(config.getStringList(BANNED_WORDS_KEY)) } } } + private fun loadRemoteWords(sources: List, timeoutMillis: Long): List { + val client = HttpClient.newBuilder() + .connectTimeout(Duration.ofMillis(timeoutMillis)) + .build() + + val words = mutableListOf() + for (source in sources) { + runCatching { + val request = HttpRequest.newBuilder(URI.create(source)) + .timeout(Duration.ofMillis(timeoutMillis)) + .header("Accept", "text/plain, text/yaml, application/yaml, application/json") + .GET() + .build() + + val response = client.send(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8)) + if (response.statusCode() in 200..299) { + words.addAll(extractWordsFromPayload(response.body())) + } else { + plugin.logger.warning("Banned words sync skipped source $source due to HTTP ${response.statusCode()}.") + } + }.onFailure { exception -> + plugin.logger.warning("Banned words sync failed for $source: ${exception.message}") + } + } + + return sanitizeWords(words) + } + + private fun extractWordsFromPayload(payload: String): List = parsePayloadToWords(payload) + + private fun sanitizeWords(words: List): List { + return words.asSequence() + .map { it.trim().lowercase(Locale.ROOT) } + .filter { it.length >= MIN_WORD_LENGTH } + .filter { CANDIDATE_WORD_REGEX.matches(it) } + .distinct() + .toList() + } + private fun complete(onComplete: ((List) -> Unit)?, result: List) { if (onComplete == null) { return @@ -92,8 +146,47 @@ class BannedWordsSynchronizer(private val plugin: CleanerX) { } } - private companion object { + companion object { private const val BANNED_WORDS_FILE = "banned_words.yml" - private const val BANNED_WORDS_KEY = "bannedWords" + internal const val BANNED_WORDS_KEY = "bannedWords" + private const val REMOTE_SYNC_ENABLED_KEY = "banned-words-sync.enabled" + private const val INCLUDE_RESOURCE_WORDS_KEY = "banned-words-sync.include-default-resource" + private const val REMOTE_SYNC_TIMEOUT_KEY = "banned-words-sync.timeout-ms" + private const val REMOTE_SYNC_SOURCES_KEY = "banned-words-sync.remote-sources" + private const val DEFAULT_REMOTE_TIMEOUT_MS = 2500L + private const val MIN_WORD_LENGTH = 3 + private val CANDIDATE_WORD_REGEX = Regex("^[\\p{L}\\p{Nd}@!$+|]+$") + + internal fun parsePayloadToWords(payload: String): List { + val trimmed = payload.trim() + if (trimmed.isEmpty()) { + return emptyList() + } + + val fromYaml = runCatching { + val config = YamlConfiguration.loadConfiguration(StringReader(trimmed)) + config.getStringList(BANNED_WORDS_KEY) + }.getOrNull().orEmpty() + + val candidates = if (fromYaml.isNotEmpty()) { + fromYaml + } else { + trimmed.lineSequence() + .map { line -> + line.substringBefore("#") + .substringBefore("//") + .trim() + } + .filter { it.isNotEmpty() } + .toList() + } + + return candidates.asSequence() + .map { it.trim().lowercase(Locale.ROOT) } + .filter { it.length >= MIN_WORD_LENGTH } + .filter { CANDIDATE_WORD_REGEX.matches(it) } + .distinct() + .toList() + } } -} \ No newline at end of file +} diff --git a/CleanerX-Paper/src/main/resources/config.yml b/CleanerX-Paper/src/main/resources/config.yml index df59f17..feef53e 100644 --- a/CleanerX-Paper/src/main/resources/config.yml +++ b/CleanerX-Paper/src/main/resources/config.yml @@ -31,4 +31,12 @@ debug: false # Should StatsCollector be enabled? Recommended setting: true stats: - enabled: true \ No newline at end of file + enabled: true + +# Optional synchronization of banned words with external sources (e.g. multilingual lists). +# Sources can be plain text (one word per line) or YAML with key: bannedWords. +banned-words-sync: + enabled: false + include-default-resource: true + timeout-ms: 2500 + remote-sources: [] diff --git a/CleanerX-Paper/src/test/kotlin/pl/syntaxdevteam/cleanerx/base/WordFilterTest.kt b/CleanerX-Paper/src/test/kotlin/pl/syntaxdevteam/cleanerx/base/WordFilterTest.kt index 1fefc49..9ccfb91 100644 --- a/CleanerX-Paper/src/test/kotlin/pl/syntaxdevteam/cleanerx/base/WordFilterTest.kt +++ b/CleanerX-Paper/src/test/kotlin/pl/syntaxdevteam/cleanerx/base/WordFilterTest.kt @@ -66,4 +66,12 @@ class WordFilterTest { val partiallyCensored = wordFilter.censorMessage("To KURWA test", fullCensorship = false) assertEquals("To KU*** test", partiallyCensored) } + + @Test + fun `wykrywa odmiane slowa ale nie lapie fragmentu w srodku innego wyrazu`() { + wordFilter.updateBannedWords(listOf("chuje")) + + assertTrue(wordFilter.containsBannedWord("nie badz chujem")) + assertFalse(wordFilter.containsBannedWord("wymachuje rekami")) + } } diff --git a/CleanerX-Paper/src/test/kotlin/pl/syntaxdevteam/cleanerx/common/BannedWordsSynchronizerTest.kt b/CleanerX-Paper/src/test/kotlin/pl/syntaxdevteam/cleanerx/common/BannedWordsSynchronizerTest.kt new file mode 100644 index 0000000..6db8bf2 --- /dev/null +++ b/CleanerX-Paper/src/test/kotlin/pl/syntaxdevteam/cleanerx/common/BannedWordsSynchronizerTest.kt @@ -0,0 +1,39 @@ +package pl.syntaxdevteam.cleanerx.common + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test + +class BannedWordsSynchronizerTest { + + @Test + fun `parsuje yaml bannedWords i filtruje niepoprawne wpisy`() { + val payload = """ + bannedWords: + - Kurwa + - chuj + - xx + - test!! + - kurwa + """.trimIndent() + + val words = BannedWordsSynchronizer.parsePayloadToWords(payload) + + assertEquals(listOf("kurwa", "chuj", "test!!"), words) + } + + @Test + fun `parsuje plain text z komentarzami`() { + val payload = """ + # english + fuck + shit // inline comment + + dwa + @@@ + """.trimIndent() + + val words = BannedWordsSynchronizer.parsePayloadToWords(payload) + + assertEquals(listOf("fuck", "shit", "dwa", "@@@"), words) + } +} diff --git a/CleanerX-Spigot/src/main/kotlin/pl/syntaxdevteam/cleanerx/base/WordFilter.kt b/CleanerX-Spigot/src/main/kotlin/pl/syntaxdevteam/cleanerx/base/WordFilter.kt index 9f7cda5..dcb8dd6 100644 --- a/CleanerX-Spigot/src/main/kotlin/pl/syntaxdevteam/cleanerx/base/WordFilter.kt +++ b/CleanerX-Spigot/src/main/kotlin/pl/syntaxdevteam/cleanerx/base/WordFilter.kt @@ -52,7 +52,7 @@ class WordFilter(private val plugin: CleanerX) { whitelistWords = config.getStringList("whitelistWords") .map { it.lowercase(Locale.ROOT) } .toMutableList() - whitelistPatterns = whitelistWords.mapNotNull { toFilterPattern(it) } + whitelistPatterns = whitelistWords.mapNotNull { toFilterPattern(it, allowSuffix = false) } } /** @@ -149,7 +149,7 @@ class WordFilter(private val plugin: CleanerX) { val lowerWord = word.lowercase(Locale.ROOT) if (!bannedWords.contains(lowerWord)) { bannedWords.add(lowerWord) - bannedPatterns = bannedWords.mapNotNull { toFilterPattern(it) } + bannedPatterns = bannedWords.mapNotNull { toFilterPattern(it, allowSuffix = true) } saveBannedWords() plugin.logger.info("Word $word added to banned words list.") } @@ -182,7 +182,7 @@ class WordFilter(private val plugin: CleanerX) { val lowerWord = word.lowercase(Locale.ROOT) if (!whitelistWords.contains(lowerWord)) { whitelistWords.add(lowerWord) - whitelistPatterns = whitelistWords.mapNotNull { toFilterPattern(it) } + whitelistPatterns = whitelistWords.mapNotNull { toFilterPattern(it, allowSuffix = false) } saveWhitelistWords() plugin.logger.info("Word $word added to whitelist.") } @@ -224,7 +224,7 @@ class WordFilter(private val plugin: CleanerX) { bannedWords = words .map { it.lowercase(Locale.ROOT) } .toMutableList() - bannedPatterns = bannedWords.mapNotNull { toFilterPattern(it) } + bannedPatterns = bannedWords.mapNotNull { toFilterPattern(it, allowSuffix = true) } } /** @@ -256,16 +256,16 @@ class WordFilter(private val plugin: CleanerX) { config.save(file) } - private fun toFilterPattern(word: String): FilterPattern? { + private fun toFilterPattern(word: String, allowSuffix: Boolean): FilterPattern? { val normalized = normalizeWord(word) if (normalized.isEmpty()) { return null } - return FilterPattern(word, normalized, buildFuzzyRegex(normalized)) + return FilterPattern(word, normalized, buildFuzzyRegex(normalized, allowSuffix)) } - private fun buildFuzzyRegex(normalizedWord: String): Regex { + private fun buildFuzzyRegex(normalizedWord: String, allowSuffix: Boolean): Regex { val builder = StringBuilder() builder.append("(? @@ -274,6 +274,11 @@ class WordFilter(private val plugin: CleanerX) { builder.append("[^a-z]*") } } + if (allowSuffix) { + builder.append("(?:[^a-z]*[a-z]{1,") + builder.append(MAX_SUFFIX_LENGTH) + builder.append("})?") + } builder.append("(?![a-z])") return Regex(builder.toString()) } @@ -400,6 +405,7 @@ class WordFilter(private val plugin: CleanerX) { } companion object { + private const val MAX_SUFFIX_LENGTH = 3 private val NON_SPACING_MARKS_REGEX = "\\p{Mn}+".toRegex() private val CHARACTER_REPLACEMENTS: Map = mapOf( '4' to "a", @@ -432,4 +438,4 @@ class WordFilter(private val plugin: CleanerX) { 'ž' to "z", ) } -} \ No newline at end of file +} diff --git a/CleanerX-Spigot/src/main/kotlin/pl/syntaxdevteam/cleanerx/common/BannedWordsSynchronizer.kt b/CleanerX-Spigot/src/main/kotlin/pl/syntaxdevteam/cleanerx/common/BannedWordsSynchronizer.kt index 741f8d7..3e4aa7d 100644 --- a/CleanerX-Spigot/src/main/kotlin/pl/syntaxdevteam/cleanerx/common/BannedWordsSynchronizer.kt +++ b/CleanerX-Spigot/src/main/kotlin/pl/syntaxdevteam/cleanerx/common/BannedWordsSynchronizer.kt @@ -5,7 +5,13 @@ import org.bukkit.configuration.file.YamlConfiguration import pl.syntaxdevteam.cleanerx.CleanerX import java.io.File import java.io.InputStreamReader +import java.io.StringReader +import java.net.URI +import java.net.http.HttpClient +import java.net.http.HttpRequest +import java.net.http.HttpResponse import java.nio.charset.StandardCharsets +import java.time.Duration import java.util.Locale /** @@ -33,41 +39,50 @@ class BannedWordsSynchronizer(private val plugin: CleanerX) { val serverConfig = YamlConfiguration.loadConfiguration(serverFile) val serverWords = serverConfig.getStringList(BANNED_WORDS_KEY).toMutableList() - val resourceWords = loadResourceWords() + val syncEnabled = plugin.config.getBoolean(REMOTE_SYNC_ENABLED_KEY, false) + val includeResourceWords = plugin.config.getBoolean(INCLUDE_RESOURCE_WORDS_KEY, true) + val timeoutMillis = plugin.config.getLong(REMOTE_SYNC_TIMEOUT_KEY, DEFAULT_REMOTE_TIMEOUT_MS).coerceAtLeast(500L) + val remoteSources = plugin.config.getStringList(REMOTE_SYNC_SOURCES_KEY).filter { it.isNotBlank() } - if (resourceWords.isEmpty()) { - plugin.logger.warning("The resource banned_words.yml file is empty or missing the bannedWords section.") - complete(onComplete, serverWords.toList()) - return@Runnable - } - - val normalizedExisting = serverWords + val mergedWords = serverWords.toMutableList() + val normalizedExisting = mergedWords .map { it.lowercase(Locale.ROOT) } .toMutableSet() - var modified = false - for (word in resourceWords) { - val normalized = word.lowercase(Locale.ROOT) - if (!normalizedExisting.contains(normalized)) { - serverWords.add(word) - normalizedExisting.add(normalized) - modified = true - } + if (includeResourceWords) { + mergeWords(mergedWords, normalizedExisting, loadResourceWords()) + } + + if (syncEnabled && remoteSources.isNotEmpty()) { + val remoteWords = loadRemoteWords(remoteSources, timeoutMillis) + mergeWords(mergedWords, normalizedExisting, remoteWords) } + val modified = mergedWords.size != serverWords.size + if (modified) { - serverConfig.set(BANNED_WORDS_KEY, serverWords) + serverConfig.set(BANNED_WORDS_KEY, mergedWords) serverConfig.save(serverFile) - plugin.logger.info("Synchronized banned_words.yml with plugin resources.") + plugin.logger.info("Synchronized banned_words.yml with configured sources.") } - complete(onComplete, serverWords.toList()) + complete(onComplete, mergedWords.toList()) } catch (exception: Exception) { plugin.logger.err("Failed to synchronize banned words: ${exception.message}") } }) } + private fun mergeWords(target: MutableList, normalized: MutableSet, words: List) { + for (word in words) { + val normalizedWord = word.lowercase(Locale.ROOT) + if (!normalized.contains(normalizedWord)) { + target.add(word) + normalized.add(normalizedWord) + } + } + } + private fun loadResourceWords(): List { val resourceStream = plugin.getResource(BANNED_WORDS_FILE) if (resourceStream == null) { @@ -78,9 +93,71 @@ class BannedWordsSynchronizer(private val plugin: CleanerX) { resourceStream.use { stream -> InputStreamReader(stream, StandardCharsets.UTF_8).use { reader -> val config = YamlConfiguration.loadConfiguration(reader) - return config.getStringList(BANNED_WORDS_KEY) + return sanitizeWords(config.getStringList(BANNED_WORDS_KEY)) + } + } + } + + private fun loadRemoteWords(sources: List, timeoutMillis: Long): List { + val client = HttpClient.newBuilder() + .connectTimeout(Duration.ofMillis(timeoutMillis)) + .build() + + val words = mutableListOf() + for (source in sources) { + runCatching { + val request = HttpRequest.newBuilder(URI.create(source)) + .timeout(Duration.ofMillis(timeoutMillis)) + .header("Accept", "text/plain, text/yaml, application/yaml, application/json") + .GET() + .build() + + val response = client.send(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8)) + if (response.statusCode() in 200..299) { + words.addAll(extractWordsFromPayload(response.body())) + } else { + plugin.logger.warning("Banned words sync skipped source $source due to HTTP ${response.statusCode()}.") + } + }.onFailure { exception -> + plugin.logger.warning("Banned words sync failed for $source: ${exception.message}") } } + + return sanitizeWords(words) + } + + private fun extractWordsFromPayload(payload: String): List { + val trimmed = payload.trim() + if (trimmed.isEmpty()) { + return emptyList() + } + + val fromYaml = runCatching { + val config = YamlConfiguration.loadConfiguration(StringReader(trimmed)) + config.getStringList(BANNED_WORDS_KEY) + }.getOrNull().orEmpty() + + if (fromYaml.isNotEmpty()) { + return fromYaml + } + + return trimmed.lineSequence() + .map { line -> + line.substringBefore("#") + .substringBefore("//") + .trim() + } + .filter { it.isNotEmpty() } + .toList() + } + + private fun sanitizeWords(words: List): List { + return words.asSequence() + .map { it.trim().lowercase(Locale.ROOT) } + .filter { it.length >= MIN_WORD_LENGTH } + .filter { CANDIDATE_WORD_REGEX.matches(it) } + .distinct() + .toList() } private fun complete(onComplete: ((List) -> Unit)?, result: List) { @@ -95,5 +172,12 @@ class BannedWordsSynchronizer(private val plugin: CleanerX) { private companion object { private const val BANNED_WORDS_FILE = "banned_words.yml" private const val BANNED_WORDS_KEY = "bannedWords" + private const val REMOTE_SYNC_ENABLED_KEY = "banned-words-sync.enabled" + private const val INCLUDE_RESOURCE_WORDS_KEY = "banned-words-sync.include-default-resource" + private const val REMOTE_SYNC_TIMEOUT_KEY = "banned-words-sync.timeout-ms" + private const val REMOTE_SYNC_SOURCES_KEY = "banned-words-sync.remote-sources" + private const val DEFAULT_REMOTE_TIMEOUT_MS = 2500L + private const val MIN_WORD_LENGTH = 3 + private val CANDIDATE_WORD_REGEX = Regex("^[\\p{L}\\p{Nd}@!$+|]+$") } } diff --git a/CleanerX-Spigot/src/main/resources/config.yml b/CleanerX-Spigot/src/main/resources/config.yml index dc65712..85cd031 100644 --- a/CleanerX-Spigot/src/main/resources/config.yml +++ b/CleanerX-Spigot/src/main/resources/config.yml @@ -27,4 +27,12 @@ debug: false # Should StatsCollector be enabled? Recommended setting: true stats: - enabled: true \ No newline at end of file + enabled: true + +# Optional synchronization of banned words with external sources (e.g. multilingual lists). +# Sources can be plain text (one word per line) or YAML with key: bannedWords. +banned-words-sync: + enabled: false + include-default-resource: true + timeout-ms: 2500 + remote-sources: [] From 5f83a4e6af5cca5b446c655ef182f4b6dee252a4 Mon Sep 17 00:00:00 2001 From: SyntaxDevTeam Date: Fri, 27 Mar 2026 09:16:11 +0100 Subject: [PATCH 2/3] Massively expand default banned words with multilingual entries --- .../src/main/resources/banned_words.yml | 607 +++++++++++++++++- CleanerX-Paper/src/main/resources/config.yml | 22 +- .../src/main/resources/banned_words.yml | 607 +++++++++++++++++- CleanerX-Spigot/src/main/resources/config.yml | 22 +- 4 files changed, 1240 insertions(+), 18 deletions(-) diff --git a/CleanerX-Paper/src/main/resources/banned_words.yml b/CleanerX-Paper/src/main/resources/banned_words.yml index cd27157..55024d3 100644 --- a/CleanerX-Paper/src/main/resources/banned_words.yml +++ b/CleanerX-Paper/src/main/resources/banned_words.yml @@ -388,4 +388,609 @@ bannedWords: - kurwiszon - kurwiszona - kurwiszony - - kurwiszonów \ No newline at end of file + - kurwiszonów + - kurwa + - kurwić + - skurwysyństwo + - wypierdalać + - ass + - wanker + - bollocks + - tosser + - prick + - arse + - jerkoff + - motherfucking + + - 2g1c + - 2girls1cup + - acrotomophilia + - alabamahotpocket + - alaskanpipeline + - anilingus + - anus + - apeshit + - assmunch + - autoerotic + - babeland + - babybatter + - babyjuice + - ballgag + - ballgravy + - ballkicking + - balllicking + - ballsack + - ballsucking + - bangbros + - bangbus + - bareback + - barelylegal + - barenaked + - bastardo + - bastinado + - bbw + - bdsm + - beaner + - beaners + - beavercleaver + - beaverlips + - beastiality + - bestiality + - bigblack + - bigbreasts + - bigknockers + - bigtits + - bimbos + - birdlock + - bitches + - blackcock + - blondeaction + - blondeonblondeaction + - blowyourload + - bluewaffle + - blumpkin + - bondage + - bootycall + - brownshowers + - brunetteaction + - bukkake + - bulldyke + - bulletvibe + - bunghole + - busty + - butt + - buttcheeks + - butthole + - cameltoe + - camgirl + - camslut + - camwhore + - carpetmuncher + - chocolaterosebuds + - cialis + - circlejerk + - clevelandsteamer + - clit + - clitoris + - cloverclamps + - clusterfuck + - cocks + - coprolagnia + - coprophilia + - cornhole + - coon + - coons + - creampie + - cumming + - cumshot + - cumshots + - cunnilingus + - daterape + - deepthroat + - dendrophilia + - dingleberry + - dingleberries + - dirtypillows + - dirtysanchez + - doggiestyle + - doggystyle + - dogstyle + - dolcett + - domination + - dominatrix + - dommes + - donkeypunch + - doubledong + - doublepenetration + - dpaction + - dryhump + - dvda + - eatmyass + - ecchi + - ejaculation + - erotic + - erotism + - escort + - eunuch + - fecal + - felch + - fellatio + - feltch + - femalesquirting + - femdom + - figging + - fingerbang + - fingering + - fisting + - footfetish + - footjob + - frotting + - fuckbuttons + - fuckin + - fucking + - fucktards + - fudgepacker + - futanari + - gangbang + - gaysex + - genitals + - giantcock + - girlon + - girlontop + - girlsgonewild + - goatcx + - goatse + - goddamn + - gokkun + - goldenshower + - goodpoop + - googirl + - goregasm + - grope + - groupsex + - guro + - handjob + - hardcore + - hentai + - homoerotic + - honkey + - hooker + - horny + - hotcarl + - hotchick + - howtokill + - howtomurder + - hugefat + - humping + - incest + - intercourse + - jackoff + - jailbait + - jellydonut + - jigaboo + - jiggaboo + - jiggerboo + - jizz + - juggs + - kike + - kinbaku + - kinkster + - kinky + - knobbing + - leatherrestraint + - leatherstraightjacket + - lemonparty + - livesex + - lolita + - lovemaking + - makemecome + - malesquirting + - masturbate + - masturbating + - masturbation + - menageatrois + - milf + - missionaryposition + - mong + - moundofvenus + - mrhands + - muffdiver + - muffdiving + - nambla + - nawashi + - negro + - neonazi + - nignog + - nimphomania + - nipple + - nipples + - nsfw + - nsfwimages + - nude + - nudity + - nutten + - nympho + - nymphomania + - octopussy + - omorashi + - onecuptwogirls + - oneguyonejar + - orgasm + - orgy + - paedophile + - paki + - panties + - panty + - pedobear + - pedophile + - pegging + - phonesex + - pieceofshit + - pikey + - pissing + - pisspig + - playboy + - pleasurechest + - polesmoker + - ponyplay + - poof + - poon + - poontang + - punany + - poopchute + - porn + - porno + - pornography + - princealbertpiercing + - pthc + - pubes + - queaf + - queef + - quim + - raghead + - ragingboner + - raping + - rapist + - rectum + - reversecowgirl + - rimjob + - rimming + - rosypalm + - rosypalmandher5sisters + - rustytrombone + - sadism + - santorum + - scat + - schlong + - scissoring + - semen + - sexcam + - sexo + - sexy + - sexual + - sexually + - sexuality + - shavedbeaver + - shavedpussy + - shemale + - shibari + - shitblimp + - shitty + - shota + - shrimping + - skeet + - slanteye + - smut + - snatch + - snowballing + - sodomize + - sodomy + - spastic + - spic + - splooge + - sploogemoose + - spooge + - spreadlegs + - spunk + - strapon + - strappado + - stripclub + - styledoggy + - suck + - sucks + - suicidegirls + - sultrywomen + - swastika + - swinger + - taintedlove + - tastemy + - teabagging + - threesome + - throating + - thumbzilla + - tiedup + - tightwhite + - titty + - tongueina + - topless + - towelhead + - tranny + - tribadism + - tubgirl + - tushy + - twink + - twinkie + - twogirlsonecup + - undressing + - upskirt + - urethraplay + - urophilia + - venusmound + - viagra + - vibrator + - violetwand + - vorarephilia + - voyeur + - voyeurweb + - voyuer + - vulva + - wank + - wetback + - wetdream + - whitepower + - worldsex + - wrappingmen + - wrinkledstarfish + - xxx + - yaoi + - yellowshowers + - yiffy + - zoophilia + - burdel + - burdelmama + - chujnia + - cyc + - dmuchać + - dokurwynędzy + - dupa + - dupek + - duperele + - fiut + - gównoprawda + - hujciwdupę + - jajco + - jajko + - japierdolę + - kutafon + - lizaćpałę + - obciągaćchuja + - obciągaćfiuta + - obciągaćloda + - pieprzyć + - pierdolec + - pierdolnąć + - pierdolnięty + - pierdoła + - pierdzieć + - pojebany + - popierdolony + - robicloda + - robićloda + - ruchać + - rzygać + - sraczka + - srać + - syf + - zajebisty + - analritter + - arsch + - arschficker + - arschlecker + - arschloch + - bimbo + - bratze + - bumsen + - bonze + - dödel + - fick + - ficken + - flittchen + - fotze + - fratze + - hackfresse + - hure + - hurensohn + - ische + - kackbratze + - kacke + - kacken + - kackwurst + - kampflesbe + - kanake + - kimme + - lümmel + - möpse + - morgenlatte + - möse + - mufti + - muschi + - nackt + - neger + - nippel + - nutte + - onanieren + - orgasmus + - pimmel + - pimpern + - pinkeln + - pissen + - pisser + - popel + - poppen + - reudig + - rosette + - schabracke + - schlampe + - scheiße + - scheisser + - schiesser + - schnackeln + - schwanzlutscher + - schwuchtel + - tittchen + - titten + - vögeln + - vollpfosten + - wichse + - wichsen + - wichser + - asesinato + - asno + - bollera + - cabrón + - caca + - chupada + - chupapollas + - chupetón + - concha + - conchadetumadre + - coño + - coprofagía + - culo + - drogas + - esperma + - fiestadesalchichas + - follador + - follar + - gilipichis + - gilipollas + - hacerunapaja + - haciendoelamor + - heroína + - hijadeputa + - hijaputa + - hijodeputa + - hijoputa + - idiota + - imbécil + - infierno + - jilipollas + - kapullo + - lameculos + - maciza + - macizorra + - maldito + - mamada + - marica + - maricón + - mariconazo + - martillo + - mierda + - nazi + - orina + - pedo + - pendejo + - pervertido + - pezón + - pinche + - pis + - prostituta + - puta + - racista + - ramera + - sádico + - sexooral + - soplagaitas + - soplapollas + - tetasgrandes + - tíabuena + - travesti + - trio + - verga + - vetealamierda + - baiser + - bander + - bigornette + - bite + - bitte + - bloblos + - bordel + - bourré + - bourrée + - brackmard + - branlage + - branler + - branlette + - branleur + - branleuse + - brouterlecresson + - chatte + - chiasse + - chier + - chiottes + - clito + - con + - connard + - connasse + - conne + - couilles + - cramouille + - cul + - déconne + - déconner + - emmerdant + - emmerder + - emmerdeur + - emmerdeuse + - enculé + - enculée + - enculeur + - enculeurs + - enfoiré + - enfoirée + - étron + - filledepute + - filsdepute + - folle + - foutre + - gerbe + - gerber + - gouine + - grandefolle + - grogniasse + - gueule + - jouir + - laputaindetamère + - malpt + - ménageàtrois + - merde + - merdeuse + - merdeux + - meuf + - nègre + - niquetamère + - niquetarace + - palucher + - pédale + - pédé + - péter + - pipi + - pouffiasse + - putain + - pute + - ramoner + - sacàfoutre + - sacàmerde + - salaud + - salope + - suce + - tapette + - tanche + - teuch + - tringler + - trique + - troncher + - trouducul + - turlute + - zigounette + - zizi \ No newline at end of file diff --git a/CleanerX-Paper/src/main/resources/config.yml b/CleanerX-Paper/src/main/resources/config.yml index feef53e..e268be2 100644 --- a/CleanerX-Paper/src/main/resources/config.yml +++ b/CleanerX-Paper/src/main/resources/config.yml @@ -10,6 +10,20 @@ fullCensorship: false # Enable link blocking in chat? block-links: true +# Optional synchronization of banned words with external sources (e.g. multilingual lists). +# Sources can be plain text (one word per line) or YAML with key: bannedWords. +# Example sources: +# - https://raw.githubusercontent.com/LDNOOBW/List-of-Dirty-Naughty-Obscene-and-Otherwise-Bad-Words/master/en +# - https://raw.githubusercontent.com/LDNOOBW/List-of-Dirty-Naughty-Obscene-and-Otherwise-Bad-Words/master/pl +# - https://raw.githubusercontent.com/LDNOOBW/List-of-Dirty-Naughty-Obscene-and-Otherwise-Bad-Words/master/de +# - https://raw.githubusercontent.com/LDNOOBW/List-of-Dirty-Naughty-Obscene-and-Otherwise-Bad-Words/master/es +# - https://raw.githubusercontent.com/LDNOOBW/List-of-Dirty-Naughty-Obscene-and-Otherwise-Bad-Words/master/fr +banned-words-sync: + enabled: false + include-default-resource: true + timeout-ms: 2500 + remote-sources: [] + use-punishment: true # Use command execution for offensive language? swear-word-thresholds: # Set your own commands to be applied after a specific number of detected swear words. We recommend our punishment plugin – PunisherX. @@ -32,11 +46,3 @@ debug: false # Should StatsCollector be enabled? Recommended setting: true stats: enabled: true - -# Optional synchronization of banned words with external sources (e.g. multilingual lists). -# Sources can be plain text (one word per line) or YAML with key: bannedWords. -banned-words-sync: - enabled: false - include-default-resource: true - timeout-ms: 2500 - remote-sources: [] diff --git a/CleanerX-Spigot/src/main/resources/banned_words.yml b/CleanerX-Spigot/src/main/resources/banned_words.yml index 264cecc..55462bf 100644 --- a/CleanerX-Spigot/src/main/resources/banned_words.yml +++ b/CleanerX-Spigot/src/main/resources/banned_words.yml @@ -390,4 +390,609 @@ bannedWords: - kurwiszon - kurwiszona - kurwiszony - - kurwiszonów \ No newline at end of file + - kurwiszonów + - kurwa + - kurwić + - skurwysyństwo + - wypierdalać + - ass + - wanker + - bollocks + - tosser + - prick + - arse + - jerkoff + - motherfucking + + - 2g1c + - 2girls1cup + - acrotomophilia + - alabamahotpocket + - alaskanpipeline + - anilingus + - anus + - apeshit + - assmunch + - autoerotic + - babeland + - babybatter + - babyjuice + - ballgag + - ballgravy + - ballkicking + - balllicking + - ballsack + - ballsucking + - bangbros + - bangbus + - bareback + - barelylegal + - barenaked + - bastardo + - bastinado + - bbw + - bdsm + - beaner + - beaners + - beavercleaver + - beaverlips + - beastiality + - bestiality + - bigblack + - bigbreasts + - bigknockers + - bigtits + - bimbos + - birdlock + - bitches + - blackcock + - blondeaction + - blondeonblondeaction + - blowyourload + - bluewaffle + - blumpkin + - bondage + - bootycall + - brownshowers + - brunetteaction + - bukkake + - bulldyke + - bulletvibe + - bunghole + - busty + - butt + - buttcheeks + - butthole + - cameltoe + - camgirl + - camslut + - camwhore + - carpetmuncher + - chocolaterosebuds + - cialis + - circlejerk + - clevelandsteamer + - clit + - clitoris + - cloverclamps + - clusterfuck + - cocks + - coprolagnia + - coprophilia + - cornhole + - coon + - coons + - creampie + - cumming + - cumshot + - cumshots + - cunnilingus + - daterape + - deepthroat + - dendrophilia + - dingleberry + - dingleberries + - dirtypillows + - dirtysanchez + - doggiestyle + - doggystyle + - dogstyle + - dolcett + - domination + - dominatrix + - dommes + - donkeypunch + - doubledong + - doublepenetration + - dpaction + - dryhump + - dvda + - eatmyass + - ecchi + - ejaculation + - erotic + - erotism + - escort + - eunuch + - fecal + - felch + - fellatio + - feltch + - femalesquirting + - femdom + - figging + - fingerbang + - fingering + - fisting + - footfetish + - footjob + - frotting + - fuckbuttons + - fuckin + - fucking + - fucktards + - fudgepacker + - futanari + - gangbang + - gaysex + - genitals + - giantcock + - girlon + - girlontop + - girlsgonewild + - goatcx + - goatse + - goddamn + - gokkun + - goldenshower + - goodpoop + - googirl + - goregasm + - grope + - groupsex + - guro + - handjob + - hardcore + - hentai + - homoerotic + - honkey + - hooker + - horny + - hotcarl + - hotchick + - howtokill + - howtomurder + - hugefat + - humping + - incest + - intercourse + - jackoff + - jailbait + - jellydonut + - jigaboo + - jiggaboo + - jiggerboo + - jizz + - juggs + - kike + - kinbaku + - kinkster + - kinky + - knobbing + - leatherrestraint + - leatherstraightjacket + - lemonparty + - livesex + - lolita + - lovemaking + - makemecome + - malesquirting + - masturbate + - masturbating + - masturbation + - menageatrois + - milf + - missionaryposition + - mong + - moundofvenus + - mrhands + - muffdiver + - muffdiving + - nambla + - nawashi + - negro + - neonazi + - nignog + - nimphomania + - nipple + - nipples + - nsfw + - nsfwimages + - nude + - nudity + - nutten + - nympho + - nymphomania + - octopussy + - omorashi + - onecuptwogirls + - oneguyonejar + - orgasm + - orgy + - paedophile + - paki + - panties + - panty + - pedobear + - pedophile + - pegging + - phonesex + - pieceofshit + - pikey + - pissing + - pisspig + - playboy + - pleasurechest + - polesmoker + - ponyplay + - poof + - poon + - poontang + - punany + - poopchute + - porn + - porno + - pornography + - princealbertpiercing + - pthc + - pubes + - queaf + - queef + - quim + - raghead + - ragingboner + - raping + - rapist + - rectum + - reversecowgirl + - rimjob + - rimming + - rosypalm + - rosypalmandher5sisters + - rustytrombone + - sadism + - santorum + - scat + - schlong + - scissoring + - semen + - sexcam + - sexo + - sexy + - sexual + - sexually + - sexuality + - shavedbeaver + - shavedpussy + - shemale + - shibari + - shitblimp + - shitty + - shota + - shrimping + - skeet + - slanteye + - smut + - snatch + - snowballing + - sodomize + - sodomy + - spastic + - spic + - splooge + - sploogemoose + - spooge + - spreadlegs + - spunk + - strapon + - strappado + - stripclub + - styledoggy + - suck + - sucks + - suicidegirls + - sultrywomen + - swastika + - swinger + - taintedlove + - tastemy + - teabagging + - threesome + - throating + - thumbzilla + - tiedup + - tightwhite + - titty + - tongueina + - topless + - towelhead + - tranny + - tribadism + - tubgirl + - tushy + - twink + - twinkie + - twogirlsonecup + - undressing + - upskirt + - urethraplay + - urophilia + - venusmound + - viagra + - vibrator + - violetwand + - vorarephilia + - voyeur + - voyeurweb + - voyuer + - vulva + - wank + - wetback + - wetdream + - whitepower + - worldsex + - wrappingmen + - wrinkledstarfish + - xxx + - yaoi + - yellowshowers + - yiffy + - zoophilia + - burdel + - burdelmama + - chujnia + - cyc + - dmuchać + - dokurwynędzy + - dupa + - dupek + - duperele + - fiut + - gównoprawda + - hujciwdupę + - jajco + - jajko + - japierdolę + - kutafon + - lizaćpałę + - obciągaćchuja + - obciągaćfiuta + - obciągaćloda + - pieprzyć + - pierdolec + - pierdolnąć + - pierdolnięty + - pierdoła + - pierdzieć + - pojebany + - popierdolony + - robicloda + - robićloda + - ruchać + - rzygać + - sraczka + - srać + - syf + - zajebisty + - analritter + - arsch + - arschficker + - arschlecker + - arschloch + - bimbo + - bratze + - bumsen + - bonze + - dödel + - fick + - ficken + - flittchen + - fotze + - fratze + - hackfresse + - hure + - hurensohn + - ische + - kackbratze + - kacke + - kacken + - kackwurst + - kampflesbe + - kanake + - kimme + - lümmel + - möpse + - morgenlatte + - möse + - mufti + - muschi + - nackt + - neger + - nippel + - nutte + - onanieren + - orgasmus + - pimmel + - pimpern + - pinkeln + - pissen + - pisser + - popel + - poppen + - reudig + - rosette + - schabracke + - schlampe + - scheiße + - scheisser + - schiesser + - schnackeln + - schwanzlutscher + - schwuchtel + - tittchen + - titten + - vögeln + - vollpfosten + - wichse + - wichsen + - wichser + - asesinato + - asno + - bollera + - cabrón + - caca + - chupada + - chupapollas + - chupetón + - concha + - conchadetumadre + - coño + - coprofagía + - culo + - drogas + - esperma + - fiestadesalchichas + - follador + - follar + - gilipichis + - gilipollas + - hacerunapaja + - haciendoelamor + - heroína + - hijadeputa + - hijaputa + - hijodeputa + - hijoputa + - idiota + - imbécil + - infierno + - jilipollas + - kapullo + - lameculos + - maciza + - macizorra + - maldito + - mamada + - marica + - maricón + - mariconazo + - martillo + - mierda + - nazi + - orina + - pedo + - pendejo + - pervertido + - pezón + - pinche + - pis + - prostituta + - puta + - racista + - ramera + - sádico + - sexooral + - soplagaitas + - soplapollas + - tetasgrandes + - tíabuena + - travesti + - trio + - verga + - vetealamierda + - baiser + - bander + - bigornette + - bite + - bitte + - bloblos + - bordel + - bourré + - bourrée + - brackmard + - branlage + - branler + - branlette + - branleur + - branleuse + - brouterlecresson + - chatte + - chiasse + - chier + - chiottes + - clito + - con + - connard + - connasse + - conne + - couilles + - cramouille + - cul + - déconne + - déconner + - emmerdant + - emmerder + - emmerdeur + - emmerdeuse + - enculé + - enculée + - enculeur + - enculeurs + - enfoiré + - enfoirée + - étron + - filledepute + - filsdepute + - folle + - foutre + - gerbe + - gerber + - gouine + - grandefolle + - grogniasse + - gueule + - jouir + - laputaindetamère + - malpt + - ménageàtrois + - merde + - merdeuse + - merdeux + - meuf + - nègre + - niquetamère + - niquetarace + - palucher + - pédale + - pédé + - péter + - pipi + - pouffiasse + - putain + - pute + - ramoner + - sacàfoutre + - sacàmerde + - salaud + - salope + - suce + - tapette + - tanche + - teuch + - tringler + - trique + - troncher + - trouducul + - turlute + - zigounette + - zizi \ No newline at end of file diff --git a/CleanerX-Spigot/src/main/resources/config.yml b/CleanerX-Spigot/src/main/resources/config.yml index 85cd031..5322d4c 100644 --- a/CleanerX-Spigot/src/main/resources/config.yml +++ b/CleanerX-Spigot/src/main/resources/config.yml @@ -10,6 +10,20 @@ fullCensorship: false # Enable link blocking in chat? block-links: true +# Optional synchronization of banned words with external sources (e.g. multilingual lists). +# Sources can be plain text (one word per line) or YAML with key: bannedWords. +# Example sources: +# - https://raw.githubusercontent.com/LDNOOBW/List-of-Dirty-Naughty-Obscene-and-Otherwise-Bad-Words/master/en +# - https://raw.githubusercontent.com/LDNOOBW/List-of-Dirty-Naughty-Obscene-and-Otherwise-Bad-Words/master/pl +# - https://raw.githubusercontent.com/LDNOOBW/List-of-Dirty-Naughty-Obscene-and-Otherwise-Bad-Words/master/de +# - https://raw.githubusercontent.com/LDNOOBW/List-of-Dirty-Naughty-Obscene-and-Otherwise-Bad-Words/master/es +# - https://raw.githubusercontent.com/LDNOOBW/List-of-Dirty-Naughty-Obscene-and-Otherwise-Bad-Words/master/fr +banned-words-sync: + enabled: false + include-default-resource: true + timeout-ms: 2500 + remote-sources: [] + use-punishment: true # Use command execution for offensive language? swear-word-thresholds: # Set your own commands to be applied after a specific number of detected swear words. We recommend our punishment plugin – PunisherX. @@ -28,11 +42,3 @@ debug: false # Should StatsCollector be enabled? Recommended setting: true stats: enabled: true - -# Optional synchronization of banned words with external sources (e.g. multilingual lists). -# Sources can be plain text (one word per line) or YAML with key: bannedWords. -banned-words-sync: - enabled: false - include-default-resource: true - timeout-ms: 2500 - remote-sources: [] From ff82e2d6dc57646ed26fcadb20f7866270985a48 Mon Sep 17 00:00:00 2001 From: SyntaxDevTeam Date: Fri, 27 Mar 2026 09:29:18 +0100 Subject: [PATCH 3/3] Avoid YAML parse errors for plain remote word lists --- .../cleanerx/common/BannedWordsSynchronizer.kt | 18 ++++++++++++++---- .../cleanerx/common/BannedWordsSynchronizer.kt | 18 ++++++++++++++---- 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/CleanerX-Paper/src/main/kotlin/pl/syntaxdevteam/cleanerx/common/BannedWordsSynchronizer.kt b/CleanerX-Paper/src/main/kotlin/pl/syntaxdevteam/cleanerx/common/BannedWordsSynchronizer.kt index 840a8ea..da1b643 100644 --- a/CleanerX-Paper/src/main/kotlin/pl/syntaxdevteam/cleanerx/common/BannedWordsSynchronizer.kt +++ b/CleanerX-Paper/src/main/kotlin/pl/syntaxdevteam/cleanerx/common/BannedWordsSynchronizer.kt @@ -163,10 +163,14 @@ class BannedWordsSynchronizer(private val plugin: CleanerX) { return emptyList() } - val fromYaml = runCatching { - val config = YamlConfiguration.loadConfiguration(StringReader(trimmed)) - config.getStringList(BANNED_WORDS_KEY) - }.getOrNull().orEmpty() + val fromYaml = if (looksLikeYamlWithBannedWords(trimmed)) { + runCatching { + val config = YamlConfiguration.loadConfiguration(StringReader(trimmed)) + config.getStringList(BANNED_WORDS_KEY) + }.getOrNull().orEmpty() + } else { + emptyList() + } val candidates = if (fromYaml.isNotEmpty()) { fromYaml @@ -188,5 +192,11 @@ class BannedWordsSynchronizer(private val plugin: CleanerX) { .distinct() .toList() } + + private fun looksLikeYamlWithBannedWords(payload: String): Boolean { + return payload.lineSequence() + .map { it.trimStart() } + .any { it.startsWith("$BANNED_WORDS_KEY:") } + } } } diff --git a/CleanerX-Spigot/src/main/kotlin/pl/syntaxdevteam/cleanerx/common/BannedWordsSynchronizer.kt b/CleanerX-Spigot/src/main/kotlin/pl/syntaxdevteam/cleanerx/common/BannedWordsSynchronizer.kt index 3e4aa7d..8c3ed03 100644 --- a/CleanerX-Spigot/src/main/kotlin/pl/syntaxdevteam/cleanerx/common/BannedWordsSynchronizer.kt +++ b/CleanerX-Spigot/src/main/kotlin/pl/syntaxdevteam/cleanerx/common/BannedWordsSynchronizer.kt @@ -132,10 +132,14 @@ class BannedWordsSynchronizer(private val plugin: CleanerX) { return emptyList() } - val fromYaml = runCatching { - val config = YamlConfiguration.loadConfiguration(StringReader(trimmed)) - config.getStringList(BANNED_WORDS_KEY) - }.getOrNull().orEmpty() + val fromYaml = if (looksLikeYamlWithBannedWords(trimmed)) { + runCatching { + val config = YamlConfiguration.loadConfiguration(StringReader(trimmed)) + config.getStringList(BANNED_WORDS_KEY) + }.getOrNull().orEmpty() + } else { + emptyList() + } if (fromYaml.isNotEmpty()) { return fromYaml @@ -151,6 +155,12 @@ class BannedWordsSynchronizer(private val plugin: CleanerX) { .toList() } + private fun looksLikeYamlWithBannedWords(payload: String): Boolean { + return payload.lineSequence() + .map { it.trimStart() } + .any { it.startsWith("$BANNED_WORDS_KEY:") } + } + private fun sanitizeWords(words: List): List { return words.asSequence() .map { it.trim().lowercase(Locale.ROOT) }