Skip to content

Commit

Permalink
feat: parse strings for emotes without serializing to components first
Browse files Browse the repository at this point in the history
  • Loading branch information
Boy0000 committed Jul 11, 2024
1 parent 4485728 commit fecffa4
Show file tree
Hide file tree
Showing 4 changed files with 96 additions and 52 deletions.
93 changes: 87 additions & 6 deletions core/src/main/kotlin/com/mineinabyss/emojy/EmojyHelpers.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,14 @@ val escapedSpaceRegex: Regex = "\\\\(:space_(-?\\d+):)".toRegex()
val colorableRegex: Regex = "\\|(c|colorable)".toRegex()
val bitmapIndexRegex: Regex = "\\|([0-9]+)".toRegex()

private val defaultKey = Key.key("default")
private val randomKey = Key.key("random")

val ORIGINAL_SIGN_FRONT_LINES = NamespacedKey.fromString("emojy:original_front_lines")!!
val ORIGINAL_SIGN_BACK_LINES = NamespacedKey.fromString("emojy:original_back_lines")!!
val ORIGINAL_ITEM_RENAME_TEXT = NamespacedKey.fromString("emojy:original_item_rename")!!

fun spaceString(space: Int) = "<font:${emojyConfig.spaceFont.asMinimalString()}>${Space.of(space)}</font>"
fun spaceComponent(space: Int) = Component.textOfChildren(Component.text(Space.of(space)).font(emojyConfig.spaceFont))

private fun Component.asFlatTextContent(): String {
Expand All @@ -36,6 +40,37 @@ private fun Component.asFlatTextContent(): String {
}
}

fun String.transformEmotes(insert: Boolean = false): String {
var content = this

for (emote in emojy.emotes) emote.baseRegex.findAll(this).forEach { match ->

val colorable = colorableRegex in match.value
val bitmapIndex = bitmapIndexRegex.find(match.value)?.groupValues?.get(1)?.toIntOrNull() ?: -1

content = content.replaceFirst(
emote.baseRegex, emote.formattedUnicode(
insert = insert,
colorable = colorable,
bitmapIndex = bitmapIndex
).serialize()
)
}

for (gif in emojy.gifs) gif.baseRegex.findAll(this).forEach { _ ->
content = content.replaceFirst(gif.baseRegex, gif.formattedUnicode(insert = insert).serialize())
}

spaceRegex.findAll(this).forEach { match ->
val space = match.groupValues[1].toIntOrNull() ?: return@forEach
val spaceRegex = "(?<!\\\\):space_(-?$space+):".toRegex()

content = content.replaceFirst(spaceRegex, spaceString(space))
}

return content
}

fun Component.transformEmotes(locale: Locale? = null, insert: Boolean = false): Component {
var component = GlobalTranslator.render(this, locale ?: Locale.US)
val serialized = component.asFlatTextContent()
Expand All @@ -49,11 +84,13 @@ fun Component.transformEmotes(locale: Locale? = null, insert: Boolean = false):
TextReplacementConfig.builder()
.match(emote.baseRegex.pattern).once()
.replacement(
Component.textOfChildren(emote.formattedUnicode(
insert = insert,
colorable = colorable,
bitmapIndex = bitmapIndex
))
Component.textOfChildren(
emote.formattedUnicode(
insert = insert,
colorable = colorable,
bitmapIndex = bitmapIndex
)
)
)
.build()
)
Expand Down Expand Up @@ -82,13 +119,39 @@ fun Component.transformEmotes(locale: Locale? = null, insert: Boolean = false):
return component
}

fun String.escapeEmoteIDs(player: Player?): String {
var content = this
for (emote in emojy.emotes.filter { it.font == defaultKey && !it.checkPermission(player) }) emote.unicodes.forEach {
content = content.replaceFirst(it, "<font:random>$it</font>")
}

for (emote in emojy.emotes) emote.baseRegex.findAll(this).forEach { match ->
if (emote.checkPermission(player)) return@forEach

content = content.replaceFirst("(?<!\\\\)${match.value}", "\\${match.value}")
}

for (gif in emojy.gifs) gif.baseRegex.findAll(this).forEach { match ->
if (gif.checkPermission(player)) return@forEach
content = content.replaceFirst("(?<!\\\\)${match.value}", "\\${match.value}")
}

spaceRegex.findAll(this).forEach { match ->
if (player?.hasPermission(SPACE_PERMISSION) != false) return@forEach
val space = match.groupValues[1].toIntOrNull() ?: return@forEach

content = content.replaceFirst("(?<!\\\\)${match.value}", "\\:space_$space:")
}

return content
}

fun Component.escapeEmoteIDs(player: Player?): Component {
var component = this
val serialized = component.asFlatTextContent()

// Replace all unicodes found in default font with a random one
// This is to prevent use of unicodes from the font the chat is in
val (defaultKey, randomKey) = Key.key("default") to Key.key("random")
for (emote in emojy.emotes.filter { it.font == defaultKey && !it.checkPermission(player) }) emote.unicodes.forEach {
component = component.replaceText(
TextReplacementConfig.builder()
Expand Down Expand Up @@ -134,6 +197,24 @@ fun Component.escapeEmoteIDs(player: Player?): Component {
return component
}

fun String.unescapeEmoteIds(): String {
var content = this

for (emote in emojy.emotes) emote.escapedRegex.findAll(this).forEach { match ->
content = content.replaceFirst(emote.escapedRegex, match.value.removePrefix("\\"))
}

for (gif in emojy.gifs) gif.escapedRegex.findAll(this).forEach { match ->
content = content.replaceFirst(gif.escapedRegex, match.value.removePrefix("\\"))
}

escapedSpaceRegex.findAll(this).forEach { match ->
content = content.replaceFirst(match.value, match.value.removePrefix("\\"))
}

return content
}

fun Component.unescapeEmoteIds(): Component {
var component = this
val serialized = component.asFlatTextContent()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,7 @@
package com.mineinabyss.emojy.config

import co.touchlab.kermit.Severity
import com.mineinabyss.emojy.emojy
import com.mineinabyss.emojy.emojyConfig
import com.mineinabyss.emojy.spaceComponent
import com.mineinabyss.emojy.templates
import com.mineinabyss.emojy.*
import com.mineinabyss.idofront.serialization.KeySerializer
import com.mineinabyss.idofront.textcomponents.miniMsg
import com.mineinabyss.idofront.textcomponents.serialize
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@ import net.minecraft.core.NonNullList
import net.minecraft.network.Connection
import net.minecraft.network.chat.ChatType
import net.minecraft.network.chat.Component
import net.minecraft.network.chat.MessageSignature
import net.minecraft.network.chat.SignedMessageBody
import net.minecraft.network.protocol.game.*
import net.minecraft.network.syncher.EntityDataSerializer
import net.minecraft.network.syncher.SynchedEntityData
Expand Down Expand Up @@ -88,28 +86,20 @@ class EmojyNMSHandler(emojy: EmojyPlugin) : IEmojyNMSHandler {
when (operation::class.java.simpleName) {
"AddOperation" -> {
val nameField = operation::class.java.getDeclaredField("name").apply { isAccessible = true }
// Get the component, serialize it and replace "\\<" as it might be escaped if not an AdventureBossbar
val name = PaperAdventure.asAdventure(nameField.get(operation) as Component).serialize().replace("\\<", "<").miniMsg()
nameField.set(operation, PaperAdventure.asVanilla(name.transformEmotes(connection.locale())))
nameField.set(operation, (nameField.get(operation) as Component).transformEmotes(connection.locale()))
}
"UpdateNameOperation" -> {
val accessorMethod = operation::class.java.methods.find { it.name == "name" }
accessorMethod?.isAccessible = true
if (accessorMethod != null) {
val name = PaperAdventure.asAdventure(accessorMethod.invoke(operation) as Component)
.serialize().replace("\\<", "<")
.miniMsg().transformEmotes(connection.locale())

val updateNameOperationClass = operation::class.java.enclosingClass.declaredClasses.find {
it.simpleName == "UpdateNameOperation"
} ?: throw IllegalStateException("UpdateNameOperation class not found")

val constructor = updateNameOperationClass.getDeclaredConstructor(Component::class.java).apply { isAccessible = true }
// Create a new instance of UpdateNameOperation with the modified name

val updatedOperation = constructor.newInstance(PaperAdventure.asVanilla(name))
val name = (accessorMethod.invoke(operation) as Component).transformEmotes(connection.locale())
val updatedOperation = constructor.newInstance(name)

// Set the updated operation in the packet
operationField.set(packet, updatedOperation)
}
}
Expand Down Expand Up @@ -143,7 +133,7 @@ class EmojyNMSHandler(emojy: EmojyPlugin) : IEmojyNMSHandler {
itemName(if (hasItemName()) itemName().transformEmotes(locale) else null)
lore(lore()?.map { l -> l.transformEmotes(locale) })
persistentDataContainer.get(ORIGINAL_ITEM_RENAME_TEXT, DataType.STRING)?.let {
displayName(it.miniMsg().escapeEmoteIDs(player).transformEmotes(locale).unescapeEmoteIds())
displayName(it.escapeEmoteIDs(player).transformEmotes().unescapeEmoteIds().miniMsg())
}
})
}
Expand All @@ -154,18 +144,10 @@ class EmojyNMSHandler(emojy: EmojyPlugin) : IEmojyNMSHandler {

companion object {

fun String.transformEmotes(locale: Locale? = null, insert: Boolean = false): String {
return miniMsg().transformEmotes(locale, insert).serialize()
}

fun Component.transformEmotes(locale: Locale? = null, insert: Boolean = false): Component {
return PaperAdventure.asVanilla(PaperAdventure.asAdventure(this).transformEmotes(locale, insert))
}

fun String.escapeEmoteIDs(player: Player?): String {
return miniMsg().escapeEmoteIDs(player).serialize()
}

fun Component.escapeEmoteIDs(player: Player?): Component {
return PaperAdventure.asVanilla((PaperAdventure.asAdventure(this)).escapeEmoteIDs(player))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,28 +92,20 @@ class EmojyNMSHandler(emojy: EmojyPlugin) : IEmojyNMSHandler {
when (operation::class.java.simpleName) {
"AddOperation" -> {
val nameField = operation::class.java.getDeclaredField("name").apply { isAccessible = true }
// Get the component, serialize it and replace "\\<" as it might be escaped if not an AdventureBossbar
val name = PaperAdventure.asAdventure(nameField.get(operation) as Component).serialize().replace("\\<", "<").miniMsg()
nameField.set(operation, PaperAdventure.asVanilla(name.transformEmotes(connection.locale())))
nameField.set(operation, (nameField.get(operation) as Component).transformEmotes(connection.locale()))
}
"UpdateNameOperation" -> {
val accessorMethod = operation::class.java.methods.find { it.name == "name" }
accessorMethod?.isAccessible = true
if (accessorMethod != null) {
val name = PaperAdventure.asAdventure(accessorMethod.invoke(operation) as Component)
.serialize().replace("\\<", "<")
.miniMsg().transformEmotes(connection.locale())

val updateNameOperationClass = operation::class.java.enclosingClass.declaredClasses.find {
it.simpleName == "UpdateNameOperation"
} ?: throw IllegalStateException("UpdateNameOperation class not found")

val constructor = updateNameOperationClass.getDeclaredConstructor(Component::class.java).apply { isAccessible = true }
// Create a new instance of UpdateNameOperation with the modified name

val updatedOperation = constructor.newInstance(PaperAdventure.asVanilla(name))
val name = (accessorMethod.invoke(operation) as Component).transformEmotes(connection.locale())
val updatedOperation = constructor.newInstance(name)

// Set the updated operation in the packet
operationField.set(packet, updatedOperation)
}
}
Expand Down Expand Up @@ -147,7 +139,7 @@ class EmojyNMSHandler(emojy: EmojyPlugin) : IEmojyNMSHandler {
itemName(if (hasItemName()) itemName().transformEmotes(locale) else null)
lore(lore()?.map { l -> l.transformEmotes(locale) })
persistentDataContainer.get(ORIGINAL_ITEM_RENAME_TEXT, DataType.STRING)?.let {
displayName(it.miniMsg().escapeEmoteIDs(player).transformEmotes(locale).unescapeEmoteIds())
displayName(it.escapeEmoteIDs(player).transformEmotes().unescapeEmoteIds().miniMsg())
}
})
}
Expand All @@ -158,18 +150,10 @@ class EmojyNMSHandler(emojy: EmojyPlugin) : IEmojyNMSHandler {

companion object {

fun String.transformEmotes(locale: Locale? = null, insert: Boolean = false): String {
return miniMsg().transformEmotes(locale, insert).serialize()
}

fun Component.transformEmotes(locale: Locale? = null, insert: Boolean = false): Component {
return PaperAdventure.asVanilla(PaperAdventure.asAdventure(this).transformEmotes(locale, insert))
}

fun String.escapeEmoteIDs(player: Player?): String {
return miniMsg().escapeEmoteIDs(player).serialize()
}

fun Component.escapeEmoteIDs(player: Player?): Component {
return PaperAdventure.asVanilla((PaperAdventure.asAdventure(this)).escapeEmoteIDs(player))
}
Expand Down

0 comments on commit fecffa4

Please sign in to comment.