diff options
| author | Kendell R <KTibow@users.noreply.github.com> | 2022-05-29 12:39:36 -0700 |
|---|---|---|
| committer | Kendell R <KTibow@users.noreply.github.com> | 2022-05-29 12:41:30 -0700 |
| commit | 93a32bbbd183cf45af10a7fbe9a5238585eb442e (patch) | |
| tree | 6ea38aeaca53beb1584007ae8268dcbe905269ee /src/main | |
| parent | 5da5f6efd284076b65d1811f13e1b4825c34d2db (diff) | |
| download | Chatting-93a32bbbd183cf45af10a7fbe9a5238585eb442e.tar.gz Chatting-93a32bbbd183cf45af10a7fbe9a5238585eb442e.tar.bz2 Chatting-93a32bbbd183cf45af10a7fbe9a5238585eb442e.zip | |
spam block v2
Diffstat (limited to 'src/main')
| -rw-r--r-- | src/main/kotlin/cc/woverflow/chatting/Chatting.kt | 137 | ||||
| -rw-r--r-- | src/main/kotlin/cc/woverflow/chatting/chat/ChatSpamBlock.kt | 124 | ||||
| -rw-r--r-- | src/main/kotlin/cc/woverflow/chatting/config/ChattingConfig.kt | 101 | ||||
| -rw-r--r-- | src/main/resources/spamInfo.json | 6146 |
4 files changed, 6446 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 } } } } diff --git a/src/main/resources/spamInfo.json b/src/main/resources/spamInfo.json new file mode 100644 index 0000000..8f5d44d --- /dev/null +++ b/src/main/resources/spamInfo.json @@ -0,0 +1,6146 @@ +{ + "0": { + "fine": 1.6680000000000001, + "spam": 0.23 + }, + "1": { + "fine": 5.668, + "spam": 14.23 + }, + "2": { + "fine": 9.668, + "spam": 3.23 + }, + "3": { + "fine": 4.668, + "spam": 9.23 + }, + "4": { + "fine": 0.668, + "spam": 7.23 + }, + "5": { + "fine": 3.668, + "spam": 14.23 + }, + "6": { + "fine": 3.668, + "spam": 2.23 + }, + "7": { + "fine": 4.668, + "spam": 5.23 + }, + "8": { + "fine": 0.668, + "spam": 1.23 + }, + "9": { + "fine": 2.668, + "spam": 1.23 + }, + "10": { + "fine": 1.6680000000000001, + "spam": 2.23 + }, + "11": { + "fine": 1.6680000000000001, + "spam": 0.23 + }, + "12": { + "fine": 0.668, + "spam": 2.23 + }, + "15": { + "fine": 0.668, + "spam": 2.23 + }, + "16": { + "fine": 1.6680000000000001, + "spam": 0.23 + }, + "18": { + "fine": 0.668, + "spam": 1.23 + }, + "20": { + "fine": 3.668, + "spam": 0.23 + }, + "25": { + "fine": 0.668, + "spam": 1.23 + }, + "29": { + "fine": 1.6680000000000001, + "spam": 0.23 + }, + "30": { + "fine": 1.6680000000000001, + "spam": 0.23 + }, + "32": { + "fine": 1.6680000000000001, + "spam": 0.23 + }, + "36": { + "fine": 1.6680000000000001, + "spam": 0.23 + }, + "50": { + "fine": 0.668, + "spam": 1.23 + }, + "85": { + "fine": 0.668, + "spam": 1.23 + }, + "92": { + "fine": 0.668, + "spam": 1.23 + }, + "99": { + "fine": 0.668, + "spam": 1.23 + }, + "100": { + "fine": 2.668, + "spam": 1.23 + }, + "108": { + "fine": 1.6680000000000001, + "spam": 0.23 + }, + "123": { + "fine": 0.668, + "spam": 1.23 + }, + "280": { + "fine": 1.6680000000000001, + "spam": 0.23 + }, + "345": { + "fine": 0.668, + "spam": 1.23 + }, + "350": { + "fine": 1.6680000000000001, + "spam": 0.23 + }, + "374": { + "fine": 1.6680000000000001, + "spam": 0.23 + }, + "500": { + "fine": 0.668, + "spam": 3.23 + }, + "553": { + "fine": 1.6680000000000001, + "spam": 0.23 + }, + "4078": { + "fine": 1.6680000000000001, + "spam": 0.23 + }, + "8945": { + "fine": 0.668, + "spam": 1.23 + }, + "34598": { + "fine": 0.668, + "spam": 1.23 + }, + "172000": { + "fine": 0.668, + "spam": 1.23 + }, + "1999999": { + "fine": 0.668, + "spam": 1.23 + }, + "84758934": { + "fine": 0.668, + "spam": 1.23 + }, + "then": { + "fine": 3.668, + "spam": 1.23 + }, + "report": { + "fine": 2.668, + "spam": 0.23 + }, + "in": { + "fine": 19.668, + "spam": 16.23 + }, + "the": { + "fine": 49.668, + "spam": 9.23 + }, + "skyblockz": { + "fine": 1.6680000000000001, + "spam": 0.23 + }, + "discord": { + "fine": 1.6680000000000001, + "spam": 2.23 + }, + "MEDIUM_LENGTH": { + "fine": 137.668, + "spam": 66.23 + }, + "LOW_SPECIAL_CHARS": { + "fine": 531.668, + "spam": 57.230000000000004 + }, + "LOW_CAPS": { + "fine": 645.668, + "spam": 175.23000000000002 + }, + "money": { + "fine": 7.668, + "spam": 9.23 + }, + "at": { + "fine": 10.668, + "spam": 19.23 + }, + "least": { + "fine": 0.668, + "spam": 1.23 + }, + "50m": { + "fine": 0.668, + "spam": 3.23 + }, + "SMALL_LENGTH": { + "fine": 153.668, + "spam": 18.23 + }, + "SPECIAL_CHAR": { + "fine": 116.668, + "spam": 77.23 + }, + "im": { + "fine": 16.668, + "spam": 6.23 + }, + "on": { + "fine": 11.668, + "spam": 46.230000000000004 + }, + "ironman": { + "fine": 1.6680000000000001, + "spam": 0.23 + }, + "just": { + "fine": 18.668, + "spam": 6.23 + }, + "check": { + "fine": 3.668, + "spam": 1.23 + }, + "ah": { + "fine": 5.668, + "spam": 45.230000000000004 + }, + "": { + "fine": 10.668, + "spam": 0.23 + }, + "TINY_LENGTH": { + "fine": 301.668, + "spam": 1.23 + }, + "no": { + "fine": 16.668, + "spam": 11.23 + }, + "comprende": { + "fine": 1.6680000000000001, + "spam": 0.23 + }, + "let": { + "fine": 4.668, + "spam": 0.23 + }, + "me": { + "fine": 19.668, + "spam": 64.23 + }, + "recipe": { + "fine": 2.668, + "spam": 0.23 + }, + "anyone": { + "fine": 9.668, + "spam": 12.23 + }, + "selling": { + "fine": 2.668, + "spam": 41.230000000000004 + }, + "a": { + "fine": 35.668, + "spam": 28.23 + }, + "god": { + "fine": 2.668, + "spam": 1.23 + }, + "pot": { + "fine": 0.668, + "spam": 1.23 + }, + "sorry": { + "fine": 3.668, + "spam": 0.23 + }, + "bro": { + "fine": 4.668, + "spam": 0.23 + }, + "nonist": { + "fine": 1.6680000000000001, + "spam": 0.23 + }, + "i": { + "fine": 76.668, + "spam": 25.23 + }, + "added": { + "fine": 2.668, + "spam": 0.23 + }, + "some": { + "fine": 6.668, + "spam": 3.23 + }, + "more": { + "fine": 7.668, + "spam": 1.23 + }, + "debug": { + "fine": 2.668, + "spam": 0.23 + }, + "get": { + "fine": 17.668, + "spam": 15.23 + }, + "new": { + "fine": 5.668, + "spam": 2.23 + }, + "pick": { + "fine": 3.668, + "spam": 0.23 + }, + "how": { + "fine": 16.668, + "spam": 2.23 + }, + "much": { + "fine": 8.668, + "spam": 1.23 + }, + "aight": { + "fine": 2.668, + "spam": 0.23 + }, + "neu": { + "fine": 2.668, + "spam": 0.23 + }, + "has": { + "fine": 4.668, + "spam": 1.23 + }, + "tons": { + "fine": 1.6680000000000001, + "spam": 2.23 + }, + "of": { + "fine": 18.668, + "spam": 16.23 + }, + "qol": { + "fine": 1.6680000000000001, + "spam": 0.23 + }, + "features": { + "fine": 1.6680000000000001, + "spam": 0.23 + }, + "and": { + "fine": 22.668, + "spam": 21.23 + }, + "my": { + "fine": 20.668, + "spam": 55.230000000000004 + }, + "raider": { + "fine": 2.668, + "spam": 0.23 + }, + "axe": { + "fine": 3.668, + "spam": 1.23 + }, + "k": { + "fine": 4.668, + "spam": 1.23 + }, + "when": { + "fine": 3.668, + "spam": 0.23 + }, + "tried": { + "fine": 1.6680000000000001, + "spam": 0.23 + }, + "to": { + "fine": 40.668, + "spam": 22.23 + }, + "apply": { + "fine": 1.6680000000000001, + "spam": 1.23 + }, + "it": { + "fine": 33.668, + "spam": 7.23 + }, + "HIGH_CAPS": { + "fine": 23.668, + "spam": 55.230000000000004 + }, + "minon": { + "fine": 1.6680000000000001, + "spam": 0.23 + }, + "underpriced": { + "fine": 0.668, + "spam": 1.23 + }, + "lowest": { + "fine": 0.668, + "spam": 1.23 + }, + "bin": { + "fine": 0.668, + "spam": 3.23 + }, + "gemstone": { + "fine": 0.668, + "spam": 1.23 + }, + "container": { + "fine": 0.668, + "spam": 1.23 + }, + "/": { + "fine": 2.668, + "spam": 6.23 + }, + "1guana": { + "fine": 0.668, + "spam": 1.23 + }, + "easy": { + "fine": 0.668, + "spam": 3.23 + }, + "flip": { + "fine": 3.668, + "spam": 10.23 + }, + "LONG_LENGTH": { + "fine": 77.668, + "spam": 145.23000000000002 + }, + "SPECIAL_CHARS": { + "fine": 21.668, + "spam": 96.23 + }, + "cheap": { + "fine": 0.668, + "spam": 11.23 + }, + "bal": { + "fine": 1.6680000000000001, + "spam": 1.23 + }, + "pet": { + "fine": 2.668, + "spam": 1.23 + }, + "its": { + "fine": 11.668, + "spam": 1.23 + }, + "high": { + "fine": 1.6680000000000001, + "spam": 3.23 + }, + "level": { + "fine": 0.668, + "spam": 5.23 + }, + "too": { + "fine": 6.668, + "spam": 1.23 + }, + "do": { + "fine": 23.668, + "spam": 5.23 + }, + "you": { + "fine": 47.668, + "spam": 14.23 + }, + "support": { + "fine": 0.668, + "spam": 2.23 + }, + "lgtbq": { + "fine": 0.668, + "spam": 1.23 + }, + "vote": { + "fine": 0.668, + "spam": 5.23 + }, + "_____________": { + "fine": 0.668, + "spam": 1.23 + }, + "freshest": { + "fine": 0.668, + "spam": 1.23 + }, + "cuts": { + "fine": 0.668, + "spam": 1.23 + }, + "island": { + "fine": 3.668, + "spam": 8.23 + }, + "/visit": { + "fine": 2.668, + "spam": 27.23 + }, + "benedeqn": { + "fine": 0.668, + "spam": 1.23 + }, + "buying": { + "fine": 1.6680000000000001, + "spam": 21.23 + }, + "sorrow": { + "fine": 0.668, + "spam": 1.23 + }, + "leggings": { + "fine": 0.668, + "spam": 3.23 + }, + "for": { + "fine": 22.668, + "spam": 48.230000000000004 + }, + "m": { + "fine": 6.668, + "spam": 3.23 + }, + "/p": { + "fine": 0.668, + "spam": 32.230000000000004 + }, + "smithmonger": { + "fine": 1.6680000000000001, + "spam": 0.23 + }, + "nobody": { + "fine": 1.6680000000000001, + "spam": 0.23 + }, + "ive": { + "fine": 1.6680000000000001, + "spam": 0.23 + }, + "been": { + "fine": 2.668, + "spam": 0.23 + }, + "looking": { + "fine": 1.6680000000000001, + "spam": 0.23 + }, + "exotic": { + "fine": 1.6680000000000001, + "spam": 0.23 + }, + "squid": { + "fine": 1.6680000000000001, + "spam": 0.23 + }, + "leather": { + "fine": 1.6680000000000001, + "spam": 0.23 + }, + "books": { + "fine": 2.668, + "spam": 2.23 + }, + "not": { + "fine": 13.668, + "spam": 2.23 |
