refactor: rewrite ComponentUtilities component parser (still a bit broken but at least a bit better on the codestyle side)

This commit is contained in:
Chayapak 2024-12-28 17:55:04 +07:00
parent 9b675bfed4
commit 052e57a799
Signed by: ChomeNS
SSH key fingerprint: SHA256:0YoxhdyXsgbc0nfeB2N6FYE60mxMU7DS4uCUMaw2mvA
6 changed files with 265 additions and 253 deletions

View file

@ -1 +1 @@
1303
1341

View file

@ -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,

View file

@ -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));

View file

@ -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
);
}

View file

@ -278,7 +278,7 @@ public class NBSConverter implements Converter {
private static final Map<String, String> subtitles = new HashMap<>();
static {
for (Map.Entry<String, String> entry : ComponentUtilities.language.entrySet()) {
for (Map.Entry<String, String> entry : ComponentUtilities.LANGUAGE.entrySet()) {
if (!entry.getKey().startsWith("subtitles.")) continue;
subtitles.put(entry.getKey(), entry.getValue());

View file

@ -20,10 +20,41 @@ 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<String, String> language = loadJsonStringMap("language.json");
private static final Map<String, String> voiceChatLanguage = loadJsonStringMap("voiceChatLanguage.json");
private static final Map<String, String> keybinds = loadJsonStringMap("keybinds.json");
public static final Map<String, String> LANGUAGE = loadJsonStringMap("language.json");
public static final Map<String, String> VOICE_CHAT_LANGUAGE = loadJsonStringMap("voiceChatLanguage.json");
public static final Map<String, String> KEYBINDINGS = loadJsonStringMap("keybinds.json");
private static Map<String, String> loadJsonStringMap (String name) {
Map<String, String> map = new HashMap<>();
InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream(name);
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
JsonObject json = JsonParser.parseReader(reader).getAsJsonObject();
for (Map.Entry<String, JsonElement> entry : json.entrySet()) {
map.put(entry.getKey(), json.get(entry.getKey()).getAsString());
}
return map;
}
public static String getOrReturnFallback (TranslatableComponent component) {
final String key = component.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 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); }
private static class ComponentParser {
public static final Pattern ARG_PATTERN = Pattern.compile("%(?:(\\d+)\\$)?([s%])");
public static final int MAX_DEPTH = 16;
@ -55,106 +86,57 @@ public class ComponentUtilities {
ansiMap.put("r", "\u001b[0m");
}
public record PartiallyStringified(
String output,
String lastColor
) {}
private ParseType type;
private int formatsPlaceholdersCount = 0;
private static Map<String, String> loadJsonStringMap (String name) {
Map<String, String> map = new HashMap<>();
private String lastStyle = "";
InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream(name);
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
JsonObject json = JsonParser.parseReader(reader).getAsJsonObject();
private String stringify (Component message, ParseType type) {
this.type = type;
for (Map.Entry<String, JsonElement> entry : json.entrySet()) {
map.put(entry.getKey(), json.get(entry.getKey()).getAsString());
}
return map;
}
private static String getOrReturnFallback (TranslatableComponent component) {
final String key = component.key();
final String minecraftKey = language.get(key);
final String voiceChatKey = voiceChatLanguage.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 "";
if (formatsPlaceholdersCount > MAX_DEPTH) return "";
try {
final StringBuilder builder = new StringBuilder();
final PartiallyStringified output = stringifyPartially(message, false, false, lastColor, false, depth);
final String color = getColor(message.color());
final String style = getStyle(message.style());
builder.append(output.output);
final String output = stringifyPartially(message, color, style);
for (Component child : message.children()) builder.append(stringify(child, lastColor != null ? lastColor : output.lastColor, depth));
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 "";
}
}
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) {
public String stringifyPartially (Component message, String color, String style) {
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);
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 -> "";
};
}
public static String getStyle (Style textStyle, boolean motd) {
if (textStyle == null) return null;
public String getStyle (Style textStyle) {
if (textStyle == null) return "";
StringBuilder style = new StringBuilder();
@ -164,7 +146,7 @@ public class ComponentUtilities {
if (state == TextDecoration.State.NOT_SET || state == TextDecoration.State.FALSE) continue;
if (!motd) {
if (type == ParseType.ANSI) {
switch (decoration) {
case BOLD -> style.append(ansiMap.get("l"));
case ITALIC -> style.append(ansiMap.get("o"));
@ -172,7 +154,7 @@ public class ComponentUtilities {
case UNDERLINED -> style.append(ansiMap.get("n"));
case STRIKETHROUGH -> style.append(ansiMap.get("m"));
}
} else {
} else if (type == ParseType.SECTION_SIGNS) {
switch (decoration) {
case BOLD -> style.append("§l");
case ITALIC -> style.append("§o");
@ -186,8 +168,8 @@ public class ComponentUtilities {
return style.toString();
}
public static String getColor (TextColor color, boolean motd, boolean ansi, boolean noHex) {
if (color == null) return null;
public String getColor (TextColor color) {
if (color == null) return "";
// map totallynotskidded too from https://github.com/PrismarineJS/prismarine-chat/blob/master/index.js#L299
String code;
@ -215,12 +197,14 @@ public class ComponentUtilities {
}
}
if (motd) {
if (type == ParseType.SECTION_SIGNS) {
return "§" + code;
} else if (ansi) {
} else if (type == ParseType.ANSI || type == ParseType.DISCORD_ANSI) {
String ansiCode = ansiMap.get(code);
if (ansiCode == null) {
if (noHex) {
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);
@ -238,22 +222,42 @@ public class ComponentUtilities {
}
return ansiCode;
} else return null;
} else {
return "";
}
}
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);
private String getPartialResultAndSetLastColor (String originalResult, String color, String style) {
if (type == ParseType.PLAIN) return originalResult;
String replacedContent = message.content();
// seems very mabe mabe
if (ansi && replacedContent.contains("§")) {
// is try-catch a great idea?
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,45 +267,47 @@ 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)
// totallynotskiddedfrom 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;
// not checking if arguments length equals input format length
// is INTENTIONAL and is a FEATURE
int i = 0;
while (matcher.find()) {
depth++;
formatsPlaceholdersCount++;
if (matcher.group().equals("%%")) {
matcher.appendReplacement(sb, "%");
} else {
String idxStr = matcher.group(1);
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(
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)
)
parser.stringify(
message.arguments()
.get(idx)
.asComponent(),
type
) + color // + color IMPORTANT!!!!
)
);
} else {
@ -309,23 +315,34 @@ public class ComponentUtilities {
}
}
}
matcher.appendTail(sb);
return new PartiallyStringified((lastColor != null ? lastColor : "") + color + (style != null && ansi ? style : "") + sb + (ansi ? ansiMap.get("r") : ""), _color);
return getPartialResultAndSetLastColor(sb.toString(), color, style);
}
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.
// 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 (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);
public String stringifyPartially (KeybindComponent message, String color, String style) {
final String keybind = message.keybind();
// 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
}
}
}