From 2196e822d5642799aee4c427eb262c5694b976da Mon Sep 17 00:00:00 2001 From: ChomeNS <95471003+ChomeNS@users.noreply.github.com> Date: Mon, 31 Mar 2025 10:48:11 +0700 Subject: [PATCH] feat: support ANSI color/styles in discord reply and also some refactors in ComponentUtilities --- build-number.txt | 2 +- .../chomens_bot/plugins/DiscordPlugin.java | 20 +++-- .../chomens_bot/util/ComponentUtilities.java | 80 +++++++++++++------ 3 files changed, 70 insertions(+), 32 deletions(-) diff --git a/build-number.txt b/build-number.txt index f8eec334..47b9a822 100644 --- a/build-number.txt +++ b/build-number.txt @@ -1 +1 @@ -2290 \ No newline at end of file +2300 \ No newline at end of file diff --git a/src/main/java/me/chayapak1/chomens_bot/plugins/DiscordPlugin.java b/src/main/java/me/chayapak1/chomens_bot/plugins/DiscordPlugin.java index 7083477f..5b774da7 100644 --- a/src/main/java/me/chayapak1/chomens_bot/plugins/DiscordPlugin.java +++ b/src/main/java/me/chayapak1/chomens_bot/plugins/DiscordPlugin.java @@ -203,7 +203,7 @@ public class DiscordPlugin extends ListenerAdapter { bot ); - Component messageComponent = Component.text(snapshot.getContentRaw()); + Component messageComponent = Component.text(replaceMessageContent(snapshot.getContentRaw())); if (!extraComponents.isEmpty()) { messageComponent = messageComponent @@ -342,13 +342,7 @@ public class DiscordPlugin extends ListenerAdapter { if (nameComponent.color() == null) nameComponent = nameComponent.color(NamedTextColor.RED); - final String replacedMessageContent = message.getContentDisplay() - // replaces skull emoji - .replace("\uD83D\uDC80", "☠") - // replaces all ANSI codes (used when a user replies to the bot's log message) with nothing - .replaceAll("\u001B\\[[;\\d]*[ -/]*[@-~]", "") - // replaces all ZWSP with nothing - .replace("\u200b", ""); + final String replacedMessageContent = replaceMessageContent(message.getContentDisplay()); Component actualMessage = LegacyComponentSerializer .legacyAmpersand() @@ -391,6 +385,16 @@ public class DiscordPlugin extends ListenerAdapter { ).color(NamedTextColor.DARK_GRAY); } + private String replaceMessageContent (String content) { + return ComponentUtilities + // replaces the ANSI codes (from the bot) with section signs corresponding to the color/style + .deserializeFromDiscordAnsi(content) + // replaces skull emoji + .replace("\uD83D\uDC80", "☠") + // replaces all ZWSP with nothing + .replace("\u200b", ""); + } + private void addExtraComponents ( List extraComponents, List attachments, diff --git a/src/main/java/me/chayapak1/chomens_bot/util/ComponentUtilities.java b/src/main/java/me/chayapak1/chomens_bot/util/ComponentUtilities.java index 239a29f9..83ce43ce 100644 --- a/src/main/java/me/chayapak1/chomens_bot/util/ComponentUtilities.java +++ b/src/main/java/me/chayapak1/chomens_bot/util/ComponentUtilities.java @@ -99,6 +99,8 @@ public class ComponentUtilities { public static String stringifyAnsi (Component message) { return new ComponentParser().stringify(message, ComponentParser.ParseType.ANSI); } public static String stringifyDiscordAnsi (Component message) { return new ComponentParser().stringify(message, ComponentParser.ParseType.DISCORD_ANSI); } + public static String deserializeFromDiscordAnsi (String original) { return new ComponentParser().deserializeFromDiscordAnsi(original); } + private static class ComponentParser { public static final Pattern ARG_PATTERN = Pattern.compile("%(?:(\\d+)\\$)?([s%])"); @@ -106,7 +108,6 @@ public class ComponentUtilities { public static final Map ANSI_MAP = new HashMap<>(); static { - // map totallynotskidded™ from https://github.com/PrismarineJS/prismarine-chat/blob/master/index.js#L10 ANSI_MAP.put("0", "\u001b[38;2;0;0;0m"); ANSI_MAP.put("1", "\u001b[38;2;0;0;170m"); ANSI_MAP.put("2", "\u001b[38;2;0;170;0m"); @@ -134,6 +135,15 @@ public class ComponentUtilities { public static final Map DISCORD_ANSI_MAP = new HashMap<>(); static { // map totallynotskidded™ from https://github.com/PrismarineJS/prismarine-chat/blob/master/index.js#L10 + + // these bright colors have to come first because of deserialization + DISCORD_ANSI_MAP.put("a", "\u001b[32m"); + DISCORD_ANSI_MAP.put("b", "\u001b[36m"); + DISCORD_ANSI_MAP.put("c", "\u001b[31m"); + DISCORD_ANSI_MAP.put("d", "\u001b[35m"); + DISCORD_ANSI_MAP.put("e", "\u001b[33m"); + DISCORD_ANSI_MAP.put("f", "\u001b[37m"); + DISCORD_ANSI_MAP.put("0", "\u001b[30m"); DISCORD_ANSI_MAP.put("1", "\u001b[34m"); DISCORD_ANSI_MAP.put("2", "\u001b[32m"); @@ -142,14 +152,9 @@ public class ComponentUtilities { DISCORD_ANSI_MAP.put("5", "\u001b[35m"); DISCORD_ANSI_MAP.put("6", "\u001b[33m"); DISCORD_ANSI_MAP.put("7", "\u001b[37m"); - DISCORD_ANSI_MAP.put("8", "\u001b[90m"); - DISCORD_ANSI_MAP.put("9", "\u001b[94m"); - DISCORD_ANSI_MAP.put("a", "\u001b[92m"); - DISCORD_ANSI_MAP.put("b", "\u001b[96m"); - DISCORD_ANSI_MAP.put("c", "\u001b[91m"); - DISCORD_ANSI_MAP.put("d", "\u001b[95m"); - DISCORD_ANSI_MAP.put("e", "\u001b[93m"); - DISCORD_ANSI_MAP.put("f", "\u001b[97m"); + DISCORD_ANSI_MAP.put("8", "\u001b[30m"); + DISCORD_ANSI_MAP.put("9", "\u001b[34m"); + DISCORD_ANSI_MAP.put("l", "\u001b[1m"); DISCORD_ANSI_MAP.put("o", "\u001b[3m"); DISCORD_ANSI_MAP.put("n", "\u001b[4m"); @@ -158,6 +163,9 @@ public class ComponentUtilities { DISCORD_ANSI_MAP.put("r", "\u001b[0m"); } + // we only focus on the discord ones that we made + private static final Pattern DISCORD_ANSI_PATTERN = Pattern.compile("(\\u001b\\[\\d+m)"); + private ParseType type; private long parseStartTime = System.currentTimeMillis(); @@ -166,6 +174,33 @@ public class ComponentUtilities { private boolean isSubParsing = false; + public String deserializeFromDiscordAnsi (String original) { + final Matcher matcher = DISCORD_ANSI_PATTERN.matcher(original); + final StringBuilder builder = new StringBuilder(); + + while (matcher.find()) { + final String match = matcher.group(); + + boolean replaced = false; + + for (Map.Entry entry : DISCORD_ANSI_MAP.entrySet()) { + if (!entry.getValue().equals(match)) continue; + + matcher.appendReplacement(builder, "§" + entry.getKey()); + + replaced = true; + + break; + } + + if (!replaced) matcher.appendReplacement(builder, match); + } + + matcher.appendTail(builder); + + return builder.toString(); + } + private String stringify (Component message, ParseType type) { this.type = type; @@ -193,15 +228,10 @@ public class ComponentUtilities { !isSubParsing && (type == ParseType.ANSI || type == ParseType.DISCORD_ANSI) ) { - builder.append(DISCORD_ANSI_MAP.get("r")); + builder.append(ANSI_MAP.get("r")); } - if (type == ParseType.DISCORD_ANSI) { - // as of the time writing this (2024-12-28) discord doesn't support the bright colors yet - return builder.toString().replace("\u001b[9", "\u001b[3"); - } else { - return builder.toString(); - } + return builder.toString(); } catch (Exception e) { LoggerUtilities.error(e); return ""; @@ -214,7 +244,7 @@ public class ComponentUtilities { case TranslatableComponent t_component -> stringifyPartially(t_component, color, style); case SelectorComponent t_component -> stringifyPartially(t_component, color, style); case KeybindComponent t_component -> stringifyPartially(t_component, color, style); - default -> ""; + default -> String.format("[Component type %s not implemented!]", message.getClass().getSimpleName()); }; } @@ -229,13 +259,17 @@ public class ComponentUtilities { if (state == TextDecoration.State.NOT_SET || state == TextDecoration.State.FALSE) continue; - if (type == ParseType.ANSI) { // right now discord doesn't care about styling + if (type == ParseType.ANSI || type == ParseType.DISCORD_ANSI) { + final Map map = type == ParseType.ANSI ? + ANSI_MAP : + DISCORD_ANSI_MAP; + switch (decoration) { - case BOLD -> style.append(ANSI_MAP.get("l")); - case ITALIC -> style.append(ANSI_MAP.get("o")); - case OBFUSCATED -> style.append(ANSI_MAP.get("k")); - case UNDERLINED -> style.append(ANSI_MAP.get("n")); - case STRIKETHROUGH -> style.append(ANSI_MAP.get("m")); + case BOLD -> style.append(map.get("l")); + case ITALIC -> style.append(map.get("o")); + case OBFUSCATED -> style.append(map.get("k")); + case UNDERLINED -> style.append(map.get("n")); + case STRIKETHROUGH -> style.append(map.get("m")); } } else if (type == ParseType.SECTION_SIGNS) { switch (decoration) {