diff --git a/build-number.txt b/build-number.txt index 61a41990..438ea09f 100644 --- a/build-number.txt +++ b/build-number.txt @@ -1 +1 @@ -1303 \ No newline at end of file +1341 \ No newline at end of file diff --git a/src/main/java/me/chayapak1/chomens_bot/Bot.java b/src/main/java/me/chayapak1/chomens_bot/Bot.java index 5f0c414a..f608c853 100644 --- a/src/main/java/me/chayapak1/chomens_bot/Bot.java +++ b/src/main/java/me/chayapak1/chomens_bot/Bot.java @@ -209,7 +209,7 @@ public class Bot { // we also set other stuffs here session.send( new ServerboundClientInformationPacket( - ComponentUtilities.language.getOrDefault("language.code", "en-us"), + ComponentUtilities.LANGUAGE.getOrDefault("language.code", "en-us"), 16, ChatVisibility.FULL, true, diff --git a/src/main/java/me/chayapak1/chomens_bot/plugins/ChatPlugin.java b/src/main/java/me/chayapak1/chomens_bot/plugins/ChatPlugin.java index 5055a70f..7f9e24c6 100644 --- a/src/main/java/me/chayapak1/chomens_bot/plugins/ChatPlugin.java +++ b/src/main/java/me/chayapak1/chomens_bot/plugins/ChatPlugin.java @@ -339,7 +339,7 @@ public class ChatPlugin extends Bot.Listener { if (bot.options.useChat) { if (!targets.equals("@a")) return; // worst fix of all time!1! - final String stringified = ComponentUtilities.stringifyMotd(component).replace("§", "&"); + final String stringified = ComponentUtilities.stringifySectionSign(component).replace("§", "&"); send(stringified); } else { bot.core.run("minecraft:tellraw " + targets + " " + GsonComponentSerializer.gson().serialize(component)); 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 14b06a56..f7d3f349 100644 --- a/src/main/java/me/chayapak1/chomens_bot/plugins/DiscordPlugin.java +++ b/src/main/java/me/chayapak1/chomens_bot/plugins/DiscordPlugin.java @@ -98,15 +98,10 @@ public class DiscordPlugin { if (string.length() > 2000 - 12) { sendMessage(CodeBlockUtilities.escape(string), channelId); } else { - final String ansi = ComponentUtilities.stringifyAnsi(component, true); + final String ansi = ComponentUtilities.stringifyDiscordAnsi(component); sendMessage( - CodeBlockUtilities.escape( - ansi - .replace( - "\u001b[9", "\u001b[3" - ) - ), + CodeBlockUtilities.escape(ansi), channelId ); } diff --git a/src/main/java/me/chayapak1/chomens_bot/song/NBSConverter.java b/src/main/java/me/chayapak1/chomens_bot/song/NBSConverter.java index 02a5599e..c5436364 100644 --- a/src/main/java/me/chayapak1/chomens_bot/song/NBSConverter.java +++ b/src/main/java/me/chayapak1/chomens_bot/song/NBSConverter.java @@ -278,7 +278,7 @@ public class NBSConverter implements Converter { private static final Map subtitles = new HashMap<>(); static { - for (Map.Entry entry : ComponentUtilities.language.entrySet()) { + for (Map.Entry entry : ComponentUtilities.LANGUAGE.entrySet()) { if (!entry.getKey().startsWith("subtitles.")) continue; subtitles.put(entry.getKey(), entry.getValue()); 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 0ae606a4..1b7256d1 100644 --- a/src/main/java/me/chayapak1/chomens_bot/util/ComponentUtilities.java +++ b/src/main/java/me/chayapak1/chomens_bot/util/ComponentUtilities.java @@ -20,45 +20,9 @@ import java.util.regex.Pattern; // totallynotskidded™ from chipmunkbot and added colors (ignore the ohio code please,..,.) public class ComponentUtilities { // component parsing - public static final Map language = loadJsonStringMap("language.json"); - private static final Map voiceChatLanguage = loadJsonStringMap("voiceChatLanguage.json"); - private static final Map keybinds = loadJsonStringMap("keybinds.json"); - - public static final Pattern ARG_PATTERN = Pattern.compile("%(?:(\\d+)\\$)?([s%])"); - - public static final int MAX_DEPTH = 16; - - public static final Map ansiMap = new HashMap<>(); - static { - // map totallynotskidded™ from https://github.com/PrismarineJS/prismarine-chat/blob/master/index.js#L10 - ansiMap.put("0", "\u001b[30m"); - ansiMap.put("1", "\u001b[34m"); - ansiMap.put("2", "\u001b[32m"); - ansiMap.put("3", "\u001b[36m"); - ansiMap.put("4", "\u001b[31m"); - ansiMap.put("5", "\u001b[35m"); - ansiMap.put("6", "\u001b[33m"); - ansiMap.put("7", "\u001b[37m"); - ansiMap.put("8", "\u001b[90m"); - ansiMap.put("9", "\u001b[94m"); - ansiMap.put("a", "\u001b[92m"); - ansiMap.put("b", "\u001b[96m"); - ansiMap.put("c", "\u001b[91m"); - ansiMap.put("d", "\u001b[95m"); - ansiMap.put("e", "\u001b[93m"); - ansiMap.put("f", "\u001b[97m"); - ansiMap.put("l", "\u001b[1m"); - ansiMap.put("o", "\u001b[3m"); - ansiMap.put("n", "\u001b[4m"); - ansiMap.put("m", "\u001b[9m"); - ansiMap.put("k", "\u001b[6m"); - ansiMap.put("r", "\u001b[0m"); - } - - public record PartiallyStringified( - String output, - String lastColor - ) {} + public static final Map LANGUAGE = loadJsonStringMap("language.json"); + public static final Map VOICE_CHAT_LANGUAGE = loadJsonStringMap("voiceChatLanguage.json"); + public static final Map KEYBINDINGS = loadJsonStringMap("keybinds.json"); private static Map loadJsonStringMap (String name) { Map map = new HashMap<>(); @@ -74,186 +38,226 @@ public class ComponentUtilities { return map; } - private static String getOrReturnFallback (TranslatableComponent component) { + public static String getOrReturnFallback (TranslatableComponent component) { final String key = component.key(); - final String minecraftKey = language.get(key); - final String voiceChatKey = voiceChatLanguage.get(key); + final String minecraftKey = LANGUAGE.get(key); + final String voiceChatKey = VOICE_CHAT_LANGUAGE.get(key); if (minecraftKey != null) return minecraftKey; else if (voiceChatKey != null) return voiceChatKey; else return component.fallback() != null ? component.fallback() : key; } - public static String stringify (Component message) { return stringify(message, null, 0); } - private static String stringify (Component message, String lastColor, int depth) { - if (depth > MAX_DEPTH) return ""; + public static String stringify (Component message) { return new ComponentParser().stringify(message, ComponentParser.ParseType.PLAIN); } + public static String stringifySectionSign (Component message) { return new ComponentParser().stringify(message, ComponentParser.ParseType.SECTION_SIGNS); } + 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); } - try { - final StringBuilder builder = new StringBuilder(); + private static class ComponentParser { + public static final Pattern ARG_PATTERN = Pattern.compile("%(?:(\\d+)\\$)?([s%])"); - final PartiallyStringified output = stringifyPartially(message, false, false, lastColor, false, depth); + public static final int MAX_DEPTH = 16; - builder.append(output.output); - - for (Component child : message.children()) builder.append(stringify(child, lastColor != null ? lastColor : output.lastColor, depth)); - - return builder.toString(); - } catch (Exception e) { - return ""; - } - } - - public static String stringifyMotd (Component message) { return stringifyMotd(message, null, 0); } - private static String stringifyMotd (Component message, String lastColor, int depth) { - if (depth > MAX_DEPTH) return ""; - - try { - final StringBuilder builder = new StringBuilder(); - - final PartiallyStringified output = stringifyPartially(message, true, false, lastColor, false, depth); - - builder.append(output.output); - - for (Component child : message.children()) builder.append(stringifyMotd(child, lastColor != null ? lastColor : output.lastColor, depth)); - - return builder.toString(); - } catch (Exception e) { - return ""; - } - } - - public static String stringifyAnsi (Component message) { return stringifyAnsi(message, null, false, 0); } - public static String stringifyAnsi (Component message, boolean noHex) { return stringifyAnsi(message, null, noHex, 0); } - private static String stringifyAnsi (Component message, String lastColor, boolean noHex, int depth) { - if (depth > MAX_DEPTH) return ""; - - try { - final StringBuilder builder = new StringBuilder(); - - final PartiallyStringified output = stringifyPartially(message, false, true, lastColor, noHex, depth); - - builder.append(output.output); - - for (Component child : message.children()) builder.append(stringifyAnsi(child, lastColor != null ? lastColor : output.lastColor, noHex, depth)); - - return builder.toString(); - } catch (Exception e) { - return ""; - } - } - - public static PartiallyStringified stringifyPartially (Component message, boolean motd, boolean ansi, String lastColor, boolean noHex, int depth) { - return switch (message) { - case TextComponent t_component -> stringifyPartially(t_component, motd, ansi, lastColor, noHex); - case TranslatableComponent t_component -> stringifyPartially(t_component, motd, ansi, lastColor, noHex, depth); - case SelectorComponent t_component -> stringifyPartially(t_component, motd, ansi, lastColor, noHex); - case KeybindComponent t_component -> stringifyPartially(t_component, motd, ansi, lastColor, noHex); - default -> new PartiallyStringified("", null); - }; - } - - public static String getStyle (Style textStyle, boolean motd) { - if (textStyle == null) return null; - - StringBuilder style = new StringBuilder(); - - for (Map.Entry decorationEntry : textStyle.decorations().entrySet()) { - final TextDecoration decoration = decorationEntry.getKey(); - final TextDecoration.State state = decorationEntry.getValue(); - - if (state == TextDecoration.State.NOT_SET || state == TextDecoration.State.FALSE) continue; - - if (!motd) { - switch (decoration) { - case BOLD -> style.append(ansiMap.get("l")); - case ITALIC -> style.append(ansiMap.get("o")); - case OBFUSCATED -> style.append(ansiMap.get("k")); - case UNDERLINED -> style.append(ansiMap.get("n")); - case STRIKETHROUGH -> style.append(ansiMap.get("m")); - } - } else { - switch (decoration) { - case BOLD -> style.append("§l"); - case ITALIC -> style.append("§o"); - case OBFUSCATED -> style.append("§k"); - case UNDERLINED -> style.append("§n"); - case STRIKETHROUGH -> style.append("§m"); - } - } + public static final Map ansiMap = new HashMap<>(); + static { + // map totallynotskidded™ from https://github.com/PrismarineJS/prismarine-chat/blob/master/index.js#L10 + ansiMap.put("0", "\u001b[30m"); + ansiMap.put("1", "\u001b[34m"); + ansiMap.put("2", "\u001b[32m"); + ansiMap.put("3", "\u001b[36m"); + ansiMap.put("4", "\u001b[31m"); + ansiMap.put("5", "\u001b[35m"); + ansiMap.put("6", "\u001b[33m"); + ansiMap.put("7", "\u001b[37m"); + ansiMap.put("8", "\u001b[90m"); + ansiMap.put("9", "\u001b[94m"); + ansiMap.put("a", "\u001b[92m"); + ansiMap.put("b", "\u001b[96m"); + ansiMap.put("c", "\u001b[91m"); + ansiMap.put("d", "\u001b[95m"); + ansiMap.put("e", "\u001b[93m"); + ansiMap.put("f", "\u001b[97m"); + ansiMap.put("l", "\u001b[1m"); + ansiMap.put("o", "\u001b[3m"); + ansiMap.put("n", "\u001b[4m"); + ansiMap.put("m", "\u001b[9m"); + ansiMap.put("k", "\u001b[6m"); + ansiMap.put("r", "\u001b[0m"); } - return style.toString(); - } + private ParseType type; + private int formatsPlaceholdersCount = 0; - public static String getColor (TextColor color, boolean motd, boolean ansi, boolean noHex) { - if (color == null) return null; + private String lastStyle = ""; + + private String stringify (Component message, ParseType type) { + this.type = type; + + if (formatsPlaceholdersCount > MAX_DEPTH) return ""; - // map totallynotskidded™ too from https://github.com/PrismarineJS/prismarine-chat/blob/master/index.js#L299 - String code; - if (color == NamedTextColor.BLACK) code = "0"; - else if (color == NamedTextColor.DARK_BLUE) code = "1"; - else if (color == NamedTextColor.DARK_GREEN) code = "2"; - else if (color == NamedTextColor.DARK_AQUA) code = "3"; - else if (color == NamedTextColor.DARK_RED) code = "4"; - else if (color == NamedTextColor.DARK_PURPLE) code = "5"; - else if (color == NamedTextColor.GOLD) code = "6"; - else if (color == NamedTextColor.GRAY) code = "7"; - else if (color == NamedTextColor.DARK_GRAY) code = "8"; - else if (color == NamedTextColor.BLUE) code = "9"; - else if (color == NamedTextColor.GREEN) code = "a"; - else if (color == NamedTextColor.AQUA) code = "b"; - else if (color == NamedTextColor.RED) code = "c"; - else if (color == NamedTextColor.LIGHT_PURPLE) code = "d"; - else if (color == NamedTextColor.YELLOW) code = "e"; - else if (color == NamedTextColor.WHITE) code = "f"; - else { try { - code = color.asHexString(); - } catch (NullPointerException e) { - code = ""; // mabe...,,.,.., + final StringBuilder builder = new StringBuilder(); + + final String color = getColor(message.color()); + final String style = getStyle(message.style()); + + final String output = stringifyPartially(message, color, style); + + builder.append(output); + + for (Component child : message.children()) { + final ComponentParser parser = new ComponentParser(); + parser.lastStyle = lastStyle + color + style; + parser.formatsPlaceholdersCount = formatsPlaceholdersCount; + builder.append(parser.stringify(child, type)); + } + + 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(); + } + } catch (Exception e) { + e.printStackTrace(); + return ""; } } - if (motd) { - return "§" + code; - } else if (ansi) { - String ansiCode = ansiMap.get(code); - if (ansiCode == null) { - if (noHex) { - final int rgb = Integer.parseInt(code.substring(1), 16); + public String stringifyPartially (Component message, String color, String style) { + return switch (message) { + case TextComponent t_component -> stringifyPartially(t_component, color, style); + 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 -> ""; + }; + } - final String chatColor = ColorUtilities.getClosestChatColor(rgb); + public String getStyle (Style textStyle) { + if (textStyle == null) return ""; - ansiCode = ansiMap.get(chatColor); - } else { - ansiCode = "\u001b[38;2;" + - color.red() + - ";" + - color.green() + - ";" + - color.blue() + - "m"; + StringBuilder style = new StringBuilder(); + + for (Map.Entry decorationEntry : textStyle.decorations().entrySet()) { + final TextDecoration decoration = decorationEntry.getKey(); + final TextDecoration.State state = decorationEntry.getValue(); + + if (state == TextDecoration.State.NOT_SET || state == TextDecoration.State.FALSE) continue; + + if (type == ParseType.ANSI) { + switch (decoration) { + case BOLD -> style.append(ansiMap.get("l")); + case ITALIC -> style.append(ansiMap.get("o")); + case OBFUSCATED -> style.append(ansiMap.get("k")); + case UNDERLINED -> style.append(ansiMap.get("n")); + case STRIKETHROUGH -> style.append(ansiMap.get("m")); + } + } else if (type == ParseType.SECTION_SIGNS) { + switch (decoration) { + case BOLD -> style.append("§l"); + case ITALIC -> style.append("§o"); + case OBFUSCATED -> style.append("§k"); + case UNDERLINED -> style.append("§n"); + case STRIKETHROUGH -> style.append("§m"); + } } } - return ansiCode; - } else return null; - } + return style.toString(); + } - public static PartiallyStringified stringifyPartially (TextComponent message, boolean motd, boolean ansi, String lastColor, boolean noHex) { - if ((motd || ansi) && /* don't color big messages -> */ message.content().length() < 25_000) { - final String color = getColor(message.color(), motd, ansi, noHex); - final String style = getStyle(message.style(), motd); + public String getColor (TextColor color) { + if (color == null) return ""; - String replacedContent = message.content(); - // seems very mabe mabe - if (ansi && replacedContent.contains("§")) { - // is try-catch a great idea? + // map totallynotskidded™ too from https://github.com/PrismarineJS/prismarine-chat/blob/master/index.js#L299 + String code; + if (color == NamedTextColor.BLACK) code = "0"; + else if (color == NamedTextColor.DARK_BLUE) code = "1"; + else if (color == NamedTextColor.DARK_GREEN) code = "2"; + else if (color == NamedTextColor.DARK_AQUA) code = "3"; + else if (color == NamedTextColor.DARK_RED) code = "4"; + else if (color == NamedTextColor.DARK_PURPLE) code = "5"; + else if (color == NamedTextColor.GOLD) code = "6"; + else if (color == NamedTextColor.GRAY) code = "7"; + else if (color == NamedTextColor.DARK_GRAY) code = "8"; + else if (color == NamedTextColor.BLUE) code = "9"; + else if (color == NamedTextColor.GREEN) code = "a"; + else if (color == NamedTextColor.AQUA) code = "b"; + else if (color == NamedTextColor.RED) code = "c"; + else if (color == NamedTextColor.LIGHT_PURPLE) code = "d"; + else if (color == NamedTextColor.YELLOW) code = "e"; + else if (color == NamedTextColor.WHITE) code = "f"; + else { + try { + code = color.asHexString(); + } catch (NullPointerException e) { + code = ""; // mabe...,,.,.., + } + } + + if (type == ParseType.SECTION_SIGNS) { + return "§" + code; + } else if (type == ParseType.ANSI || type == ParseType.DISCORD_ANSI) { + String ansiCode = ansiMap.get(code); + if (ansiCode == null) { + if (type == ParseType.DISCORD_ANSI) { + // gets the closest color to the hex + + final int rgb = Integer.parseInt(code.substring(1), 16); + + final String chatColor = ColorUtilities.getClosestChatColor(rgb); + + ansiCode = ansiMap.get(chatColor); + } else { + ansiCode = "\u001b[38;2;" + + color.red() + + ";" + + color.green() + + ";" + + color.blue() + + "m"; + } + } + + return ansiCode; + } else { + return ""; + } + } + + private String getPartialResultAndSetLastColor (String originalResult, String color, String style) { + if (type == ParseType.PLAIN) return originalResult; + + String resetCode; + if (type == ParseType.ANSI || type == ParseType.DISCORD_ANSI) resetCode = ansiMap.get("r"); + else resetCode = "§r"; + + final String result = + lastStyle + color + style + + originalResult + + resetCode; + + lastStyle = color + style; + + return result; + } + + private String stringifyPartially (String message, String color, String style) { + if (type == ParseType.PLAIN) return message; + + final boolean isAllAnsi = type == ParseType.ANSI || type == ParseType.DISCORD_ANSI; + + String replacedContent = message; + // processes section signs + // not processing invalid codes is INTENTIONAL and it is a FEATURE + if (isAllAnsi && replacedContent.contains("§")) { + // is try-catch a great idea for these? try { replacedContent = Pattern .compile("(§.)") - .matcher(message.content()) + .matcher(message) .replaceAll(m -> { final String code = m.group(0).substring(1); @@ -263,69 +267,82 @@ public class ComponentUtilities { } catch (Exception ignored) {} } - // messy af - return new PartiallyStringified((lastColor != null ? lastColor : "") + (color != null ? color : "") + (style != null ? style : "") + replacedContent + (ansi ? ansiMap.get("r") : ""), color); + return getPartialResultAndSetLastColor(replacedContent, color, style); } - return new PartiallyStringified(message.content(), null); - } + private String stringifyPartially (TextComponent message, String color, String style) { + return stringifyPartially(message.content(), color, style); + } - public static PartiallyStringified stringifyPartially (TranslatableComponent message, boolean motd, boolean ansi, String lastColor, boolean noHex, int depth) { - String format = getOrReturnFallback(message); + private String stringifyPartially (TranslatableComponent message, String color, String style) { + final String format = getOrReturnFallback(message); - // totallynotskidded™️ from HBot (and changed a bit) - Matcher matcher = ARG_PATTERN.matcher(format); - StringBuilder sb = new StringBuilder(); + // totallynotskidded™️from HBot (and changed a bit) + Matcher matcher = ARG_PATTERN.matcher(format); + StringBuilder sb = new StringBuilder(); - final String style = getStyle(message.style(), motd); - final String _color = getColor(message.color(), motd, ansi, noHex); - String color; - if (_color == null) color = ""; - else color = _color; - - int i = 0; - while (matcher.find()) { - depth++; - if (matcher.group().equals("%%")) { - matcher.appendReplacement(sb, "%"); - } else { - String idxStr = matcher.group(1); - int idx = idxStr == null ? i++ : (Integer.parseInt(idxStr) - 1); - if (idx >= 0 && idx < message.arguments().size()) { - matcher.appendReplacement( - sb, - Matcher.quoteReplacement( - motd ? - stringifyMotd(message.arguments().get(idx).asComponent(), lastColor, depth + 1) + color : - ( - ansi ? - stringifyAnsi(message.arguments().get(idx).asComponent(), lastColor, noHex, depth + 1) + color : - stringify(message.arguments().get(idx).asComponent(), lastColor, depth + 1) - ) - ) - ); + // not checking if arguments length equals input format length + // is INTENTIONAL and is a FEATURE + int i = 0; + while (matcher.find()) { + formatsPlaceholdersCount++; + if (matcher.group().equals("%%")) { + matcher.appendReplacement(sb, "%"); } else { - matcher.appendReplacement(sb, ""); + final String idxStr = matcher.group(1); + + int idx = idxStr == null ? i++ : (Integer.parseInt(idxStr) - 1); + + if (idx >= 0 && idx < message.arguments().size()) { + final ComponentParser parser = new ComponentParser(); + + parser.lastStyle = lastStyle + color + style; + parser.formatsPlaceholdersCount = formatsPlaceholdersCount; + + matcher.appendReplacement( + sb, + Matcher.quoteReplacement( + parser.stringify( + message.arguments() + .get(idx) + .asComponent(), + type + ) + color // + color IMPORTANT!!!! + ) + ); + } else { + matcher.appendReplacement(sb, ""); + } } } + + matcher.appendTail(sb); + + return getPartialResultAndSetLastColor(sb.toString(), color, style); } - matcher.appendTail(sb); - return new PartiallyStringified((lastColor != null ? lastColor : "") + color + (style != null && ansi ? style : "") + sb + (ansi ? ansiMap.get("r") : ""), _color); - } + // on the client side, this acts just like TextComponent + // and does NOT process any players stuff + private String stringifyPartially (SelectorComponent message, String color, String style) { + return stringifyPartially(message.pattern(), style, color); + } - public static PartiallyStringified stringifyPartially (SelectorComponent message, boolean motd, boolean ansi, String lastColor, boolean noHex) { - final String style = getStyle(message.style(), motd); - final String _color = getColor(message.color(), motd, ansi, noHex); - String color; - if (_color == null) color = ""; - else color = _color; - return new PartiallyStringified((lastColor != null ? lastColor : "") + color + (style != null && ansi ? style : "") + message.pattern(), _color); // * Client-side selector components are equivalent to text ones, and do NOT list entities. - } + public String stringifyPartially (KeybindComponent message, String color, String style) { + final String keybind = message.keybind(); - public static PartiallyStringified stringifyPartially (KeybindComponent message, boolean motd, boolean ansi, String lastColor, boolean noHex) { - String keybind = message.keybind(); - Component component = keybinds.containsKey(keybind) ? Component.translatable(keybinds.get(keybind)) : Component.text(keybind); - return stringifyPartially(component, motd, ansi, lastColor, noHex, 0); + // FIXME: this isn't the correct way to parse keybinds + final Component component = KEYBINDINGS.containsKey(keybind) ? + Component.translatable(KEYBINDINGS.get(keybind)) : + Component.text(keybind); + + return stringifyPartially(component, color, style); + } + + public enum ParseType { + PLAIN, + SECTION_SIGNS, + ANSI, + DISCORD_ANSI + } } }