From 340acc0c9af2c278c65d89a80db2b781572cac75 Mon Sep 17 00:00:00 2001 From: Erdragh Date: Tue, 16 Apr 2024 15:41:41 +0200 Subject: [PATCH 01/20] Start implementing minecraft message embeds for discord --- .../astralbot/handlers/MinecraftHandler.kt | 62 +++++++++++++++++-- .../dev/erdragh/astralbot/fabric/BotMod.kt | 4 +- .../dev/erdragh/astralbot/forge/BotMod.kt | 4 +- 3 files changed, 62 insertions(+), 8 deletions(-) diff --git a/common/src/main/kotlin/dev/erdragh/astralbot/handlers/MinecraftHandler.kt b/common/src/main/kotlin/dev/erdragh/astralbot/handlers/MinecraftHandler.kt index fdf3ab2..b84ab54 100644 --- a/common/src/main/kotlin/dev/erdragh/astralbot/handlers/MinecraftHandler.kt +++ b/common/src/main/kotlin/dev/erdragh/astralbot/handlers/MinecraftHandler.kt @@ -4,8 +4,10 @@ import com.mojang.authlib.GameProfile import dev.erdragh.astralbot.* import dev.erdragh.astralbot.config.AstralBotConfig import dev.erdragh.astralbot.config.AstralBotTextConfig +import net.dv8tion.jda.api.EmbedBuilder import net.dv8tion.jda.api.entities.Member import net.dv8tion.jda.api.entities.Message +import net.dv8tion.jda.api.entities.MessageEmbed import net.dv8tion.jda.api.events.message.MessageReceivedEvent import net.dv8tion.jda.api.hooks.ListenerAdapter import net.minecraft.ChatFormatting @@ -15,6 +17,8 @@ import net.minecraft.network.chat.HoverEvent import net.minecraft.network.chat.MutableComponent import net.minecraft.server.MinecraftServer import net.minecraft.server.level.ServerPlayer +import net.minecraft.world.entity.EntityType +import java.awt.Color import java.text.DecimalFormat import java.util.* import kotlin.jvm.optionals.getOrNull @@ -121,19 +125,69 @@ class MinecraftHandler(private val server: MinecraftServer) : ListenerAdapter() * @param player the Player who sent the message * @param message the String contents of the message */ - fun sendChatToDiscord(player: ServerPlayer?, message: String) { + fun sendChatToDiscord(player: ServerPlayer?, message: Component) { if (shuttingDown.get()) return + + val attachments = message.toFlatList().mapNotNull { + it.style.hoverEvent + } + + val formattedEmbeds: MutableList = mutableListOf() + + for (attachment in attachments) { + attachment.getValue(HoverEvent.Action.SHOW_TEXT)?.let { + formattedEmbeds.add( + EmbedBuilder() + .setDescription(it.string) + .let { builder: EmbedBuilder -> + it.style.color?.value?.let { color -> builder.setColor(color) } + builder + } + .build() + ) + } + attachment.getValue(HoverEvent.Action.SHOW_ITEM)?.itemStack?.let { + formattedEmbeds.add( + EmbedBuilder() + .setDescription(it.item.description.string) + .setTitle("${it.displayName.string} ${if (it.count > 0) "(${it.count})" else ""}") + .let { builder: EmbedBuilder -> + it.rarity.color.color?.let { color -> builder.setColor(color) } + builder + } + .build() + ) + } + attachment.getValue(HoverEvent.Action.SHOW_ENTITY)?.let { + if (it.type == EntityType.PLAYER) return@let + formattedEmbeds.add( + EmbedBuilder() + .setTitle(it.name?.string) + .setDescription(it.type.description.string) + .let { builder: EmbedBuilder -> + val mobCategory = it.type.category + if (mobCategory.isFriendly) { + builder.setColor(Color.GREEN) + } + builder + } + .build() + ) + } + } + val escape = { it: String -> it.replace("_", "\\_") } textChannel?.sendMessage( if (player != null) AstralBotTextConfig.PLAYER_MESSAGE.get() - .replace("{{message}}", escape(message)) + .replace("{{message}}", escape(message.string)) .replace("{{fullName}}", escape(player.displayName.string)) .replace("{{name}}", escape(player.name.string)) - else escape(message) + else escape(message.string) ) + ?.addEmbeds(formattedEmbeds) ?.setSuppressedNotifications(true) - ?.setSuppressEmbeds(true)?.queue() + ?.queue() } /** diff --git a/fabric/src/main/kotlin/dev/erdragh/astralbot/fabric/BotMod.kt b/fabric/src/main/kotlin/dev/erdragh/astralbot/fabric/BotMod.kt index 203ae0e..59d97b9 100644 --- a/fabric/src/main/kotlin/dev/erdragh/astralbot/fabric/BotMod.kt +++ b/fabric/src/main/kotlin/dev/erdragh/astralbot/fabric/BotMod.kt @@ -29,11 +29,11 @@ object BotMod : ModInitializer { } ServerMessageEvents.CHAT_MESSAGE.register { message, player, _ -> - minecraftHandler?.sendChatToDiscord(player, message.signedContent()) + minecraftHandler?.sendChatToDiscord(player, message.decoratedContent()) } ServerMessageEvents.GAME_MESSAGE.register { _, message, _ -> if (message !is DiscordMessageComponent) { - minecraftHandler?.sendChatToDiscord(null as ServerPlayer?, message.string) + minecraftHandler?.sendChatToDiscord(null as ServerPlayer?, message) } } diff --git a/forge/src/main/kotlin/dev/erdragh/astralbot/forge/BotMod.kt b/forge/src/main/kotlin/dev/erdragh/astralbot/forge/BotMod.kt index 40d145a..8708334 100644 --- a/forge/src/main/kotlin/dev/erdragh/astralbot/forge/BotMod.kt +++ b/forge/src/main/kotlin/dev/erdragh/astralbot/forge/BotMod.kt @@ -44,12 +44,12 @@ object BotMod { } private fun onChatMessage(event: ServerChatEvent) { - minecraftHandler?.sendChatToDiscord(event.player, event.message.string) + minecraftHandler?.sendChatToDiscord(event.player, event.message) } private fun onSystemMessage(event: SystemMessageEvent) { if (event.message !is DiscordMessageComponent) { - minecraftHandler?.sendChatToDiscord(null as ServerPlayer?, event.message.string) + minecraftHandler?.sendChatToDiscord(null as ServerPlayer?, event.message) } } From c0bc5a85eb7ea2d5a7fefd072c0c621733432f55 Mon Sep 17 00:00:00 2001 From: Erdragh Date: Tue, 16 Apr 2024 15:42:48 +0200 Subject: [PATCH 02/20] Register link command on forge --- forge/src/main/kotlin/dev/erdragh/astralbot/forge/BotMod.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/forge/src/main/kotlin/dev/erdragh/astralbot/forge/BotMod.kt b/forge/src/main/kotlin/dev/erdragh/astralbot/forge/BotMod.kt index 8708334..955f367 100644 --- a/forge/src/main/kotlin/dev/erdragh/astralbot/forge/BotMod.kt +++ b/forge/src/main/kotlin/dev/erdragh/astralbot/forge/BotMod.kt @@ -29,6 +29,7 @@ object BotMod { FORGE_BUS.addListener(::onServerStop) FORGE_BUS.addListener(::onChatMessage) FORGE_BUS.addListener(::onSystemMessage) + FORGE_BUS.addListener(::onCommandRegistration) FORGE_BUS.addListener(::onPlayerJoin) FORGE_BUS.addListener(::onPlayerLeave) From 0c9e97d2c3edc402b8386d303a6d38cf4f180864 Mon Sep 17 00:00:00 2001 From: Erdragh Date: Tue, 16 Apr 2024 15:44:00 +0200 Subject: [PATCH 03/20] Adjust Changelog.md --- Changelog.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Changelog.md b/Changelog.md index 3fef427..4935f10 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,3 +1,7 @@ +# Indev +- Message Embeds to better represent Minecraft messages on Discord +- Fix `/link` Minecraft command not being registered on Forge + # 1.2.1 Allow providing token via config From b3c1a32ed824797ecd0b3f7dc5c5b4bae8fcbda7 Mon Sep 17 00:00:00 2001 From: Erdragh Date: Thu, 18 Apr 2024 17:01:58 +0200 Subject: [PATCH 04/20] Improve Item display on discord --- .../dev/erdragh/astralbot/handlers/MinecraftHandler.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/common/src/main/kotlin/dev/erdragh/astralbot/handlers/MinecraftHandler.kt b/common/src/main/kotlin/dev/erdragh/astralbot/handlers/MinecraftHandler.kt index b84ab54..890d32b 100644 --- a/common/src/main/kotlin/dev/erdragh/astralbot/handlers/MinecraftHandler.kt +++ b/common/src/main/kotlin/dev/erdragh/astralbot/handlers/MinecraftHandler.kt @@ -18,6 +18,7 @@ import net.minecraft.network.chat.MutableComponent import net.minecraft.server.MinecraftServer import net.minecraft.server.level.ServerPlayer import net.minecraft.world.entity.EntityType +import net.minecraft.world.item.ItemStack import java.awt.Color import java.text.DecimalFormat import java.util.* @@ -133,6 +134,7 @@ class MinecraftHandler(private val server: MinecraftServer) : ListenerAdapter() } val formattedEmbeds: MutableList = mutableListOf() + val items: MutableList = mutableListOf() for (attachment in attachments) { attachment.getValue(HoverEvent.Action.SHOW_TEXT)?.let { @@ -147,10 +149,12 @@ class MinecraftHandler(private val server: MinecraftServer) : ListenerAdapter() ) } attachment.getValue(HoverEvent.Action.SHOW_ITEM)?.itemStack?.let { + if (items.contains(it)) return@let + items.add(it) formattedEmbeds.add( EmbedBuilder() .setDescription(it.item.description.string) - .setTitle("${it.displayName.string} ${if (it.count > 0) "(${it.count})" else ""}") + .setTitle("${it.displayName.string} ${if (it.count > 1) "(${it.count})" else ""}") .let { builder: EmbedBuilder -> it.rarity.color.color?.let { color -> builder.setColor(color) } builder From a51197b01f8713e1ea9a5d2310723a2fbac260ac Mon Sep 17 00:00:00 2001 From: Erdragh Date: Thu, 18 Apr 2024 17:08:31 +0200 Subject: [PATCH 05/20] Refactor embed formatting into separate methods --- .../astralbot/handlers/MinecraftHandler.kt | 76 ++++++++++--------- 1 file changed, 41 insertions(+), 35 deletions(-) diff --git a/common/src/main/kotlin/dev/erdragh/astralbot/handlers/MinecraftHandler.kt b/common/src/main/kotlin/dev/erdragh/astralbot/handlers/MinecraftHandler.kt index 890d32b..b7f46b2 100644 --- a/common/src/main/kotlin/dev/erdragh/astralbot/handlers/MinecraftHandler.kt +++ b/common/src/main/kotlin/dev/erdragh/astralbot/handlers/MinecraftHandler.kt @@ -120,6 +120,44 @@ class MinecraftHandler(private val server: MinecraftServer) : ListenerAdapter() return server.profileCache?.get(name)?.getOrNull() } + private fun formatHoverText(text: Component): MessageEmbed { + return EmbedBuilder() + .setDescription(text.string) + .let { builder: EmbedBuilder -> + text.style.color?.value?.let { color -> builder.setColor(color) } + builder + } + .build() + } + + private fun formatHoverItems(stack: ItemStack, knownItems: MutableList): MessageEmbed? { + if (knownItems.contains(stack)) return null + knownItems.add(stack) + return EmbedBuilder() + .setDescription(stack.item.description.string) + .setTitle("${stack.displayName.string} ${if (stack.count > 1) "(${stack.count})" else ""}") + .let { builder: EmbedBuilder -> + stack.rarity.color.color?.let { color -> builder.setColor(color) } + builder + } + .build() + } + + private fun formatHoverEntity(entity: HoverEvent.EntityTooltipInfo): MessageEmbed? { + if (entity.type == EntityType.PLAYER) return null + return EmbedBuilder() + .setTitle(entity.name?.string) + .setDescription(entity.type.description.string) + .let { builder: EmbedBuilder -> + val mobCategory = entity.type.category + if (mobCategory.isFriendly) { + builder.setColor(Color.GREEN) + } + builder + } + .build() + } + /** * Sends a message into the configured Discord channel based on * the Chat [message] the [player] sent. @@ -138,45 +176,13 @@ class MinecraftHandler(private val server: MinecraftServer) : ListenerAdapter() for (attachment in attachments) { attachment.getValue(HoverEvent.Action.SHOW_TEXT)?.let { - formattedEmbeds.add( - EmbedBuilder() - .setDescription(it.string) - .let { builder: EmbedBuilder -> - it.style.color?.value?.let { color -> builder.setColor(color) } - builder - } - .build() - ) + formattedEmbeds.add(formatHoverText(it)) } attachment.getValue(HoverEvent.Action.SHOW_ITEM)?.itemStack?.let { - if (items.contains(it)) return@let - items.add(it) - formattedEmbeds.add( - EmbedBuilder() - .setDescription(it.item.description.string) - .setTitle("${it.displayName.string} ${if (it.count > 1) "(${it.count})" else ""}") - .let { builder: EmbedBuilder -> - it.rarity.color.color?.let { color -> builder.setColor(color) } - builder - } - .build() - ) + formatHoverItems(it, items)?.let(formattedEmbeds::add) } attachment.getValue(HoverEvent.Action.SHOW_ENTITY)?.let { - if (it.type == EntityType.PLAYER) return@let - formattedEmbeds.add( - EmbedBuilder() - .setTitle(it.name?.string) - .setDescription(it.type.description.string) - .let { builder: EmbedBuilder -> - val mobCategory = it.type.category - if (mobCategory.isFriendly) { - builder.setColor(Color.GREEN) - } - builder - } - .build() - ) + formatHoverEntity(it)?.let(formattedEmbeds::add) } } From ed521b7e04a2dec3a6b82dc2c67f5427f70d3180 Mon Sep 17 00:00:00 2001 From: Erdragh Date: Thu, 18 Apr 2024 17:45:22 +0200 Subject: [PATCH 06/20] Format item tooltip for discord embed --- .../astralbot/handlers/MinecraftHandler.kt | 31 +++++++++++++++++-- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/common/src/main/kotlin/dev/erdragh/astralbot/handlers/MinecraftHandler.kt b/common/src/main/kotlin/dev/erdragh/astralbot/handlers/MinecraftHandler.kt index b7f46b2..d50ddb8 100644 --- a/common/src/main/kotlin/dev/erdragh/astralbot/handlers/MinecraftHandler.kt +++ b/common/src/main/kotlin/dev/erdragh/astralbot/handlers/MinecraftHandler.kt @@ -19,6 +19,7 @@ import net.minecraft.server.MinecraftServer import net.minecraft.server.level.ServerPlayer import net.minecraft.world.entity.EntityType import net.minecraft.world.item.ItemStack +import net.minecraft.world.item.TooltipFlag import java.awt.Color import java.text.DecimalFormat import java.util.* @@ -120,6 +121,27 @@ class MinecraftHandler(private val server: MinecraftServer) : ListenerAdapter() return server.profileCache?.get(name)?.getOrNull() } + private fun formatComponentToMarkdown(comp: Component): String { + return comp.toFlatList() + .map { + var formatted = it.string + // TODO filter out bad domains + if (it.style.isBold) { + formatted = "**$formatted**" + } + if (it.style.isItalic) { + formatted = "_${formatted}_" + } + it.style.clickEvent?.let { clickEvent -> + if (clickEvent.action == ClickEvent.Action.OPEN_URL) { + formatted = "[$formatted](${clickEvent.value})" + } + } + return@map formatted + } + .joinToString("") + } + private fun formatHoverText(text: Component): MessageEmbed { return EmbedBuilder() .setDescription(text.string) @@ -133,9 +155,10 @@ class MinecraftHandler(private val server: MinecraftServer) : ListenerAdapter() private fun formatHoverItems(stack: ItemStack, knownItems: MutableList): MessageEmbed? { if (knownItems.contains(stack)) return null knownItems.add(stack) + val tooltip = stack.getTooltipLines(null, TooltipFlag.NORMAL).map(::formatComponentToMarkdown) return EmbedBuilder() - .setDescription(stack.item.description.string) - .setTitle("${stack.displayName.string} ${if (stack.count > 1) "(${stack.count})" else ""}") + .setTitle("${tooltip[0]} ${if (stack.count > 1) "(${stack.count})" else ""}") + .setDescription(tooltip.drop(1).joinToString("\n")) .let { builder: EmbedBuilder -> stack.rarity.color.color?.let { color -> builder.setColor(color) } builder @@ -371,7 +394,9 @@ class MinecraftHandler(private val server: MinecraftServer) : ListenerAdapter() private fun formatEmbeds(message: Message): MutableComponent { val comp = Component.empty() // Adds a newline with space if there are embeds and the message isn't empty - if (message.embeds.size + message.attachments.size + message.stickers.size > 0 && message.contentDisplay.isNotBlank()) comp.append("\n ") + if (message.embeds.size + message.attachments.size + message.stickers.size > 0 && message.contentDisplay.isNotBlank()) comp.append( + "\n " + ) var i = 0 message.embeds.forEach { if (i++ != 0) comp.append(", ") From 17b9b09b15d838ed93543a4c65bae33c1bb917c2 Mon Sep 17 00:00:00 2001 From: Erdragh Date: Thu, 18 Apr 2024 18:40:29 +0200 Subject: [PATCH 07/20] Fix wrong numbers in item tooltips --- .../dev/erdragh/astralbot/handlers/MinecraftHandler.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/common/src/main/kotlin/dev/erdragh/astralbot/handlers/MinecraftHandler.kt b/common/src/main/kotlin/dev/erdragh/astralbot/handlers/MinecraftHandler.kt index d50ddb8..d674938 100644 --- a/common/src/main/kotlin/dev/erdragh/astralbot/handlers/MinecraftHandler.kt +++ b/common/src/main/kotlin/dev/erdragh/astralbot/handlers/MinecraftHandler.kt @@ -33,7 +33,8 @@ import kotlin.math.min * @author Erdragh */ class MinecraftHandler(private val server: MinecraftServer) : ListenerAdapter() { - private val playerNames = HashSet(server.maxPlayers) + private val playerNames = HashSet(server.maxPlayers); + private val notchPlayer = byName("Notch")?.let { ServerPlayer(this.server, this.server.allLevels.elementAt(0), it) } companion object { private val numberFormat = DecimalFormat("###.##") @@ -155,7 +156,7 @@ class MinecraftHandler(private val server: MinecraftServer) : ListenerAdapter() private fun formatHoverItems(stack: ItemStack, knownItems: MutableList): MessageEmbed? { if (knownItems.contains(stack)) return null knownItems.add(stack) - val tooltip = stack.getTooltipLines(null, TooltipFlag.NORMAL).map(::formatComponentToMarkdown) + val tooltip = stack.getTooltipLines(notchPlayer, TooltipFlag.NORMAL).map(::formatComponentToMarkdown) return EmbedBuilder() .setTitle("${tooltip[0]} ${if (stack.count > 1) "(${stack.count})" else ""}") .setDescription(tooltip.drop(1).joinToString("\n")) From 9f2d2ff4b6990c1641c7055b5b2b1e7d31b6686e Mon Sep 17 00:00:00 2001 From: Erdragh Date: Thu, 18 Apr 2024 18:43:49 +0200 Subject: [PATCH 08/20] Use formatComponent method on player messages --- .../kotlin/dev/erdragh/astralbot/handlers/MinecraftHandler.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/src/main/kotlin/dev/erdragh/astralbot/handlers/MinecraftHandler.kt b/common/src/main/kotlin/dev/erdragh/astralbot/handlers/MinecraftHandler.kt index d674938..58fb027 100644 --- a/common/src/main/kotlin/dev/erdragh/astralbot/handlers/MinecraftHandler.kt +++ b/common/src/main/kotlin/dev/erdragh/astralbot/handlers/MinecraftHandler.kt @@ -214,7 +214,7 @@ class MinecraftHandler(private val server: MinecraftServer) : ListenerAdapter() textChannel?.sendMessage( if (player != null) AstralBotTextConfig.PLAYER_MESSAGE.get() - .replace("{{message}}", escape(message.string)) + .replace("{{message}}", formatComponentToMarkdown(message)) .replace("{{fullName}}", escape(player.displayName.string)) .replace("{{name}}", escape(player.name.string)) else escape(message.string) From faee486737dcd28b9fa21a55115c766f6f68b2b1 Mon Sep 17 00:00:00 2001 From: Erdragh Date: Thu, 18 Apr 2024 19:06:34 +0200 Subject: [PATCH 09/20] Show item name in addition to custom name --- .../dev/erdragh/astralbot/handlers/MinecraftHandler.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/common/src/main/kotlin/dev/erdragh/astralbot/handlers/MinecraftHandler.kt b/common/src/main/kotlin/dev/erdragh/astralbot/handlers/MinecraftHandler.kt index 58fb027..5bdfc91 100644 --- a/common/src/main/kotlin/dev/erdragh/astralbot/handlers/MinecraftHandler.kt +++ b/common/src/main/kotlin/dev/erdragh/astralbot/handlers/MinecraftHandler.kt @@ -159,7 +159,11 @@ class MinecraftHandler(private val server: MinecraftServer) : ListenerAdapter() val tooltip = stack.getTooltipLines(notchPlayer, TooltipFlag.NORMAL).map(::formatComponentToMarkdown) return EmbedBuilder() .setTitle("${tooltip[0]} ${if (stack.count > 1) "(${stack.count})" else ""}") - .setDescription(tooltip.drop(1).joinToString("\n")) + .setDescription(tooltip.drop(1).let { + if (stack.hasCustomHoverName()) { + listOf(stack.item.description.string).plus(it) + } else it + }.joinToString("\n")) .let { builder: EmbedBuilder -> stack.rarity.color.color?.let { color -> builder.setColor(color) } builder From a7ac09202696bb8888896e55175895a0d77b7e39 Mon Sep 17 00:00:00 2001 From: Erdragh Date: Tue, 23 Apr 2024 08:58:24 +0200 Subject: [PATCH 10/20] Implement blocking certain URLs in messages sent to discord --- .../astralbot/handlers/MinecraftHandler.kt | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/common/src/main/kotlin/dev/erdragh/astralbot/handlers/MinecraftHandler.kt b/common/src/main/kotlin/dev/erdragh/astralbot/handlers/MinecraftHandler.kt index 5bdfc91..93eb2a1 100644 --- a/common/src/main/kotlin/dev/erdragh/astralbot/handlers/MinecraftHandler.kt +++ b/common/src/main/kotlin/dev/erdragh/astralbot/handlers/MinecraftHandler.kt @@ -23,6 +23,7 @@ import net.minecraft.world.item.TooltipFlag import java.awt.Color import java.text.DecimalFormat import java.util.* +import java.util.regex.Pattern import kotlin.jvm.optionals.getOrNull import kotlin.math.min @@ -36,8 +37,18 @@ class MinecraftHandler(private val server: MinecraftServer) : ListenerAdapter() private val playerNames = HashSet(server.maxPlayers); private val notchPlayer = byName("Notch")?.let { ServerPlayer(this.server, this.server.allLevels.elementAt(0), it) } + companion object { private val numberFormat = DecimalFormat("###.##") + + // Pattern for recognizing a URL, based off RFC 3986 + // Source: https://stackoverflow.com/questions/5713558/detect-and-extract-url-from-a-string + private val urlPattern: Pattern = Pattern.compile( + "(?:^|[\\W])((ht|f)tp(s?):\\/\\/|www\\.)" + + "(([\\w\\-]+\\.){1,}?([\\w\\-.~]+\\/?)*" + + "[\\p{Alnum}.,%_=?&#\\-+()\\[\\]\\*$~@!:/{};']*)", + Pattern.CASE_INSENSITIVE or Pattern.MULTILINE or Pattern.DOTALL + ) } /** @@ -126,7 +137,7 @@ class MinecraftHandler(private val server: MinecraftServer) : ListenerAdapter() return comp.toFlatList() .map { var formatted = it.string - // TODO filter out bad domains + if (it.style.isBold) { formatted = "**$formatted**" } @@ -138,6 +149,18 @@ class MinecraftHandler(private val server: MinecraftServer) : ListenerAdapter() formatted = "[$formatted](${clickEvent.value})" } } + + val matcher = urlPattern.matcher(formatted) + val replaced = matcher.replaceAll { match -> + val group = match.group() + if (AstralBotConfig.urlAllowed(group)) { + return@replaceAll group + } else { + return@replaceAll "`URL BLOCKED`" + } + } + formatted = replaced + return@map formatted } .joinToString("") From 973a98fc011fab41174fc7b759e14b6844b5e8c5 Mon Sep 17 00:00:00 2001 From: Erdragh Date: Tue, 23 Apr 2024 08:59:30 +0200 Subject: [PATCH 11/20] Add more URLs to default blocklist --- .../kotlin/dev/erdragh/astralbot/config/AstralBotConfig.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/common/src/main/kotlin/dev/erdragh/astralbot/config/AstralBotConfig.kt b/common/src/main/kotlin/dev/erdragh/astralbot/config/AstralBotConfig.kt index 6b58514..96ef6eb 100644 --- a/common/src/main/kotlin/dev/erdragh/astralbot/config/AstralBotConfig.kt +++ b/common/src/main/kotlin/dev/erdragh/astralbot/config/AstralBotConfig.kt @@ -117,7 +117,9 @@ object AstralBotConfig { "https://pornhub.com", "https://xhamster.com", "https://xvideos.com", - "https://rule34.xyz" + "https://rule34.xyz", + "https://rule34.xxx", + "https://discord.gg" ) ) ) { From dcbdc7e06be925c0250a29410ea9aaaca65ef288 Mon Sep 17 00:00:00 2001 From: Erdragh Date: Tue, 23 Apr 2024 09:08:53 +0200 Subject: [PATCH 12/20] Fix dependencies workflow run condition --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f2f5947..b062f23 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -65,6 +65,7 @@ jobs: dependencies: runs-on: ubuntu-latest + if: ${{github.ref == 'refs/heads/version/1.20.1' && github.event_name != 'pull_request'}} steps: - name: checkout repository uses: actions/checkout@v4 @@ -76,7 +77,6 @@ jobs: distribution: 'temurin' - name: Build with Gradle and generate dependency Graph - if: ${{github.ref == 'refs/heads/version/1.20.1' && github.event_name != 'pull_request'}} uses: gradle/gradle-build-action@v2 with: arguments: dependencies From 598a7fe34ad91a2831eace8bb55304e208b05bd9 Mon Sep 17 00:00:00 2001 From: Erdragh Date: Tue, 23 Apr 2024 14:51:55 +0200 Subject: [PATCH 13/20] Implement markdown -> component parsing with commonmark --- build.gradle.kts | 6 +- .../astralbot/config/AstralBotTextConfig.kt | 7 + .../astralbot/handlers/MinecraftHandler.kt | 106 +------- .../astralbot/util/ComponentRenderer.kt | 247 ++++++++++++++++++ .../astralbot/util/MessageFormatting.kt | 110 ++++++++ common/src/main/kotlin/module-info.java | 2 + gradle.properties | 7 +- 7 files changed, 387 insertions(+), 98 deletions(-) create mode 100644 common/src/main/kotlin/dev/erdragh/astralbot/util/ComponentRenderer.kt create mode 100644 common/src/main/kotlin/dev/erdragh/astralbot/util/MessageFormatting.kt diff --git a/build.gradle.kts b/build.gradle.kts index 529f941..c5a74c8 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -67,6 +67,7 @@ subprojects { val jdaVersion: String by project val exposedVersion: String by project val sqliteJDBCVersion: String by project + val commonmarkVersion: String by project // This array gets used at multiple places, so it's easier to // just specify all dependencies at once and re-use them. This @@ -85,7 +86,10 @@ subprojects { // Database driver that allows Exposed to communicate with // the SQLite database. This will not be in the JAR and needs to be provided // otherwise (e.g. https://www.curseforge.com/minecraft/mc-mods/sqlite-jdbc) - "org.xerial:sqlite-jdbc:$sqliteJDBCVersion" + "org.xerial:sqlite-jdbc:$sqliteJDBCVersion", + + // Markdown parser used for formatting Discord messages in Minecraft + "org.commonmark:commonmark:$commonmarkVersion", ) dependencies { diff --git a/common/src/main/kotlin/dev/erdragh/astralbot/config/AstralBotTextConfig.kt b/common/src/main/kotlin/dev/erdragh/astralbot/config/AstralBotTextConfig.kt index 6f46b95..b41b8b5 100644 --- a/common/src/main/kotlin/dev/erdragh/astralbot/config/AstralBotTextConfig.kt +++ b/common/src/main/kotlin/dev/erdragh/astralbot/config/AstralBotTextConfig.kt @@ -7,6 +7,7 @@ object AstralBotTextConfig { val GENERIC_ERROR: ForgeConfigSpec.ConfigValue val GENERIC_SUCCESS: ForgeConfigSpec.ConfigValue + val GENERIC_BLOCKED: ForgeConfigSpec.ConfigValue val FAQ_ERROR: ForgeConfigSpec.ConfigValue val FAQ_NO_REGISTERED: ForgeConfigSpec.ConfigValue @@ -17,6 +18,7 @@ object AstralBotTextConfig { val DISCORD_MESSAGE: ForgeConfigSpec.ConfigValue val DISCORD_REPLY: ForgeConfigSpec.ConfigValue + val DISCORD_EMBEDS: ForgeConfigSpec.ConfigValue val RELOAD_ERROR: ForgeConfigSpec.ConfigValue val RELOAD_SUCCESS: ForgeConfigSpec.ConfigValue @@ -41,6 +43,8 @@ object AstralBotTextConfig { .define("genericError", "Something went wrong!") GENERIC_SUCCESS = builder.comment("Generic success message sent to Discord") .define("genericSuccess", "Success!") + GENERIC_BLOCKED = builder.comment("Generic string that replaces blocked URLs/Links") + .define("genericBlocked", "[BLOCKED]") FAQ_ERROR = builder.comment("Message sent to Discord if an error ocurrs during FAQ loading") .define(mutableListOf("faq", "error"), "Bot Error (Contact Bot Operator)") @@ -76,6 +80,9 @@ object AstralBotTextConfig { The user the message is in reply to is referenced by {{replied}} """.replace(whitespaceRegex, "\n")) .define(mutableListOf("messages", "discord", "reply"), " replying to {{replied}}") + DISCORD_EMBEDS = + builder.comment("Template for the label of embeds of a message.") + .define(mutableListOf("messages", "discord", "embeds"), "Embeds:") RELOAD_ERROR = builder.comment("""Template for the error message sent to Discord when reloading fails. diff --git a/common/src/main/kotlin/dev/erdragh/astralbot/handlers/MinecraftHandler.kt b/common/src/main/kotlin/dev/erdragh/astralbot/handlers/MinecraftHandler.kt index 93eb2a1..a317547 100644 --- a/common/src/main/kotlin/dev/erdragh/astralbot/handlers/MinecraftHandler.kt +++ b/common/src/main/kotlin/dev/erdragh/astralbot/handlers/MinecraftHandler.kt @@ -4,7 +4,7 @@ import com.mojang.authlib.GameProfile import dev.erdragh.astralbot.* import dev.erdragh.astralbot.config.AstralBotConfig import dev.erdragh.astralbot.config.AstralBotTextConfig -import net.dv8tion.jda.api.EmbedBuilder +import dev.erdragh.astralbot.util.* import net.dv8tion.jda.api.entities.Member import net.dv8tion.jda.api.entities.Message import net.dv8tion.jda.api.entities.MessageEmbed @@ -17,13 +17,9 @@ import net.minecraft.network.chat.HoverEvent import net.minecraft.network.chat.MutableComponent import net.minecraft.server.MinecraftServer import net.minecraft.server.level.ServerPlayer -import net.minecraft.world.entity.EntityType import net.minecraft.world.item.ItemStack -import net.minecraft.world.item.TooltipFlag -import java.awt.Color import java.text.DecimalFormat import java.util.* -import java.util.regex.Pattern import kotlin.jvm.optionals.getOrNull import kotlin.math.min @@ -40,15 +36,6 @@ class MinecraftHandler(private val server: MinecraftServer) : ListenerAdapter() companion object { private val numberFormat = DecimalFormat("###.##") - - // Pattern for recognizing a URL, based off RFC 3986 - // Source: https://stackoverflow.com/questions/5713558/detect-and-extract-url-from-a-string - private val urlPattern: Pattern = Pattern.compile( - "(?:^|[\\W])((ht|f)tp(s?):\\/\\/|www\\.)" - + "(([\\w\\-]+\\.){1,}?([\\w\\-.~]+\\/?)*" - + "[\\p{Alnum}.,%_=?&#\\-+()\\[\\]\\*$~@!:/{};']*)", - Pattern.CASE_INSENSITIVE or Pattern.MULTILINE or Pattern.DOTALL - ) } /** @@ -133,82 +120,6 @@ class MinecraftHandler(private val server: MinecraftServer) : ListenerAdapter() return server.profileCache?.get(name)?.getOrNull() } - private fun formatComponentToMarkdown(comp: Component): String { - return comp.toFlatList() - .map { - var formatted = it.string - - if (it.style.isBold) { - formatted = "**$formatted**" - } - if (it.style.isItalic) { - formatted = "_${formatted}_" - } - it.style.clickEvent?.let { clickEvent -> - if (clickEvent.action == ClickEvent.Action.OPEN_URL) { - formatted = "[$formatted](${clickEvent.value})" - } - } - - val matcher = urlPattern.matcher(formatted) - val replaced = matcher.replaceAll { match -> - val group = match.group() - if (AstralBotConfig.urlAllowed(group)) { - return@replaceAll group - } else { - return@replaceAll "`URL BLOCKED`" - } - } - formatted = replaced - - return@map formatted - } - .joinToString("") - } - - private fun formatHoverText(text: Component): MessageEmbed { - return EmbedBuilder() - .setDescription(text.string) - .let { builder: EmbedBuilder -> - text.style.color?.value?.let { color -> builder.setColor(color) } - builder - } - .build() - } - - private fun formatHoverItems(stack: ItemStack, knownItems: MutableList): MessageEmbed? { - if (knownItems.contains(stack)) return null - knownItems.add(stack) - val tooltip = stack.getTooltipLines(notchPlayer, TooltipFlag.NORMAL).map(::formatComponentToMarkdown) - return EmbedBuilder() - .setTitle("${tooltip[0]} ${if (stack.count > 1) "(${stack.count})" else ""}") - .setDescription(tooltip.drop(1).let { - if (stack.hasCustomHoverName()) { - listOf(stack.item.description.string).plus(it) - } else it - }.joinToString("\n")) - .let { builder: EmbedBuilder -> - stack.rarity.color.color?.let { color -> builder.setColor(color) } - builder - } - .build() - } - - private fun formatHoverEntity(entity: HoverEvent.EntityTooltipInfo): MessageEmbed? { - if (entity.type == EntityType.PLAYER) return null - return EmbedBuilder() - .setTitle(entity.name?.string) - .setDescription(entity.type.description.string) - .let { builder: EmbedBuilder -> - val mobCategory = entity.type.category - if (mobCategory.isFriendly) { - builder.setColor(Color.GREEN) - } - builder - } - .build() - } - /** * Sends a message into the configured Discord channel based on * the Chat [message] the [player] sent. @@ -230,7 +141,7 @@ class MinecraftHandler(private val server: MinecraftServer) : ListenerAdapter() formattedEmbeds.add(formatHoverText(it)) } attachment.getValue(HoverEvent.Action.SHOW_ITEM)?.itemStack?.let { - formatHoverItems(it, items)?.let(formattedEmbeds::add) + formatHoverItems(it, items, notchPlayer)?.let(formattedEmbeds::add) } attachment.getValue(HoverEvent.Action.SHOW_ENTITY)?.let { formatHoverEntity(it)?.let(formattedEmbeds::add) @@ -304,7 +215,7 @@ class MinecraftHandler(private val server: MinecraftServer) : ListenerAdapter() val messageContents = Component.empty() // This is the actual message content - val actualMessage = Component.literal(message.contentDisplay) + val actualMessage = formatMarkdownToComponent(message.contentDisplay) // If it's enabled in the config you can click on a message and get linked to said message // in the actual Discord client if (AstralBotConfig.CLICKABLE_MESSAGES.get()) { @@ -423,7 +334,7 @@ class MinecraftHandler(private val server: MinecraftServer) : ListenerAdapter() val comp = Component.empty() // Adds a newline with space if there are embeds and the message isn't empty if (message.embeds.size + message.attachments.size + message.stickers.size > 0 && message.contentDisplay.isNotBlank()) comp.append( - "\n " + "\n ${AstralBotTextConfig.DISCORD_EMBEDS.get()} " ) var i = 0 message.embeds.forEach { @@ -469,14 +380,19 @@ class MinecraftHandler(private val server: MinecraftServer) : ListenerAdapter() if (AstralBotConfig.CLICKABLE_EMBEDS.get()) { embedComponent.withStyle { style -> if (url != null && AstralBotConfig.CLICKABLE_EMBEDS.get()) { - style.withColor(ChatFormatting.BLUE).withUnderlined(true) + style.withColor(ChatFormatting.BLUE) + .withUnderlined(true) .withClickEvent(ClickEvent(ClickEvent.Action.OPEN_URL, url)) + .withHoverEvent(HoverEvent(HoverEvent.Action.SHOW_TEXT, Component.nullToEmpty(url))) } else style } } comp.append(embedComponent) } else { - comp.append(Component.literal("BLOCKED").withStyle(ChatFormatting.RED)) + comp.append( + Component.literal(AstralBotTextConfig.GENERIC_BLOCKED.get()) + .withStyle(ChatFormatting.RED, ChatFormatting.UNDERLINE) + ) } return comp } diff --git a/common/src/main/kotlin/dev/erdragh/astralbot/util/ComponentRenderer.kt b/common/src/main/kotlin/dev/erdragh/astralbot/util/ComponentRenderer.kt new file mode 100644 index 0000000..db4bb44 --- /dev/null +++ b/common/src/main/kotlin/dev/erdragh/astralbot/util/ComponentRenderer.kt @@ -0,0 +1,247 @@ +package dev.erdragh.astralbot.util + +import dev.erdragh.astralbot.config.AstralBotConfig +import dev.erdragh.astralbot.config.AstralBotTextConfig +import net.minecraft.ChatFormatting +import net.minecraft.network.chat.ClickEvent +import net.minecraft.network.chat.Component +import net.minecraft.network.chat.HoverEvent +import net.minecraft.network.chat.MutableComponent +import org.commonmark.node.* +import org.commonmark.renderer.NodeRenderer +import java.util.Stack + +class ComponentRenderer : AbstractVisitor(), NodeRenderer { + companion object { + private abstract class ListHolder(val parent: ListHolder?) + + private class BulletListHolder(parent: ListHolder?, list: BulletList) : ListHolder(parent) { + val marker: String = list.marker + } + + private class OrderedListHolder(parent: ListHolder?, list: OrderedList) : ListHolder(parent) { + val delimiter: String = list.markerDelimiter + var number: Int = list.markerStartNumber + } + } + + private var currentComponent: MutableComponent = Component.empty() + private val prefixes: Stack = Stack() + private var listHolder: ListHolder? = null + private var shouldAddBlock: Boolean = false + + override fun getNodeTypes(): MutableSet> { + return mutableSetOf( + Document::class.java, + Heading::class.java, + Emphasis::class.java, + Link::class.java, + Paragraph::class.java, + StrongEmphasis::class.java, + Text::class.java, + BlockQuote::class.java, + Code::class.java, + FencedCodeBlock::class.java, + BulletList::class.java, + OrderedList::class.java, + ListItem::class.java, + SoftLineBreak::class.java, + HardLineBreak::class.java + ) + } + + private fun childIntoCurrent(component: MutableComponent, action: () -> Unit) { + val temp = currentComponent + currentComponent = component + action() + temp.append(currentComponent) + currentComponent = temp + } + + fun renderToComponent(node: Node?): MutableComponent { + this.render(node) + return currentComponent + } + + override fun render(node: Node?) { + node?.accept(this) + } + + override fun visit(document: Document?) { + visitChildren(document) + } + + override fun visit(heading: Heading?) { + if (heading == null) return + + block() + childIntoCurrent(Component.empty().withStyle(ChatFormatting.BOLD)) { + visitChildren(heading) + } + block() + } + + override fun visit(emphasis: Emphasis?) { + childIntoCurrent(Component.empty().withStyle(ChatFormatting.ITALIC)) { + super.visit(emphasis) + } + } + + override fun visit(link: Link?) { + if (link == null) return + this.formatLink(link, link.title, link.destination) + } + + override fun visit(paragraph: Paragraph?) { + visitChildren(paragraph) + block() + } + + override fun visit(strongEmphasis: StrongEmphasis?) { + childIntoCurrent(Component.empty().withStyle(ChatFormatting.BOLD)) { + super.visit(strongEmphasis) + } + } + + override fun visit(text: Text?) { + if (text == null) return + + val literal = text.literal + + val matcher = urlPattern.matcher(text.literal) + var lastEnd = 0 + for (result in matcher.results()) { + append(literal.substring(lastEnd.. ").withStyle(ChatFormatting.DARK_GRAY).withStyle { it.withItalic(false) }) + block() + childIntoCurrent(Component.empty().withStyle(ChatFormatting.GRAY, ChatFormatting.ITALIC)) { + visitChildren(blockQuote) + } + prefixes.pop() + block() + } + + override fun visit(code: Code?) { + if (code == null) return + append( + Component.literal("`${code.literal}`") + .withStyle(ChatFormatting.YELLOW) + ) + } + + override fun visit(fencedCodeBlock: FencedCodeBlock?) { + if (fencedCodeBlock == null) return + append(Component.literal(fencedCodeBlock.literal).withStyle(ChatFormatting.YELLOW)) + } + + override fun visit(bulletList: BulletList?) { + if (bulletList == null) return + listHolder = BulletListHolder(listHolder, bulletList) + visitChildren(bulletList) + listHolder = listHolder!!.parent + block() + } + + override fun visit(orderedList: OrderedList?) { + if (orderedList == null) return + listHolder = OrderedListHolder(listHolder, orderedList) + visitChildren(orderedList) + listHolder = listHolder!!.parent + block() + } + + override fun visit(listItem: ListItem?) { + if (listItem == null) return + val markerIndent = listItem.markerIndent + val marker: String + + when (listHolder) { + is BulletListHolder -> { + marker = " ".repeat(markerIndent) + (listHolder!! as BulletListHolder).marker + } + is OrderedListHolder -> { + val holder = listHolder!! as OrderedListHolder + marker = " ".repeat(markerIndent) + holder.number + holder.delimiter + holder.number++ + } + else -> { + throw IllegalStateException("Unknown list holder type: $listHolder") + } + } + + block() + + val contentIndent = listItem.contentIndent + append(marker) + append(" ".repeat(contentIndent - marker.length)) + prefixes.push(Component.literal(" ".repeat(contentIndent))) + + if (listItem.firstChild != null) { + // not an empty list + visitChildren(listItem) + } + prefixes.pop() + } + + override fun visit(softLineBreak: SoftLineBreak?) { + newline() + } + + override fun visit(hardLineBreak: HardLineBreak?) { + currentComponent.append(" ") + newline() + } + + private fun newline() { + currentComponent.append("\n") + for (prefix in prefixes.asIterable()) { + currentComponent.append(prefix) + println("applying prefix $prefix") + } + } + + private fun formatLink(node: Node?, title: String?, destination: String) { + childIntoCurrent(Component.empty() + .withStyle(ChatFormatting.BLUE, ChatFormatting.UNDERLINE) + .withStyle { + if (AstralBotConfig.urlAllowed(destination)) { + it + .withClickEvent(ClickEvent(ClickEvent.Action.OPEN_URL, destination)) + .withHoverEvent(HoverEvent(HoverEvent.Action.SHOW_TEXT, Component.literal(destination))) + } else { + it + .withColor(ChatFormatting.RED) + .withHoverEvent(HoverEvent(HoverEvent.Action.SHOW_TEXT, Component.literal(AstralBotTextConfig.GENERIC_BLOCKED.get()))) + } + } + ) { + node?.let(::visitChildren) + if (title != null) { + append(title) + } + } + } + + private fun block() { + this.shouldAddBlock = true + } + + private fun append(component: Component) { + if (this.shouldAddBlock) { + this.shouldAddBlock = false + newline() + } + this.currentComponent.append(component) + } + + private fun append(literal: String) { + append(Component.literal(literal)) + } +} \ No newline at end of file diff --git a/common/src/main/kotlin/dev/erdragh/astralbot/util/MessageFormatting.kt b/common/src/main/kotlin/dev/erdragh/astralbot/util/MessageFormatting.kt new file mode 100644 index 0000000..5b075f5 --- /dev/null +++ b/common/src/main/kotlin/dev/erdragh/astralbot/util/MessageFormatting.kt @@ -0,0 +1,110 @@ +package dev.erdragh.astralbot.util + +import dev.erdragh.astralbot.config.AstralBotConfig +import dev.erdragh.astralbot.config.AstralBotTextConfig +import net.dv8tion.jda.api.EmbedBuilder +import net.dv8tion.jda.api.entities.MessageEmbed +import net.minecraft.network.chat.ClickEvent +import net.minecraft.network.chat.Component +import net.minecraft.network.chat.HoverEvent +import net.minecraft.network.chat.MutableComponent +import net.minecraft.world.entity.EntityType +import net.minecraft.world.entity.player.Player +import net.minecraft.world.item.ItemStack +import net.minecraft.world.item.TooltipFlag +import org.commonmark.parser.Parser +import java.awt.Color +import java.util.regex.Pattern + +// Pattern for recognizing a URL, based off RFC 3986 +// Source: https://stackoverflow.com/questions/5713558/detect-and-extract-url-from-a-string +val urlPattern: Pattern = Pattern.compile( + "(?:^|[\\W])((ht|f)tp(s?):\\/\\/|www\\.)" + + "(([\\w\\-]+\\.){1,}?([\\w\\-.~]+\\/?)*" + + "[\\p{Alnum}.,%_=?&#\\-+()\\[\\]\\*$~@!:/{};']*)", + Pattern.CASE_INSENSITIVE or Pattern.MULTILINE or Pattern.DOTALL +) + +fun formatMarkdownToComponent(md: String): MutableComponent { + val parser = Parser.builder().build() + val parsed = parser.parse(md) + val renderer = ComponentRenderer() + + return renderer.renderToComponent(parsed) +} + +fun formatComponentToMarkdown(comp: Component): String { + return comp.toFlatList() + .map { + var formatted = it.string + + if (it.style.isBold) { + formatted = "**$formatted**" + } + if (it.style.isItalic) { + formatted = "_${formatted}_" + } + it.style.clickEvent?.let { clickEvent -> + if (clickEvent.action == ClickEvent.Action.OPEN_URL) { + formatted = "[$formatted](${clickEvent.value})" + } + } + + val matcher = urlPattern.matcher(formatted) + val replaced = matcher.replaceAll { match -> + val group = match.group() + if (AstralBotConfig.urlAllowed(group)) { + return@replaceAll group + } else { + return@replaceAll AstralBotTextConfig.GENERIC_BLOCKED.get() + } + } + formatted = replaced + + return@map formatted + } + .joinToString("") +} + +fun formatHoverText(text: Component): MessageEmbed { + return EmbedBuilder() + .setDescription(text.string) + .let { builder: EmbedBuilder -> + text.style.color?.value?.let { color -> builder.setColor(color) } + builder + } + .build() +} + +fun formatHoverItems(stack: ItemStack, knownItems: MutableList, player: Player?): MessageEmbed? { + if (knownItems.contains(stack)) return null + knownItems.add(stack) + val tooltip = stack.getTooltipLines(player, TooltipFlag.NORMAL).map(::formatComponentToMarkdown) + return EmbedBuilder() + .setTitle("${tooltip[0]} ${if (stack.count > 1) "(${stack.count})" else ""}") + .setDescription(tooltip.drop(1).let { + if (stack.hasCustomHoverName()) { + listOf(stack.item.description.string).plus(it) + } else it + }.joinToString("\n")) + .let { builder: EmbedBuilder -> + stack.rarity.color.color?.let { color -> builder.setColor(color) } + builder + } + .build() +} + +fun formatHoverEntity(entity: HoverEvent.EntityTooltipInfo): MessageEmbed? { + if (entity.type == EntityType.PLAYER) return null + return EmbedBuilder() + .setTitle(entity.name?.string) + .setDescription(entity.type.description.string) + .let { builder: EmbedBuilder -> + val mobCategory = entity.type.category + if (mobCategory.isFriendly) { + builder.setColor(Color.GREEN) + } + builder + } + .build() +} \ No newline at end of file diff --git a/common/src/main/kotlin/module-info.java b/common/src/main/kotlin/module-info.java index d9bf252..cc50849 100644 --- a/common/src/main/kotlin/module-info.java +++ b/common/src/main/kotlin/module-info.java @@ -14,6 +14,8 @@ // For Discord Interaction requires net.dv8tion.jda; + // For message parsing + requires org.commonmark; // For Minecraft itself requires authlib; diff --git a/gradle.properties b/gradle.properties index fdc1733..88c92ce 100644 --- a/gradle.properties +++ b/gradle.properties @@ -15,8 +15,11 @@ parchmentVersion=2023.09.03 forgeConfigAPIVersion=8.0.0 # Discord Interactions -jdaVersion=5.0.0-beta.22 +jdaVersion=5.0.0-beta.23 # Database Interactions exposedVersion=0.49.0 -sqliteJDBCVersion=3.44.1.0 \ No newline at end of file +sqliteJDBCVersion=3.44.1.0 + +# Message parsing +commonmarkVersion=0.22.0 \ No newline at end of file From 2f0874e2130e933e048a91b1f341ae1869772c3c Mon Sep 17 00:00:00 2001 From: Erdragh Date: Sat, 27 Apr 2024 08:38:26 +0200 Subject: [PATCH 14/20] Remove debug prefix printing --- .../main/kotlin/dev/erdragh/astralbot/util/ComponentRenderer.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/common/src/main/kotlin/dev/erdragh/astralbot/util/ComponentRenderer.kt b/common/src/main/kotlin/dev/erdragh/astralbot/util/ComponentRenderer.kt index db4bb44..1d2dc3e 100644 --- a/common/src/main/kotlin/dev/erdragh/astralbot/util/ComponentRenderer.kt +++ b/common/src/main/kotlin/dev/erdragh/astralbot/util/ComponentRenderer.kt @@ -203,7 +203,6 @@ class ComponentRenderer : AbstractVisitor(), NodeRenderer { currentComponent.append("\n") for (prefix in prefixes.asIterable()) { currentComponent.append(prefix) - println("applying prefix $prefix") } } From 16fc1e7a6d9f6b534d5e8bd74dd5ee7c54dcfff6 Mon Sep 17 00:00:00 2001 From: Erdragh Date: Sat, 27 Apr 2024 08:38:57 +0200 Subject: [PATCH 15/20] Replace IllegalStateException with kotlin error --- .../main/kotlin/dev/erdragh/astralbot/util/ComponentRenderer.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/src/main/kotlin/dev/erdragh/astralbot/util/ComponentRenderer.kt b/common/src/main/kotlin/dev/erdragh/astralbot/util/ComponentRenderer.kt index 1d2dc3e..f564cdf 100644 --- a/common/src/main/kotlin/dev/erdragh/astralbot/util/ComponentRenderer.kt +++ b/common/src/main/kotlin/dev/erdragh/astralbot/util/ComponentRenderer.kt @@ -172,7 +172,7 @@ class ComponentRenderer : AbstractVisitor(), NodeRenderer { holder.number++ } else -> { - throw IllegalStateException("Unknown list holder type: $listHolder") + error("Unknown list holder type: $listHolder") } } From cfb36afe474d2d83bb467ab91cf1f5f8294c9b73 Mon Sep 17 00:00:00 2001 From: Erdragh Date: Sat, 27 Apr 2024 08:50:23 +0200 Subject: [PATCH 16/20] Implement a few more markdown nodes --- .../erdragh/astralbot/util/ComponentRenderer.kt | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/common/src/main/kotlin/dev/erdragh/astralbot/util/ComponentRenderer.kt b/common/src/main/kotlin/dev/erdragh/astralbot/util/ComponentRenderer.kt index f564cdf..f23343a 100644 --- a/common/src/main/kotlin/dev/erdragh/astralbot/util/ComponentRenderer.kt +++ b/common/src/main/kotlin/dev/erdragh/astralbot/util/ComponentRenderer.kt @@ -42,11 +42,13 @@ class ComponentRenderer : AbstractVisitor(), NodeRenderer { BlockQuote::class.java, Code::class.java, FencedCodeBlock::class.java, + IndentedCodeBlock::class.java, BulletList::class.java, OrderedList::class.java, ListItem::class.java, SoftLineBreak::class.java, - HardLineBreak::class.java + HardLineBreak::class.java, + ThematicBreak::class.java ) } @@ -141,6 +143,11 @@ class ComponentRenderer : AbstractVisitor(), NodeRenderer { append(Component.literal(fencedCodeBlock.literal).withStyle(ChatFormatting.YELLOW)) } + override fun visit(indentedCodeBlock: IndentedCodeBlock?) { + if (indentedCodeBlock == null) return + append(Component.literal(indentedCodeBlock.literal).withStyle(ChatFormatting.YELLOW)) + } + override fun visit(bulletList: BulletList?) { if (bulletList == null) return listHolder = BulletListHolder(listHolder, bulletList) @@ -199,6 +206,13 @@ class ComponentRenderer : AbstractVisitor(), NodeRenderer { newline() } + override fun visit(thematicBreak: ThematicBreak?) { + if (thematicBreak == null) return + block() + append(thematicBreak.literal) + block() + } + private fun newline() { currentComponent.append("\n") for (prefix in prefixes.asIterable()) { From ac38938a5488637bb0bddd296987c12a449af3d9 Mon Sep 17 00:00:00 2001 From: Erdragh Date: Sat, 27 Apr 2024 08:57:05 +0200 Subject: [PATCH 17/20] Add config option to disable markdown parsing --- .../dev/erdragh/astralbot/config/AstralBotConfig.kt | 9 +++++++++ .../dev/erdragh/astralbot/util/MessageFormatting.kt | 2 ++ 2 files changed, 11 insertions(+) diff --git a/common/src/main/kotlin/dev/erdragh/astralbot/config/AstralBotConfig.kt b/common/src/main/kotlin/dev/erdragh/astralbot/config/AstralBotConfig.kt index 96ef6eb..f50870c 100644 --- a/common/src/main/kotlin/dev/erdragh/astralbot/config/AstralBotConfig.kt +++ b/common/src/main/kotlin/dev/erdragh/astralbot/config/AstralBotConfig.kt @@ -81,6 +81,12 @@ object AstralBotConfig { */ val ENABLED_COMMANDS: ForgeConfigSpec.ConfigValue> + /** + * Enables parsing Discord messages into Minecraft's Chat Components. + * This includes making links clickable, etc. + */ + val ENABLE_MARKDOWN_PARSING: ForgeConfigSpec.BooleanValue + init { val builder = ForgeConfigSpec.Builder() @@ -151,6 +157,9 @@ object AstralBotConfig { return@defineList true } + ENABLE_MARKDOWN_PARSING = builder.comment("Parse Discord messages into Minecraft's Chat Components") + .define("enableMarkdownParsing", true) + SPEC = builder.build() } diff --git a/common/src/main/kotlin/dev/erdragh/astralbot/util/MessageFormatting.kt b/common/src/main/kotlin/dev/erdragh/astralbot/util/MessageFormatting.kt index 5b075f5..4ce4796 100644 --- a/common/src/main/kotlin/dev/erdragh/astralbot/util/MessageFormatting.kt +++ b/common/src/main/kotlin/dev/erdragh/astralbot/util/MessageFormatting.kt @@ -26,6 +26,8 @@ val urlPattern: Pattern = Pattern.compile( ) fun formatMarkdownToComponent(md: String): MutableComponent { + if (!AstralBotConfig.ENABLE_MARKDOWN_PARSING.get()) return Component.literal(md) + val parser = Parser.builder().build() val parsed = parser.parse(md) val renderer = ComponentRenderer() From e91bb5f1c2cdd10ef7211b47158de51f0a7cd586 Mon Sep 17 00:00:00 2001 From: Erdragh Date: Sat, 27 Apr 2024 08:57:52 +0200 Subject: [PATCH 18/20] Improve code quality slightly --- .../kotlin/dev/erdragh/astralbot/config/AstralBotConfig.kt | 2 +- .../kotlin/dev/erdragh/astralbot/util/MessageFormatting.kt | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/common/src/main/kotlin/dev/erdragh/astralbot/config/AstralBotConfig.kt b/common/src/main/kotlin/dev/erdragh/astralbot/config/AstralBotConfig.kt index f50870c..882a5fd 100644 --- a/common/src/main/kotlin/dev/erdragh/astralbot/config/AstralBotConfig.kt +++ b/common/src/main/kotlin/dev/erdragh/astralbot/config/AstralBotConfig.kt @@ -174,7 +174,7 @@ object AstralBotConfig { try { val parsedURL = URL(url) for (blockedURL in URL_BLOCKLIST.get()) { - if (parsedURL.host.equals(URL(blockedURL).host)) return false + if (parsedURL.host == URL(blockedURL).host) return false } } catch (e: Exception) { LOGGER.warn("URL $url", e) diff --git a/common/src/main/kotlin/dev/erdragh/astralbot/util/MessageFormatting.kt b/common/src/main/kotlin/dev/erdragh/astralbot/util/MessageFormatting.kt index 4ce4796..e528f92 100644 --- a/common/src/main/kotlin/dev/erdragh/astralbot/util/MessageFormatting.kt +++ b/common/src/main/kotlin/dev/erdragh/astralbot/util/MessageFormatting.kt @@ -55,10 +55,10 @@ fun formatComponentToMarkdown(comp: Component): String { val matcher = urlPattern.matcher(formatted) val replaced = matcher.replaceAll { match -> val group = match.group() - if (AstralBotConfig.urlAllowed(group)) { - return@replaceAll group + return@replaceAll if (AstralBotConfig.urlAllowed(group)) { + group } else { - return@replaceAll AstralBotTextConfig.GENERIC_BLOCKED.get() + AstralBotTextConfig.GENERIC_BLOCKED.get() } } formatted = replaced From a3097314058a3ec78574e6978ee6caf4c4e60b01 Mon Sep 17 00:00:00 2001 From: Erdragh Date: Sat, 27 Apr 2024 09:02:23 +0200 Subject: [PATCH 19/20] Allow disabling auto links --- .../dev/erdragh/astralbot/config/AstralBotConfig.kt | 10 +++++++++- .../dev/erdragh/astralbot/util/ComponentRenderer.kt | 5 +++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/common/src/main/kotlin/dev/erdragh/astralbot/config/AstralBotConfig.kt b/common/src/main/kotlin/dev/erdragh/astralbot/config/AstralBotConfig.kt index 882a5fd..d6921aa 100644 --- a/common/src/main/kotlin/dev/erdragh/astralbot/config/AstralBotConfig.kt +++ b/common/src/main/kotlin/dev/erdragh/astralbot/config/AstralBotConfig.kt @@ -87,6 +87,12 @@ object AstralBotConfig { */ val ENABLE_MARKDOWN_PARSING: ForgeConfigSpec.BooleanValue + /** + * Enables converting detected URLs into clickable links, requires + * [ENABLE_MARKDOWN_PARSING] to be enabled to do anything + */ + val ENABLE_AUTO_LINKS: ForgeConfigSpec.BooleanValue + init { val builder = ForgeConfigSpec.Builder() @@ -158,7 +164,9 @@ object AstralBotConfig { } ENABLE_MARKDOWN_PARSING = builder.comment("Parse Discord messages into Minecraft's Chat Components") - .define("enableMarkdownParsing", true) + .define(listOf("markdown", "enabled"), true) + ENABLE_AUTO_LINKS = builder.comment("Automatically convert detected URLs into clickable links") + .define(listOf("markdown", "autoLinks"), true) SPEC = builder.build() } diff --git a/common/src/main/kotlin/dev/erdragh/astralbot/util/ComponentRenderer.kt b/common/src/main/kotlin/dev/erdragh/astralbot/util/ComponentRenderer.kt index f23343a..a447712 100644 --- a/common/src/main/kotlin/dev/erdragh/astralbot/util/ComponentRenderer.kt +++ b/common/src/main/kotlin/dev/erdragh/astralbot/util/ComponentRenderer.kt @@ -110,6 +110,11 @@ class ComponentRenderer : AbstractVisitor(), NodeRenderer { val literal = text.literal + if (!AstralBotConfig.ENABLE_AUTO_LINKS.get()) { + append(literal) + return + } + val matcher = urlPattern.matcher(text.literal) var lastEnd = 0 for (result in matcher.results()) { From 61d2a6872182c068ad1ceb2c235d18a26d1b046e Mon Sep 17 00:00:00 2001 From: Erdragh Date: Sat, 27 Apr 2024 09:06:03 +0200 Subject: [PATCH 20/20] Adjust Changelog --- Changelog.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Changelog.md b/Changelog.md index 4935f10..4f3e0e8 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,6 +1,9 @@ # Indev - Message Embeds to better represent Minecraft messages on Discord - Fix `/link` Minecraft command not being registered on Forge +- Implement Markdown parsing for Discord to Minecraft message sync +- Implement automatically converting URLs to clickable links in Minecraft chat +- Replace blocked URLs before sending Minecraft chat to Discord # 1.2.1 Allow providing token via config