aboutsummaryrefslogtreecommitdiff
path: root/src/main/kotlin
diff options
context:
space:
mode:
authorKendell R <KTibow@users.noreply.github.com>2022-05-01 07:47:57 -0700
committerKendell R <KTibow@users.noreply.github.com>2022-05-01 07:48:17 -0700
commit2eded39259f3b717cce1a58e7fdabf6852ffdfcb (patch)
tree3acdef2a8f67a54ed9a1ab6e1414bb0953268438 /src/main/kotlin
parentd3b3d5273d6754c991d6ba9a69884071a8e56c91 (diff)
downloadChatting-2eded39259f3b717cce1a58e7fdabf6852ffdfcb.tar.gz
Chatting-2eded39259f3b717cce1a58e7fdabf6852ffdfcb.tar.bz2
Chatting-2eded39259f3b717cce1a58e7fdabf6852ffdfcb.zip
feat: spam block
Diffstat (limited to 'src/main/kotlin')
-rw-r--r--src/main/kotlin/cc/woverflow/chatting/Chatting.kt2
-rw-r--r--src/main/kotlin/cc/woverflow/chatting/chat/ChatSpamBlock.kt124
-rw-r--r--src/main/kotlin/cc/woverflow/chatting/config/ChattingConfig.kt26
3 files changed, 152 insertions, 0 deletions
diff --git a/src/main/kotlin/cc/woverflow/chatting/Chatting.kt b/src/main/kotlin/cc/woverflow/chatting/Chatting.kt
index 0fe85f9..5718f14 100644
--- a/src/main/kotlin/cc/woverflow/chatting/Chatting.kt
+++ b/src/main/kotlin/cc/woverflow/chatting/Chatting.kt
@@ -2,6 +2,7 @@ package cc.woverflow.chatting
import cc.woverflow.chatting.chat.ChatSearchingManager
import cc.woverflow.chatting.chat.ChatShortcuts
+import cc.woverflow.chatting.chat.ChatSpamBlock
import cc.woverflow.chatting.chat.ChatTabs
import cc.woverflow.chatting.config.ChattingConfig
import cc.woverflow.chatting.hook.GuiNewChatHook
@@ -80,6 +81,7 @@ object Chatting {
}
ClientRegistry.registerKeyBinding(keybind)
EVENT_BUS.register(this)
+ EVENT_BUS.register(ChatSpamBlock)
ChatTabs.initialize()
ChatShortcuts.initialize()
}
diff --git a/src/main/kotlin/cc/woverflow/chatting/chat/ChatSpamBlock.kt b/src/main/kotlin/cc/woverflow/chatting/chat/ChatSpamBlock.kt
new file mode 100644
index 0000000..224e614
--- /dev/null
+++ b/src/main/kotlin/cc/woverflow/chatting/chat/ChatSpamBlock.kt
@@ -0,0 +1,124 @@
+package cc.woverflow.chatting.chat
+
+import cc.woverflow.chatting.config.ChattingConfig
+import com.google.gson.JsonObject
+import com.google.gson.JsonParser
+import net.minecraft.util.ChatComponentText
+import net.minecraft.util.EnumChatFormatting
+import net.minecraftforge.client.event.ClientChatReceivedEvent
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
+import java.text.Normalizer
+
+object ChatSpamBlock {
+ /*
+ Made by @KTibow
+ Based off of Unspam (also by @KTibow)
+ Algorithm based off of https://paulgraham.com/spam.html
+ */
+ private val PLAYER_MESSAGE = Regex("^(\\[VIP\\+?\\] |\\[MVP\\+?\\+?\\] |)(\\w{2,16}): (.*)$")
+
+ @SubscribeEvent
+ fun onChat(event: ClientChatReceivedEvent) {
+ val message = event.message.unformattedText.replace(Regex("\u00A7."), "")
+ if (!PLAYER_MESSAGE.matches(message)) return
+ val (rank, player, content) = PLAYER_MESSAGE.matchEntire(message)!!.destructured
+ val tokens = tokenize(content)
+ val spamProb = findSpamProbability(tokens)
+ println("\n[CHATTING]$message")
+ if (spamProb * 100 > ChattingConfig.spamThreshold) {
+ if (ChattingConfig.showSpamInGray) {
+ var newMessage = EnumChatFormatting.DARK_GRAY.toString() + EnumChatFormatting.STRIKETHROUGH.toString()
+ if (!ChattingConfig.customFormatting) {
+ newMessage += rank
+ }
+ newMessage += "$player${EnumChatFormatting.DARK_GRAY.toString()}: $content"
+ event.message = ChatComponentText(newMessage)
+ } else {
+ event.isCanceled = true
+ }
+ return
+ }
+ if (ChattingConfig.customFormatting) {
+ val coloredPlayer = findRankColor(rank) + player + EnumChatFormatting.RESET.toString()
+ event.message = ChatComponentText("$coloredPlayer: $content")
+ }
+ }
+
+ private fun tokenize(message: String): MutableList<String> {
+ val strippedMessage =
+ Normalizer.normalize(message, Normalizer.Form.NFKC).replace(Regex("[^\\w\\s/]"), " ").lowercase().trim()
+ val tokens = strippedMessage.split(Regex("\\s+")).toMutableList()
+ if (tokens.size <= 2) {
+ tokens.add("TINY_LENGTH")
+ } else if (tokens.size <= 4) {
+ tokens.add("SMALL_LENGTH")
+ } else if (tokens.size <= 7) {
+ tokens.add("MEDIUM_LENGTH")
+ } else {
+ tokens.add("LONG_LENGTH")
+ }
+ if (message.replace(Regex("[\\w\\s]"), "").length > 2) {
+ tokens.add("SPECIAL_CHARS")
+ } else if (message.replace(Regex("[\\w\\s]"), "").length > 0) {
+ tokens.add("SPECIAL_CHAR")
+ } else {
+ tokens.add("LOW_SPECIAL_CHARS")
+ }
+ if (message.replace(Regex("[^A-Z]"), "").length >= message.length / 4) {
+ tokens.add("HIGH_CAPS")
+ } else {
+ tokens.add("LOW_CAPS")
+ }
+ return tokens
+ }
+
+ private fun findSpamProbability(tokens: MutableList<String>): Double {
+ val tokenProbs = mutableMapOf<String, Double>()
+ for (token in tokens) {
+ if (!spamInfoJson.has(token)) continue
+ val spamInToken = spamInfoJson.get(token).asJsonObject.get("spam").asDouble
+ val fineInToken = spamInfoJson.get(token).asJsonObject.get("fine").asDouble
+ tokenProbs[token] = (
+ (spamInToken / messageCountsJson.get("spam").asInt) /
+ (fineInToken / messageCountsJson.get("fine").asInt +
+ spamInToken / messageCountsJson.get("spam").asInt)
+ )
+ }
+ val spamProbs = tokenProbs.values.toMutableList()
+ val fineProbs = tokenProbs.values.map {
+ 1 - it
+ }.toMutableList()
+ val spamProbability = spamProbs.reduce { a, b ->
+ a * b
+ }
+ val fineProbability = fineProbs.reduce { a, b ->
+ a * b
+ }
+ return spamProbability / (spamProbability + fineProbability)
+ }
+
+ private fun findRankColor(rank: String): String {
+ println(rank)
+ return when (rank) {
+ "[VIP] ", "[VIP+] " -> EnumChatFormatting.GREEN.toString()
+ "[MVP] ", "[MVP+] " -> EnumChatFormatting.AQUA.toString()
+ "[MVP++] " -> EnumChatFormatting.GOLD.toString()
+ else -> EnumChatFormatting.GRAY.toString()
+ }
+ }
+
+ private fun getResourceAsText(path: String): String? = object {}.javaClass.getResource(path)?.readText()
+ private val spamInfoJson: JsonObject
+ private val messageCountsJson: JsonObject
+
+ init {
+ // Load the file spamInfo.json from resources/
+ val spamInfo = getResourceAsText("/spamInfo.json")
+ spamInfoJson = JsonParser().parse(spamInfo).asJsonObject
+ messageCountsJson = JsonParser().parse(" { \"fine\": 668, \"spam\": 230 }").asJsonObject
+ }
+}
+
+
+
+
diff --git a/src/main/kotlin/cc/woverflow/chatting/config/ChattingConfig.kt b/src/main/kotlin/cc/woverflow/chatting/config/ChattingConfig.kt
index 93a8723..24729be 100644
--- a/src/main/kotlin/cc/woverflow/chatting/config/ChattingConfig.kt
+++ b/src/main/kotlin/cc/woverflow/chatting/config/ChattingConfig.kt
@@ -79,6 +79,32 @@ object ChattingConfig :
var informForAlternatives = true
@Property(
+ type = PropertyType.SLIDER,
+ min = 0,
+ max = 100,
+ name = "Spam Threshold",
+ description = "If Chatting detects a player message seems like spam, and the probability is above this threshold, it will hide it. Set to 0 to disable.",
+ category = "Player Spam Blocker"
+ )
+ var spamThreshold = 95
+
+ @Property(
+ type = PropertyType.SWITCH,
+ name = "Show Spam (with styling)",
+ description = "Show messages Chatting detects as spam in gray, instead of hiding them.",
+ category = "Player Spam Blocker"
+ )
+ var showSpamInGray = true
+
+ @Property(
+ type = PropertyType.SWITCH,
+ name = "Custom Message Formatting",
+ description = "Hide ranks, and show messages in public chat from no-ranks as white.",
+ category = "Player Spam Blocker"
+ )
+ var customFormatting = false
+
+ @Property(
type = PropertyType.SWITCH,
name = "Custom Chat Height",
description = "Allows you to change the height of chat to heights greater than before.",