aboutsummaryrefslogtreecommitdiff
path: root/src/main/kotlin
diff options
context:
space:
mode:
authorKendell R <KTibow@users.noreply.github.com>2022-05-29 12:39:36 -0700
committerKendell R <KTibow@users.noreply.github.com>2022-05-29 12:41:30 -0700
commit93a32bbbd183cf45af10a7fbe9a5238585eb442e (patch)
tree6ea38aeaca53beb1584007ae8268dcbe905269ee /src/main/kotlin
parent5da5f6efd284076b65d1811f13e1b4825c34d2db (diff)
downloadChatting-93a32bbbd183cf45af10a7fbe9a5238585eb442e.tar.gz
Chatting-93a32bbbd183cf45af10a7fbe9a5238585eb442e.tar.bz2
Chatting-93a32bbbd183cf45af10a7fbe9a5238585eb442e.zip
spam block v2
Diffstat (limited to 'src/main/kotlin')
-rw-r--r--src/main/kotlin/cc/woverflow/chatting/Chatting.kt137
-rw-r--r--src/main/kotlin/cc/woverflow/chatting/chat/ChatSpamBlock.kt124
-rw-r--r--src/main/kotlin/cc/woverflow/chatting/config/ChattingConfig.kt101
3 files changed, 300 insertions, 62 deletions
diff --git a/src/main/kotlin/cc/woverflow/chatting/Chatting.kt b/src/main/kotlin/cc/woverflow/chatting/Chatting.kt
index d012ab3..ce18f13 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
@@ -13,6 +14,10 @@ import cc.woverflow.chatting.utils.screenshot
import cc.woverflow.onecore.utils.*
import gg.essential.universal.UDesktop
import gg.essential.universal.UResolution
+import java.awt.image.BufferedImage
+import java.io.File
+import java.text.SimpleDateFormat
+import java.util.*
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.*
import net.minecraft.client.renderer.GlStateManager
@@ -36,6 +41,7 @@ import java.io.File
import java.text.SimpleDateFormat
import java.util.*
+import skytils.skytilsmod.core.Config
@Mod(
modid = Chatting.ID,
@@ -72,13 +78,10 @@ object Chatting {
@Mod.EventHandler
fun onInitialization(event: FMLInitializationEvent) {
ChattingConfig.preload()
- command("chatting") {
- main {
- ChattingConfig.openScreen()
- }
- }
+ command("chatting") { main { ChattingConfig.openScreen() } }
ClientRegistry.registerKeyBinding(keybind)
EVENT_BUS.register(this)
+ EVENT_BUS.register(ChatSpamBlock)
ChatTabs.initialize()
ChatShortcuts.initialize()
}
@@ -95,20 +98,42 @@ object Chatting {
fun onForgeLoad(event: FMLLoadCompleteEvent) {
if (ChattingConfig.informForAlternatives) {
if (isHychat) {
- sendBrandedNotification(NAME, "Hychat can be removed at it is replaced by Chatting. Click here for more information.", action = {
- UDesktop.browseURL("https://github.com/MicrocontrollersDev/Alternatives/blob/main/Hychat.md")
- })
+ sendBrandedNotification(
+ NAME,
+ "Hychat can be removed at it is replaced by Chatting. Click here for more information.",
+ action = {
+ UDesktop.browseURL(
+ "https://github.com/MicrocontrollersDev/Alternatives/blob/main/Hychat.md"
+ )
+ }
+ )
}
if (isSkytils) {
- try {
- skytilsCompat(Class.forName("gg.skytils.skytilsmod.core.Config"))
- } catch (e: Exception) {
- e.printStackTrace()
- try {
- skytilsCompat(Class.forName("skytils.skytilsmod.core.Config"))
- } catch (e: Exception) {
- e.printStackTrace()
- }
+ if (Config.chatTabs) {
+ sendBrandedNotification(
+ NAME,
+ "Skytils' chat tabs can be disabled as it is replace by Chatting.\nClick here to automatically do this.",
+ 6F,
+ action = {
+ Config.chatTabs = false
+ ChattingConfig.chatTabs = true
+ ChattingConfig.hypixelOnlyChatTabs = true
+ Config.markDirty()
+ Config.writeData()
+ }
+ )
+ }
+ if (Config.copyChat) {
+ sendBrandedNotification(
+ NAME,
+ "Skytils' copy chat messages can be disabled as it is replace by Chatting.\nClick here to automatically do this.",
+ 6F,
+ action = {
+ Config.copyChat = false
+ Config.markDirty()
+ Config.writeData()
+ }
+ )
}
}
}
@@ -142,7 +167,12 @@ object Chatting {
@SubscribeEvent
fun onTickEvent(event: TickEvent.ClientTickEvent) {
- if (event.phase == TickEvent.Phase.START && Minecraft.getMinecraft().theWorld != null && Minecraft.getMinecraft().thePlayer != null && (Minecraft.getMinecraft().currentScreen == null || Minecraft.getMinecraft().currentScreen is GuiChat)) {
+ if (event.phase == TickEvent.Phase.START &&
+ Minecraft.getMinecraft().theWorld != null &&
+ Minecraft.getMinecraft().thePlayer != null &&
+ (Minecraft.getMinecraft().currentScreen == null ||
+ Minecraft.getMinecraft().currentScreen is GuiChat)
+ ) {
if (doTheThing) {
screenshotChat()
doTheThing = false
@@ -154,8 +184,13 @@ object Chatting {
var height = if (opened) ChattingConfig.focusedHeight else ChattingConfig.unfocusedHeight
height = (height * Minecraft.getMinecraft().gameSettings.chatScale).toInt()
val chatY = ModCompatHooks.yOffset + ModCompatHooks.chatPosition
- if (height + chatY + 27 > (UResolution.scaledHeight / Minecraft.getMinecraft().gameSettings.chatScale).toInt() - 27 - chatY) {
- height = (UResolution.scaledHeight / Minecraft.getMinecraft().gameSettings.chatScale).toInt() - 27 - chatY
+ if (height + chatY + 27 >
+ (UResolution.scaledHeight / Minecraft.getMinecraft().gameSettings.chatScale)
+ .toInt() - 27 - chatY
+ ) {
+ height =
+ (UResolution.scaledHeight / Minecraft.getMinecraft().gameSettings.chatScale)
+ .toInt() - 27 - chatY
}
return height
}
@@ -164,7 +199,17 @@ object Chatting {
val hud = Minecraft.getMinecraft().ingameGUI
val chat = hud.chatGUI
val i = MathHelper.floor_float(chat.chatWidth / chat.chatScale)
- return screenshot(GuiUtilRenderComponents.splitText(line.chatComponent, i, Minecraft.getMinecraft().fontRendererObj, false, false).map { it.formattedText }.reversed())
+ return screenshot(
+ GuiUtilRenderComponents.splitText(
+ line.chatComponent,
+ i,
+ Minecraft.getMinecraft().fontRendererObj,
+ false,
+ false
+ )
+ .map { it.formattedText }
+ .reversed()
+ )
}
private fun screenshotChat() {
@@ -175,14 +220,23 @@ object Chatting {
val hud = Minecraft.getMinecraft().ingameGUI
val chat = hud.chatGUI
val chatLines = ArrayList<String>()
- ChatSearchingManager.filterMessages((chat as GuiNewChatHook).prevText, (chat as GuiNewChatAccessor).drawnChatLines)?.let { drawnLines ->
- val chatHeight = if (ChattingConfig.customChatHeight) getChatHeight(true) / 9 else GuiNewChat.calculateChatboxHeight(Minecraft.getMinecraft().gameSettings.chatHeightFocused / 9)
- for (i in scrollPos until drawnLines.size.coerceAtMost(scrollPos + chatHeight)) {
- chatLines.add(drawnLines[i].chatComponent.formattedText)
- }
+ ChatSearchingManager.filterMessages(
+ (chat as GuiNewChatHook).prevText,
+ (chat as GuiNewChatAccessor).drawnChatLines
+ )
+ ?.let { drawnLines ->
+ val chatHeight =
+ if (ChattingConfig.customChatHeight) getChatHeight(true) / 9
+ else
+ GuiNewChat.calculateChatboxHeight(
+ Minecraft.getMinecraft().gameSettings.chatHeightFocused / 9
+ )
+ for (i in scrollPos until drawnLines.size.coerceAtMost(scrollPos + chatHeight)) {
+ chatLines.add(drawnLines[i].chatComponent.formattedText)
+ }
- screenshot(chatLines)?.copyToClipboard()
- }
+ screenshot(chatLines)?.copyToClipboard()
+ }
}
private fun screenshot(messages: List<String>): BufferedImage? {
@@ -191,30 +245,47 @@ object Chatting {
return null
}
if (!OpenGlHelper.isFramebufferEnabled()) {
- sendBrandedNotification("Chatting", "Screenshot failed, please disable “Fast Render” in OptiFine’s “Performance” tab.")
+ sendBrandedNotification(
+ "Chatting",
+ "Screenshot failed, please disable “Fast Render” in OptiFine’s “Performance” tab."
+ )
return null
}
val fr: FontRenderer = ModCompatHooks.fontRenderer
val width = messages.maxOf { fr.getStringWidth(it) } + 4
val fb: Framebuffer = createBindFramebuffer(width * 2, (messages.size * 9) * 2)
- val file = File(Minecraft.getMinecraft().mcDataDir, "screenshots/chat/" + fileFormatter.format(Date()))
+ val file =
+ File(
+ Minecraft.getMinecraft().mcDataDir,
+ "screenshots/chat/" + fileFormatter.format(Date())
+ )
GlStateManager.scale(2f, 2f, 1f)
val scale = Minecraft.getMinecraft().gameSettings.chatScale
GlStateManager.scale(scale, scale, 1f)
for (i in messages.indices) {
- ModCompatHooks.redirectDrawString(messages[i], 0f, (messages.size - 1 - i) * 9f, 0xffffff)
+ ModCompatHooks.redirectDrawString(
+ messages[i],
+ 0f,
+ (messages.size - 1 - i) * 9f,
+ 0xffffff
+ )
}
val image = fb.screenshot(file)
Minecraft.getMinecraft().entityRenderer.setupOverlayRendering()
Minecraft.getMinecraft().framebuffer.bindFramebuffer(true)
- sendBrandedNotification("Chatting", "Chat screenshotted successfully." + (if (ChattingConfig.copyMode != 1) "\nClick to open." else ""), action = {
+ sendBrandedNotification(
+ "Chatting",
+ "Chat screenshotted successfully." +
+ (if (ChattingConfig.copyMode != 1) "\nClick to open." else ""),
+ action = {
if (!UDesktop.open(file)) {
sendBrandedNotification("Chatting", "Could not browse!")
}
- })
+ }
+ )
return image
}
}
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..91ea6b5
--- /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 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.toString()}: $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]"), "").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 1a936c9..3041af8 100644
--- a/src/main/kotlin/cc/woverflow/chatting/config/ChattingConfig.kt
+++ b/src/main/kotlin/cc/woverflow/chatting/config/ChattingConfig.kt
@@ -17,7 +17,11 @@ import java.awt.Color
import java.io.File
object ChattingConfig :
- Vigilant(File(Chatting.modDir, "${Chatting.ID}.toml"), Chatting.NAME, sortingBehavior = ConfigSorting) {
+ Vigilant(
+ File(Chatting.modDir, "${Chatting.ID}.toml"),
+ Chatting.NAME,
+ sortingBehavior = ConfigSorting
+ ) {
@Property(
type = PropertyType.SELECTOR,
@@ -73,7 +77,8 @@ object ChattingConfig :
@Property(
type = PropertyType.SWITCH,
name = "Inform for Alternatives",
- description = "Inform the user if a mod they are using can be replaced by a feature in Chatting.",
+ description =
+ "Inform the user if a mod they are using can be replaced by a feature in Chatting.",
category = "General"
)
var informForAlternatives = true
@@ -98,6 +103,40 @@ object ChattingConfig :
*/
@Property(
+ type = PropertyType.SLIDER,
+ min = 80,
+ max = 100,
+ name = "Spam Blocker Threshold",
+ description =
+ "If Chatting detects a public chat message that seems like spam, and the probability is higher than this, it will hide it.\n" +
+ "Made for Hypixel Skyblock. Set to 100% to disable. 95% is a reasonable threshold to use it at.\n" +
+ "Note that this is not and never will be 100% accurate; however, it's pretty much guaranteed to block most spam.",
+ category = "Player Chats"
+ )
+ var spamThreshold = 100
+
+ @Property(
+ type = PropertyType.SWITCH,
+ name = "Custom Chat Formatting",
+ description =
+ "Reformat all Skyblock chat messages. Example:\n" +
+ "§a[VIP] Person§f: Message\n§7Person2: Message\n" +
+ "§eBecomes:\n" +
+ "§aPerson§f: Message\n§7Person2§f: Message",
+ category = "Player Chats"
+ )
+ var customChatFormatting = false
+
+ @Property(
+ type = PropertyType.SWITCH,
+ name = "Hide Spam",
+ description =
+ "When Chatting detects spam (if it's enabled), hide it instead of just graying it out.",
+ category = "Player Chats"
+ )
+ var hideSpam = false
+
+ @Property(
type = PropertyType.SWITCH,
name = "Custom Chat Height",
description = "Allows you to change the height of chat to heights greater than before.",
@@ -143,7 +182,10 @@ object ChattingConfig :
var chatSearch = true
@Property(
- type = PropertyType.SWITCH, name = "Chat Tabs", description = "Add chat tabs.", category = "Tabs"
+ type = PropertyType.SWITCH,
+ name = "Chat Tabs",
+ description = "Add chat tabs.",
+ category = "Tabs"
)
var chatTabs = true
get() {
@@ -164,7 +206,10 @@ object ChattingConfig :
var hypixelOnlyChatTabs = true
@Property(
- type = PropertyType.SWITCH, name = "Chat Shortcuts", description = "Add chat shortcuts.", category = "Shortcuts"
+ type = PropertyType.SWITCH,
+ name = "Chat Shortcuts",
+ description = "Add chat shortcuts.",
+ category = "Shortcuts"
)
var chatShortcuts = false
get() {
@@ -198,26 +243,27 @@ object ChattingConfig :
chatTabs = funny
ChatTabs.initialize()
if (!funny) {
- val dummy = ChatTab(
- true,
- "ALL",
- false,
- false,
- null,
- null,
- null,
- null,
- null,
- null,
- null,
- null,
- null,
- null,
- TabButton.color,
- TabButton.hoveredColor,
- TabButton.selectedColor,
- ""
- )
+ val dummy =
+ ChatTab(
+ true,
+ "ALL",
+ false,
+ false,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ TabButton.color,
+ TabButton.hoveredColor,
+ TabButton.selectedColor,
+ ""
+ )
dummy.initialize()
ChatTabs.currentTab = dummy
} else {
@@ -228,16 +274,13 @@ object ChattingConfig :
chatShortcuts = funny
ChatShortcuts.initialize()
}
- //addDependency("showTimestampHover", "showTimestamp")
+ // addDependency("showTimestampHover", "showTimestamp")
}
private object ConfigSorting : SortingBehavior() {
override fun getCategoryComparator(): Comparator<in Category> = Comparator { o1, o2 ->
if (o1.name == "General") return@Comparator -1
- if (o2.name == "General") return@Comparator 1
- else compareValuesBy(o1, o2) {
- it.name
- }
+ if (o2.name == "General") return@Comparator 1 else compareValuesBy(o1, o2) { it.name }
}
}
}