mirror of
https://github.com/DiscordSRV/Ascension.git
synced 2024-11-01 08:39:31 +01:00
Implement legacy code parsing and rendering into ansi + markdown for console
This commit is contained in:
parent
7193100047
commit
81c109c2d2
@ -33,13 +33,15 @@ import dev.vankka.mcdiscordreserializer.minecraft.MinecraftSerializerOptions;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.TranslatableComponent;
|
||||
import net.kyori.adventure.text.flattener.ComponentFlattener;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import net.kyori.adventure.text.format.TextColor;
|
||||
import net.kyori.adventure.text.serializer.ansi.ANSIComponentSerializer;
|
||||
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
|
||||
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
|
||||
import net.kyori.ansi.ColorLevel;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.*;
|
||||
|
||||
public class ComponentFactory implements MinecraftComponentFactory {
|
||||
|
||||
@ -58,6 +60,7 @@ public class ComponentFactory implements MinecraftComponentFactory {
|
||||
private final DiscordSerializer discordSerializer;
|
||||
private final PlainTextComponentSerializer plainSerializer;
|
||||
private final ANSIComponentSerializer ansiSerializer;
|
||||
private final ANSIComponentSerializerWrapper ansiWrapper = new ANSIComponentSerializerWrapper();
|
||||
|
||||
// Not the same as Adventure's TranslationRegistry
|
||||
private final TranslationRegistry translationRegistry = new TranslationRegistry();
|
||||
@ -131,10 +134,47 @@ public class ComponentFactory implements MinecraftComponentFactory {
|
||||
}
|
||||
|
||||
public ANSIComponentSerializer ansiSerializer() {
|
||||
return ansiSerializer;
|
||||
return ansiWrapper;
|
||||
}
|
||||
|
||||
public TranslationRegistry translationRegistry() {
|
||||
return translationRegistry;
|
||||
}
|
||||
|
||||
private static final Set<TextColor> ANSI_SUPPORTED_COLORS = new HashSet<>(Arrays.asList(
|
||||
NamedTextColor.BLACK,
|
||||
NamedTextColor.DARK_BLUE,
|
||||
NamedTextColor.DARK_GREEN,
|
||||
NamedTextColor.DARK_AQUA,
|
||||
NamedTextColor.DARK_RED,
|
||||
NamedTextColor.DARK_PURPLE,
|
||||
NamedTextColor.GOLD,
|
||||
NamedTextColor.GRAY
|
||||
));
|
||||
|
||||
/**
|
||||
* "Fix" for Discord only supporting 8 colors.
|
||||
* <a href="https://github.com/KyoriPowered/ansi/issues/35">KyoriPowered/ansi issue</a>
|
||||
*/
|
||||
private class ANSIComponentSerializerWrapper implements ANSIComponentSerializer {
|
||||
|
||||
@Override
|
||||
public @NotNull String serialize(@NotNull Component component) {
|
||||
return ansiSerializer.serialize(recursivelyCheckColor(component));
|
||||
}
|
||||
|
||||
private Component recursivelyCheckColor(Component component) {
|
||||
if (!ANSI_SUPPORTED_COLORS.contains(component.color())) {
|
||||
component = component.color(null);
|
||||
}
|
||||
|
||||
List<Component> children = component.children();
|
||||
List<Component> newChildren = new ArrayList<>(children.size());
|
||||
for (Component child : children) {
|
||||
newChildren.add(recursivelyCheckColor(child));
|
||||
}
|
||||
|
||||
return component.children(newChildren);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,10 +2,12 @@ package com.discordsrv.common.console;
|
||||
|
||||
import com.discordsrv.api.discord.entity.channel.DiscordGuildMessageChannel;
|
||||
import com.discordsrv.api.discord.entity.message.SendableDiscordMessage;
|
||||
import com.discordsrv.api.placeholder.provider.SinglePlaceholder;
|
||||
import com.discordsrv.common.DiscordSRV;
|
||||
import com.discordsrv.common.config.main.ConsoleConfig;
|
||||
import com.discordsrv.common.console.entry.LogEntry;
|
||||
import com.discordsrv.common.console.entry.LogMessage;
|
||||
import com.discordsrv.common.console.message.ConsoleMessage;
|
||||
import com.discordsrv.common.logging.LogLevel;
|
||||
import net.dv8tion.jda.api.entities.Message;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
@ -18,26 +20,10 @@ import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class SingleConsoleHandler {
|
||||
|
||||
private static final int MESSAGE_MAX_LENGTH = Message.MAX_CONTENT_LENGTH;
|
||||
private static final String ESCAPE = "\u001B";
|
||||
private static final Pattern ANSI_PATTERN = Pattern.compile(
|
||||
ESCAPE
|
||||
+ "\\["
|
||||
+ "(\\d{1,3}"
|
||||
+ "(;\\d{1,3}"
|
||||
+ "(;\\d{1,3}"
|
||||
+ "(?:(?:;\\d{1,3}){2})?"
|
||||
+ ")?"
|
||||
+ ")?"
|
||||
+ ")"
|
||||
+ "m"
|
||||
);
|
||||
private static final Pattern COLOR_CODE_PATTERN = Pattern.compile("\\u007F[0-9a-fk-orx]");
|
||||
|
||||
private final DiscordSRV discordSRV;
|
||||
private final ConsoleConfig config;
|
||||
@ -187,41 +173,28 @@ public class SingleConsoleHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private String getAnsiEscapeSequence(String codePart) {
|
||||
String[] split = codePart.split(";");
|
||||
int[] numbers = new int[split.length];
|
||||
for (int i = 0; i < split.length; i++) {
|
||||
numbers[i] = Integer.parseInt(split[i]);
|
||||
}
|
||||
if (numbers.length == 1) {
|
||||
return String.valueOf(numbers[0]);
|
||||
} else if (numbers.length == 2) {
|
||||
return numbers[0] + ";" + numbers[1];
|
||||
} else {
|
||||
// longer than supported by Discord, so drop the ansi here
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private List<String> formatEntry(LogEntry entry, ConsoleConfig.OutputMode outputMode) {
|
||||
int blockLength = outputMode.blockLength();
|
||||
int maximumPart = MESSAGE_MAX_LENGTH - blockLength - "\n".length();
|
||||
|
||||
String message = discordSRV.placeholderService().replacePlaceholders(config.appender.lineFormat, entry) + "\n";
|
||||
String parsedMessage;
|
||||
switch (outputMode) {
|
||||
case ANSI:
|
||||
parsedMessage = new ConsoleMessage(discordSRV, entry.message()).asAnsi();
|
||||
break;
|
||||
case MARKDOWN:
|
||||
parsedMessage = new ConsoleMessage(discordSRV, entry.message()).asMarkdown();
|
||||
break;
|
||||
default:
|
||||
parsedMessage = entry.message();
|
||||
break;
|
||||
}
|
||||
|
||||
// TODO: make a parser for ANSI + color codes that makes a intermediary format that can be converted to
|
||||
// TODO: either 16 color ansi (ANSI mode) or just bold/italics/underline/strikethrough markdown (MARKDOWN mode)
|
||||
Matcher matcher = ANSI_PATTERN.matcher(message);
|
||||
while (matcher.find()) {
|
||||
String codes = matcher.group(1);
|
||||
String escapeSequence = getAnsiEscapeSequence(codes);
|
||||
if (escapeSequence != null && outputMode == ConsoleConfig.OutputMode.ANSI) {
|
||||
message = matcher.replaceAll(ESCAPE + escapeSequence + "m");
|
||||
} else {
|
||||
message = matcher.replaceAll("");
|
||||
}
|
||||
}
|
||||
message = message.replaceAll(COLOR_CODE_PATTERN.pattern(), "");
|
||||
String message = discordSRV.placeholderService().replacePlaceholders(
|
||||
config.appender.lineFormat,
|
||||
entry,
|
||||
new SinglePlaceholder("message", parsedMessage)
|
||||
) + "\n";
|
||||
|
||||
if (outputMode == ConsoleConfig.OutputMode.DIFF) {
|
||||
message = getLogLevelDiffCharacter(entry.level()) + message;
|
||||
|
@ -35,7 +35,6 @@ public class LogEntry {
|
||||
return level;
|
||||
}
|
||||
|
||||
@Placeholder("message")
|
||||
public String message() {
|
||||
return message;
|
||||
}
|
||||
|
@ -0,0 +1,145 @@
|
||||
package com.discordsrv.common.console.message;
|
||||
|
||||
import com.discordsrv.common.DiscordSRV;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import net.kyori.adventure.text.format.*;
|
||||
|
||||
import java.util.EnumSet;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class ConsoleMessage {
|
||||
|
||||
private static final String ANSI_ESCAPE = "\u001B";
|
||||
// Paper uses 007F as an intermediary
|
||||
private static final String SECTION = "[§\u007F]";
|
||||
|
||||
// Regex pattern for matching ANSI + Legacy
|
||||
private static final Pattern PATTERN = Pattern.compile(
|
||||
// ANSI
|
||||
ANSI_ESCAPE
|
||||
+ "\\["
|
||||
+ "(?<ansi>\\d{1,3}"
|
||||
+ "(;\\d{1,3}"
|
||||
+ "(;\\d{1,3}"
|
||||
+ "(?:(?:;\\d{1,3}){2})?"
|
||||
+ ")?"
|
||||
+ ")?"
|
||||
+ ")"
|
||||
+ "m"
|
||||
+ "|"
|
||||
+ "(?<legacy>"
|
||||
// Legacy color/formatting
|
||||
+ "(?:" + SECTION + "[0-9a-fk-or])"
|
||||
+ "|"
|
||||
// Bungee/Spigot legacy
|
||||
+ "(?:" + SECTION + "x(?:" + SECTION + "[0-9a-f]){6})"
|
||||
+ ")"
|
||||
);
|
||||
|
||||
private final TextComponent.Builder builder = Component.text();
|
||||
private final DiscordSRV discordSRV;
|
||||
|
||||
public ConsoleMessage(DiscordSRV discordSRV, String input) {
|
||||
this.discordSRV = discordSRV;
|
||||
parse(input);
|
||||
}
|
||||
|
||||
public String asMarkdown() {
|
||||
Component component = builder.build();
|
||||
return discordSRV.componentFactory().discordSerializer().serialize(component);
|
||||
}
|
||||
|
||||
public String asAnsi() {
|
||||
Component component = builder.build();
|
||||
return discordSRV.componentFactory().ansiSerializer().serialize(component);
|
||||
}
|
||||
|
||||
private void parse(String input) {
|
||||
Matcher matcher = PATTERN.matcher(input);
|
||||
|
||||
Style.Builder style = Style.style();
|
||||
|
||||
int lastMatchEnd = 0;
|
||||
while (matcher.find()) {
|
||||
int start = matcher.start();
|
||||
int end = matcher.end();
|
||||
|
||||
if (start != lastMatchEnd) {
|
||||
builder.append(Component.text(input.substring(lastMatchEnd, start), style.build()));
|
||||
}
|
||||
|
||||
String ansi = matcher.group("ansi");
|
||||
if (ansi != null) {
|
||||
parseAnsi(ansi, style);
|
||||
}
|
||||
|
||||
String legacy = matcher.group("legacy");
|
||||
if (legacy != null) {
|
||||
parseLegacy(legacy, style);
|
||||
}
|
||||
|
||||
lastMatchEnd = end;
|
||||
}
|
||||
|
||||
int length = input.length();
|
||||
if (lastMatchEnd != length) {
|
||||
builder.append(Component.text(input.substring(lastMatchEnd, length), style.build()));
|
||||
}
|
||||
}
|
||||
|
||||
private void parseAnsi(String ansiEscape, Style.Builder style) {
|
||||
// TODO: implement
|
||||
}
|
||||
|
||||
private void parseLegacy(String legacy, Style.Builder style) {
|
||||
if (legacy.length() == 2) {
|
||||
char character = legacy.toCharArray()[1];
|
||||
if (character == 'r') {
|
||||
style.color(null).decorations(EnumSet.allOf(TextDecoration.class), false);
|
||||
} else {
|
||||
TextFormat format = getFormat(character);
|
||||
if (format instanceof TextColor) {
|
||||
style.color((TextColor) format);
|
||||
} else if (format instanceof TextDecoration) {
|
||||
style.decorate((TextDecoration) format);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
char[] characters = legacy.toCharArray();
|
||||
StringBuilder hex = new StringBuilder(7).append(TextColor.HEX_PREFIX);
|
||||
for (int i = 2; i < characters.length; i += 2) {
|
||||
hex.append(characters[i]);
|
||||
}
|
||||
style.color(TextColor.fromHexString(hex.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
private TextFormat getFormat(char character) {
|
||||
switch (character) {
|
||||
case '0': return NamedTextColor.BLACK;
|
||||
case '1': return NamedTextColor.DARK_BLUE;
|
||||
case '2': return NamedTextColor.DARK_GREEN;
|
||||
case '3': return NamedTextColor.DARK_AQUA;
|
||||
case '4': return NamedTextColor.DARK_RED;
|
||||
case '5': return NamedTextColor.DARK_PURPLE;
|
||||
case '6': return NamedTextColor.GOLD;
|
||||
case '7': return NamedTextColor.GRAY;
|
||||
case '8': return NamedTextColor.DARK_GRAY;
|
||||
case '9': return NamedTextColor.BLUE;
|
||||
case 'a': return NamedTextColor.GREEN;
|
||||
case 'b': return NamedTextColor.AQUA;
|
||||
case 'c': return NamedTextColor.RED;
|
||||
case 'd': return NamedTextColor.LIGHT_PURPLE;
|
||||
case 'e': return NamedTextColor.YELLOW;
|
||||
case 'f': return NamedTextColor.WHITE;
|
||||
case 'k': return TextDecoration.OBFUSCATED;
|
||||
case 'l': return TextDecoration.BOLD;
|
||||
case 'm': return TextDecoration.STRIKETHROUGH;
|
||||
case 'n': return TextDecoration.UNDERLINED;
|
||||
case 'o': return TextDecoration.ITALIC;
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user