1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
|
package cc.woverflow.chatting.chat
import cc.woverflow.chatting.config.ChattingConfig
import com.google.gson.JsonObject
import com.google.gson.JsonParser
import java.text.Normalizer
import net.minecraft.util.ChatComponentText
import net.minecraft.util.EnumChatFormatting
import net.minecraftforge.client.event.ClientChatReceivedEvent
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
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
if (ChattingConfig.spamThreshold != 100) {
val tokens = tokenize(content)
val spamProb = findSpamProbability(tokens)
if (spamProb * 100 > ChattingConfig.spamThreshold) {
if (ChattingConfig.hideSpam) {
event.isCanceled = true
} else {
var newMessage =
EnumChatFormatting.DARK_GRAY.toString() +
EnumChatFormatting.STRIKETHROUGH.toString()
if (!ChattingConfig.customChatFormatting) {
newMessage += rank
}
newMessage += "$player${EnumChatFormatting.DARK_GRAY}: $content"
event.message = ChatComponentText(newMessage)
}
return
}
}
if (ChattingConfig.customChatFormatting) {
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]"), "").isNotEmpty()) {
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
}
}
|