diff --git a/CHANGELOG.md b/CHANGELOG.md index 562a687..3978f20 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,10 +2,11 @@ NotAlexNoyle 05/06/26: -- Drop PlaceholderAPI dependency. Resolve Unions-OG union color tags - through MiniPlaceholders via Utilities-OG. -- Forward more types of exceptions to the server console. -- Tighten LuckPerms prefix/suffix spacing and trim handling. +- Port all uses of PlaceholderAPI to MiniPlaceholders / Utilities-OG APIs. +- Fix Unions-OG Union colors breaking MC -> Discord link. +- Drop the per-Discord-role suffix configuration. Player suffixes shown in messages now come exclusively from each player's individual LuckPerms suffix. +- Fix LuckPerms spacing so exactly one space appears between the player name and any prefix/suffix. +- Configure Color Code Roles. NotAlexNoyle 05/04/26: diff --git a/build.gradle.kts b/build.gradle.kts index 7c9855f..a20eefe 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -51,13 +51,15 @@ repositories { /* ---------------------- Java project deps ---------------------------- */ dependencies { compileOnly("org.purpurmc.purpur:purpur-api:1.19.4-R0.1-SNAPSHOT") // Declare Purpur API version to be packaged. - compileOnly("net.essentialsx:EssentialsX:2.21.0") // Import EssentialsX API (internally deprecated). compileOnly( files("libs/AnnouncerPlus-1.4.1.jar") ) // Import AnnouncerPlus API (binary-compatible with AnnouncerPlus-OG API). compileOnlyApi(project(":libs:Utilities-OG")) // Import TrueOG Network Utilities-OG Java API (from source). compileOnlyApi(project(":libs:Vanish-OG")) // Import TrueOG Network Vanish-OG Java API (from source). compileOnly("net.luckperms:api:5.5") // Import LuckPerms API. + compileOnly( + "io.github.miniplaceholders:miniplaceholders-api:2.2.3" + ) // Import MiniPlaceholders API for direct tag access. implementation("tools.jackson.core:jackson-databind:3.1.1") implementation("tools.jackson.dataformat:jackson-dataformat-yaml:3.1.1") diff --git a/libs/Utilities-OG b/libs/Utilities-OG index cfb722c..3e26fb1 160000 --- a/libs/Utilities-OG +++ b/libs/Utilities-OG @@ -1 +1 @@ -Subproject commit cfb722c3946b07952467b769a7dd313a6ca6a7e8 +Subproject commit 3e26fb1a84cd46754a57fc1e67d90fcc82d11ad5 diff --git a/libs/Vanish-OG b/libs/Vanish-OG index eb4f80f..3fcdfee 160000 --- a/libs/Vanish-OG +++ b/libs/Vanish-OG @@ -1 +1 @@ -Subproject commit eb4f80f46778f8bc70bb4c99d5189e5ef6bfc058 +Subproject commit 3fcdfee824c875c397af9793e276b197a03b5d3d diff --git a/src/main/kotlin/nl/skbotnl/chatog/ChatOG.kt b/src/main/kotlin/nl/skbotnl/chatog/ChatOG.kt index daf0705..bc5e44d 100755 --- a/src/main/kotlin/nl/skbotnl/chatog/ChatOG.kt +++ b/src/main/kotlin/nl/skbotnl/chatog/ChatOG.kt @@ -1,6 +1,5 @@ package nl.skbotnl.chatog -import com.earth2me.essentials.Essentials import java.util.* import java.util.concurrent.locks.ReentrantReadWriteLock import kotlin.concurrent.read @@ -51,7 +50,6 @@ internal class ChatOG : JavaPlugin() { var translator: OpenAITranslator? = null var discordBridge: DiscordBridge? = null val discordBridgeLock = ReentrantReadWriteLock() - var essentials = Bukkit.getServer().pluginManager.getPlugin("Essentials-OG") as Essentials var lastMessagedMap: MutableMap = HashMap() fun isConfigInitialized() = ::config.isInitialized diff --git a/src/main/kotlin/nl/skbotnl/chatog/Events.kt b/src/main/kotlin/nl/skbotnl/chatog/Events.kt index d0634be..158ff0e 100755 --- a/src/main/kotlin/nl/skbotnl/chatog/Events.kt +++ b/src/main/kotlin/nl/skbotnl/chatog/Events.kt @@ -19,8 +19,8 @@ import nl.skbotnl.chatog.ChatOG.Companion.discordBridgeLock import nl.skbotnl.chatog.ChatOG.Companion.scope import nl.skbotnl.chatog.util.ChatUtil import nl.skbotnl.chatog.util.ChatUtil.legacyToMm -import nl.skbotnl.chatog.util.PlayerAffix import nl.skbotnl.chatog.util.PlayerExtensions.chatSystem +import nl.skbotnl.chatog.util.PlayerUtils import org.bukkit.Bukkit import org.bukkit.event.EventHandler import org.bukkit.event.Listener @@ -42,7 +42,7 @@ internal class Events : Listener { return } - val playerPartString = ChatUtil.getPlayerPartString(event.player) + val playerPartString = ChatUtil.getPlayerPartString(event.player, includeSuffix = true) scope.launch { discordBridgeLock.read { @@ -66,7 +66,7 @@ internal class Events : Listener { return } - val playerPartString = ChatUtil.getPlayerPartString(event.player) + val playerPartString = ChatUtil.getPlayerPartString(event.player, includeSuffix = true) scope.launch { discordBridgeLock.read { @@ -90,7 +90,7 @@ internal class Events : Listener { return } - val playerPartString = ChatUtil.getPlayerPartString(event.player) + val playerPartString = ChatUtil.getPlayerPartString(event.player, includeSuffix = true) val reason = PlainTextComponentSerializer.plainText().serialize(event.reason()) @@ -116,7 +116,7 @@ internal class Events : Listener { return } - val playerPartString = ChatUtil.getPlayerPartString(event.player) + val playerPartString = ChatUtil.getPlayerPartString(event.player, includeSuffix = true) val advancementTitleKey = event.advancement.display?.title() ?: return val advancementTitle = PlainTextComponentSerializer.plainText().serialize(advancementTitleKey) @@ -188,13 +188,12 @@ internal class Events : Listener { return } - var nameString = "${PlayerAffix.getPrefix(event.player.uniqueId)}${event.player.name}" + var nameString = "${PlayerUtils.getPrefix(event.player.uniqueId)}${event.player.name}" - val unionColorTag = - PlainTextComponentSerializer.plainText() - .serialize(UtilitiesOG.trueogExpand("", event.player)) - if (unionColorTag.isNotEmpty() && unionColorTag != "&8None") { - nameString = "&8[$unionColorTag&8] $nameString" + val unionTag = UtilitiesOG.trueogExpand("", event.player) + val unionPlainTag = UtilitiesOG.stripFormatting(unionTag) + if (unionPlainTag.isNotEmpty() && unionPlainTag != "None") { + nameString = "&8[$unionTag&8] $nameString" } val nameComponent = UtilitiesOG.trueogColorize(legacyToMm(nameString)) diff --git a/src/main/kotlin/nl/skbotnl/chatog/chatsystem/ChatSystem.kt b/src/main/kotlin/nl/skbotnl/chatog/chatsystem/ChatSystem.kt index b5df718..e0c990b 100644 --- a/src/main/kotlin/nl/skbotnl/chatog/chatsystem/ChatSystem.kt +++ b/src/main/kotlin/nl/skbotnl/chatog/chatsystem/ChatSystem.kt @@ -9,7 +9,7 @@ import net.kyori.adventure.text.event.HoverEvent import net.trueog.utilitiesog.UtilitiesOG import nl.skbotnl.chatog.translation.command.TranslateMessage import nl.skbotnl.chatog.util.ChatUtil -import nl.skbotnl.chatog.util.PlayerAffix +import nl.skbotnl.chatog.util.PlayerUtils import org.bukkit.entity.Player internal abstract class ChatSystem { @@ -23,13 +23,17 @@ internal abstract class ChatSystem { var playerPartString = ChatUtil.getPlayerPartString(player) playerPartString = listOfNotNull(prefix, playerPartString).joinToString(" | ") - sendDiscordMessage(text, playerPartString, player.uniqueId) + val discordPlayerPartString = + listOfNotNull(prefix, ChatUtil.getPlayerPartString(player, includeSuffix = true)).joinToString(" | ") - val messageComponent = ChatUtil.processText(text, player) ?: return + sendDiscordMessage(text, discordPlayerPartString, player.uniqueId) + + val messageComponent = + ChatUtil.processText(text, player)?.color(PlayerUtils.getMessageColor(player.uniqueId)) ?: return val chatComponent = UtilitiesOG.trueogColorize( - ChatUtil.legacyToMm("$playerPartString${PlayerAffix.getSuffix(player.uniqueId)} &7> ") + ChatUtil.legacyToMm("$playerPartString${PlayerUtils.getSuffix(player.uniqueId)} &7> ") ) var textComponent = Component.join(JoinConfiguration.noSeparators(), chatComponent, messageComponent) diff --git a/src/main/kotlin/nl/skbotnl/chatog/config/discord/DiscordConfig.kt b/src/main/kotlin/nl/skbotnl/chatog/config/discord/DiscordConfig.kt index 14959a5..5959fd5 100644 --- a/src/main/kotlin/nl/skbotnl/chatog/config/discord/DiscordConfig.kt +++ b/src/main/kotlin/nl/skbotnl/chatog/config/discord/DiscordConfig.kt @@ -23,7 +23,4 @@ constructor( @param:JsonProperty("useColorCodeRoles") val useColorCodeRoles: Boolean, @param:JsonProperty("colorCodeRoles") @field:JsonSetter(nulls = Nulls.AS_EMPTY) val colorCodeRoles: List, @param:JsonProperty("roles") @field:JsonSetter(nulls = Nulls.AS_EMPTY) val roles: Map, - @param:JsonProperty("roleSuffixes") - @field:JsonSetter(nulls = Nulls.AS_EMPTY) - val roleSuffixes: Map, ) diff --git a/src/main/kotlin/nl/skbotnl/chatog/config/discord/DiscordSuffixConfig.kt b/src/main/kotlin/nl/skbotnl/chatog/config/discord/DiscordSuffixConfig.kt deleted file mode 100644 index 4162114..0000000 --- a/src/main/kotlin/nl/skbotnl/chatog/config/discord/DiscordSuffixConfig.kt +++ /dev/null @@ -1,6 +0,0 @@ -package nl.skbotnl.chatog.config.discord - -import com.fasterxml.jackson.annotation.JsonCreator -import com.fasterxml.jackson.annotation.JsonProperty - -data class DiscordSuffixConfig @JsonCreator constructor(@param:JsonProperty("suffix") val suffix: String) diff --git a/src/main/kotlin/nl/skbotnl/chatog/discord/DiscordBridge.kt b/src/main/kotlin/nl/skbotnl/chatog/discord/DiscordBridge.kt index da403ef..34e2848 100644 --- a/src/main/kotlin/nl/skbotnl/chatog/discord/DiscordBridge.kt +++ b/src/main/kotlin/nl/skbotnl/chatog/discord/DiscordBridge.kt @@ -187,12 +187,7 @@ internal class DiscordBridge private constructor() { ?: NamedTextColor.GRAY } - val suffix = - if (config.discord.roleSuffixes.isEmpty()) { - "> " - } else { - sortedRoles.firstNotNullOfOrNull { r -> config.discord.roleSuffixes[r.id] }?.suffix ?: "> " - } + val suffix = " > " val messageComponents = mutableListOf(UtilitiesOG.trueogColorize(suffix)) @@ -368,7 +363,7 @@ internal class DiscordBridge private constructor() { message.contentDisplay, member.effectiveName, Component.join(JoinConfiguration.noSeparators(), discordComponent, userComponent), - Component.text("$suffix "), + Component.text(suffix), ) } return discordBridge @@ -398,7 +393,7 @@ internal class DiscordBridge private constructor() { suspend fun sendMessage(message: String, player: Player) { val webhookMessage = WebhookMessageBuilder() - .setUsername(UtilitiesOG.stripFormatting(ChatUtil.getPlayerPartString(player))) + .setUsername(UtilitiesOG.stripFormatting(ChatUtil.getPlayerPartString(player, includeSuffix = true))) .setContent(UtilitiesOG.stripFormatting(ChatUtil.stripGroupMentions(ChatUtil.convertMentions(message)))) .setAvatarUrl("https://minotar.net/helm/${player.uniqueId}.png") diff --git a/src/main/kotlin/nl/skbotnl/chatog/translation/command/TranslateMessage.kt b/src/main/kotlin/nl/skbotnl/chatog/translation/command/TranslateMessage.kt index af243fe..ca6dab5 100755 --- a/src/main/kotlin/nl/skbotnl/chatog/translation/command/TranslateMessage.kt +++ b/src/main/kotlin/nl/skbotnl/chatog/translation/command/TranslateMessage.kt @@ -4,14 +4,13 @@ import java.util.* import net.kyori.adventure.text.Component import net.kyori.adventure.text.JoinConfiguration import net.kyori.adventure.text.format.NamedTextColor -import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer import net.trueog.utilitiesog.UtilitiesOG import nl.skbotnl.chatog.ChatOG.Companion.config import nl.skbotnl.chatog.ChatOG.Companion.languageDatabase import nl.skbotnl.chatog.ChatOG.Companion.translator import nl.skbotnl.chatog.util.ChatUtil import nl.skbotnl.chatog.util.ChatUtil.legacyToMm -import nl.skbotnl.chatog.util.PlayerAffix +import nl.skbotnl.chatog.util.PlayerUtils import org.bukkit.Bukkit import org.bukkit.command.Command import org.bukkit.command.CommandExecutor @@ -138,15 +137,15 @@ internal class TranslateMessage : CommandExecutor { when (messageType) { 1 -> { val sentChatMessage = sentMessage as SentChatMessage + // TODO: Should use ChatUtil.getPlayerPartString var playerString = - "${PlayerAffix.getPrefix(sentChatMessage.player.uniqueId)}${sentChatMessage.player.name}${ - PlayerAffix.getSuffix(sentChatMessage.player.uniqueId) + "${PlayerUtils.getPrefix(sentChatMessage.player.uniqueId)}${sentChatMessage.player.name}${ + PlayerUtils.getSuffix(sentChatMessage.player.uniqueId) }" - val unionColorTag = - PlainTextComponentSerializer.plainText() - .serialize(UtilitiesOG.trueogExpand("", sentMessage.player)) - if (unionColorTag.isNotEmpty() && unionColorTag != "&8None") { - playerString = "&8[$unionColorTag&8] $playerString" + val unionTag = UtilitiesOG.trueogExpand("", player) + val unionPlainTag = UtilitiesOG.stripFormatting(unionTag) + if (unionPlainTag.isNotEmpty() && unionPlainTag != "None") { + playerString = "&8[$unionTag&8] $playerString" } val playerComponent = UtilitiesOG.trueogColorize( @@ -155,7 +154,6 @@ internal class TranslateMessage : CommandExecutor { ) ) - // Don't convert color in translated messages translateMessage = Component.join( JoinConfiguration.noSeparators(), diff --git a/src/main/kotlin/nl/skbotnl/chatog/util/ChatUtil.kt b/src/main/kotlin/nl/skbotnl/chatog/util/ChatUtil.kt index a9d8435..838260f 100644 --- a/src/main/kotlin/nl/skbotnl/chatog/util/ChatUtil.kt +++ b/src/main/kotlin/nl/skbotnl/chatog/util/ChatUtil.kt @@ -25,14 +25,14 @@ import org.bukkit.Sound import org.bukkit.entity.Player internal object ChatUtil { - fun getPlayerPartString(player: Player): String { - var playerPart = "${PlayerAffix.getPrefix(player.uniqueId)}${player.name}" + fun getPlayerPartString(player: Player, includeSuffix: Boolean = false): String { + var playerPart = + "${PlayerUtils.getPrefix(player.uniqueId)}${player.name}${if (includeSuffix) PlayerUtils.getSuffix(player.uniqueId) else ""}" - val unionColorTag = - PlainTextComponentSerializer.plainText() - .serialize(UtilitiesOG.trueogExpand("", player)) - if (unionColorTag.isNotEmpty() && unionColorTag != "&8None") { - playerPart = legacyToMm("&8[$unionColorTag&8] ") + playerPart + val unionTag = UtilitiesOG.trueogExpand("", player) + val unionPlainTag = UtilitiesOG.stripFormatting(unionTag) + if (unionPlainTag.isNotEmpty() && unionPlainTag != "None") { + playerPart = legacyToMm("&8[$unionTag&8] ") + playerPart } return playerPart @@ -102,6 +102,10 @@ internal object ChatUtil { return legacyRegex.replace(text) { legacyToMmMap[it.groupValues[1].lowercase()] ?: it.value } } + fun stripFormatting(text: String): String { + return MiniMessage.miniMessage().stripTags(legacyRegex.replace(text, "")) + } + private val getHandle = Regex("@([a-z0-9_.]{2,32})") suspend fun convertMentions(text: String): String { @@ -170,8 +174,10 @@ internal object ChatUtil { private fun processText(text: String, player: Player?, username: String?, color: Boolean): Component? { val words: MutableList = mutableListOf() + val canUseFormatting = player?.hasPermission("chat-og.color") == true || color + val textToProcess = if (canUseFormatting) legacyToMm(text) else stripFormatting(text) - legacyToMm(text).split(" ").forEach { word -> + textToProcess.split(" ").forEach { word -> val url = urlRegex.find(word) if (url != null) { @@ -206,14 +212,10 @@ internal object ChatUtil { } } - return if (player?.hasPermission("chat-og.color") == true) { + return if (canUseFormatting) { colorMm.deserialize(words.joinToString(" ")) } else { - if (color) { - colorMm.deserialize(words.joinToString(" ")) - } else { - noColorMm.deserialize(words.joinToString(" ")) - } + noColorMm.deserialize(words.joinToString(" ")) } } diff --git a/src/main/kotlin/nl/skbotnl/chatog/util/PlayerAffix.kt b/src/main/kotlin/nl/skbotnl/chatog/util/PlayerAffix.kt deleted file mode 100644 index 4c770be..0000000 --- a/src/main/kotlin/nl/skbotnl/chatog/util/PlayerAffix.kt +++ /dev/null @@ -1,23 +0,0 @@ -package nl.skbotnl.chatog.util - -import java.util.* -import nl.skbotnl.chatog.ChatOG.Companion.luckPerms -import nl.skbotnl.chatog.util.ChatUtil.legacyToMm - -internal object PlayerAffix { - fun getPrefix(uuid: UUID): String { - val prefix = getMetaData(uuid).prefix ?: return "" - return legacyToMm(prefix).trimEnd() + " " - } - - fun getSuffix(uuid: UUID): String { - val suffix = getMetaData(uuid).suffix ?: return "" - return " " + legacyToMm(suffix).trim() - } - - private fun getMetaData(uuid: UUID) = - with(luckPerms.userManager.getUser(uuid) ?: luckPerms.userManager.loadUser(uuid).join()) { - val queryOptions = luckPerms.contextManager.getQueryOptions(this).orElse(queryOptions) - cachedData.getMetaData(queryOptions) - } -} diff --git a/src/main/kotlin/nl/skbotnl/chatog/util/PlayerUtils.kt b/src/main/kotlin/nl/skbotnl/chatog/util/PlayerUtils.kt new file mode 100644 index 0000000..d62d726 --- /dev/null +++ b/src/main/kotlin/nl/skbotnl/chatog/util/PlayerUtils.kt @@ -0,0 +1,24 @@ +package nl.skbotnl.chatog.util + +import java.util.* +import net.kyori.adventure.text.format.NamedTextColor +import net.kyori.adventure.text.format.TextColor +import nl.skbotnl.chatog.ChatOG.Companion.luckPerms + +internal object PlayerUtils { + fun getPrefix(uuid: UUID): String { + val prefix = getMetaData(uuid).prefix?.trim() ?: return "" + return if (prefix.endsWith(' ')) prefix else "$prefix " + } + + fun getSuffix(uuid: UUID) = getMetaData(uuid).suffix?.trim() ?: "" + + fun getMessageColor(uuid: UUID): TextColor { + return getMetaData(uuid).getMetaValue("chat-og.messageColor")?.lowercase()?.let { + NamedTextColor.NAMES.value(it) + } ?: NamedTextColor.GRAY + } + + private fun getMetaData(uuid: UUID) = + with(luckPerms.userManager.getUser(uuid) ?: luckPerms.userManager.loadUser(uuid).join()) { cachedData.metaData } +} diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 2be98c6..f09f14f 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -64,31 +64,53 @@ discord: listCommandText: "There are %onlineplayers% out of maximum 200 players online." # Color code roles - # TODO: Configure these roles. - useColorCodeRoles: false - # Configure which roles can use color codes with Discord -> Minecraft - # For example: - # - # colorCodeRoles: [1234567891274567890, 0987654321983654321] + # When true, only Discord users with one of the colorCodeRoles below can use + # Utilities-OG ColorCodeMap Bukkit codes (&c, &l, &*) or MiniMessage tags + # in Discord -> Minecraft chat. All others have their input stripped of + # formatting before it is rendered in-game. + useColorCodeRoles: true - # Configure the text color (after the username (Discord -> Minecraft)) for certain roles - # For example: - # - # roles: - # 1234567891239567890: - # messageColor: red - # - # message_color can be one of the following: black, dark_blue, dark_green, dark_aqua, dark_red, dark_purple, gold, gray, dark_gray, blue, green, aqua, red, light_purple, yellow, white - # - # You can also use RGB for the color: - # - # roles: - # 1234567891234567890: - # messageColor: [255, 255, 255] + # Discord role IDs allowed to use color codes from Discord. + colorCodeRoles: + - "897300356395073546" # OG-Pro + - "934876475285467247" # OG Master + - "897299946926125167" # Tester (same level as OG Master) + - "897299018407890945" # YouTube + - "1391741516204081162" # Technical Contributor + - "1096297706034569287" # Helper + - "897776965849858101" # Builder + - "916661431850639420" # MVP + - "1080912883694719047" # Developer + - "897297817574440970" # Moderator + - "897296878490763344" # Admin - # Configure the suffix for roles (has to include the space). color codes are allowed. - # For example: - - # roleSuffixes: - # 1234567891234527890: - # suffix: ": " + # Default Minecraft chat color applied to a Discord -> Minecraft message when + # the sender did not type their own color code. If a user has multiple + # configured roles the entry for their highest-positioned role wins. + roles: + "897657230575431710": # Member + messageColor: gray + "897300768619659314": # OG + messageColor: white + "897300356395073546": # OG-Pro + messageColor: white + "934876475285467247": # OG Master + messageColor: white + "897299946926125167": # Tester + messageColor: white + "897299018407890945": # YouTube + messageColor: white + "1391741516204081162": # Technical Contributor + messageColor: white + "1096297706034569287": # Helper + messageColor: white + "897776965849858101": # Builder + messageColor: white + "916661431850639420": # MVP + messageColor: white + "1080912883694719047": # Developer + messageColor: white + "897297817574440970": # Moderator + messageColor: white + "897296878490763344": # Admin + messageColor: white diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 7fdd172..b00711b 100755 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -7,8 +7,7 @@ depend: - LuckPerms - AnnouncerPlus - Utilities-OG -softdepend: - - Essentials-OG + - Vanish-OG commands: translatemessage: description: Internal command for translating messages