aboutsummaryrefslogtreecommitdiff
path: root/src/main/kotlin/org/polyfrost/chatting/chat
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/kotlin/org/polyfrost/chatting/chat')
-rw-r--r--src/main/kotlin/org/polyfrost/chatting/chat/ChatRegexes.kt11
-rw-r--r--src/main/kotlin/org/polyfrost/chatting/chat/ChatScrollingHook.kt5
-rw-r--r--src/main/kotlin/org/polyfrost/chatting/chat/ChatSearchingManager.kt42
-rw-r--r--src/main/kotlin/org/polyfrost/chatting/chat/ChatShortcuts.kt79
-rw-r--r--src/main/kotlin/org/polyfrost/chatting/chat/ChatSpamBlock.kt124
-rw-r--r--src/main/kotlin/org/polyfrost/chatting/chat/ChatTab.kt112
-rw-r--r--src/main/kotlin/org/polyfrost/chatting/chat/ChatTabs.kt354
-rw-r--r--src/main/kotlin/org/polyfrost/chatting/chat/ChatTabsJson.kt15
8 files changed, 742 insertions, 0 deletions
diff --git a/src/main/kotlin/org/polyfrost/chatting/chat/ChatRegexes.kt b/src/main/kotlin/org/polyfrost/chatting/chat/ChatRegexes.kt
new file mode 100644
index 0000000..0d6909e
--- /dev/null
+++ b/src/main/kotlin/org/polyfrost/chatting/chat/ChatRegexes.kt
@@ -0,0 +1,11 @@
+package org.polyfrost.chatting.chat
+
+data class ChatRegexes(val regexList: List<String>?) {
+ val compiledRegexList: MutableList<Regex> = arrayListOf()
+
+ init {
+ regexList?.forEach {
+ compiledRegexList.add(Regex(it))
+ }
+ }
+}
diff --git a/src/main/kotlin/org/polyfrost/chatting/chat/ChatScrollingHook.kt b/src/main/kotlin/org/polyfrost/chatting/chat/ChatScrollingHook.kt
new file mode 100644
index 0000000..982329a
--- /dev/null
+++ b/src/main/kotlin/org/polyfrost/chatting/chat/ChatScrollingHook.kt
@@ -0,0 +1,5 @@
+package org.polyfrost.chatting.chat
+
+object ChatScrollingHook {
+ var shouldSmooth = false
+} \ No newline at end of file
diff --git a/src/main/kotlin/org/polyfrost/chatting/chat/ChatSearchingManager.kt b/src/main/kotlin/org/polyfrost/chatting/chat/ChatSearchingManager.kt
new file mode 100644
index 0000000..d20a358
--- /dev/null
+++ b/src/main/kotlin/org/polyfrost/chatting/chat/ChatSearchingManager.kt
@@ -0,0 +1,42 @@
+package org.polyfrost.chatting.chat
+
+import cc.polyfrost.oneconfig.libs.caffeine.cache.Cache
+import cc.polyfrost.oneconfig.libs.caffeine.cache.Caffeine
+import cc.polyfrost.oneconfig.libs.universal.wrappers.message.UTextComponent
+import net.minecraft.client.gui.ChatLine
+import java.util.concurrent.LinkedBlockingQueue
+import java.util.concurrent.ThreadPoolExecutor
+import java.util.concurrent.TimeUnit
+import java.util.concurrent.atomic.AtomicInteger
+
+object ChatSearchingManager {
+ private var counter: AtomicInteger = AtomicInteger(0)
+ private var POOL: ThreadPoolExecutor = ThreadPoolExecutor(
+ 50, 50,
+ 0L, TimeUnit.SECONDS,
+ LinkedBlockingQueue()
+ ) { r ->
+ Thread(
+ r,
+ "Chat Filter Cache Thread ${counter.incrementAndGet()}"
+ )
+ }
+
+ @JvmStatic
+ val cache: Cache<String, List<ChatLine>> = Caffeine.newBuilder().executor(POOL).maximumSize(5000).build()
+
+ var lastSearch = ""
+
+ @JvmStatic
+ fun filterMessages(text: String, list: List<ChatLine>): List<ChatLine>? {
+ if (text.isBlank()) return list
+ val cached = cache.getIfPresent(text)
+ return cached ?: run {
+ cache.put(text, list.filter {
+ UTextComponent.stripFormatting(it.chatComponent.unformattedText).lowercase()
+ .contains(text.lowercase())
+ })
+ cache.getIfPresent(text)
+ }
+ }
+} \ No newline at end of file
diff --git a/src/main/kotlin/org/polyfrost/chatting/chat/ChatShortcuts.kt b/src/main/kotlin/org/polyfrost/chatting/chat/ChatShortcuts.kt
new file mode 100644
index 0000000..0c85553
--- /dev/null
+++ b/src/main/kotlin/org/polyfrost/chatting/chat/ChatShortcuts.kt
@@ -0,0 +1,79 @@
+package org.polyfrost.chatting.chat
+
+import cc.polyfrost.oneconfig.config.core.ConfigUtils
+import org.polyfrost.chatting.Chatting
+import com.google.gson.JsonObject
+import com.google.gson.JsonParser
+import java.io.File
+
+object ChatShortcuts {
+ private val oldShortcutsFile = File(Chatting.oldModDir, "chatshortcuts.json")
+ private val shortcutsFile = ConfigUtils.getProfileFile("chatshortcuts.json")
+ private val PARSER = JsonParser()
+
+ private var initialized = false
+
+ val shortcuts = object : ArrayList<Pair<String, String>>() {
+ private val comparator = Comparator<Pair<String, String>> { o1, o2 ->
+ return@Comparator o2.first.length.compareTo(o1.first.length)
+ }
+
+ override fun add(element: Pair<String, String>): Boolean {
+ val value = super.add(element)
+ sortWith(comparator)
+ return value
+ }
+ }
+
+ fun initialize() {
+ if (initialized) {
+ return
+ } else {
+ initialized = true
+ }
+ if (shortcutsFile.exists()) {
+ try {
+ val jsonObj = PARSER.parse(shortcutsFile.readText()).asJsonObject
+ for (shortcut in jsonObj.entrySet()) {
+ shortcuts.add(shortcut.key to shortcut.value.asString)
+ }
+ return
+ } catch (_: Throwable) {
+ shortcutsFile.renameTo(File(shortcutsFile.parentFile, "chatshortcuts.json.bak"))
+ }
+ }
+ shortcutsFile.createNewFile()
+ if (oldShortcutsFile.exists()) {
+ shortcutsFile.writeText(
+ oldShortcutsFile.readText()
+ )
+ } else {
+ shortcutsFile.writeText(JsonObject().toString())
+ }
+ }
+
+ fun removeShortcut(key: String) {
+ shortcuts.removeIf { it.first == key }
+ val jsonObj = PARSER.parse(shortcutsFile.readText()).asJsonObject
+ jsonObj.remove(key)
+ shortcutsFile.writeText(jsonObj.toString())
+ }
+
+ fun writeShortcut(key: String, value: String) {
+ shortcuts.add(key to value)
+ val jsonObj = PARSER.parse(shortcutsFile.readText()).asJsonObject
+ jsonObj.addProperty(key, value)
+ shortcutsFile.writeText(jsonObj.toString())
+ }
+
+ fun handleSentCommand(command: String): String {
+ shortcuts.forEach {
+ if (command == it.first || (command.startsWith(it.first) && command.substringAfter(it.first)
+ .startsWith(" "))
+ ) {
+ return command.replaceFirst(it.first, it.second)
+ }
+ }
+ return command
+ }
+} \ No newline at end of file
diff --git a/src/main/kotlin/org/polyfrost/chatting/chat/ChatSpamBlock.kt b/src/main/kotlin/org/polyfrost/chatting/chat/ChatSpamBlock.kt
new file mode 100644
index 0000000..da5dde8
--- /dev/null
+++ b/src/main/kotlin/org/polyfrost/chatting/chat/ChatSpamBlock.kt
@@ -0,0 +1,124 @@
+package org.polyfrost.chatting.chat
+
+import org.polyfrost.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
+ }
+}
diff --git a/src/main/kotlin/org/polyfrost/chatting/chat/ChatTab.kt b/src/main/kotlin/org/polyfrost/chatting/chat/ChatTab.kt
new file mode 100644
index 0000000..bd65f11
--- /dev/null
+++ b/src/main/kotlin/org/polyfrost/chatting/chat/ChatTab.kt
@@ -0,0 +1,112 @@
+package org.polyfrost.chatting.chat
+
+import org.polyfrost.chatting.gui.components.TabButton
+import com.google.gson.annotations.SerializedName
+import net.minecraft.client.Minecraft
+import net.minecraft.util.EnumChatFormatting
+import net.minecraft.util.IChatComponent
+import java.util.*
+
+data class ChatTab(
+ val enabled: Boolean,
+ val name: String,
+ val unformatted: Boolean,
+ val lowercase: Boolean?,
+ @SerializedName("starts") val startsWith: List<String>?,
+ val contains: List<String>?,
+ @SerializedName("ends") val endsWith: List<String>?,
+ val equals: List<String>?,
+ @SerializedName("regex") val uncompiledRegex: List<String>?,
+ @SerializedName("ignore_starts") val ignoreStartsWith: List<String>?,
+ @SerializedName("ignore_contains") val ignoreContains: List<String>?,
+ @SerializedName("ignore_ends") val ignoreEndsWith: List<String>?,
+ @SerializedName("ignore_equals") val ignoreEquals: List<String>?,
+ @SerializedName("ignore_regex") val uncompiledIgnoreRegex: List<String>?,
+ val color: Int?,
+ @SerializedName("hovered_color") val hoveredColor: Int?,
+ @SerializedName("selected_color") val selectedColor: Int?,
+ val prefix: String?,
+) {
+ lateinit var button: TabButton
+ lateinit var compiledRegex: ChatRegexes
+ lateinit var compiledIgnoreRegex: ChatRegexes
+
+ //Ugly hack to make GSON not make button / regex null
+ fun initialize() {
+ compiledRegex = ChatRegexes(uncompiledRegex)
+ compiledIgnoreRegex = ChatRegexes(uncompiledIgnoreRegex)
+ val width = Minecraft.getMinecraft().fontRendererObj.getStringWidth(name)
+ button = TabButton(653452, run {
+ val returnValue = x - 2
+ x += 6 + width
+ return@run returnValue
+ }, width + 4, 12, this)
+ }
+
+ fun shouldRender(chatComponent: IChatComponent): Boolean {
+ val message =
+ (if (unformatted) EnumChatFormatting.getTextWithoutFormattingCodes(chatComponent.unformattedText) else chatComponent.formattedText).let {
+ if (lowercase == true) it.lowercase(
+ Locale.ENGLISH
+ ) else it
+ }
+ ignoreStartsWith?.forEach {
+ if (message.startsWith(it)) {
+ return false
+ }
+ }
+ ignoreEquals?.forEach {
+ if (message == it) {
+ return false
+ }
+ }
+ ignoreEndsWith?.forEach {
+ if (message.endsWith(it)) {
+ return false
+ }
+ }
+ ignoreContains?.forEach {
+ if (message.contains(it)) {
+ return false
+ }
+ }
+ compiledIgnoreRegex.compiledRegexList.forEach {
+ if (it.matches(message)) {
+ return false
+ }
+ }
+ if (startsWith.isNullOrEmpty() && equals.isNullOrEmpty() && endsWith.isNullOrEmpty() && contains.isNullOrEmpty() && uncompiledRegex.isNullOrEmpty()) {
+ return true
+ }
+ equals?.forEach {
+ if (message == it) {
+ return true
+ }
+ }
+ startsWith?.forEach {
+ if (message.startsWith(it)) {
+ return true
+ }
+ }
+ endsWith?.forEach {
+ if (message.endsWith(it)) {
+ return true
+ }
+ }
+ contains?.forEach {
+ if (message.contains(it)) {
+ return true
+ }
+ }
+ compiledRegex.compiledRegexList.forEach {
+ if (it.matches(message)) {
+ return true
+ }
+ }
+ return false
+ }
+
+ companion object {
+ private var x = 4
+ }
+}
diff --git a/src/main/kotlin/org/polyfrost/chatting/chat/ChatTabs.kt b/src/main/kotlin/org/polyfrost/chatting/chat/ChatTabs.kt
new file mode 100644
index 0000000..b46f55d
--- /dev/null
+++ b/src/main/kotlin/org/polyfrost/chatting/chat/ChatTabs.kt
@@ -0,0 +1,354 @@
+package org.polyfrost.chatting.chat
+
+import cc.polyfrost.oneconfig.config.core.ConfigUtils
+import org.polyfrost.chatting.Chatting
+import org.polyfrost.chatting.gui.components.TabButton
+import com.google.gson.GsonBuilder
+import com.google.gson.JsonArray
+import com.google.gson.JsonObject
+import com.google.gson.JsonParser
+import com.google.gson.JsonPrimitive
+import net.minecraft.client.Minecraft
+import net.minecraft.util.IChatComponent
+import java.io.File
+
+object ChatTabs {
+ private val GSON = GsonBuilder().setPrettyPrinting().create()
+ private val PARSER = JsonParser()
+ val tabs = arrayListOf<ChatTab>()
+ var currentTabs: ArrayList<ChatTab?> = object : ArrayList<ChatTab?>() {
+ override fun add(element: ChatTab?): Boolean {
+ if (element == null) return false
+ val returnValue = super.add(element)
+ if (Minecraft.getMinecraft().theWorld != null && returnValue) {
+ Minecraft.getMinecraft().ingameGUI.chatGUI.refreshChat()
+ }
+ return returnValue
+ }
+ }
+ var hasCancelledAnimation = false
+ private var initialized = false
+
+ private val tabFile = ConfigUtils.getProfileFile("chattabs.json")
+ private val oldTabFile = File(Chatting.oldModDir, "chattabs.json")
+
+ fun initialize() {
+ if (initialized) {
+ return
+ } else {
+ initialized = true
+ }
+ if (!tabFile.exists()) {
+ if (oldTabFile.exists()) {
+ tabFile.writeText(oldTabFile.readText())
+ handleFile()
+ } else {
+ generateNewFile()
+ }
+ } else {
+ handleFile()
+ }
+ tabs.forEach {
+ it.initialize()
+ }
+ currentTabs.clear()
+ currentTabs.add(tabs[0])
+ }
+
+ private fun handleFile() {
+ try {
+ val chatTabJson = GSON.fromJson(tabFile.readText(), ChatTabsJson::class.java)
+ when (chatTabJson.version) {
+ 1 -> {
+ // ver 2 adds `enabled`
+ chatTabJson.tabs.forEach {
+ applyVersion2Changes(it.asJsonObject)
+ applyVersion3Changes(it.asJsonObject)
+ applyVersion4Changes(it.asJsonObject)
+ applyVersion5Changes(it.asJsonObject)
+ applyVersion6Changes(it.asJsonObject)
+ }
+ chatTabJson.version = ChatTabsJson.VERSION
+ tabFile.writeText(GSON.toJson(chatTabJson))
+ }
+ 2 -> {
+ // ver 3 adds ignore_
+ chatTabJson.tabs.forEach {
+ applyVersion3Changes(it.asJsonObject)
+ applyVersion4Changes(it.asJsonObject)
+ applyVersion5Changes(it.asJsonObject)
+ applyVersion6Changes(it.asJsonObject)
+ }
+ chatTabJson.version = ChatTabsJson.VERSION
+ tabFile.writeText(GSON.toJson(chatTabJson))
+ }
+ 3 -> {
+ // ver 4 adds color options
+ chatTabJson.tabs.forEach {
+ applyVersion4Changes(it.asJsonObject)
+ applyVersion5Changes(it.asJsonObject)
+ applyVersion6Changes(it.asJsonObject)
+ }
+ chatTabJson.version = ChatTabsJson.VERSION
+ tabFile.writeText(GSON.toJson(chatTabJson))
+ }
+ 4 -> {
+ // ver 5 adds lowercase
+ chatTabJson.tabs.forEach {
+ applyVersion5Changes(it.asJsonObject)
+ applyVersion6Changes(it.asJsonObject)
+ }
+ chatTabJson.version = ChatTabsJson.VERSION
+ tabFile.writeText(GSON.toJson(chatTabJson))
+ }
+ 5 -> {
+ // ver 6 changes pm regex
+ chatTabJson.tabs.forEach {
+ applyVersion6Changes(it.asJsonObject)
+ }
+ chatTabJson.version = ChatTabsJson.VERSION
+ tabFile.writeText(GSON.toJson(chatTabJson))
+ }
+ }
+ chatTabJson.tabs.forEach {
+ val chatTab = GSON.fromJson(it.toString(), ChatTab::class.java)
+ if (chatTab.enabled) {
+ tabs.add(chatTab)
+ }
+ }
+ } catch (e: Throwable) {
+ e.printStackTrace()
+ tabFile.delete()
+ generateNewFile()
+ }
+ }
+
+ private fun applyVersion2Changes(json: JsonObject) {
+ json.addProperty("enabled", true)
+ }
+
+ private fun applyVersion3Changes(json: JsonObject) {
+ json.add("ignore_starts", JsonArray())
+ json.add("ignore_contains", JsonArray())
+ json.add("ignore_ends", JsonArray())
+ json.add("ignore_equals", JsonArray())
+ json.add("ignore_regex", JsonArray())
+ }
+
+ private fun applyVersion4Changes(json: JsonObject) {
+ json.addProperty("color", TabButton.color)
+ json.addProperty("hovered_color", TabButton.hoveredColor)
+ json.addProperty("selected_color", TabButton.selectedColor)
+ }
+
+ private fun applyVersion5Changes(json: JsonObject) {
+ json.addProperty("lowercase", false)
+ }
+
+ private fun applyVersion6Changes(json: JsonObject) {
+ if (json.has("starts")) {
+ val starts = json["starts"].asJsonArray
+ var detected = false
+ starts.iterator().let {
+ while (it.hasNext()) {
+ when (it.next().asString) {
+ "To " -> {
+ detected = true
+ it.remove()
+ }
+ "From " -> {
+ detected = true
+ it.remove()
+ }
+ }
+ }
+ }
+ if (detected) {
+ json.add("regex", JsonArray().apply {
+ add(JsonPrimitive("^(?<type>§dTo|§dFrom) (?<prefix>.+): §r(?<message>.*)(?:§r)?\$"))
+ })
+ json.remove("unformatted")
+ json.addProperty("unformatted", false)
+ }
+ }
+ if (json.has("ends")) {
+ val ends = json["ends"].asJsonArray
+ var detected = false
+ ends.iterator().let {
+ while (it.hasNext()) {
+ when (it.next().asString) {
+ "§r§ehas invited you to join their party!", -> {
+ detected = true
+ it.remove()
+ }
+ }
+ }
+ }
+ if (detected) {
+ json.add("contains", JsonArray().apply {
+ add(JsonPrimitive("§r§ehas invited you to join their party!"))
+ })
+ }
+ }
+ }
+
+ fun shouldRender(message: IChatComponent): Boolean {
+ if (currentTabs.isEmpty()) return true
+ for (tab in currentTabs) {
+ if (tab?.shouldRender(message) == true) {
+ return true
+ }
+ }
+ return false
+ }
+
+ private fun generateNewFile() {
+ tabFile.createNewFile()
+ val jsonObject = JsonObject()
+ val defaultTabs = generateDefaultTabs()
+ jsonObject.add("tabs", defaultTabs)
+ jsonObject.addProperty("version", ChatTabsJson.VERSION)
+ tabFile.writeText(GSON.toJson(jsonObject))
+ }
+
+ private fun generateDefaultTabs(): JsonArray {
+ val all = ChatTab(
+ true,
+ "ALL",
+ unformatted = false,
+ lowercase = false,
+ startsWith = null,
+ contains = null,
+ endsWith = null,
+ equals = null,
+ uncompiledRegex = null,
+ ignoreStartsWith = null,
+ ignoreContains = null,
+ ignoreEndsWith = null,
+ ignoreEquals = null,
+ uncompiledIgnoreRegex = null,
+ color = TabButton.color,
+ hoveredColor = TabButton.hoveredColor,
+ selectedColor = TabButton.selectedColor,
+ prefix = ""
+ )
+ val party = ChatTab(
+ true,
+ "PARTY",
+ unformatted = false,
+ lowercase = false,
+ startsWith = listOf("§r§9Party §8> ", "§r§9P §8> ", "§eThe party was transferred to §r", "§eKicked §r"),
+ contains = listOf("§r§ehas invited you to join their party!"),
+ endsWith = listOf(
+ "§r§eto the party! They have §r§c60 §r§eseconds to accept.§r",
+ "§r§ehas disbanded the party!§r",
+ "§r§ehas disconnected, they have §r§c5 §r§eminutes to rejoin before they are removed from the party.§r",
+ " §r§ejoined the party.§r",
+ " §r§ehas left the party.§r",
+ " §r§ehas been removed from the party.§r",
+ "§r§e because they were offline.§r"
+ ),
+ equals = listOf("§cThe party was disbanded because all invites expired and the party was empty§r"),
+ uncompiledRegex = listOf( //regexes from https://github.com/kwevin/Hychat-Tabs/blob/main/tabs/re-add%20prefixes%20%26%20fix%20shortened%20tags/chat.json cause i cant write regex
+ "(§r)*(§9Party §8\u003e)+(.*)",
+ "(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP)|(?:VIP§r§6\\+)|(?:MVP)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+\\+)|(?:(?:§r)?§fYOUTUBE))(?:§r)?(?:(?:§[a-zA-Z0-9]))?\\] [a-zA-Z0-9_]+|§7[a-zA-Z0-9_]+) §r§einvited §r(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP)|(?:VIP§r§6\\+)|(?:MVP)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+\\+)|(?:(?:§r)?§fYOUTUBE))(?:§r)?(?:(?:§[a-zA-Z0-9]))?\\] [a-zA-Z0-9_]+|§7[a-zA-Z0-9_]+) §r§eto the party! They have §r§c60 §r§eseconds to accept\\.§r",
+ "(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP)|(?:VIP§r§6\\+)|(?:MVP)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+\\+)|(?:(?:§r)?§fYOUTUBE))(?:§r)?(?:(?:§[a-zA-Z0-9]))?\\] [a-zA-Z0-9_]+|§7[a-zA-Z0-9_]+) §r§ehas left the party\\.§r",
+ "(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP)|(?:VIP§r§6\\+)|(?:MVP)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+\\+)|(?:(?:§r)?§fYOUTUBE))(?:§r)?(?:(?:§[a-zA-Z0-9]))?\\] [a-zA-Z0-9_]+|§7[a-zA-Z0-9_]+) §r§ejoined the party\\.§r",
+ "§eYou left the party\\.§r",
+ "§eYou have joined §r(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP)|(?:VIP§r§6\\+)|(?:MVP)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+\\+)|(?:(?:§r)?§fYOUTUBE))(?:§r)?(?:(?:§[a-zA-Z0-9]))?\\] [a-zA-Z0-9_]+|§7[a-zA-Z0-9_]+)\u0027s §r§eparty!§r",
+ "§cThe party was disbanded because all invites expired and the party was empty§r",
+ "§cYou cannot invite that player since they\u0027re not online\\.§r",
+ "§eThe party leader, §r(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP)|(?:VIP§r§6\\+)|(?:MVP)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+\\+)|(?:(?:§r)?§fYOUTUBE))(?:§r)?(?:(?:§[a-zA-Z0-9]))?\\] [a-zA-Z0-9_]+|§7[a-zA-Z0-9_]+)§r§e, warped you to §r(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP)|(?:VIP§r§6\\+)|(?:MVP)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+\\+)|(?:(?:§r)?§fYOUTUBE))(?:§r)?(?:(?:§[a-zA-Z0-9]))?\\] [a-zA-Z0-9_]+|§7[a-zA-Z0-9_]+)§r§e\u0027s house\\.§r",
+ "§eSkyBlock Party Warp §r§7\\([0-9]+ players?\\)§r",
+ "§a. §r(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP)|(?:VIP§r§6\\+)|(?:MVP)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+\\+)|(?:(?:§r)?§fYOUTUBE))(?:§r)?(?:(?:§[a-zA-Z0-9]))?\\] [a-zA-Z0-9_]+|§7[a-zA-Z0-9_]+)§r§f §r§awarped to your server§r",
+ "§eYou summoned §r(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP)|(?:VIP§r§6\\+)|(?:MVP)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+\\+)|(?:(?:§r)?§fYOUTUBE))(?:§r)?(?:(?:§[a-zA-Z0-9]))?\\] [a-zA-Z0-9_]+|§7[a-zA-Z0-9_]+)§r§f §r§eto your server\\.§r",
+ "§eThe party leader, §r(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP)|(?:VIP§r§6\\+)|(?:MVP)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+\\+)|(?:(?:§r)?§fYOUTUBE))(?:§r)?(?:(?:§[a-zA-Z0-9]))?\\] [a-zA-Z0-9_]+|§7[a-zA-Z0-9_]+)§r§e, warped you to their house\\.§r",
+ "(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP)|(?:VIP§r§6\\+)|(?:MVP)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+\\+)|(?:(?:§r)?§fYOUTUBE))(?:§r)?(?:(?:§[a-zA-Z0-9]))?\\] [a-zA-Z0-9_]+|§7[a-zA-Z0-9_]+) §r§aenabled Private Game§r",
+ "(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP)|(?:VIP§r§6\\+)|(?:MVP)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+\\+)|(?:(?:§r)?§fYOUTUBE))(?:§r)?(?:(?:§[a-zA-Z0-9]))?\\] [a-zA-Z0-9_]+|§7[a-zA-Z0-9_]+) §r§cdisabled Private Game§r",
+ "§cThe party is now muted\\. §r",
+ "§aThe party is no longer muted\\.§r",
+ "§cThere are no offline players to remove\\.§r",
+ "(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP)|(?:VIP§r§6\\+)|(?:MVP)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+\\+)|(?:(?:§r)?§fYOUTUBE))(?:§r)?(?:(?:§[a-zA-Z0-9]))?\\] [a-zA-Z0-9_]+|§7[a-zA-Z0-9_]+) §r§ehas been removed from the party\\.§r",
+ "§eThe party was transferred to §r(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP)|(?:VIP§r§6\\+)|(?:MVP)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+\\+)|(?:(?:§r)?§fYOUTUBE))(?:§r)?(?:(?:§[a-zA-Z0-9]))?\\] [a-zA-Z0-9_]+|§7[a-zA-Z0-9_]+) §r§eby §r(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP)|(?:VIP§r§6\\+)|(?:MVP)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+\\+)|(?:(?:§r)?§fYOUTUBE))(?:§r)?(?:(?:§[a-zA-Z0-9]))?\\] [a-zA-Z0-9_]+|§7[a-zA-Z0-9_]+)§r",
+ "(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP)|(?:VIP§r§6\\+)|(?:MVP)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+\\+)|(?:(?:§r)?§fYOUTUBE))(?:§r)?(?:(?:§[a-zA-Z0-9]))?\\] [a-zA-Z0-9_]+|§7[a-zA-Z0-9_]+)§r§e has promoted §r(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP)|(?:VIP§r§6\\+)|(?:MVP)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+\\+)|(?:(?:§r)?§fYOUTUBE))(?:§r)?(?:(?:§[a-zA-Z0-9]))?\\] [a-zA-Z0-9_]+|§7[a-zA-Z0-9_]+) §r§eto Party Leader§r",
+ "(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP)|(?:VIP§r§6\\+)|(?:MVP)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+\\+)|(?:(?:§r)?§fYOUTUBE))(?:§r)?(?:(?:§[a-zA-Z0-9]))?\\] [a-zA-Z0-9_]+|§7[a-zA-Z0-9_]+)§r§e has promoted §r(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP)|(?:VIP§r§6\\+)|(?:MVP)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+\\+)|(?:(?:§r)?§fYOUTUBE))(?:§r)?(?:(?:§[a-zA-Z0-9]))?\\] [a-zA-Z0-9_]+|§7[a-zA-Z0-9_]+) §r§eto Party Moderator§r",
+ "(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP)|(?:VIP§r§6\\+)|(?:MVP)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+\\+)|(?:(?:§r)?§fYOUTUBE))(?:§r)?(?:(?:§[a-zA-Z0-9]))?\\] [a-zA-Z0-9_]+|§7[a-zA-Z0-9_]+) §r§eis now a Party Moderator§r",
+ "(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP)|(?:VIP§r§6\\+)|(?:MVP)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+\\+)|(?:(?:§r)?§fYOUTUBE))(?:§r)?(?:(?:§[a-zA-Z0-9]))?\\] [a-zA-Z0-9_]+|§7[a-zA-Z0-9_]+)§r§e has demoted §r(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP)|(?:VIP§r§6\\+)|(?:MVP)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+\\+)|(?:(?:§r)?§fYOUTUBE))(?:§r)?(?:(?:§[a-zA-Z0-9]))?\\] [a-zA-Z0-9_]+|§7[a-zA-Z0-9_]+) §r§eto Party Member§r",
+ "§cYou can\u0027t demote yourself!§r",
+ "§6Party Members \\([0-9]+\\)§r",
+ "§eParty Leader: §r(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP)|(?:VIP§r§6\\+)|(?:MVP)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+\\+)|(?:(?:§r)?§fYOUTUBE))(?:§r)?(?:(?:§[a-zA-Z0-9]))?\\] [a-zA-Z0-9_]+|§7[a-zA-Z0-9_]+) ?§r(?:§[a-zA-Z0-9]).§r",
+ "§eParty Members: §r(?:(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP)|(?:VIP§r§6\\+)|(?:MVP)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+\\+)|(?:(?:§r)?§fYOUTUBE))(?:§r)?(?:(?:§[a-zA-Z0-9]))?\\] [a-zA-Z0-9_]+|§7[a-zA-Z0-9_]+)§r(?:§[a-zA-Z0-9]) . §r)+",
+ "§eParty Moderators: §r(?:(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP)|(?:VIP§r§6\\+)|(?:MVP)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+\\+)|(?:(?:§r)?§fYOUTUBE))(?:§r)?(?:(?:§[a-zA-Z0-9]))?\\] [a-zA-Z0-9_]+|§7[a-zA-Z0-9_]+)§r(?:§[a-zA-Z0-9]) . §r)+",
+ "§eThe party invite to §r(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP)|(?:VIP§r§6\\+)|(?:MVP)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+\\+)|(?:(?:§r)?§fYOUTUBE))(?:§r)?(?:(?:§[a-zA-Z0-9]))?\\] [a-zA-Z0-9_]+|§7[a-zA-Z0-9_]+) §r§ehas expired§r",
+ "(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP)|(?:VIP§r§6\\+)|(?:MVP)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+\\+)|(?:(?:§r)?§fYOUTUBE))(?:§r)?(?:(?:§[a-zA-Z0-9]))?\\] [a-zA-Z0-9_]+|§7[a-zA-Z0-9_]+) §r§cdisabled All Invite§r",
+ "(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP)|(?:VIP§r§6\\+)|(?:MVP)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+\\+)|(?:(?:§r)?§fYOUTUBE))(?:§r)?(?:(?:§[a-zA-Z0-9]))?\\] [a-zA-Z0-9_]+|§7[a-zA-Z0-9_]+) §r§aenabled All Invite§r",
+ "§cYou cannot invite that player\\.§r",
+ "§cYou are not allowed to invite players\\.§r",
+ "§eThe party leader, §r(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP)|(?:VIP§r§6\\+)|(?:MVP)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+\\+)|(?:(?:§r)?§fYOUTUBE))(?:§r)?(?:(?:§[a-zA-Z0-9]))?\\] [a-zA-Z0-9_]+|§7[a-zA-Z0-9_]+) §r§ehas disconnected, they have §r§c5 §r§eminutes to rejoin before the party is disbanded\\.§r",
+ "(?:(?:§[a-zA-Z0-9])*\\[(?:(?:VIP)|(?:VIP§r§6\\+)|(?:MVP)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+)|(?:MVP(?:§r)?(?:§[a-zA-Z0-9])\\+\\+)|(?:(?:§r)?§fYOUTUBE))(?:§r)?(?:(?:§[a-zA-Z0-9]))?\\] [a-zA-Z0-9_]+|§7[a-zA-Z0-9_]+) §r§ehas disconnected, they have §r§c5 §r§eminutes to rejoin before they are removed from the party.§r",
+ "§cYou are not in a party right now\\.§r",
+ "§cThis party is currently muted\\.§r",
+ "(§r)*(§9P §8\u003e)+(.*)"
+ ),
+ ignoreStartsWith = null,
+ ignoreContains = null,
+ ignoreEndsWith = null,
+ ignoreEquals = null,
+ uncompiledIgnoreRegex = null,
+ color = TabButton.color,
+ hoveredColor = TabButton.hoveredColor,
+ selectedColor = TabButton.selectedColor,
+ prefix = "/pc "
+ )
+ val guild = ChatTab(
+ true,
+ "GUILD",
+ unformatted = true,
+ lowercase = false,
+ startsWith = listOf("Guild >", "G >"),
+ contains = null,
+ endsWith = null,
+ equals = null,
+ uncompiledRegex = null,
+ ignoreStartsWith = null,
+ ignoreContains = null,
+ ignoreEndsWith = null,
+ ignoreEquals = null,
+ uncompiledIgnoreRegex = null,
+ color = TabButton.color,
+ hoveredColor = TabButton.hoveredColor,
+ selectedColor = TabButton.selectedColor,
+ prefix = "/gc "
+ )
+ val pm = ChatTab(
+ true,
+ "PM",
+ unformatted = false,
+ lowercase = false,
+ startsWith = null,
+ contains = null,
+ endsWith = null,
+ equals = null,
+ uncompiledRegex = listOf("^(?<type>§dTo|§dFrom) (?<prefix>.+): §r(?<message>.*)(?:§r)?\$"),
+ ignoreStartsWith = null,
+ ignoreContains = null,
+ ignoreEndsWith = null,
+ ignoreEquals = null,
+ uncompiledIgnoreRegex = null,
+ color = TabButton.color,
+ hoveredColor = TabButton.hoveredColor,
+ selectedColor = TabButton.selectedColor,
+ prefix = "/r "
+ )
+ tabs.add(all)
+ tabs.add(party)
+ tabs.add(guild)
+ tabs.add(pm)
+ val jsonArray = JsonArray()
+ jsonArray.add(PARSER.parse(GSON.toJson(all)).asJsonObject)
+ jsonArray.add(PARSER.parse(GSON.toJson(party)).asJsonObject)
+ jsonArray.add(PARSER.parse(GSON.toJson(guild)).asJsonObject)
+ jsonArray.add(PARSER.parse(GSON.toJson(pm)).asJsonObject)
+ return jsonArray
+ }
+}
diff --git a/src/main/kotlin/org/polyfrost/chatting/chat/ChatTabsJson.kt b/src/main/kotlin/org/polyfrost/chatting/chat/ChatTabsJson.kt
new file mode 100644
index 0000000..c5939c3
--- /dev/null
+++ b/src/main/kotlin/org/polyfrost/chatting/chat/ChatTabsJson.kt
@@ -0,0 +1,15 @@
+package org.polyfrost.chatting.chat
+
+import com.google.gson.JsonArray
+import com.google.gson.annotations.SerializedName
+
+data class ChatTabsJson(@SerializedName("tabs") val tabs: JsonArray, var version: Int) {
+
+ override fun toString(): String {
+ return "{\"tabs\": $tabs, \"version\": $version}"
+ }
+
+ companion object {
+ const val VERSION = 6
+ }
+} \ No newline at end of file