From 107138fbe08bc14b2a208f8a3642beb3c18fd2ff Mon Sep 17 00:00:00 2001 From: MiniDigger Date: Sat, 14 Dec 2019 19:17:26 +0100 Subject: [PATCH 001/339] Initial commit --- .gitignore | 3 + README.md | 102 ++++++ pom.xml | 25 ++ .../me/minidigger/minimessage/Constants.java | 17 + .../minimessage/MiniMessageParser.java | 295 ++++++++++++++++++ .../minimessage/MiniMessageSerializer.java | 166 ++++++++++ .../minimessage/MiniMessageParserTest.java | 148 +++++++++ .../MiniMessageSerializerTest.java | 124 ++++++++ 8 files changed, 880 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 pom.xml create mode 100644 src/main/java/me/minidigger/minimessage/Constants.java create mode 100644 src/main/java/me/minidigger/minimessage/MiniMessageParser.java create mode 100644 src/main/java/me/minidigger/minimessage/MiniMessageSerializer.java create mode 100644 src/test/java/me/minidigger/minimessage/MiniMessageParserTest.java create mode 100644 src/test/java/me/minidigger/minimessage/MiniMessageSerializerTest.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..7466b121e --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.idea +*.iml +target/ diff --git a/README.md b/README.md new file mode 100644 index 000000000..7e2665428 --- /dev/null +++ b/README.md @@ -0,0 +1,102 @@ +# MiniMessage + +Simple library that implements an easy to use textual format to send rich json messages. + +## Examples + +MiniMessage: +`TESTnestedTest` +Json: +```json +{ + "extra": [ + { + "color": "yellow", + "text": "TEST" + }, + { + "color": "green", + "text": "nested" + }, + { + "color": "yellow", + "text": "Test" + } + ], + "text": "" +} +``` + +MiniMessage: +`test:TEST">TEST` +Json: +```json +{ + "hoverEvent": { + "action": "show_text", + "value": [ + { + "color": "red", + "text": "test:TEST" + } + ] + }, + "text": "TEST" +} +``` + +MiniMessage: +`TEST` +Json: +```json +{ + "clickEvent": { + "action": "run_command", + "value": "test command" + }, + "text": "TEST" +} +``` + +## Usage + +```java +// simple parsing +String input = "Hai"; +BaseComponent[] comp = MiniMessageParser.parseFormat(input); +// parsing with placeholder +String placeholderInput = "Hello " +BaseComponent[] comp2 = MiniMessageParser.parseFormat(placeholderInput, "guy", "You!"); // replaces with You! +// parsing with placeholder map +Map placeholders = new HashMap<>(); +placeholders.put("guy", "You!") +BaseComponent[] comp3 = MiniMessageParser.parseFormat(placeholderInput, placeholders); + +// serialization +String minimessage = MiniMessageSerializer.serialize(comp3); // use any BaseComponent, or array, and convert it into a nice string +// mini message is 'Hello You!' + +// escaping (useful for escaping user input, dont want users to send click events ;)) +String escaped = MiniMessageParser.escapeTokens(minimessage); +// escaped is '\Hello \You!' +// stripping (useful for sending to stuff that doesnt support color) +String stripped = MiniMessageParser.stripTokens(minimessage); +// stripped is 'Hello You!' +``` + +## Maven Artifact + +Artifact: +```xml + + me.minidigger + MiniMessage + 1.0.0-SNAPSHOT + compile + +``` +Repo: +pending... + +## Licence +MIT diff --git a/pom.xml b/pom.xml new file mode 100644 index 000000000..f85ba6ff1 --- /dev/null +++ b/pom.xml @@ -0,0 +1,25 @@ + + + 4.0.0 + + me.minidigger + MiniMessage + 1.0.0-SNAPSHOT + + + 1.8 + 1.8 + UTF-8 + + + + + com.destroystokyo.paper + paper-api + 1.15-R0.1-SNAPSHOT + provided + + + diff --git a/src/main/java/me/minidigger/minimessage/Constants.java b/src/main/java/me/minidigger/minimessage/Constants.java new file mode 100644 index 000000000..050d8bb40 --- /dev/null +++ b/src/main/java/me/minidigger/minimessage/Constants.java @@ -0,0 +1,17 @@ +package me.minidigger.minimessage; + +public class Constants { + public static final String CLICK = "click"; + public static final String HOVER = "hover"; + + public static final String UNDERLINED = "underlined"; + public static final String STRIKETHROUGH = "strikethrough"; + public static final String OBFUSCATED = "obfuscated"; + public static final String ITALIC = "italic"; + public static final String BOLD = "bold"; + + public static final String TAG_START = "<"; + public static final String TAG_END = ">"; + public static final String CLOSE_TAG = "/"; + public static final String SEPARATOR = ":"; +} diff --git a/src/main/java/me/minidigger/minimessage/MiniMessageParser.java b/src/main/java/me/minidigger/minimessage/MiniMessageParser.java new file mode 100644 index 000000000..1d1c9c69b --- /dev/null +++ b/src/main/java/me/minidigger/minimessage/MiniMessageParser.java @@ -0,0 +1,295 @@ +package me.minidigger.minimessage; + +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.chat.BaseComponent; +import net.md_5.bungee.api.chat.ClickEvent; +import net.md_5.bungee.api.chat.ComponentBuilder; +import net.md_5.bungee.api.chat.HoverEvent; + +import java.util.EnumSet; +import java.util.Map; +import java.util.Optional; +import java.util.Stack; +import java.util.function.Consumer; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.annotation.Nonnull; + +import static me.minidigger.minimessage.Constants.CLICK; +import static me.minidigger.minimessage.Constants.CLOSE_TAG; +import static me.minidigger.minimessage.Constants.HOVER; +import static me.minidigger.minimessage.Constants.SEPARATOR; +import static me.minidigger.minimessage.Constants.TAG_END; +import static me.minidigger.minimessage.Constants.TAG_START; + +public class MiniMessageParser { + + // regex group names + private static final String START = "start"; + private static final String TOKEN = "token"; + private static final String INNER = "inner"; + private static final String END = "end"; + // https://regex101.com/r/8VZ7uA/5 + private static Pattern pattern = Pattern.compile("((?<)(?([^<>]+)|([^<>]+\"(?[^\"]+)\"))(?>))+?"); + + @Nonnull + public static String escapeTokens(@Nonnull String richMessage) { + StringBuilder sb = new StringBuilder(); + Matcher matcher = pattern.matcher(richMessage); + int lastEnd = 0; + while (matcher.find()) { + int startIndex = matcher.start(); + int endIndex = matcher.end(); + + if (startIndex > lastEnd) { + sb.append(richMessage, lastEnd, startIndex); + } + lastEnd = endIndex; + + String start = matcher.group(START); + String token = matcher.group(TOKEN); + String inner = matcher.group(INNER); + String end = matcher.group(END); + + // also escape inner + if (inner != null) { + token = token.replace(inner, escapeTokens(inner)); + } + + sb.append("\\").append(start).append(token).append("\\").append(end); + } + + if (richMessage.length() > lastEnd) { + sb.append(richMessage.substring(lastEnd)); + } + + return sb.toString(); + } + + @Nonnull + public static String stripTokens(@Nonnull String richMessage) { + StringBuilder sb = new StringBuilder(); + Matcher matcher = pattern.matcher(richMessage); + int lastEnd = 0; + while (matcher.find()) { + int startIndex = matcher.start(); + int endIndex = matcher.end(); + + if (startIndex > lastEnd) { + sb.append(richMessage, lastEnd, startIndex); + } + lastEnd = endIndex; + } + + if (richMessage.length() > lastEnd) { + sb.append(richMessage.substring(lastEnd)); + } + + return sb.toString(); + } + + @Nonnull + public static String handlePlaceholders(@Nonnull String richMessage, @Nonnull String... placeholders) { + if (placeholders.length % 2 != 0) { + throw new RuntimeException( + "Invalid number placeholders defined, usage: parseFormat(format, key, value, key, value...)"); + } + for (int i = 0; i < placeholders.length; i += 2) { + richMessage = richMessage.replace(TAG_START + placeholders[i] + TAG_END, placeholders[i + 1]); + } + return richMessage; + } + + @Nonnull + public static String handlePlaceholders(@Nonnull String richMessage, @Nonnull Map placeholders) { + for (Map.Entry entry : placeholders.entrySet()) { + richMessage = richMessage.replace(TAG_START + entry.getKey() + TAG_END, entry.getValue()); + } + return richMessage; + } + + @Nonnull + public static BaseComponent[] parseFormat(@Nonnull String richMessage, @Nonnull String... placeholders) { + return parseFormat(handlePlaceholders(richMessage, placeholders)); + } + + @Nonnull + public static BaseComponent[] parseFormat(@Nonnull String richMessage, @Nonnull Map placeholders) { + return parseFormat(handlePlaceholders(richMessage, placeholders)); + } + + @Nonnull + public static BaseComponent[] parseFormat(@Nonnull String richMessage) { + ComponentBuilder builder = null; + + Stack clickEvents = new Stack<>(); + Stack hoverEvents = new Stack<>(); + Stack colors = new Stack<>(); + EnumSet decorations = EnumSet.noneOf(TextDecoration.class); + + Matcher matcher = pattern.matcher(richMessage); + int lastEnd = 0; + while (matcher.find()) { + int startIndex = matcher.start(); + int endIndex = matcher.end(); + + String msg = null; + if (startIndex > lastEnd) { + msg = richMessage.substring(lastEnd, startIndex); + } + lastEnd = endIndex; + + // handle message + if (msg != null && msg.length() != 0) { + // append message + if (builder == null) { + builder = new ComponentBuilder(msg); + } else { + builder.append(msg, ComponentBuilder.FormatRetention.NONE); + } + + // set everything that is not closed yet + if (clickEvents.size() > 0) { + builder.event(clickEvents.peek()); + } + if (hoverEvents.size() > 0) { + builder.event(hoverEvents.peek()); + } + if (colors.size() > 0) { + builder.color(colors.peek()); + } + if (decorations.size() > 0) { + // no lambda because builder isn't effective final :/ + for (TextDecoration decor : decorations) { + decor.apply(builder); + } + } + } + +// String group = matcher.group(); +// String start = matcher.group(START); + String token = matcher.group(TOKEN); + String inner = matcher.group(INNER); +// String end = matcher.group(END); + + Optional deco; + Optional color; + + // click + if (token.startsWith(CLICK + SEPARATOR)) { + clickEvents.push(handleClick(token, inner)); + } else if (token.equals(CLOSE_TAG + CLICK)) { + clickEvents.pop(); + } + // hover + else if (token.startsWith(HOVER + SEPARATOR)) { + hoverEvents.push(handleHover(token, inner)); + } else if (token.equals(CLOSE_TAG + HOVER)) { + hoverEvents.pop(); + } + // decoration + else if ((deco = resolveDecoration(token)).isPresent()) { + decorations.add(deco.get()); + } else if (token.startsWith(CLOSE_TAG) && (deco = resolveDecoration(token.replace(CLOSE_TAG, ""))).isPresent()) { + decorations.remove(deco.get()); + } + // color + else if ((color = resolveColor(token)).isPresent()) { + colors.push(color.get()); + } else if (token.startsWith(CLOSE_TAG) && resolveColor(token.replace(CLOSE_TAG, "")).isPresent()) { + colors.pop(); + } + } + + // handle last message part + if (richMessage.length() > lastEnd) { + String msg = richMessage.substring(lastEnd); + // append message + if (builder == null) { + builder = new ComponentBuilder(msg); + } else { + builder.append(msg, ComponentBuilder.FormatRetention.NONE); + } + + // set everything that is not closed yet + if (clickEvents.size() > 0) { + builder.event(clickEvents.peek()); + } + if (hoverEvents.size() > 0) { + builder.event(hoverEvents.peek()); + } + if (colors.size() > 0) { + builder.color(colors.peek()); + } + if (decorations.size() > 0) { + // no lambda because builder isn't effective final :/ + for (TextDecoration decor : decorations) { + decor.apply(builder); + } + } + } + + if (builder == null) { + // lets just return an empty component + builder = new ComponentBuilder(""); + } + + return builder.create(); + } + + @Nonnull + private static ClickEvent handleClick(@Nonnull String token, @Nonnull String inner) { + String[] args = token.split(SEPARATOR); + if (args.length < 2) { + throw new RuntimeException("Can't parse click action (too few args) " + token); + } + ClickEvent.Action action = ClickEvent.Action.valueOf(args[1].toUpperCase()); + return new ClickEvent(action, token.replace(CLICK + SEPARATOR + args[1] + SEPARATOR, "")); + } + + @Nonnull + private static HoverEvent handleHover(@Nonnull String token, @Nonnull String inner) { + String[] args = token.split(SEPARATOR); + if (args.length < 2) { + throw new RuntimeException("Can't parse hover action (too few args) " + token); + } + HoverEvent.Action action = HoverEvent.Action.valueOf(args[1].toUpperCase()); + return new HoverEvent(action, parseFormat(inner)); + } + + @Nonnull + private static Optional resolveColor(@Nonnull String token) { + try { + return Optional.of(ChatColor.valueOf(token.toUpperCase())); + } catch (IllegalArgumentException ex) { + return Optional.empty(); + } + } + + @Nonnull + private static Optional resolveDecoration(@Nonnull String token) { + try { + return Optional.of(TextDecoration.valueOf(token.toUpperCase())); + } catch (IllegalArgumentException ex) { + return Optional.empty(); + } + } + + enum TextDecoration { + BOLD(builder -> builder.bold(true)), + ITALIC(builder -> builder.italic(true)), + UNDERLINED(builder -> builder.underlined(true)), + STRIKETHROUGH(builder -> builder.strikethrough(true)), + OBFUSCATED(builder -> builder.obfuscated(true)); + + private Consumer builder; + + TextDecoration(@Nonnull Consumer builder) { + this.builder = builder; + } + + public void apply(@Nonnull ComponentBuilder comp) { + builder.accept(comp); + } + } +} diff --git a/src/main/java/me/minidigger/minimessage/MiniMessageSerializer.java b/src/main/java/me/minidigger/minimessage/MiniMessageSerializer.java new file mode 100644 index 000000000..9c2e7f450 --- /dev/null +++ b/src/main/java/me/minidigger/minimessage/MiniMessageSerializer.java @@ -0,0 +1,166 @@ +package me.minidigger.minimessage; + +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.chat.BaseComponent; +import net.md_5.bungee.api.chat.ClickEvent; +import net.md_5.bungee.api.chat.HoverEvent; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import static me.minidigger.minimessage.Constants.BOLD; +import static me.minidigger.minimessage.Constants.CLICK; +import static me.minidigger.minimessage.Constants.CLOSE_TAG; +import static me.minidigger.minimessage.Constants.HOVER; +import static me.minidigger.minimessage.Constants.ITALIC; +import static me.minidigger.minimessage.Constants.OBFUSCATED; +import static me.minidigger.minimessage.Constants.SEPARATOR; +import static me.minidigger.minimessage.Constants.STRIKETHROUGH; +import static me.minidigger.minimessage.Constants.TAG_END; +import static me.minidigger.minimessage.Constants.TAG_START; +import static me.minidigger.minimessage.Constants.UNDERLINED; + +public class MiniMessageSerializer { + + @Nonnull + public static String serialize(@Nonnull BaseComponent... components) { + StringBuilder sb = new StringBuilder(); + + for (int i = 0; i < components.length; i++) { + BaseComponent comp = components[i]; + + // # start tags + + // ## get prev comp + BaseComponent prevComp = null; + if (i > 0) { + prevComp = components[i - 1]; + } + + // ## color + // ### white is not important + if (!ChatColor.WHITE.equals(comp.getColor())) { + sb.append(startColor(comp.getColor())); + } + + // ## decoration + // ### only start if prevComp didn't start + if (comp.isBold() && (prevComp == null || !prevComp.isBold())) { + sb.append(startTag(BOLD)); + } + if (comp.isItalic() && (prevComp == null || !prevComp.isItalic())) { + sb.append(startTag(ITALIC)); + } + if (comp.isObfuscated() && (prevComp == null || !prevComp.isObfuscated())) { + sb.append(startTag(OBFUSCATED)); + } + if (comp.isStrikethrough() && (prevComp == null || !prevComp.isStrikethrough())) { + sb.append(startTag(STRIKETHROUGH)); + } + if (comp.isUnderlined() && (prevComp == null || !prevComp.isUnderlined())) { + sb.append(startTag(UNDERLINED)); + } + + // ## hover + // ### only start if prevComp didn't start the same one + HoverEvent hov = comp.getHoverEvent(); + if (hov != null && (prevComp == null || areDifferent(hov, prevComp.getHoverEvent()))) { + sb.append(startTag(String.format("%s" + SEPARATOR + "%s" + SEPARATOR + "\"%s\"", HOVER, hov.getAction().name().toLowerCase(), serialize(hov.getValue())))); + } + + // ## click + // ### only start if prevComp didn't start the same one + ClickEvent click = comp.getClickEvent(); + if (click != null && (prevComp == null || areDifferent(click, prevComp.getClickEvent()))) { + sb.append(startTag(String.format("%s" + SEPARATOR + "%s" + SEPARATOR + "\"%s\"", CLICK, click.getAction().name().toLowerCase(), click.getValue()))); + } + + // # append text + sb.append(comp.toPlainText()); + + // # end tags + + // ## get next comp + BaseComponent nextComp = null; + if (i + 1 < components.length) { + nextComp = components[i + 1]; + } + + // ## color + // ### only end color if next comp is white and current isn't + if (nextComp != null && comp.getColor() != ChatColor.WHITE) { + if (nextComp.getColor() == ChatColor.WHITE || nextComp.getColor() == null) { + sb.append(endColor(comp.getColor())); + } + } + + // ## decoration + // ### only end decoration if next tag is different + if (nextComp != null) { + if (comp.isBold() && !nextComp.isBold()) { + sb.append(endTag(BOLD)); + } + if (comp.isItalic() && !nextComp.isItalic()) { + sb.append(endTag(ITALIC)); + } + if (comp.isObfuscated() && !nextComp.isObfuscated()) { + sb.append(endTag(OBFUSCATED)); + } + if (comp.isStrikethrough() && !nextComp.isStrikethrough()) { + sb.append(endTag(STRIKETHROUGH)); + } + if (comp.isUnderlined() && !nextComp.isUnderlined()) { + sb.append(endTag(UNDERLINED)); + } + } + + // ## hover + // ### only end hover if next tag is different + if (nextComp != null && comp.getHoverEvent() != null) { + if (areDifferent(comp.getHoverEvent(), nextComp.getHoverEvent())) { + sb.append(endTag(HOVER)); + } + } + + // ## click + // ### only end click if next tag is different + if (nextComp != null && comp.getClickEvent() != null) { + if (areDifferent(comp.getClickEvent(), nextComp.getClickEvent())) { + sb.append(endTag(CLICK)); + } + } + } + + return sb.toString(); + } + + private static boolean areDifferent(@Nonnull ClickEvent c1, @Nullable ClickEvent c2) { + if (c2 == null) return true; + return !c1.equals(c2) && (!c1.getAction().equals(c2.getAction()) || !c1.getValue().equals(c2.getValue())); + } + + private static boolean areDifferent(@Nonnull HoverEvent h1, @Nullable HoverEvent h2) { + if (h2 == null) return true; + return !h1.equals(h2) && (!h1.getAction().equals(h2.getAction()));// TODO also compare value + } + + @Nonnull + private static String startColor(@Nonnull ChatColor color) { + return startTag(color.name().toLowerCase()); + } + + @Nonnull + private static String endColor(@Nonnull ChatColor color) { + return endTag(color.name().toLowerCase()); + } + + @Nonnull + private static String startTag(@Nonnull String content) { + return TAG_START + content + TAG_END; + } + + @Nonnull + private static String endTag(@Nonnull String content) { + return TAG_START + CLOSE_TAG + content + TAG_END; + } +} diff --git a/src/test/java/me/minidigger/minimessage/MiniMessageParserTest.java b/src/test/java/me/minidigger/minimessage/MiniMessageParserTest.java new file mode 100644 index 000000000..b324dab15 --- /dev/null +++ b/src/test/java/me/minidigger/minimessage/MiniMessageParserTest.java @@ -0,0 +1,148 @@ +package me.minidigger.minimessage; + +import net.md_5.bungee.api.chat.BaseComponent; +import net.md_5.bungee.chat.ComponentSerializer; + +import org.junit.Test; + +import javax.annotation.Nonnull; + +import static org.junit.Assert.assertEquals; + +public class MiniMessageParserTest { + + @Test + public void test() { + String input1 = "TEST nestedTest"; + String input2 = "TEST nestedTest"; + String out1 = ComponentSerializer.toString(MiniMessageParser.parseFormat(input1)); + String out2 = ComponentSerializer.toString(MiniMessageParser.parseFormat(input2)); + System.out.println(out1); + System.out.println(out2); + } + + @Test + public void testStripSimple() { + String input = "TEST nestedTest"; + String expected = "TEST nestedTest"; + assertEquals(expected, MiniMessageParser.stripTokens(input)); + } + + @Test + public void testStripComplex() { + String input = " random strangerclick here to FEEL it"; + String expected = " random strangerclick here to FEEL it"; + assertEquals(expected, MiniMessageParser.stripTokens(input)); + } + + @Test + public void testStripInner() { + String input = "test:TEST\">TEST"; + String expected = "TEST"; + assertEquals(expected, MiniMessageParser.stripTokens(input)); + } + + @Test + public void testEscapeSimple() { + String input = "TEST nestedTest"; + String expected = "\\TEST\\ nested\\Test"; + assertEquals(expected, MiniMessageParser.escapeTokens(input)); + } + + @Test + public void testEscapeComplex() { + String input = " random strangerclick here to FEEL it"; + String expected = "\\\\ random \\stranger\\\\\\\\click here\\\\ to \\FEEL\\ it"; + assertEquals(expected, MiniMessageParser.escapeTokens(input)); + } + + @Test + public void testEscapeInner() { + String input = "test:TEST\">TEST"; + String expected = "\\test:TEST\"\\>TEST"; + assertEquals(expected, MiniMessageParser.escapeTokens(input)); + } + + + @Test + public void checkPlaceholder() { + String input = ""; + String expected = "{\"text\":\"Hello!\"}"; + BaseComponent[] comp = MiniMessageParser.parseFormat(input, "test", "Hello!"); + + test(comp, expected); + } + + @Test + public void testNiceMix() { + String input = " random strangerclick here to FEEL it"; + String expected = "{\"extra\":[{\"color\":\"yellow\",\"text\":\"Hello! random \"},{\"color\":\"yellow\",\"bold\":true,\"text\":\"stranger\"},{\"color\":\"red\",\"underlined\":true,\"clickEvent\":{\"action\":\"run_command\",\"value\":\"test command\"},\"text\":\"click here\"},{\"color\":\"blue\",\"underlined\":true,\"text\":\" to \"},{\"color\":\"blue\",\"bold\":true,\"underlined\":true,\"text\":\"FEEL\"},{\"color\":\"blue\",\"bold\":true,\"text\":\" it\"}],\"text\":\"\"}"; + BaseComponent[] comp = MiniMessageParser.parseFormat(input, "test", "Hello!"); + + test(comp, expected); + } + + @Test + public void testColorSimple() { + String input = "TEST"; + String expected = "{\"color\":\"yellow\",\"text\":\"TEST\"}"; + + test(input, expected); + } + + @Test + public void testColorNested() { + String input = "TESTnestedTest"; + String expected = "{\"extra\":[{\"color\":\"yellow\",\"text\":\"TEST\"},{\"color\":\"green\",\"text\":\"nested\"},{\"color\":\"yellow\",\"text\":\"Test\"}],\"text\":\"\"}"; + + test(input, expected); + } + + @Test + public void testColorNotNested() { + String input = "TESTnestedTest"; + String expected = "{\"extra\":[{\"color\":\"yellow\",\"text\":\"TEST\"},{\"color\":\"green\",\"text\":\"nested\"},{\"text\":\"Test\"}],\"text\":\"\"}"; + + test(input, expected); + } + + @Test + public void testHover() { + String input = "test\">TEST"; + String expected = "{\"hoverEvent\":{\"action\":\"show_text\",\"value\":[{\"color\":\"red\",\"text\":\"test\"}]},\"text\":\"TEST\"}"; + + test(input, expected); + } + + @Test + public void testHoverWithColon() { + String input = "test:TEST\">TEST"; + String expected = "{\"hoverEvent\":{\"action\":\"show_text\",\"value\":[{\"color\":\"red\",\"text\":\"test:TEST\"}]},\"text\":\"TEST\"}"; + + test(input, expected); + } + + @Test + public void testClick() { + String input = "TEST"; + String expected = "{\"clickEvent\":{\"action\":\"run_command\",\"value\":\"test\"},\"text\":\"TEST\"}"; + + test(input, expected); + } + + @Test + public void testClickExtendedCommand() { + String input = "TEST"; + String expected = "{\"clickEvent\":{\"action\":\"run_command\",\"value\":\"test command\"},\"text\":\"TEST\"}"; + + test(input, expected); + } + + private void test(@Nonnull String input, @Nonnull String expected) { + test(MiniMessageParser.parseFormat(input), expected); + } + + private void test(@Nonnull BaseComponent[] comp, @Nonnull String expected) { + assertEquals(expected, ComponentSerializer.toString(comp)); + } +} diff --git a/src/test/java/me/minidigger/minimessage/MiniMessageSerializerTest.java b/src/test/java/me/minidigger/minimessage/MiniMessageSerializerTest.java new file mode 100644 index 000000000..01f1d453e --- /dev/null +++ b/src/test/java/me/minidigger/minimessage/MiniMessageSerializerTest.java @@ -0,0 +1,124 @@ +package me.minidigger.minimessage; + +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.chat.ClickEvent; +import net.md_5.bungee.api.chat.ComponentBuilder; +import net.md_5.bungee.api.chat.ComponentBuilder.FormatRetention; +import net.md_5.bungee.api.chat.HoverEvent; +import net.md_5.bungee.api.chat.HoverEvent.Action; + +import org.junit.Test; + +import javax.annotation.Nonnull; + +import static org.junit.Assert.assertEquals; + +public class MiniMessageSerializerTest { + + @Test + public void testColor() { + String expected = "This is a test"; + + ComponentBuilder builder = new ComponentBuilder("This is a test"); + builder.color(ChatColor.RED); + + test(builder, expected); + } + + @Test + public void testColorClosing() { + String expected = "This is a test"; + + ComponentBuilder builder = new ComponentBuilder("This is a "); + builder.color(ChatColor.RED); + builder.append("test", FormatRetention.NONE); + + test(builder, expected); + } + + @Test + public void testNestedColor() { + String expected = "This is a blue test"; + + ComponentBuilder builder = new ComponentBuilder("This is a ").color(ChatColor.RED)// + .append("blue ", FormatRetention.NONE).color(ChatColor.BLUE)// + .append("test", FormatRetention.NONE).color(ChatColor.RED); + + test(builder, expected); + } + + @Test + public void testDecoration() { + String expected = "This is underlined, this isn't"; + + ComponentBuilder builder = new ComponentBuilder("This is ").underlined(true)// + .append("underlined", FormatRetention.NONE).bold(true).underlined(true)// + .append(", this", FormatRetention.NONE).bold(true)// + .append(" isn't", FormatRetention.NONE); + + test(builder, expected); + } + + @Test + public void testHover() { + String expected = "Some hover that ends here"; + + ComponentBuilder builder = new ComponentBuilder("Some hover") + .event(new HoverEvent(Action.SHOW_TEXT, new ComponentBuilder("---").create()))// + .append(" that ends here", FormatRetention.NONE); + + test(builder, expected); + } + + @Test + public void testHoverWithNested() { + String expected = "----\">Some hover that ends here"; + + ComponentBuilder builder = new ComponentBuilder("Some hover").event(new HoverEvent(Action.SHOW_TEXT, // + new ComponentBuilder("---").color(ChatColor.RED)// + .append("-").color(ChatColor.BLUE).bold(true).create()))// + .append(" that ends here", FormatRetention.NONE); + + test(builder, expected); + } + + @Test + public void testClick() { + String expected = "Some click that ends here"; + + ComponentBuilder builder = new ComponentBuilder("Some click") + .event(new ClickEvent(net.md_5.bungee.api.chat.ClickEvent.Action.RUN_COMMAND, "test"))// + .append(" that ends here", FormatRetention.NONE); + + test(builder, expected); + } + + @Test + public void testContinuedClick() { + String expected = "Some click that doesn't ends here"; + + ComponentBuilder builder = new ComponentBuilder("Some click") + .event(new ClickEvent(net.md_5.bungee.api.chat.ClickEvent.Action.RUN_COMMAND, "test"))// + .append(" that doesn't ends here", FormatRetention.EVENTS).color(ChatColor.RED); + + test(builder, expected); + } + + @Test + public void testContinuedClick2() { + String expected = "Some click that doesn't ends here"; + + ComponentBuilder builder = new ComponentBuilder("Some click") + .event(new ClickEvent(net.md_5.bungee.api.chat.ClickEvent.Action.RUN_COMMAND, "test"))// + .append(" that doesn't ends here", FormatRetention.NONE) + .event(new ClickEvent(net.md_5.bungee.api.chat.ClickEvent.Action.RUN_COMMAND, "test")) + .color(ChatColor.RED); + + test(builder, expected); + } + + private void test(@Nonnull ComponentBuilder builder, @Nonnull String expected) { + String string = MiniMessageSerializer.serialize(builder.create()); + assertEquals(expected, string); + } +} From 7a158fd3d839fe04c27fda7c1c17fce8333cc845 Mon Sep 17 00:00:00 2001 From: MiniDigger Date: Sat, 14 Dec 2019 19:19:30 +0100 Subject: [PATCH 002/339] Create LICENSE --- LICENSE | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..5fe04da45 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 MiniDigger + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. From bdbf6c0c4383fdc7ad73e168716ac923ddfc24ce Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Sun, 22 Dec 2019 03:57:11 +0000 Subject: [PATCH 003/339] Add renovate.json --- renovate.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 renovate.json diff --git a/renovate.json b/renovate.json new file mode 100644 index 000000000..f45d8f110 --- /dev/null +++ b/renovate.json @@ -0,0 +1,5 @@ +{ + "extends": [ + "config:base" + ] +} From 3a0af03ab3126801e3e14abb8686c7ed6413134a Mon Sep 17 00:00:00 2001 From: MiniDigger Date: Sat, 11 Jan 2020 21:35:07 +0100 Subject: [PATCH 004/339] prepare for release --- .github/workflows/Build.yml | 16 +++++ .github/workflows/Publish.yml | 33 +++++++++ README.md | 12 +++- pom.xml | 129 +++++++++++++++++++++++++++++++++- 4 files changed, 184 insertions(+), 6 deletions(-) create mode 100644 .github/workflows/Build.yml create mode 100644 .github/workflows/Publish.yml diff --git a/.github/workflows/Build.yml b/.github/workflows/Build.yml new file mode 100644 index 000000000..f07465b54 --- /dev/null +++ b/.github/workflows/Build.yml @@ -0,0 +1,16 @@ +name: Build + +on: [pull_request] + +jobs: + build: + name: Build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set up JDK + uses: actions/setup-java@v1 + with: + java-version: 11 + - name: Build with Maven + run: mvn package diff --git a/.github/workflows/Publish.yml b/.github/workflows/Publish.yml new file mode 100644 index 000000000..01746aa0f --- /dev/null +++ b/.github/workflows/Publish.yml @@ -0,0 +1,33 @@ +name: Publish + +on: [push] + +jobs: + publish: + name: Publish + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + + - name: Set up JDK + uses: actions/setup-java@v1 + with: + java-version: 11 + + - name: Release + uses: MiniDigger/github-actions-maven-release@master + with: + release-branch-name: "master" + + gpg-key: ${{ secrets.GITHUB_GPG_KEY }} + + maven-repo-server-id: ${{ secrets.MVN_REPO_PRIVATE_REPO_ID }} + maven-repo-server-username: ${{ secrets.MVN_REPO_PRIVATE_REPO_USER }} + maven-repo-server-password: ${{ secrets.MVN_REPO_PRIVATE_REPO_PASSWORD }} + + git-release-bot-name: "release-bot" + git-release-bot-email: "release-bot@minidigger.me" + + access-token: ${{ secrets.GITHUB_ACCESS_TOKEN }} + + maven-args: ${{ '-Dgpg.passphrase=' }}${{ secrets.MVN_REPO_PRIVATE_REPO_PASSWORD }} diff --git a/README.md b/README.md index 7e2665428..6a5d57214 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # MiniMessage -Simple library that implements an easy to use textual format to send rich json messages. +Simple library that implements an easy to use textual format to send rich json messages. ## Examples @@ -95,8 +95,14 @@ Artifact: compile ``` -Repo: -pending... +Repo (for snapshots): +```xml + + sonatype + https://oss.sonatype.org/content/repositories/snapshots + +``` +Releases get deployed to maven central. ## Licence MIT diff --git a/pom.xml b/pom.xml index f85ba6ff1..235f2ae1e 100644 --- a/pom.xml +++ b/pom.xml @@ -1,12 +1,13 @@ - + 4.0.0 me.minidigger MiniMessage 1.0.0-SNAPSHOT + MiniMessage + Simple library that implements an easy to use textual format to send rich json messages. + https://github.com/MiniDigger/MiniMessage 1.8 @@ -22,4 +23,126 @@ provided + + + + papermc + https://papermc.io/repo/repository/maven-public/ + + + + + + + org.apache.maven.plugins + maven-release-plugin + 3.0.0-M1 + + deploy + + true + + + + + + + + deploy + + + deploy + + + + clean deploy + + + org.apache.maven.plugins + maven-source-plugin + 3.2.1 + + + attach-sources + + jar + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 3.1.1 + + + attach-javadocs + + jar + + + none + + + + + + org.apache.maven.plugins + maven-gpg-plugin + 1.6 + + + sign-artifacts + verify + + sign + + + + + + + + + default + + true + + + + + + scm:git:https://github.com/MiniDigger/MiniMessage.git + scm:git:https://github.com/MiniDigger/MiniMessage.git + https://github.com/MiniDigger/MiniMessage + HEAD + + + + + MIT + https://github.com/MiniDigger/MiniMessage/blob/master/LICENSE + + + + + + MiniDigger + admin@minidigger.me + + Maintainer + + + + + + + sonatype + https://oss.sonatype.org/service/local/staging/deploy/maven2 + + + sonatype + https://oss.sonatype.org/content/repositories/snapshots + + From 2f0f22526edaa030266977a5021cdfad3213b269 Mon Sep 17 00:00:00 2001 From: release-bot Date: Sat, 11 Jan 2020 23:22:29 +0000 Subject: [PATCH 005/339] [maven-release-plugin] prepare release MiniMessage-1.0.0 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 235f2ae1e..fe25ecf74 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ me.minidigger MiniMessage - 1.0.0-SNAPSHOT + 1.0.0 MiniMessage Simple library that implements an easy to use textual format to send rich json messages. https://github.com/MiniDigger/MiniMessage @@ -115,7 +115,7 @@ scm:git:https://github.com/MiniDigger/MiniMessage.git scm:git:https://github.com/MiniDigger/MiniMessage.git https://github.com/MiniDigger/MiniMessage - HEAD + MiniMessage-1.0.0 From 28efa5e4c903741a6b95c07bbdee13c82094e04b Mon Sep 17 00:00:00 2001 From: release-bot Date: Sat, 11 Jan 2020 23:22:33 +0000 Subject: [PATCH 006/339] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index fe25ecf74..3bd7564a0 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ me.minidigger MiniMessage - 1.0.0 + 1.0.1-SNAPSHOT MiniMessage Simple library that implements an easy to use textual format to send rich json messages. https://github.com/MiniDigger/MiniMessage @@ -115,7 +115,7 @@ scm:git:https://github.com/MiniDigger/MiniMessage.git scm:git:https://github.com/MiniDigger/MiniMessage.git https://github.com/MiniDigger/MiniMessage - MiniMessage-1.0.0 + HEAD From f76a515a98d47c06fac7faf12d0926a971991e09 Mon Sep 17 00:00:00 2001 From: MiniDigger Date: Sun, 12 Jan 2020 00:27:02 +0100 Subject: [PATCH 007/339] try to fix signing error --- pom.xml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pom.xml b/pom.xml index 3bd7564a0..15918ab08 100644 --- a/pom.xml +++ b/pom.xml @@ -99,6 +99,13 @@ + + + --batch + --pinentry-mode + loopback + + From 35ae99187f2ba4056a9f0287288b7ff02eaf3f49 Mon Sep 17 00:00:00 2001 From: release-bot Date: Sat, 11 Jan 2020 23:28:38 +0000 Subject: [PATCH 008/339] [maven-release-plugin] prepare release MiniMessage-1.0.1 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 15918ab08..38b1e866d 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ me.minidigger MiniMessage - 1.0.1-SNAPSHOT + 1.0.1 MiniMessage Simple library that implements an easy to use textual format to send rich json messages. https://github.com/MiniDigger/MiniMessage @@ -122,7 +122,7 @@ scm:git:https://github.com/MiniDigger/MiniMessage.git scm:git:https://github.com/MiniDigger/MiniMessage.git https://github.com/MiniDigger/MiniMessage - HEAD + MiniMessage-1.0.1 From a8948bbe40948ceebd68daf883a2e0a3a2e68b76 Mon Sep 17 00:00:00 2001 From: release-bot Date: Sat, 11 Jan 2020 23:28:43 +0000 Subject: [PATCH 009/339] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 38b1e866d..bec899ea7 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ me.minidigger MiniMessage - 1.0.1 + 1.0.2-SNAPSHOT MiniMessage Simple library that implements an easy to use textual format to send rich json messages. https://github.com/MiniDigger/MiniMessage @@ -122,7 +122,7 @@ scm:git:https://github.com/MiniDigger/MiniMessage.git scm:git:https://github.com/MiniDigger/MiniMessage.git https://github.com/MiniDigger/MiniMessage - MiniMessage-1.0.1 + HEAD From 689665da6b96cf537b261dbfcc7774c1b5622aa1 Mon Sep 17 00:00:00 2001 From: MiniDigger Date: Sat, 28 Mar 2020 11:00:59 +0100 Subject: [PATCH 010/339] dont eat invalid tags --- .../minimessage/MiniMessageParser.java | 7 +++++++ .../minimessage/MiniMessageParserTest.java | 20 +++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/src/main/java/me/minidigger/minimessage/MiniMessageParser.java b/src/main/java/me/minidigger/minimessage/MiniMessageParser.java index 1d1c9c69b..804c22843 100644 --- a/src/main/java/me/minidigger/minimessage/MiniMessageParser.java +++ b/src/main/java/me/minidigger/minimessage/MiniMessageParser.java @@ -198,6 +198,13 @@ else if ((color = resolveColor(token)).isPresent()) { colors.push(color.get()); } else if (token.startsWith(CLOSE_TAG) && resolveColor(token.replace(CLOSE_TAG, "")).isPresent()) { colors.pop(); + } else { + // invalid tag + if (builder == null) { + builder = new ComponentBuilder(TAG_START + token + TAG_END); + } else { + builder.append(TAG_START + token + TAG_END, ComponentBuilder.FormatRetention.NONE); + } } } diff --git a/src/test/java/me/minidigger/minimessage/MiniMessageParserTest.java b/src/test/java/me/minidigger/minimessage/MiniMessageParserTest.java index b324dab15..c8aefaeeb 100644 --- a/src/test/java/me/minidigger/minimessage/MiniMessageParserTest.java +++ b/src/test/java/me/minidigger/minimessage/MiniMessageParserTest.java @@ -138,6 +138,26 @@ public void testClickExtendedCommand() { test(input, expected); } + @Test + public void testInvalidTag() { + String input = ""; + String expected = "{\"text\":\"\\u003ctest\\u003e\"}"; // gson makes it html save + BaseComponent[] comp = MiniMessageParser.parseFormat(input); + + test(comp, expected); + + // am not totally happy about this yet, invalid tags arent getting colored for example, but good enough for now + } + + @Test + public void testInvalidTagComplex() { + String input = " random strangerclick here to FEEL it"; + String expected = "{\"extra\":[{\"text\":\"\\u003ctest\\u003e\"},{\"color\":\"yellow\",\"text\":\" random \"},{\"color\":\"yellow\",\"bold\":true,\"text\":\"stranger\"},{\"text\":\"\\u003coof\\u003e\"},{\"text\":\"\\u003c/oof\\u003e\"},{\"color\":\"red\",\"underlined\":true,\"clickEvent\":{\"action\":\"run_command\",\"value\":\"test command\"},\"text\":\"click here\"},{\"color\":\"blue\",\"underlined\":true,\"text\":\" to \"},{\"color\":\"blue\",\"bold\":true,\"underlined\":true,\"text\":\"FEEL\"},{\"color\":\"blue\",\"bold\":true,\"text\":\" it\"}],\"text\":\"\"}"; + BaseComponent[] comp = MiniMessageParser.parseFormat(input); + + test(comp, expected); + } + private void test(@Nonnull String input, @Nonnull String expected) { test(MiniMessageParser.parseFormat(input), expected); } From e3b11199a1ab082681379f82fec3d4c2541a4613 Mon Sep 17 00:00:00 2001 From: release-bot Date: Sat, 28 Mar 2020 10:02:41 +0000 Subject: [PATCH 011/339] [maven-release-plugin] prepare release MiniMessage-1.0.2 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index bec899ea7..3274c5f42 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ me.minidigger MiniMessage - 1.0.2-SNAPSHOT + 1.0.2 MiniMessage Simple library that implements an easy to use textual format to send rich json messages. https://github.com/MiniDigger/MiniMessage @@ -122,7 +122,7 @@ scm:git:https://github.com/MiniDigger/MiniMessage.git scm:git:https://github.com/MiniDigger/MiniMessage.git https://github.com/MiniDigger/MiniMessage - HEAD + MiniMessage-1.0.2 From 6c790dab9dd115f5014d70411e6259463e5a1e06 Mon Sep 17 00:00:00 2001 From: release-bot Date: Sat, 28 Mar 2020 10:02:48 +0000 Subject: [PATCH 012/339] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 3274c5f42..1d1af7193 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ me.minidigger MiniMessage - 1.0.2 + 1.0.3-SNAPSHOT MiniMessage Simple library that implements an easy to use textual format to send rich json messages. https://github.com/MiniDigger/MiniMessage @@ -122,7 +122,7 @@ scm:git:https://github.com/MiniDigger/MiniMessage.git scm:git:https://github.com/MiniDigger/MiniMessage.git https://github.com/MiniDigger/MiniMessage - MiniMessage-1.0.2 + HEAD From bd59149ca8e03267f8a6db1c3fc11000a93ce5c1 Mon Sep 17 00:00:00 2001 From: MiniDigger Date: Wed, 22 Apr 2020 19:07:15 +0200 Subject: [PATCH 013/339] prepare for 2.0 --- README.md | 6 +++++ minimessage-bungee/pom.xml | 23 +++++++++++++++++ .../me/minidigger/minimessage/Constants.java | 0 .../minimessage/MiniMessageParser.java | 0 .../minimessage/MiniMessageSerializer.java | 0 .../minimessage/MiniMessageParserTest.java | 0 .../MiniMessageSerializerTest.java | 8 +++--- minimessage-ext-markdown/pom.xml | 15 +++++++++++ minimessage-text/pom.xml | 15 +++++++++++ pom.xml | 25 +++++++++---------- 10 files changed, 75 insertions(+), 17 deletions(-) create mode 100644 minimessage-bungee/pom.xml rename {src => minimessage-bungee/src}/main/java/me/minidigger/minimessage/Constants.java (100%) rename {src => minimessage-bungee/src}/main/java/me/minidigger/minimessage/MiniMessageParser.java (100%) rename {src => minimessage-bungee/src}/main/java/me/minidigger/minimessage/MiniMessageSerializer.java (100%) rename {src => minimessage-bungee/src}/test/java/me/minidigger/minimessage/MiniMessageParserTest.java (100%) rename {src => minimessage-bungee/src}/test/java/me/minidigger/minimessage/MiniMessageSerializerTest.java (90%) create mode 100644 minimessage-ext-markdown/pom.xml create mode 100644 minimessage-text/pom.xml diff --git a/README.md b/README.md index 6a5d57214..5d369dac8 100644 --- a/README.md +++ b/README.md @@ -104,5 +104,11 @@ Repo (for snapshots): ``` Releases get deployed to maven central. +## Upgrading to 2.0 + +2.0 seperated MiniMessage into two versions, minimessage-bungee and minimessage-text. +The bungee version continues to use the Bungee Chat Components, like 1.x did, but the new Version uses the kyori text lib, which has support for way more features. +If you dont want to change your code, use the old dependency, but you should really consider upgrading to the new one! + ## Licence MIT diff --git a/minimessage-bungee/pom.xml b/minimessage-bungee/pom.xml new file mode 100644 index 000000000..f5d1e2636 --- /dev/null +++ b/minimessage-bungee/pom.xml @@ -0,0 +1,23 @@ + + + 4.0.0 + + MiniMessage + me.minidigger + 2.0.0-SNAPSHOT + + + minimessage-bungee + MiniMessage Bungee + + + + com.destroystokyo.paper + paper-api + 1.15-R0.1-SNAPSHOT + provided + + + diff --git a/src/main/java/me/minidigger/minimessage/Constants.java b/minimessage-bungee/src/main/java/me/minidigger/minimessage/Constants.java similarity index 100% rename from src/main/java/me/minidigger/minimessage/Constants.java rename to minimessage-bungee/src/main/java/me/minidigger/minimessage/Constants.java diff --git a/src/main/java/me/minidigger/minimessage/MiniMessageParser.java b/minimessage-bungee/src/main/java/me/minidigger/minimessage/MiniMessageParser.java similarity index 100% rename from src/main/java/me/minidigger/minimessage/MiniMessageParser.java rename to minimessage-bungee/src/main/java/me/minidigger/minimessage/MiniMessageParser.java diff --git a/src/main/java/me/minidigger/minimessage/MiniMessageSerializer.java b/minimessage-bungee/src/main/java/me/minidigger/minimessage/MiniMessageSerializer.java similarity index 100% rename from src/main/java/me/minidigger/minimessage/MiniMessageSerializer.java rename to minimessage-bungee/src/main/java/me/minidigger/minimessage/MiniMessageSerializer.java diff --git a/src/test/java/me/minidigger/minimessage/MiniMessageParserTest.java b/minimessage-bungee/src/test/java/me/minidigger/minimessage/MiniMessageParserTest.java similarity index 100% rename from src/test/java/me/minidigger/minimessage/MiniMessageParserTest.java rename to minimessage-bungee/src/test/java/me/minidigger/minimessage/MiniMessageParserTest.java diff --git a/src/test/java/me/minidigger/minimessage/MiniMessageSerializerTest.java b/minimessage-bungee/src/test/java/me/minidigger/minimessage/MiniMessageSerializerTest.java similarity index 90% rename from src/test/java/me/minidigger/minimessage/MiniMessageSerializerTest.java rename to minimessage-bungee/src/test/java/me/minidigger/minimessage/MiniMessageSerializerTest.java index 01f1d453e..329cdc9e6 100644 --- a/src/test/java/me/minidigger/minimessage/MiniMessageSerializerTest.java +++ b/minimessage-bungee/src/test/java/me/minidigger/minimessage/MiniMessageSerializerTest.java @@ -87,7 +87,7 @@ public void testClick() { String expected = "Some click that ends here"; ComponentBuilder builder = new ComponentBuilder("Some click") - .event(new ClickEvent(net.md_5.bungee.api.chat.ClickEvent.Action.RUN_COMMAND, "test"))// + .event(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "test"))// .append(" that ends here", FormatRetention.NONE); test(builder, expected); @@ -98,7 +98,7 @@ public void testContinuedClick() { String expected = "Some click that doesn't ends here"; ComponentBuilder builder = new ComponentBuilder("Some click") - .event(new ClickEvent(net.md_5.bungee.api.chat.ClickEvent.Action.RUN_COMMAND, "test"))// + .event(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "test"))// .append(" that doesn't ends here", FormatRetention.EVENTS).color(ChatColor.RED); test(builder, expected); @@ -109,9 +109,9 @@ public void testContinuedClick2() { String expected = "Some click that doesn't ends here"; ComponentBuilder builder = new ComponentBuilder("Some click") - .event(new ClickEvent(net.md_5.bungee.api.chat.ClickEvent.Action.RUN_COMMAND, "test"))// + .event(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "test"))// .append(" that doesn't ends here", FormatRetention.NONE) - .event(new ClickEvent(net.md_5.bungee.api.chat.ClickEvent.Action.RUN_COMMAND, "test")) + .event(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "test")) .color(ChatColor.RED); test(builder, expected); diff --git a/minimessage-ext-markdown/pom.xml b/minimessage-ext-markdown/pom.xml new file mode 100644 index 000000000..e514a58d9 --- /dev/null +++ b/minimessage-ext-markdown/pom.xml @@ -0,0 +1,15 @@ + + + + MiniMessage + me.minidigger + 2.0.0-SNAPSHOT + + 4.0.0 + + minimessage-ext-markdown + + + diff --git a/minimessage-text/pom.xml b/minimessage-text/pom.xml new file mode 100644 index 000000000..2d977f702 --- /dev/null +++ b/minimessage-text/pom.xml @@ -0,0 +1,15 @@ + + + 4.0.0 + + MiniMessage + me.minidigger + 2.0.0-SNAPSHOT + + + minimessage-text + MiniMessage Text + + diff --git a/pom.xml b/pom.xml index 1d1af7193..d510f71c7 100644 --- a/pom.xml +++ b/pom.xml @@ -1,10 +1,12 @@ - + 4.0.0 me.minidigger MiniMessage - 1.0.3-SNAPSHOT + pom + 2.0.0-SNAPSHOT MiniMessage Simple library that implements an easy to use textual format to send rich json messages. https://github.com/MiniDigger/MiniMessage @@ -15,15 +17,6 @@ UTF-8 - - - com.destroystokyo.paper - paper-api - 1.15-R0.1-SNAPSHOT - provided - - - papermc @@ -31,6 +24,12 @@ + + minimessage-text + minimessage-bungee + minimessage-ext-markdown + + @@ -122,8 +121,8 @@ scm:git:https://github.com/MiniDigger/MiniMessage.git scm:git:https://github.com/MiniDigger/MiniMessage.git https://github.com/MiniDigger/MiniMessage - HEAD - + HEAD + From 190c19f5bd7991dbe6ff4391936f2f2eedf3a670 Mon Sep 17 00:00:00 2001 From: release-bot Date: Wed, 22 Apr 2020 17:08:51 +0000 Subject: [PATCH 014/339] [maven-release-plugin] prepare release MiniMessage-2.0.0 --- minimessage-bungee/pom.xml | 6 ++---- minimessage-ext-markdown/pom.xml | 6 ++---- minimessage-text/pom.xml | 6 ++---- pom.xml | 7 +++---- 4 files changed, 9 insertions(+), 16 deletions(-) diff --git a/minimessage-bungee/pom.xml b/minimessage-bungee/pom.xml index f5d1e2636..a685a96e1 100644 --- a/minimessage-bungee/pom.xml +++ b/minimessage-bungee/pom.xml @@ -1,12 +1,10 @@ - + 4.0.0 MiniMessage me.minidigger - 2.0.0-SNAPSHOT + 2.0.0 minimessage-bungee diff --git a/minimessage-ext-markdown/pom.xml b/minimessage-ext-markdown/pom.xml index e514a58d9..377525f8b 100644 --- a/minimessage-ext-markdown/pom.xml +++ b/minimessage-ext-markdown/pom.xml @@ -1,11 +1,9 @@ - + MiniMessage me.minidigger - 2.0.0-SNAPSHOT + 2.0.0 4.0.0 diff --git a/minimessage-text/pom.xml b/minimessage-text/pom.xml index 2d977f702..2cbadfc67 100644 --- a/minimessage-text/pom.xml +++ b/minimessage-text/pom.xml @@ -1,12 +1,10 @@ - + 4.0.0 MiniMessage me.minidigger - 2.0.0-SNAPSHOT + 2.0.0 minimessage-text diff --git a/pom.xml b/pom.xml index d510f71c7..bed2b6eb3 100644 --- a/pom.xml +++ b/pom.xml @@ -1,12 +1,11 @@ - + 4.0.0 me.minidigger MiniMessage pom - 2.0.0-SNAPSHOT + 2.0.0 MiniMessage Simple library that implements an easy to use textual format to send rich json messages. https://github.com/MiniDigger/MiniMessage @@ -121,7 +120,7 @@ scm:git:https://github.com/MiniDigger/MiniMessage.git scm:git:https://github.com/MiniDigger/MiniMessage.git https://github.com/MiniDigger/MiniMessage - HEAD + MiniMessage-2.0.0 From e1fcd2f49fcbcfb769186c440652752a9d6a9760 Mon Sep 17 00:00:00 2001 From: release-bot Date: Wed, 22 Apr 2020 17:08:57 +0000 Subject: [PATCH 015/339] [maven-release-plugin] prepare for next development iteration --- minimessage-bungee/pom.xml | 2 +- minimessage-ext-markdown/pom.xml | 2 +- minimessage-text/pom.xml | 2 +- pom.xml | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/minimessage-bungee/pom.xml b/minimessage-bungee/pom.xml index a685a96e1..e43f22089 100644 --- a/minimessage-bungee/pom.xml +++ b/minimessage-bungee/pom.xml @@ -4,7 +4,7 @@ MiniMessage me.minidigger - 2.0.0 + 2.0.1-SNAPSHOT minimessage-bungee diff --git a/minimessage-ext-markdown/pom.xml b/minimessage-ext-markdown/pom.xml index 377525f8b..2f8240f4a 100644 --- a/minimessage-ext-markdown/pom.xml +++ b/minimessage-ext-markdown/pom.xml @@ -3,7 +3,7 @@ MiniMessage me.minidigger - 2.0.0 + 2.0.1-SNAPSHOT 4.0.0 diff --git a/minimessage-text/pom.xml b/minimessage-text/pom.xml index 2cbadfc67..a3e711515 100644 --- a/minimessage-text/pom.xml +++ b/minimessage-text/pom.xml @@ -4,7 +4,7 @@ MiniMessage me.minidigger - 2.0.0 + 2.0.1-SNAPSHOT minimessage-text diff --git a/pom.xml b/pom.xml index bed2b6eb3..d9a674d14 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ me.minidigger MiniMessage pom - 2.0.0 + 2.0.1-SNAPSHOT MiniMessage Simple library that implements an easy to use textual format to send rich json messages. https://github.com/MiniDigger/MiniMessage @@ -120,7 +120,7 @@ scm:git:https://github.com/MiniDigger/MiniMessage.git scm:git:https://github.com/MiniDigger/MiniMessage.git https://github.com/MiniDigger/MiniMessage - MiniMessage-2.0.0 + HEAD From 099870ea79b587b32ec16955aa419dca26967453 Mon Sep 17 00:00:00 2001 From: MiniDigger Date: Wed, 22 Apr 2020 19:19:00 +0200 Subject: [PATCH 016/339] sonar findings --- .../minimessage/MiniMessageParser.java | 38 ++++++++++--------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/minimessage-bungee/src/main/java/me/minidigger/minimessage/MiniMessageParser.java b/minimessage-bungee/src/main/java/me/minidigger/minimessage/MiniMessageParser.java index 804c22843..c9f9539d0 100644 --- a/minimessage-bungee/src/main/java/me/minidigger/minimessage/MiniMessageParser.java +++ b/minimessage-bungee/src/main/java/me/minidigger/minimessage/MiniMessageParser.java @@ -6,6 +6,8 @@ import net.md_5.bungee.api.chat.ComponentBuilder; import net.md_5.bungee.api.chat.HoverEvent; +import java.util.ArrayDeque; +import java.util.Deque; import java.util.EnumSet; import java.util.Map; import java.util.Optional; @@ -30,7 +32,7 @@ public class MiniMessageParser { private static final String INNER = "inner"; private static final String END = "end"; // https://regex101.com/r/8VZ7uA/5 - private static Pattern pattern = Pattern.compile("((?<)(?([^<>]+)|([^<>]+\"(?[^\"]+)\"))(?>))+?"); + private static final Pattern pattern = Pattern.compile("((?<)(?([^<>]+)|([^<>]+\"(?[^\"]+)\"))(?>))+?"); @Nonnull public static String escapeTokens(@Nonnull String richMessage) { @@ -122,9 +124,9 @@ public static BaseComponent[] parseFormat(@Nonnull String richMessage, @Nonnull public static BaseComponent[] parseFormat(@Nonnull String richMessage) { ComponentBuilder builder = null; - Stack clickEvents = new Stack<>(); - Stack hoverEvents = new Stack<>(); - Stack colors = new Stack<>(); + Deque clickEvents = new ArrayDeque<>(); + Deque hoverEvents = new ArrayDeque<>(); + Deque colors = new ArrayDeque<>(); EnumSet decorations = EnumSet.noneOf(TextDecoration.class); Matcher matcher = pattern.matcher(richMessage); @@ -149,16 +151,16 @@ public static BaseComponent[] parseFormat(@Nonnull String richMessage) { } // set everything that is not closed yet - if (clickEvents.size() > 0) { + if (!clickEvents.isEmpty()) { builder.event(clickEvents.peek()); } - if (hoverEvents.size() > 0) { + if (!hoverEvents.isEmpty()) { builder.event(hoverEvents.peek()); } - if (colors.size() > 0) { + if (!colors.isEmpty()) { builder.color(colors.peek()); } - if (decorations.size() > 0) { + if (!decorations.isEmpty()) { // no lambda because builder isn't effective final :/ for (TextDecoration decor : decorations) { decor.apply(builder); @@ -219,16 +221,16 @@ else if ((color = resolveColor(token)).isPresent()) { } // set everything that is not closed yet - if (clickEvents.size() > 0) { + if (!clickEvents.isEmpty()) { builder.event(clickEvents.peek()); } - if (hoverEvents.size() > 0) { + if (!hoverEvents.isEmpty()) { builder.event(hoverEvents.peek()); } - if (colors.size() > 0) { + if (!colors.isEmpty()) { builder.color(colors.peek()); } - if (decorations.size() > 0) { + if (!decorations.isEmpty()) { // no lambda because builder isn't effective final :/ for (TextDecoration decor : decorations) { decor.apply(builder); @@ -283,13 +285,13 @@ private static Optional resolveDecoration(@Nonnull String token) } enum TextDecoration { - BOLD(builder -> builder.bold(true)), - ITALIC(builder -> builder.italic(true)), - UNDERLINED(builder -> builder.underlined(true)), - STRIKETHROUGH(builder -> builder.strikethrough(true)), - OBFUSCATED(builder -> builder.obfuscated(true)); + BOLD(b -> b.bold(true)), + ITALIC(b -> b.italic(true)), + UNDERLINED(b -> b.underlined(true)), + STRIKETHROUGH(b -> b.strikethrough(true)), + OBFUSCATED(b -> b.obfuscated(true)); - private Consumer builder; + private final Consumer builder; TextDecoration(@Nonnull Consumer builder) { this.builder = builder; From 3801ea22b7c87c1c9a7de53727719d0a50674902 Mon Sep 17 00:00:00 2001 From: MiniDigger Date: Thu, 23 Apr 2020 01:32:58 +0200 Subject: [PATCH 017/339] add markdown thingy --- minimessage-bungee/pom.xml | 2 +- .../me/minidigger/minimessage/Constants.java | 2 +- .../minimessage/MiniMessageParser.java | 1 - minimessage-ext-markdown/pom.xml | 9 +- .../me/minidigger/minimessage/Constants.java | 13 ++ .../minimessage/MiniMarkdownParser.java | 167 +++++++++++++++++ .../minimessage/MiniMarkdownParserTest.java | 168 ++++++++++++++++++ 7 files changed, 358 insertions(+), 4 deletions(-) create mode 100644 minimessage-ext-markdown/src/main/java/me/minidigger/minimessage/Constants.java create mode 100644 minimessage-ext-markdown/src/main/java/me/minidigger/minimessage/MiniMarkdownParser.java create mode 100644 minimessage-ext-markdown/src/test/java/me/minidigger/minimessage/MiniMarkdownParserTest.java diff --git a/minimessage-bungee/pom.xml b/minimessage-bungee/pom.xml index e43f22089..b86da35b7 100644 --- a/minimessage-bungee/pom.xml +++ b/minimessage-bungee/pom.xml @@ -14,7 +14,7 @@ com.destroystokyo.paper paper-api - 1.15-R0.1-SNAPSHOT + 1.15.2-R0.1-SNAPSHOT provided diff --git a/minimessage-bungee/src/main/java/me/minidigger/minimessage/Constants.java b/minimessage-bungee/src/main/java/me/minidigger/minimessage/Constants.java index 050d8bb40..ff099a636 100644 --- a/minimessage-bungee/src/main/java/me/minidigger/minimessage/Constants.java +++ b/minimessage-bungee/src/main/java/me/minidigger/minimessage/Constants.java @@ -1,6 +1,6 @@ package me.minidigger.minimessage; -public class Constants { +class Constants { public static final String CLICK = "click"; public static final String HOVER = "hover"; diff --git a/minimessage-bungee/src/main/java/me/minidigger/minimessage/MiniMessageParser.java b/minimessage-bungee/src/main/java/me/minidigger/minimessage/MiniMessageParser.java index c9f9539d0..c9f9d7c1b 100644 --- a/minimessage-bungee/src/main/java/me/minidigger/minimessage/MiniMessageParser.java +++ b/minimessage-bungee/src/main/java/me/minidigger/minimessage/MiniMessageParser.java @@ -11,7 +11,6 @@ import java.util.EnumSet; import java.util.Map; import java.util.Optional; -import java.util.Stack; import java.util.function.Consumer; import java.util.regex.Matcher; import java.util.regex.Pattern; diff --git a/minimessage-ext-markdown/pom.xml b/minimessage-ext-markdown/pom.xml index 2f8240f4a..e8ccdbcc2 100644 --- a/minimessage-ext-markdown/pom.xml +++ b/minimessage-ext-markdown/pom.xml @@ -9,5 +9,12 @@ minimessage-ext-markdown - + + + com.destroystokyo.paper + paper-api + 1.15.2-R0.1-SNAPSHOT + provided + + diff --git a/minimessage-ext-markdown/src/main/java/me/minidigger/minimessage/Constants.java b/minimessage-ext-markdown/src/main/java/me/minidigger/minimessage/Constants.java new file mode 100644 index 000000000..96864d1b4 --- /dev/null +++ b/minimessage-ext-markdown/src/main/java/me/minidigger/minimessage/Constants.java @@ -0,0 +1,13 @@ +package me.minidigger.minimessage; + +class Constants { + + public static char emphasis1 = '*'; + public static char emphasis2 = '_'; + public static char underline = '~'; + + public static String italicTag = "italic"; + public static String boldTag = "bold"; + public static String underlineTag = "underline"; + +} diff --git a/minimessage-ext-markdown/src/main/java/me/minidigger/minimessage/MiniMarkdownParser.java b/minimessage-ext-markdown/src/main/java/me/minidigger/minimessage/MiniMarkdownParser.java new file mode 100644 index 000000000..315ea31e2 --- /dev/null +++ b/minimessage-ext-markdown/src/main/java/me/minidigger/minimessage/MiniMarkdownParser.java @@ -0,0 +1,167 @@ +package me.minidigger.minimessage; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.StringJoiner; +import javax.annotation.Nonnull; + +public class MiniMarkdownParser { + + private MiniMarkdownParser() { + } + + @Nonnull + public static String stripMarkdown(@Nonnull String input) { + return handle(input, true); + } + + @Nonnull + public static String parse(@Nonnull String input) { + return handle(input, false); + } + + @Nonnull + private static String handle(@Nonnull String input, boolean strip) { + StringBuilder sb = new StringBuilder(); + + int bold = -1; + Insert boldSkip = null; + int italic = -1; + Insert italicSkip = null; + int underline = -1; + Insert underlineSkip = null; + + List inserts = new ArrayList<>(); + int skip = 0; + for (int i = 0; i + skip < input.length(); i++) { + int currIndex = i + skip; + char c = input.charAt(currIndex); + + boolean shouldSkip = false; + if (c == Constants.emphasis1) { + char n = next(currIndex, input); + if (n == Constants.emphasis1) { + if (bold == -1) { + bold = sb.length(); + boldSkip = new Insert(sb.length(), c + ""); + } else { + inserts.add(new Insert(bold, "<" + Constants.boldTag + ">")); + inserts.add(new Insert(sb.length(), "")); + bold = -1; + } + skip++; + } else { + if (italic == -1) { + italic = sb.length(); + italicSkip = new Insert(sb.length(), c + ""); + } else { + inserts.add(new Insert(italic, "<" + Constants.italicTag + ">")); + inserts.add(new Insert(sb.length(), "")); + italic = -1; + } + } + shouldSkip = true; + } else if (c == Constants.emphasis2) { + char n = next(currIndex, input); + if (n == Constants.emphasis2) { + if (bold == -1) { + bold = sb.length(); + boldSkip = new Insert(sb.length(), c + ""); + } else { + inserts.add(new Insert(bold, "<" + Constants.boldTag + ">")); + inserts.add(new Insert(sb.length(), "")); + bold = -1; + } + skip++; + } else { + if (italic == -1) { + italic = currIndex; + italicSkip = new Insert(sb.length(), c + ""); + } else { + inserts.add(new Insert(italic, "<" + Constants.italicTag + ">")); + inserts.add(new Insert(currIndex - 1, "")); + italic = -1; + } + } + shouldSkip = true; + } else if (c == Constants.underline) { + char n = next(currIndex, input); + if (n == Constants.underline) { + if (underline == -1) { + underline = sb.length(); + underlineSkip = new Insert(sb.length(), c + ""); + } else { + inserts.add(new Insert(underline, "<" + Constants.underlineTag + ">")); + inserts.add(new Insert(sb.length(), "")); + underline = -1; + } + skip++; + shouldSkip = true; + } + } + + if (!shouldSkip) { + sb.append(c); + } + } + + if (strip) { + inserts.clear(); + } else { + inserts.sort(Comparator.comparing(Insert::getPos).thenComparing(Insert::getValue).reversed()); + } + + if (underline != -1) { + inserts.add(underlineSkip); + } + if (bold != -1) { + inserts.add(boldSkip); + } + if (italic != -1) { + inserts.add(italicSkip); + } + + for (Insert el : inserts) { + sb.insert(el.getPos(), el.getValue()); + } + + return sb.toString(); + } + + private static char next(int index, @Nonnull String input) { + if (index < input.length() - 1) { + return input.charAt(index + 1); + } else { + return ' '; + } + } + + static class Insert { + private final int pos; + private final String value; + + public int getPos() { + return pos; + } + + @Nonnull + public String getValue() { + return value; + } + + public Insert(int pos, @Nonnull String value) { + this.pos = pos; + this.value = value; + } + + @Override + @Nonnull + public String toString() { + return new StringJoiner(", ", Insert.class.getSimpleName() + "[", "]") + .add("pos=" + pos) + .add("value='" + value + "'") + .toString(); + } + } +} diff --git a/minimessage-ext-markdown/src/test/java/me/minidigger/minimessage/MiniMarkdownParserTest.java b/minimessage-ext-markdown/src/test/java/me/minidigger/minimessage/MiniMarkdownParserTest.java new file mode 100644 index 000000000..9ec45009a --- /dev/null +++ b/minimessage-ext-markdown/src/test/java/me/minidigger/minimessage/MiniMarkdownParserTest.java @@ -0,0 +1,168 @@ +package me.minidigger.minimessage; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class MiniMarkdownParserTest { + + @Test + public void testBold() { + String input = "**bold**"; + String expected = "bold"; + + String output = MiniMarkdownParser.parse(input); + + assertEquals(expected, output); + } + + @Test + public void testItalic() { + String input = "*italic*"; + String expected = "italic"; + + String output = MiniMarkdownParser.parse(input); + + assertEquals(expected, output); + } + + @Test + public void testUnderline() { + String input = "~~underline~~"; + String expected = "underline"; + + String output = MiniMarkdownParser.parse(input); + + assertEquals(expected, output); + } + + @Test + public void testBoldWithSpaces() { + String input = "AaA** bold **AaA"; + String expected = "AaA bold AaA"; + + String output = MiniMarkdownParser.parse(input); + + assertEquals(expected, output); + } + + @Test + public void testMixed() { + String input = "*italic*~~underline~~**bold**"; + String expected = "italicunderlinebold"; + + String output = MiniMarkdownParser.parse(input); + + assertEquals(expected, output); + } + + @Test + public void testMushedTogether() { + String input = "*a***a**"; + String expected = "aa"; + + String output = MiniMarkdownParser.parse(input); + + assertEquals(expected, output); + } + + @Test + public void testWithFake() { + String input = "*a~*a~"; + String expected = "a~a~"; + + String output = MiniMarkdownParser.parse(input); + + assertEquals(expected, output); + } + + @Test + public void testCrossed() { + String input = "*a~~*a~~"; + String expected = "aa"; + + String output = MiniMarkdownParser.parse(input); + + assertEquals(expected, output); + } + + @Test + public void testCrossedWithSpace() { + String input = "*a~~ *a~~"; + String expected = "a a"; + + String output = MiniMarkdownParser.parse(input); + + assertEquals(expected, output); + } + + @Test + public void testNoEnd() { + String input = "*a"; + String expected = "*a"; + + String output = MiniMarkdownParser.parse(input); + + assertEquals(expected, output); + } + + @Test + public void testStripMixed() { + String input = "*italic*~~underline~~**bold**"; + String expected = "italicunderlinebold"; + + String output = MiniMarkdownParser.stripMarkdown(input); + + assertEquals(expected, output); + } + + @Test + public void testStripMushedTogether() { + String input = "*a***a**"; + String expected = "aa"; + + String output = MiniMarkdownParser.stripMarkdown(input); + + assertEquals(expected, output); + } + + @Test + public void testStripWithFake() { + String input = "*a~*a~"; + String expected = "a~a~"; + + String output = MiniMarkdownParser.stripMarkdown(input); + + assertEquals(expected, output); + } + + @Test + public void testStripCrossed() { + String input = "*a~~*a~~"; + String expected = "aa"; + + String output = MiniMarkdownParser.stripMarkdown(input); + + assertEquals(expected, output); + } + + @Test + public void testStripCrossedWithSpace() { + String input = "*a~~ *a~~"; + String expected = "a a"; + + String output = MiniMarkdownParser.stripMarkdown(input); + + assertEquals(expected, output); + } + + @Test + public void testStripNoEnd() { + String input = "*a"; + String expected = "*a"; + + String output = MiniMarkdownParser.stripMarkdown(input); + + assertEquals(expected, output); + } +} From 4ef50c493d8b58d4ebc3dbca2bc9e403e4b0de78 Mon Sep 17 00:00:00 2001 From: release-bot Date: Wed, 22 Apr 2020 23:34:31 +0000 Subject: [PATCH 018/339] [maven-release-plugin] prepare release MiniMessage-2.0.1 --- minimessage-bungee/pom.xml | 2 +- minimessage-ext-markdown/pom.xml | 2 +- minimessage-text/pom.xml | 2 +- pom.xml | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/minimessage-bungee/pom.xml b/minimessage-bungee/pom.xml index b86da35b7..8b0f759b8 100644 --- a/minimessage-bungee/pom.xml +++ b/minimessage-bungee/pom.xml @@ -4,7 +4,7 @@ MiniMessage me.minidigger - 2.0.1-SNAPSHOT + 2.0.1 minimessage-bungee diff --git a/minimessage-ext-markdown/pom.xml b/minimessage-ext-markdown/pom.xml index e8ccdbcc2..3a1c90a13 100644 --- a/minimessage-ext-markdown/pom.xml +++ b/minimessage-ext-markdown/pom.xml @@ -3,7 +3,7 @@ MiniMessage me.minidigger - 2.0.1-SNAPSHOT + 2.0.1 4.0.0 diff --git a/minimessage-text/pom.xml b/minimessage-text/pom.xml index a3e711515..a0729f721 100644 --- a/minimessage-text/pom.xml +++ b/minimessage-text/pom.xml @@ -4,7 +4,7 @@ MiniMessage me.minidigger - 2.0.1-SNAPSHOT + 2.0.1 minimessage-text diff --git a/pom.xml b/pom.xml index d9a674d14..f5782f3eb 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ me.minidigger MiniMessage pom - 2.0.1-SNAPSHOT + 2.0.1 MiniMessage Simple library that implements an easy to use textual format to send rich json messages. https://github.com/MiniDigger/MiniMessage @@ -120,7 +120,7 @@ scm:git:https://github.com/MiniDigger/MiniMessage.git scm:git:https://github.com/MiniDigger/MiniMessage.git https://github.com/MiniDigger/MiniMessage - HEAD + MiniMessage-2.0.1 From fa10ba087dc675ea1f71f8fc486ed1d5b5637417 Mon Sep 17 00:00:00 2001 From: release-bot Date: Wed, 22 Apr 2020 23:34:37 +0000 Subject: [PATCH 019/339] [maven-release-plugin] prepare for next development iteration --- minimessage-bungee/pom.xml | 2 +- minimessage-ext-markdown/pom.xml | 2 +- minimessage-text/pom.xml | 2 +- pom.xml | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/minimessage-bungee/pom.xml b/minimessage-bungee/pom.xml index 8b0f759b8..ab24b43e6 100644 --- a/minimessage-bungee/pom.xml +++ b/minimessage-bungee/pom.xml @@ -4,7 +4,7 @@ MiniMessage me.minidigger - 2.0.1 + 2.0.2-SNAPSHOT minimessage-bungee diff --git a/minimessage-ext-markdown/pom.xml b/minimessage-ext-markdown/pom.xml index 3a1c90a13..a91595caa 100644 --- a/minimessage-ext-markdown/pom.xml +++ b/minimessage-ext-markdown/pom.xml @@ -3,7 +3,7 @@ MiniMessage me.minidigger - 2.0.1 + 2.0.2-SNAPSHOT 4.0.0 diff --git a/minimessage-text/pom.xml b/minimessage-text/pom.xml index a0729f721..039515b81 100644 --- a/minimessage-text/pom.xml +++ b/minimessage-text/pom.xml @@ -4,7 +4,7 @@ MiniMessage me.minidigger - 2.0.1 + 2.0.2-SNAPSHOT minimessage-text diff --git a/pom.xml b/pom.xml index f5782f3eb..35a80b032 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ me.minidigger MiniMessage pom - 2.0.1 + 2.0.2-SNAPSHOT MiniMessage Simple library that implements an easy to use textual format to send rich json messages. https://github.com/MiniDigger/MiniMessage @@ -120,7 +120,7 @@ scm:git:https://github.com/MiniDigger/MiniMessage.git scm:git:https://github.com/MiniDigger/MiniMessage.git https://github.com/MiniDigger/MiniMessage - MiniMessage-2.0.1 + HEAD From cf676275740326d23a22166ba88a01ece4ceee3b Mon Sep 17 00:00:00 2001 From: MiniDigger Date: Fri, 24 Apr 2020 17:22:55 +0200 Subject: [PATCH 020/339] implement minimessage-text, cleanup dependencies --- minimessage-bungee/pom.xml | 7 +- .../me/minidigger/minimessage/Constants.java | 3 + .../minimessage/MiniMessageParserTest.java | 2 +- .../minimessage/MiniMarkdownParserTest.java | 20 ++ minimessage-text/pom.xml | 14 + .../me/minidigger/minimessage/Constants.java | 20 ++ .../minimessage/MiniMessageParser.java | 301 ++++++++++++++++++ .../minimessage/MiniMessageSerializer.java | 179 +++++++++++ .../minimessage/MiniMessageParserTest.java | 170 ++++++++++ .../MiniMessageSerializerTest.java | 128 ++++++++ pom.xml | 25 +- 11 files changed, 860 insertions(+), 9 deletions(-) create mode 100644 minimessage-text/src/main/java/me/minidigger/minimessage/Constants.java create mode 100644 minimessage-text/src/main/java/me/minidigger/minimessage/MiniMessageParser.java create mode 100644 minimessage-text/src/main/java/me/minidigger/minimessage/MiniMessageSerializer.java create mode 100644 minimessage-text/src/test/java/minimessage/MiniMessageParserTest.java create mode 100644 minimessage-text/src/test/java/minimessage/MiniMessageSerializerTest.java diff --git a/minimessage-bungee/pom.xml b/minimessage-bungee/pom.xml index ab24b43e6..c214d569a 100644 --- a/minimessage-bungee/pom.xml +++ b/minimessage-bungee/pom.xml @@ -12,9 +12,10 @@ - com.destroystokyo.paper - paper-api - 1.15.2-R0.1-SNAPSHOT + net.md-5 + bungeecord-chat + 1.15-SNAPSHOT + jar provided diff --git a/minimessage-bungee/src/main/java/me/minidigger/minimessage/Constants.java b/minimessage-bungee/src/main/java/me/minidigger/minimessage/Constants.java index ff099a636..1f9423479 100644 --- a/minimessage-bungee/src/main/java/me/minidigger/minimessage/Constants.java +++ b/minimessage-bungee/src/main/java/me/minidigger/minimessage/Constants.java @@ -14,4 +14,7 @@ class Constants { public static final String TAG_END = ">"; public static final String CLOSE_TAG = "/"; public static final String SEPARATOR = ":"; + + private Constants(){ + } } diff --git a/minimessage-bungee/src/test/java/me/minidigger/minimessage/MiniMessageParserTest.java b/minimessage-bungee/src/test/java/me/minidigger/minimessage/MiniMessageParserTest.java index c8aefaeeb..61f38f08f 100644 --- a/minimessage-bungee/src/test/java/me/minidigger/minimessage/MiniMessageParserTest.java +++ b/minimessage-bungee/src/test/java/me/minidigger/minimessage/MiniMessageParserTest.java @@ -146,7 +146,7 @@ public void testInvalidTag() { test(comp, expected); - // am not totally happy about this yet, invalid tags arent getting colored for example, but good enough for now + // TODO am not totally happy about this yet, invalid tags arent getting colored for example, but good enough for now } @Test diff --git a/minimessage-ext-markdown/src/test/java/me/minidigger/minimessage/MiniMarkdownParserTest.java b/minimessage-ext-markdown/src/test/java/me/minidigger/minimessage/MiniMarkdownParserTest.java index 9ec45009a..0151bf389 100644 --- a/minimessage-ext-markdown/src/test/java/me/minidigger/minimessage/MiniMarkdownParserTest.java +++ b/minimessage-ext-markdown/src/test/java/me/minidigger/minimessage/MiniMarkdownParserTest.java @@ -16,6 +16,16 @@ public void testBold() { assertEquals(expected, output); } + @Test + public void testBold2() { + String input = "__bold__"; + String expected = "bold"; + + String output = MiniMarkdownParser.parse(input); + + assertEquals(expected, output); + } + @Test public void testItalic() { String input = "*italic*"; @@ -26,6 +36,16 @@ public void testItalic() { assertEquals(expected, output); } + @Test + public void testItalic2() { + String input = "_italic_"; + String expected = "italic"; + + String output = MiniMarkdownParser.parse(input); + + assertEquals(expected, output); + } + @Test public void testUnderline() { String input = "~~underline~~"; diff --git a/minimessage-text/pom.xml b/minimessage-text/pom.xml index 039515b81..a403037e4 100644 --- a/minimessage-text/pom.xml +++ b/minimessage-text/pom.xml @@ -10,4 +10,18 @@ minimessage-text MiniMessage Text + + + net.kyori + text-api + 3.0.3 + provided + + + net.kyori + text-serializer-gson + 3.0.3 + test + + diff --git a/minimessage-text/src/main/java/me/minidigger/minimessage/Constants.java b/minimessage-text/src/main/java/me/minidigger/minimessage/Constants.java new file mode 100644 index 000000000..1f9423479 --- /dev/null +++ b/minimessage-text/src/main/java/me/minidigger/minimessage/Constants.java @@ -0,0 +1,20 @@ +package me.minidigger.minimessage; + +class Constants { + public static final String CLICK = "click"; + public static final String HOVER = "hover"; + + public static final String UNDERLINED = "underlined"; + public static final String STRIKETHROUGH = "strikethrough"; + public static final String OBFUSCATED = "obfuscated"; + public static final String ITALIC = "italic"; + public static final String BOLD = "bold"; + + public static final String TAG_START = "<"; + public static final String TAG_END = ">"; + public static final String CLOSE_TAG = "/"; + public static final String SEPARATOR = ":"; + + private Constants(){ + } +} diff --git a/minimessage-text/src/main/java/me/minidigger/minimessage/MiniMessageParser.java b/minimessage-text/src/main/java/me/minidigger/minimessage/MiniMessageParser.java new file mode 100644 index 000000000..c97bc3410 --- /dev/null +++ b/minimessage-text/src/main/java/me/minidigger/minimessage/MiniMessageParser.java @@ -0,0 +1,301 @@ +package me.minidigger.minimessage; + +import net.kyori.text.Component; +import net.kyori.text.TextComponent; +import net.kyori.text.TextComponent.Builder; +import net.kyori.text.event.ClickEvent; +import net.kyori.text.event.HoverEvent; +import net.kyori.text.format.TextColor; +import net.kyori.text.format.TextDecoration; + +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.EnumSet; +import java.util.Map; +import java.util.Optional; +import java.util.function.Consumer; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.annotation.Nonnull; + +import static me.minidigger.minimessage.Constants.CLICK; +import static me.minidigger.minimessage.Constants.CLOSE_TAG; +import static me.minidigger.minimessage.Constants.HOVER; +import static me.minidigger.minimessage.Constants.SEPARATOR; +import static me.minidigger.minimessage.Constants.TAG_END; +import static me.minidigger.minimessage.Constants.TAG_START; + +public class MiniMessageParser { + + // regex group names + private static final String START = "start"; + private static final String TOKEN = "token"; + private static final String INNER = "inner"; + private static final String END = "end"; + // https://regex101.com/r/8VZ7uA/5 + private static final Pattern pattern = Pattern.compile("((?<)(?([^<>]+)|([^<>]+\"(?[^\"]+)\"))(?>))+?"); + + @Nonnull + public static String escapeTokens(@Nonnull String richMessage) { + StringBuilder sb = new StringBuilder(); + Matcher matcher = pattern.matcher(richMessage); + int lastEnd = 0; + while (matcher.find()) { + int startIndex = matcher.start(); + int endIndex = matcher.end(); + + if (startIndex > lastEnd) { + sb.append(richMessage, lastEnd, startIndex); + } + lastEnd = endIndex; + + String start = matcher.group(START); + String token = matcher.group(TOKEN); + String inner = matcher.group(INNER); + String end = matcher.group(END); + + // also escape inner + if (inner != null) { + token = token.replace(inner, escapeTokens(inner)); + } + + sb.append("\\").append(start).append(token).append("\\").append(end); + } + + if (richMessage.length() > lastEnd) { + sb.append(richMessage.substring(lastEnd)); + } + + return sb.toString(); + } + + @Nonnull + public static String stripTokens(@Nonnull String richMessage) { + StringBuilder sb = new StringBuilder(); + Matcher matcher = pattern.matcher(richMessage); + int lastEnd = 0; + while (matcher.find()) { + int startIndex = matcher.start(); + int endIndex = matcher.end(); + + if (startIndex > lastEnd) { + sb.append(richMessage, lastEnd, startIndex); + } + lastEnd = endIndex; + } + + if (richMessage.length() > lastEnd) { + sb.append(richMessage.substring(lastEnd)); + } + + return sb.toString(); + } + + @Nonnull + public static String handlePlaceholders(@Nonnull String richMessage, @Nonnull String... placeholders) { + if (placeholders.length % 2 != 0) { + throw new RuntimeException( + "Invalid number placeholders defined, usage: parseFormat(format, key, value, key, value...)"); + } + for (int i = 0; i < placeholders.length; i += 2) { + richMessage = richMessage.replace(TAG_START + placeholders[i] + TAG_END, placeholders[i + 1]); + } + return richMessage; + } + + @Nonnull + public static String handlePlaceholders(@Nonnull String richMessage, @Nonnull Map placeholders) { + for (Map.Entry entry : placeholders.entrySet()) { + richMessage = richMessage.replace(TAG_START + entry.getKey() + TAG_END, entry.getValue()); + } + return richMessage; + } + + @Nonnull + public static Component parseFormat(@Nonnull String richMessage, @Nonnull String... placeholders) { + return parseFormat(handlePlaceholders(richMessage, placeholders)); + } + + @Nonnull + public static Component parseFormat(@Nonnull String richMessage, @Nonnull Map placeholders) { + return parseFormat(handlePlaceholders(richMessage, placeholders)); + } + + @Nonnull + public static Component parseFormat(@Nonnull String richMessage) { + Builder parent = TextComponent.builder(""); + + Deque clickEvents = new ArrayDeque<>(); + Deque hoverEvents = new ArrayDeque<>(); + Deque colors = new ArrayDeque<>(); + EnumSet decorations = EnumSet.noneOf(HelperTextDecoration.class); + + Matcher matcher = pattern.matcher(richMessage); + int lastEnd = 0; + while (matcher.find()) { + Builder current = null; + int startIndex = matcher.start(); + int endIndex = matcher.end(); + + String msg = null; + if (startIndex > lastEnd) { + msg = richMessage.substring(lastEnd, startIndex); + } + lastEnd = endIndex; + + // handle message + if (msg != null && msg.length() != 0) { + // append message + current = TextComponent.builder(msg); + + // set everything that is not closed yet + if (!clickEvents.isEmpty()) { + current.clickEvent(clickEvents.peek()); + } + if (!hoverEvents.isEmpty()) { + current.hoverEvent(hoverEvents.peek()); + } + if (!colors.isEmpty()) { + current.color(colors.peek()); + } + if (!decorations.isEmpty()) { + // no lambda because builder isn't effective final :/ + for (HelperTextDecoration decor : decorations) { + decor.apply(current); + } + } + } + +// String group = matcher.group(); +// String start = matcher.group(START); + String token = matcher.group(TOKEN); + String inner = matcher.group(INNER); +// String end = matcher.group(END); + + Optional deco; + Optional color; + + // click + if (token.startsWith(CLICK + SEPARATOR)) { + clickEvents.push(handleClick(token, inner)); + } else if (token.equals(CLOSE_TAG + CLICK)) { + clickEvents.pop(); + } + // hover + else if (token.startsWith(HOVER + SEPARATOR)) { + hoverEvents.push(handleHover(token, inner)); + } else if (token.equals(CLOSE_TAG + HOVER)) { + hoverEvents.pop(); + } + // decoration + else if ((deco = resolveDecoration(token)).isPresent()) { + decorations.add(deco.get()); + } else if (token.startsWith(CLOSE_TAG) && (deco = resolveDecoration(token.replace(CLOSE_TAG, ""))).isPresent()) { + decorations.remove(deco.get()); + } + // color + else if ((color = resolveColor(token)).isPresent()) { + colors.push(color.get()); + } else if (token.startsWith(CLOSE_TAG) && resolveColor(token.replace(CLOSE_TAG, "")).isPresent()) { + colors.pop(); + } else { + // invalid tag + current = TextComponent.builder(TAG_START + token + TAG_END); + } + + if (current != null) { + parent.append(current.build()); + } + } + + // handle last message part + if (richMessage.length() > lastEnd) { + String msg = richMessage.substring(lastEnd); + // append message + Builder current = TextComponent.builder(msg); + + // set everything that is not closed yet + if (!clickEvents.isEmpty()) { + current.clickEvent(clickEvents.peek()); + } + if (!hoverEvents.isEmpty()) { + current.hoverEvent(hoverEvents.peek()); + } + if (!colors.isEmpty()) { + current.color(colors.peek()); + } + if (!decorations.isEmpty()) { + // no lambda because builder isn't effective final :/ + for (HelperTextDecoration decor : decorations) { + decor.apply(current); + } + } + + parent.append(current.build()); + } + + // optimization, ignore empty parent + TextComponent comp = parent.build(); + if (comp.content().equals("") && comp.children().size() == 1) { + return comp.children().get(0); + } else { + return comp; + } + } + + @Nonnull + private static ClickEvent handleClick(@Nonnull String token, @Nonnull String inner) { + String[] args = token.split(SEPARATOR); + if (args.length < 2) { + throw new RuntimeException("Can't parse click action (too few args) " + token); + } + ClickEvent.Action action = ClickEvent.Action.valueOf(args[1].toUpperCase()); + return ClickEvent.of(action, token.replace(CLICK + SEPARATOR + args[1] + SEPARATOR, "")); + } + + @Nonnull + private static HoverEvent handleHover(@Nonnull String token, @Nonnull String inner) { + String[] args = token.split(SEPARATOR); + if (args.length < 2) { + throw new RuntimeException("Can't parse hover action (too few args) " + token); + } + HoverEvent.Action action = HoverEvent.Action.valueOf(args[1].toUpperCase()); + return HoverEvent.of(action, parseFormat(inner)); + } + + @Nonnull + private static Optional resolveColor(@Nonnull String token) { + try { + return Optional.of(TextColor.valueOf(token.toUpperCase())); + } catch (IllegalArgumentException ex) { + return Optional.empty(); + } + } + + @Nonnull + private static Optional resolveDecoration(@Nonnull String token) { + try { + return Optional.of(HelperTextDecoration.valueOf(token.toUpperCase())); + } catch (IllegalArgumentException ex) { + return Optional.empty(); + } + } + + enum HelperTextDecoration { + BOLD(b -> b.decoration(TextDecoration.BOLD, true)), + ITALIC(b -> b.decoration(TextDecoration.ITALIC, true)), + UNDERLINED(b -> b.decoration(TextDecoration.UNDERLINED, true)), + STRIKETHROUGH(b -> b.decoration(TextDecoration.STRIKETHROUGH, true)), + OBFUSCATED(b -> b.decoration(TextDecoration.OBFUSCATED, true)); + + private final Consumer builder; + + HelperTextDecoration(@Nonnull Consumer builder) { + this.builder = builder; + } + + public void apply(@Nonnull Builder comp) { + builder.accept(comp); + } + } +} diff --git a/minimessage-text/src/main/java/me/minidigger/minimessage/MiniMessageSerializer.java b/minimessage-text/src/main/java/me/minidigger/minimessage/MiniMessageSerializer.java new file mode 100644 index 000000000..0fbc7d9eb --- /dev/null +++ b/minimessage-text/src/main/java/me/minidigger/minimessage/MiniMessageSerializer.java @@ -0,0 +1,179 @@ +package me.minidigger.minimessage; + +import net.kyori.text.Component; +import net.kyori.text.TextComponent; +import net.kyori.text.event.ClickEvent; +import net.kyori.text.event.HoverEvent; +import net.kyori.text.format.TextColor; +import net.kyori.text.format.TextDecoration; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import static me.minidigger.minimessage.Constants.BOLD; +import static me.minidigger.minimessage.Constants.CLICK; +import static me.minidigger.minimessage.Constants.CLOSE_TAG; +import static me.minidigger.minimessage.Constants.HOVER; +import static me.minidigger.minimessage.Constants.ITALIC; +import static me.minidigger.minimessage.Constants.OBFUSCATED; +import static me.minidigger.minimessage.Constants.SEPARATOR; +import static me.minidigger.minimessage.Constants.STRIKETHROUGH; +import static me.minidigger.minimessage.Constants.TAG_END; +import static me.minidigger.minimessage.Constants.TAG_START; +import static me.minidigger.minimessage.Constants.UNDERLINED; + +public class MiniMessageSerializer { + + @Nonnull + public static String serialize(@Nonnull Component component) { + StringBuilder sb = new StringBuilder(); + + List components = new ArrayList<>(); + components.add(component); + + for (int i = 0; i < components.size(); i++) { + Component comp = components.get(i); + + // add childs + components.addAll(comp.children()); + + // # start tags + + // ## get prev comp + Component prevComp = null; + if (i > 0) { + prevComp = components.get(i - 1); + } + + // ## color + // ### white is not important + if (!TextColor.WHITE.equals(comp.color()) && comp.color() != null) { + sb.append(startColor(Objects.requireNonNull(comp.color()))); + } + + // ## decoration + // ### only start if prevComp didn't start + if (comp.hasDecoration(TextDecoration.BOLD) && (prevComp == null || !prevComp.hasDecoration(TextDecoration.BOLD))) { + sb.append(startTag(BOLD)); + } + if (comp.hasDecoration(TextDecoration.ITALIC) && (prevComp == null || !prevComp.hasDecoration(TextDecoration.ITALIC))) { + sb.append(startTag(ITALIC)); + } + if (comp.hasDecoration(TextDecoration.OBFUSCATED) && (prevComp == null || !prevComp.hasDecoration(TextDecoration.OBFUSCATED))) { + sb.append(startTag(OBFUSCATED)); + } + if (comp.hasDecoration(TextDecoration.STRIKETHROUGH) && (prevComp == null || !prevComp.hasDecoration(TextDecoration.STRIKETHROUGH))) { + sb.append(startTag(STRIKETHROUGH)); + } + if (comp.hasDecoration(TextDecoration.UNDERLINED) && (prevComp == null || !prevComp.hasDecoration(TextDecoration.UNDERLINED))) { + sb.append(startTag(UNDERLINED)); + } + + // ## hover + // ### only start if prevComp didn't start the same one + HoverEvent hov = comp.hoverEvent(); + if (hov != null && (prevComp == null || areDifferent(hov, prevComp.hoverEvent()))) { + sb.append(startTag(String.format("%s" + SEPARATOR + "%s" + SEPARATOR + "\"%s\"", HOVER, hov.action().name().toLowerCase(), serialize(hov.value())))); + } + + // ## click + // ### only start if prevComp didn't start the same one + ClickEvent click = comp.clickEvent(); + if (click != null && (prevComp == null || areDifferent(click, prevComp.clickEvent()))) { + sb.append(startTag(String.format("%s" + SEPARATOR + "%s" + SEPARATOR + "\"%s\"", CLICK, click.action().name().toLowerCase(), click.value()))); + } + + // # append text + if (comp instanceof TextComponent) { + sb.append(((TextComponent) comp).content()); + } + + // # end tags + + // ## get next comp + Component nextComp = null; + if (i + 1 < components.size()) { + nextComp = components.get(i + 1); + } + + // ## color + // ### only end color if next comp is white and current isn't + if (nextComp != null && comp.color() != TextColor.WHITE && comp.color() != null) { + if (nextComp.color() == TextColor.WHITE || nextComp.color() == null) { + sb.append(endColor(Objects.requireNonNull(comp.color()))); + } + } + + // ## decoration + // ### only end decoration if next tag is different + if (nextComp != null) { + if (comp.hasDecoration(TextDecoration.BOLD) && !nextComp.hasDecoration(TextDecoration.BOLD)) { + sb.append(endTag(BOLD)); + } + if (comp.hasDecoration(TextDecoration.ITALIC) && !nextComp.hasDecoration(TextDecoration.ITALIC)) { + sb.append(endTag(ITALIC)); + } + if (comp.hasDecoration(TextDecoration.OBFUSCATED) && !nextComp.hasDecoration(TextDecoration.OBFUSCATED)) { + sb.append(endTag(OBFUSCATED)); + } + if (comp.hasDecoration(TextDecoration.STRIKETHROUGH) && !nextComp.hasDecoration(TextDecoration.STRIKETHROUGH)) { + sb.append(endTag(STRIKETHROUGH)); + } + if (comp.hasDecoration(TextDecoration.UNDERLINED) && !nextComp.hasDecoration(TextDecoration.UNDERLINED)) { + sb.append(endTag(UNDERLINED)); + } + } + + // ## hover + // ### only end hover if next tag is different + if (nextComp != null && comp.hoverEvent() != null) { + if (areDifferent(Objects.requireNonNull(comp.hoverEvent()), nextComp.hoverEvent())) { + sb.append(endTag(HOVER)); + } + } + + // ## click + // ### only end click if next tag is different + if (nextComp != null && comp.clickEvent() != null) { + if (areDifferent(Objects.requireNonNull(comp.clickEvent()), nextComp.clickEvent())) { + sb.append(endTag(CLICK)); + } + } + } + + return sb.toString(); + } + + private static boolean areDifferent(@Nonnull ClickEvent c1, @Nullable ClickEvent c2) { + if (c2 == null) return true; + return !c1.equals(c2) && (!c1.action().equals(c2.action()) || !c1.value().equals(c2.value())); + } + + private static boolean areDifferent(@Nonnull HoverEvent h1, @Nullable HoverEvent h2) { + if (h2 == null) return true; + return !h1.equals(h2) && (!h1.action().equals(h2.action()));// TODO also compare value + } + + @Nonnull + private static String startColor(@Nonnull TextColor color) { + return startTag(color.name().toLowerCase()); + } + + @Nonnull + private static String endColor(@Nonnull TextColor color) { + return endTag(color.name().toLowerCase()); + } + + @Nonnull + private static String startTag(@Nonnull String content) { + return TAG_START + content + TAG_END; + } + + @Nonnull + private static String endTag(@Nonnull String content) { + return TAG_START + CLOSE_TAG + content + TAG_END; + } +} diff --git a/minimessage-text/src/test/java/minimessage/MiniMessageParserTest.java b/minimessage-text/src/test/java/minimessage/MiniMessageParserTest.java new file mode 100644 index 000000000..9ea62d160 --- /dev/null +++ b/minimessage-text/src/test/java/minimessage/MiniMessageParserTest.java @@ -0,0 +1,170 @@ +package minimessage; + +import net.kyori.text.Component; +import net.kyori.text.serializer.gson.GsonComponentSerializer; + +import org.junit.Test; + +import javax.annotation.Nonnull; + +import me.minidigger.minimessage.MiniMessageParser; + +import static org.junit.Assert.assertEquals; + +public class MiniMessageParserTest { + + @Test + public void test() { + String input1 = "TEST nestedTest"; + String input2 = "TEST nestedTest"; + String out1 = GsonComponentSerializer.INSTANCE.serialize(MiniMessageParser.parseFormat(input1)); + String out2 = GsonComponentSerializer.INSTANCE.serialize(MiniMessageParser.parseFormat(input2)); + System.out.println(out1); + System.out.println(out2); + } + + @Test + public void testStripSimple() { + String input = "TEST nestedTest"; + String expected = "TEST nestedTest"; + assertEquals(expected, MiniMessageParser.stripTokens(input)); + } + + @Test + public void testStripComplex() { + String input = " random strangerclick here to FEEL it"; + String expected = " random strangerclick here to FEEL it"; + assertEquals(expected, MiniMessageParser.stripTokens(input)); + } + + @Test + public void testStripInner() { + String input = "test:TEST\">TEST"; + String expected = "TEST"; + assertEquals(expected, MiniMessageParser.stripTokens(input)); + } + + @Test + public void testEscapeSimple() { + String input = "TEST nestedTest"; + String expected = "\\TEST\\ nested\\Test"; + assertEquals(expected, MiniMessageParser.escapeTokens(input)); + } + + @Test + public void testEscapeComplex() { + String input = " random strangerclick here to FEEL it"; + String expected = "\\\\ random \\stranger\\\\\\\\click here\\\\ to \\FEEL\\ it"; + assertEquals(expected, MiniMessageParser.escapeTokens(input)); + } + + @Test + public void testEscapeInner() { + String input = "test:TEST\">TEST"; + String expected = "\\test:TEST\"\\>TEST"; + assertEquals(expected, MiniMessageParser.escapeTokens(input)); + } + + + @Test + public void checkPlaceholder() { + String input = ""; + String expected = "{\"text\":\"Hello!\"}"; + Component comp = MiniMessageParser.parseFormat(input, "test", "Hello!"); + + test(comp, expected); + } + + @Test + public void testNiceMix() { + String input = " random strangerclick here to FEEL it"; + String expected = "{\"text\":\"\",\"extra\":[{\"text\":\"Hello! random \",\"color\":\"yellow\"},{\"text\":\"stranger\",\"color\":\"yellow\",\"bold\":true},{\"text\":\"click here\",\"color\":\"red\",\"underlined\":true,\"clickEvent\":{\"action\":\"run_command\",\"value\":\"test command\"}},{\"text\":\" to \",\"color\":\"blue\",\"underlined\":true},{\"text\":\"FEEL\",\"color\":\"blue\",\"bold\":true,\"underlined\":true},{\"text\":\" it\",\"color\":\"blue\",\"bold\":true}]}"; + Component comp = MiniMessageParser.parseFormat(input, "test", "Hello!"); + + test(comp, expected); + } + + @Test + public void testColorSimple() { + String input = "TEST"; + String expected = "{\"text\":\"TEST\",\"color\":\"yellow\"}"; + + test(input, expected); + } + + @Test + public void testColorNested() { + String input = "TESTnestedTest"; + String expected = "{\"text\":\"\",\"extra\":[{\"text\":\"TEST\",\"color\":\"yellow\"},{\"text\":\"nested\",\"color\":\"green\"},{\"text\":\"Test\",\"color\":\"yellow\"}]}"; + + test(input, expected); + } + + @Test + public void testColorNotNested() { + String input = "TESTnestedTest"; + String expected = "{\"text\":\"\",\"extra\":[{\"text\":\"TEST\",\"color\":\"yellow\"},{\"text\":\"nested\",\"color\":\"green\"},{\"text\":\"Test\"}]}"; + + test(input, expected); + } + + @Test + public void testHover() { + String input = "test\">TEST"; + String expected = "{\"text\":\"TEST\",\"hoverEvent\":{\"action\":\"show_text\",\"value\":{\"text\":\"test\",\"color\":\"red\"}}}"; + + test(input, expected); + } + + @Test + public void testHoverWithColon() { + String input = "test:TEST\">TEST"; + String expected = "{\"text\":\"TEST\",\"hoverEvent\":{\"action\":\"show_text\",\"value\":{\"text\":\"test:TEST\",\"color\":\"red\"}}}"; + + test(input, expected); + } + + @Test + public void testClick() { + String input = "TEST"; + String expected = "{\"text\":\"TEST\",\"clickEvent\":{\"action\":\"run_command\",\"value\":\"test\"}}"; + + test(input, expected); + } + + @Test + public void testClickExtendedCommand() { + String input = "TEST"; + String expected = "{\"text\":\"TEST\",\"clickEvent\":{\"action\":\"run_command\",\"value\":\"test command\"}}"; + + test(input, expected); + } + + @Test + public void testInvalidTag() { + String input = ""; + String expected = "{\"text\":\"\\u003ctest\\u003e\"}"; // gson makes it html save + Component comp = MiniMessageParser.parseFormat(input); + + test(comp, expected); + + // TODO am not totally happy about this yet, invalid tags arent getting colored for example, but good enough for now + } + + @Test + public void testInvalidTagComplex() { + String input = " random strangerclick here to FEEL it"; + String expected = "{\"text\":\"\",\"extra\":[{\"text\":\"\\u003ctest\\u003e\"},{\"text\":\" random \",\"color\":\"yellow\"},{\"text\":\"stranger\",\"color\":\"yellow\",\"bold\":true},{\"text\":\"\\u003coof\\u003e\"},{\"text\":\"\\u003c/oof\\u003e\"},{\"text\":\"click here\",\"color\":\"red\",\"underlined\":true,\"clickEvent\":{\"action\":\"run_command\",\"value\":\"test command\"}},{\"text\":\" to \",\"color\":\"blue\",\"underlined\":true},{\"text\":\"FEEL\",\"color\":\"blue\",\"bold\":true,\"underlined\":true},{\"text\":\" it\",\"color\":\"blue\",\"bold\":true}]}"; + Component comp = MiniMessageParser.parseFormat(input); + + test(comp, expected); + } + + private void test(@Nonnull String input, @Nonnull String expected) { + test(MiniMessageParser.parseFormat(input), expected); + } + + private void test(@Nonnull Component comp, @Nonnull String expected) { + assertEquals(expected, GsonComponentSerializer.INSTANCE.serialize(comp)); + } +} diff --git a/minimessage-text/src/test/java/minimessage/MiniMessageSerializerTest.java b/minimessage-text/src/test/java/minimessage/MiniMessageSerializerTest.java new file mode 100644 index 000000000..98ea01e0d --- /dev/null +++ b/minimessage-text/src/test/java/minimessage/MiniMessageSerializerTest.java @@ -0,0 +1,128 @@ +package minimessage; + + +import net.kyori.text.TextComponent; +import net.kyori.text.event.ClickEvent; +import net.kyori.text.event.HoverEvent; +import net.kyori.text.format.TextColor; +import net.kyori.text.format.TextDecoration; + +import org.junit.Test; + +import javax.annotation.Nonnull; + +import me.minidigger.minimessage.MiniMessageSerializer; + +import static org.junit.Assert.assertEquals; + +public class MiniMessageSerializerTest { + + @Test + public void testColor() { + String expected = "This is a test"; + + TextComponent.Builder builder = TextComponent.builder().content("This is a test").color(TextColor.RED); + + test(builder, expected); + } + + @Test + public void testColorClosing() { + String expected = "This is a test"; + + TextComponent.Builder builder = TextComponent.builder() + .content("This is a ").color(TextColor.RED) + .append("test"); + + test(builder, expected); + } + + @Test + public void testNestedColor() { + String expected = "This is a blue test"; + + TextComponent.Builder builder = TextComponent.builder() + .content("This is a ").color(TextColor.RED) + .append("blue ", TextColor.BLUE) + .append("test", TextColor.RED); + + test(builder, expected); + } + + @Test + public void testDecoration() { + String expected = "This is underlined, this isn't"; + + TextComponent.Builder builder = TextComponent.builder() + .content("This is ").decoration(TextDecoration.UNDERLINED, true) + .append("underlined", b -> b.decoration(TextDecoration.UNDERLINED, true).decoration(TextDecoration.BOLD, true)) + .append(", this", b -> b.decoration(TextDecoration.BOLD, true)) + .append(" isn't"); + + test(builder, expected); + } + + @Test + public void testHover() { + String expected = "Some hover that ends here"; + + TextComponent.Builder builder = TextComponent.builder() + .content("Some hover").hoverEvent(HoverEvent.showText(TextComponent.of("---"))) + .append(" that ends here"); + + test(builder, expected); + } + + @Test + public void testHoverWithNested() { + String expected = "----\">Some hover that ends here"; + + TextComponent.Builder builder = TextComponent.builder() + .content("Some hover").hoverEvent(HoverEvent.showText(TextComponent.builder() + .content("---").color(TextColor.RED) + .append("-", TextColor.BLUE, TextDecoration.BOLD) + .build())) + .append(" that ends here"); + + test(builder, expected); + } + + @Test + public void testClick() { + String expected = "Some click that ends here"; + + TextComponent.Builder builder = TextComponent.builder() + .content("Some click").clickEvent(ClickEvent.runCommand("test")) + .append(" that ends here"); + + test(builder, expected); + } + + @Test + public void testContinuedClick() { + String expected = "Some click that doesn't end here"; + + TextComponent.Builder builder = TextComponent.builder() + .content("Some click").clickEvent(ClickEvent.runCommand("test")) + // TODO figure out how to avoid repeating the click event here + .append(" that doesn't end here", b -> b.color(TextColor.RED).clickEvent(ClickEvent.runCommand("test"))); + + test(builder, expected); + } + + @Test + public void testContinuedClick2() { + String expected = "Some click that doesn't end here"; + + TextComponent.Builder builder = TextComponent.builder() + .content("Some click").clickEvent(ClickEvent.runCommand("test")) + .append(" that doesn't end here", b -> b.color(TextColor.RED).clickEvent(ClickEvent.runCommand("test"))); + + test(builder, expected); + } + + private void test(@Nonnull TextComponent.Builder builder, @Nonnull String expected) { + String string = MiniMessageSerializer.serialize(builder.build()); + assertEquals(expected, string); + } +} diff --git a/pom.xml b/pom.xml index 35a80b032..0f038983e 100644 --- a/pom.xml +++ b/pom.xml @@ -16,6 +16,12 @@ UTF-8 + + minimessage-text + minimessage-bungee + minimessage-ext-markdown + + papermc @@ -23,11 +29,20 @@ - - minimessage-text - minimessage-bungee - minimessage-ext-markdown - + + + com.google.code.findbugs + jsr305 + 1.3.9 + compile + + + junit + junit + 4.13 + test + + From 96369ec0aae2f8257546e356b0af7bcb685a7890 Mon Sep 17 00:00:00 2001 From: release-bot Date: Fri, 24 Apr 2020 15:25:31 +0000 Subject: [PATCH 021/339] [maven-release-plugin] prepare release MiniMessage-2.0.2 --- minimessage-bungee/pom.xml | 2 +- minimessage-ext-markdown/pom.xml | 2 +- minimessage-text/pom.xml | 2 +- pom.xml | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/minimessage-bungee/pom.xml b/minimessage-bungee/pom.xml index c214d569a..159e3ecc5 100644 --- a/minimessage-bungee/pom.xml +++ b/minimessage-bungee/pom.xml @@ -4,7 +4,7 @@ MiniMessage me.minidigger - 2.0.2-SNAPSHOT + 2.0.2 minimessage-bungee diff --git a/minimessage-ext-markdown/pom.xml b/minimessage-ext-markdown/pom.xml index a91595caa..6a864be4a 100644 --- a/minimessage-ext-markdown/pom.xml +++ b/minimessage-ext-markdown/pom.xml @@ -3,7 +3,7 @@ MiniMessage me.minidigger - 2.0.2-SNAPSHOT + 2.0.2 4.0.0 diff --git a/minimessage-text/pom.xml b/minimessage-text/pom.xml index a403037e4..cf26a1e34 100644 --- a/minimessage-text/pom.xml +++ b/minimessage-text/pom.xml @@ -4,7 +4,7 @@ MiniMessage me.minidigger - 2.0.2-SNAPSHOT + 2.0.2 minimessage-text diff --git a/pom.xml b/pom.xml index 0f038983e..2484093e4 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ me.minidigger MiniMessage pom - 2.0.2-SNAPSHOT + 2.0.2 MiniMessage Simple library that implements an easy to use textual format to send rich json messages. https://github.com/MiniDigger/MiniMessage @@ -135,7 +135,7 @@ scm:git:https://github.com/MiniDigger/MiniMessage.git scm:git:https://github.com/MiniDigger/MiniMessage.git https://github.com/MiniDigger/MiniMessage - HEAD + MiniMessage-2.0.2 From 0d33b57a32a16037f9c6b3199c9b6f10c1c8bfb6 Mon Sep 17 00:00:00 2001 From: release-bot Date: Fri, 24 Apr 2020 15:25:37 +0000 Subject: [PATCH 022/339] [maven-release-plugin] prepare for next development iteration --- minimessage-bungee/pom.xml | 2 +- minimessage-ext-markdown/pom.xml | 2 +- minimessage-text/pom.xml | 2 +- pom.xml | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/minimessage-bungee/pom.xml b/minimessage-bungee/pom.xml index 159e3ecc5..69f7b128d 100644 --- a/minimessage-bungee/pom.xml +++ b/minimessage-bungee/pom.xml @@ -4,7 +4,7 @@ MiniMessage me.minidigger - 2.0.2 + 2.0.3-SNAPSHOT minimessage-bungee diff --git a/minimessage-ext-markdown/pom.xml b/minimessage-ext-markdown/pom.xml index 6a864be4a..b4c661909 100644 --- a/minimessage-ext-markdown/pom.xml +++ b/minimessage-ext-markdown/pom.xml @@ -3,7 +3,7 @@ MiniMessage me.minidigger - 2.0.2 + 2.0.3-SNAPSHOT 4.0.0 diff --git a/minimessage-text/pom.xml b/minimessage-text/pom.xml index cf26a1e34..6221f7c00 100644 --- a/minimessage-text/pom.xml +++ b/minimessage-text/pom.xml @@ -4,7 +4,7 @@ MiniMessage me.minidigger - 2.0.2 + 2.0.3-SNAPSHOT minimessage-text diff --git a/pom.xml b/pom.xml index 2484093e4..e5577dfcd 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ me.minidigger MiniMessage pom - 2.0.2 + 2.0.3-SNAPSHOT MiniMessage Simple library that implements an easy to use textual format to send rich json messages. https://github.com/MiniDigger/MiniMessage @@ -135,7 +135,7 @@ scm:git:https://github.com/MiniDigger/MiniMessage.git scm:git:https://github.com/MiniDigger/MiniMessage.git https://github.com/MiniDigger/MiniMessage - MiniMessage-2.0.2 + HEAD From 7d1b7dfe0e8a3b5aa0e132c48d477deacbfa7940 Mon Sep 17 00:00:00 2001 From: MiniDigger Date: Fri, 24 Apr 2020 17:31:10 +0200 Subject: [PATCH 023/339] sonar stuff --- .../minimessage/MiniMessageParser.java | 15 +++++---- .../minimessage/MiniMessageSerializer.java | 5 ++- .../minimessage/MiniMessageParserTest.java | 1 + .../me/minidigger/minimessage/Constants.java | 14 ++++---- .../minimessage/MiniMarkdownParser.java | 32 +++++++++---------- .../minimessage/MiniMessageParser.java | 15 +++++---- .../minimessage/MiniMessageSerializer.java | 5 ++- .../minimessage/MiniMessageParserTest.java | 1 + 8 files changed, 52 insertions(+), 36 deletions(-) diff --git a/minimessage-bungee/src/main/java/me/minidigger/minimessage/MiniMessageParser.java b/minimessage-bungee/src/main/java/me/minidigger/minimessage/MiniMessageParser.java index c9f9d7c1b..85be47ebd 100644 --- a/minimessage-bungee/src/main/java/me/minidigger/minimessage/MiniMessageParser.java +++ b/minimessage-bungee/src/main/java/me/minidigger/minimessage/MiniMessageParser.java @@ -92,7 +92,7 @@ public static String stripTokens(@Nonnull String richMessage) { @Nonnull public static String handlePlaceholders(@Nonnull String richMessage, @Nonnull String... placeholders) { if (placeholders.length % 2 != 0) { - throw new RuntimeException( + throw new ParseException( "Invalid number placeholders defined, usage: parseFormat(format, key, value, key, value...)"); } for (int i = 0; i < placeholders.length; i += 2) { @@ -167,11 +167,8 @@ public static BaseComponent[] parseFormat(@Nonnull String richMessage) { } } -// String group = matcher.group(); -// String start = matcher.group(START); String token = matcher.group(TOKEN); String inner = matcher.group(INNER); -// String end = matcher.group(END); Optional deco; Optional color; @@ -249,7 +246,7 @@ else if ((color = resolveColor(token)).isPresent()) { private static ClickEvent handleClick(@Nonnull String token, @Nonnull String inner) { String[] args = token.split(SEPARATOR); if (args.length < 2) { - throw new RuntimeException("Can't parse click action (too few args) " + token); + throw new ParseException("Can't parse click action (too few args) " + token); } ClickEvent.Action action = ClickEvent.Action.valueOf(args[1].toUpperCase()); return new ClickEvent(action, token.replace(CLICK + SEPARATOR + args[1] + SEPARATOR, "")); @@ -259,7 +256,7 @@ private static ClickEvent handleClick(@Nonnull String token, @Nonnull String inn private static HoverEvent handleHover(@Nonnull String token, @Nonnull String inner) { String[] args = token.split(SEPARATOR); if (args.length < 2) { - throw new RuntimeException("Can't parse hover action (too few args) " + token); + throw new ParseException("Can't parse hover action (too few args) " + token); } HoverEvent.Action action = HoverEvent.Action.valueOf(args[1].toUpperCase()); return new HoverEvent(action, parseFormat(inner)); @@ -300,4 +297,10 @@ public void apply(@Nonnull ComponentBuilder comp) { builder.accept(comp); } } + + static class ParseException extends RuntimeException { + public ParseException(@Nonnull String message) { + super(message); + } + } } diff --git a/minimessage-bungee/src/main/java/me/minidigger/minimessage/MiniMessageSerializer.java b/minimessage-bungee/src/main/java/me/minidigger/minimessage/MiniMessageSerializer.java index 9c2e7f450..78d38a524 100644 --- a/minimessage-bungee/src/main/java/me/minidigger/minimessage/MiniMessageSerializer.java +++ b/minimessage-bungee/src/main/java/me/minidigger/minimessage/MiniMessageSerializer.java @@ -20,7 +20,10 @@ import static me.minidigger.minimessage.Constants.TAG_START; import static me.minidigger.minimessage.Constants.UNDERLINED; -public class MiniMessageSerializer { +public final class MiniMessageSerializer { + + private MiniMessageSerializer(){ + } @Nonnull public static String serialize(@Nonnull BaseComponent... components) { diff --git a/minimessage-bungee/src/test/java/me/minidigger/minimessage/MiniMessageParserTest.java b/minimessage-bungee/src/test/java/me/minidigger/minimessage/MiniMessageParserTest.java index 61f38f08f..4879173f9 100644 --- a/minimessage-bungee/src/test/java/me/minidigger/minimessage/MiniMessageParserTest.java +++ b/minimessage-bungee/src/test/java/me/minidigger/minimessage/MiniMessageParserTest.java @@ -19,6 +19,7 @@ public void test() { String out2 = ComponentSerializer.toString(MiniMessageParser.parseFormat(input2)); System.out.println(out1); System.out.println(out2); + assertEquals(out1, out2); } @Test diff --git a/minimessage-ext-markdown/src/main/java/me/minidigger/minimessage/Constants.java b/minimessage-ext-markdown/src/main/java/me/minidigger/minimessage/Constants.java index 96864d1b4..cae1fb97d 100644 --- a/minimessage-ext-markdown/src/main/java/me/minidigger/minimessage/Constants.java +++ b/minimessage-ext-markdown/src/main/java/me/minidigger/minimessage/Constants.java @@ -2,12 +2,14 @@ class Constants { - public static char emphasis1 = '*'; - public static char emphasis2 = '_'; - public static char underline = '~'; + public static final char EMPHASIS_1 = '*'; + public static final char EMPHASIS_2 = '_'; + public static final char UNDERLINE = '~'; - public static String italicTag = "italic"; - public static String boldTag = "bold"; - public static String underlineTag = "underline"; + public static final String ITALIC_TAG = "italic"; + public static final String BOLD_TAG = "bold"; + public static final String UNDERLINE_TAG = "underline"; + private Constants() { + } } diff --git a/minimessage-ext-markdown/src/main/java/me/minidigger/minimessage/MiniMarkdownParser.java b/minimessage-ext-markdown/src/main/java/me/minidigger/minimessage/MiniMarkdownParser.java index 315ea31e2..1e6446d44 100644 --- a/minimessage-ext-markdown/src/main/java/me/minidigger/minimessage/MiniMarkdownParser.java +++ b/minimessage-ext-markdown/src/main/java/me/minidigger/minimessage/MiniMarkdownParser.java @@ -39,15 +39,15 @@ private static String handle(@Nonnull String input, boolean strip) { char c = input.charAt(currIndex); boolean shouldSkip = false; - if (c == Constants.emphasis1) { + if (c == Constants.EMPHASIS_1) { char n = next(currIndex, input); - if (n == Constants.emphasis1) { + if (n == Constants.EMPHASIS_1) { if (bold == -1) { bold = sb.length(); boldSkip = new Insert(sb.length(), c + ""); } else { - inserts.add(new Insert(bold, "<" + Constants.boldTag + ">")); - inserts.add(new Insert(sb.length(), "")); + inserts.add(new Insert(bold, "<" + Constants.BOLD_TAG + ">")); + inserts.add(new Insert(sb.length(), "")); bold = -1; } skip++; @@ -56,21 +56,21 @@ private static String handle(@Nonnull String input, boolean strip) { italic = sb.length(); italicSkip = new Insert(sb.length(), c + ""); } else { - inserts.add(new Insert(italic, "<" + Constants.italicTag + ">")); - inserts.add(new Insert(sb.length(), "")); + inserts.add(new Insert(italic, "<" + Constants.ITALIC_TAG + ">")); + inserts.add(new Insert(sb.length(), "")); italic = -1; } } shouldSkip = true; - } else if (c == Constants.emphasis2) { + } else if (c == Constants.EMPHASIS_2) { char n = next(currIndex, input); - if (n == Constants.emphasis2) { + if (n == Constants.EMPHASIS_2) { if (bold == -1) { bold = sb.length(); boldSkip = new Insert(sb.length(), c + ""); } else { - inserts.add(new Insert(bold, "<" + Constants.boldTag + ">")); - inserts.add(new Insert(sb.length(), "")); + inserts.add(new Insert(bold, "<" + Constants.BOLD_TAG + ">")); + inserts.add(new Insert(sb.length(), "")); bold = -1; } skip++; @@ -79,21 +79,21 @@ private static String handle(@Nonnull String input, boolean strip) { italic = currIndex; italicSkip = new Insert(sb.length(), c + ""); } else { - inserts.add(new Insert(italic, "<" + Constants.italicTag + ">")); - inserts.add(new Insert(currIndex - 1, "")); + inserts.add(new Insert(italic, "<" + Constants.ITALIC_TAG + ">")); + inserts.add(new Insert(currIndex - 1, "")); italic = -1; } } shouldSkip = true; - } else if (c == Constants.underline) { + } else if (c == Constants.UNDERLINE) { char n = next(currIndex, input); - if (n == Constants.underline) { + if (n == Constants.UNDERLINE) { if (underline == -1) { underline = sb.length(); underlineSkip = new Insert(sb.length(), c + ""); } else { - inserts.add(new Insert(underline, "<" + Constants.underlineTag + ">")); - inserts.add(new Insert(sb.length(), "")); + inserts.add(new Insert(underline, "<" + Constants.UNDERLINE_TAG + ">")); + inserts.add(new Insert(sb.length(), "")); underline = -1; } skip++; diff --git a/minimessage-text/src/main/java/me/minidigger/minimessage/MiniMessageParser.java b/minimessage-text/src/main/java/me/minidigger/minimessage/MiniMessageParser.java index c97bc3410..e03ec8346 100644 --- a/minimessage-text/src/main/java/me/minidigger/minimessage/MiniMessageParser.java +++ b/minimessage-text/src/main/java/me/minidigger/minimessage/MiniMessageParser.java @@ -94,7 +94,7 @@ public static String stripTokens(@Nonnull String richMessage) { @Nonnull public static String handlePlaceholders(@Nonnull String richMessage, @Nonnull String... placeholders) { if (placeholders.length % 2 != 0) { - throw new RuntimeException( + throw new ParseException( "Invalid number placeholders defined, usage: parseFormat(format, key, value, key, value...)"); } for (int i = 0; i < placeholders.length; i += 2) { @@ -166,11 +166,8 @@ public static Component parseFormat(@Nonnull String richMessage) { } } -// String group = matcher.group(); -// String start = matcher.group(START); String token = matcher.group(TOKEN); String inner = matcher.group(INNER); -// String end = matcher.group(END); Optional deco; Optional color; @@ -247,7 +244,7 @@ else if ((color = resolveColor(token)).isPresent()) { private static ClickEvent handleClick(@Nonnull String token, @Nonnull String inner) { String[] args = token.split(SEPARATOR); if (args.length < 2) { - throw new RuntimeException("Can't parse click action (too few args) " + token); + throw new ParseException("Can't parse click action (too few args) " + token); } ClickEvent.Action action = ClickEvent.Action.valueOf(args[1].toUpperCase()); return ClickEvent.of(action, token.replace(CLICK + SEPARATOR + args[1] + SEPARATOR, "")); @@ -257,7 +254,7 @@ private static ClickEvent handleClick(@Nonnull String token, @Nonnull String inn private static HoverEvent handleHover(@Nonnull String token, @Nonnull String inner) { String[] args = token.split(SEPARATOR); if (args.length < 2) { - throw new RuntimeException("Can't parse hover action (too few args) " + token); + throw new ParseException("Can't parse hover action (too few args) " + token); } HoverEvent.Action action = HoverEvent.Action.valueOf(args[1].toUpperCase()); return HoverEvent.of(action, parseFormat(inner)); @@ -298,4 +295,10 @@ public void apply(@Nonnull Builder comp) { builder.accept(comp); } } + + static class ParseException extends RuntimeException { + public ParseException(@Nonnull String message) { + super(message); + } + } } diff --git a/minimessage-text/src/main/java/me/minidigger/minimessage/MiniMessageSerializer.java b/minimessage-text/src/main/java/me/minidigger/minimessage/MiniMessageSerializer.java index 0fbc7d9eb..0259b0650 100644 --- a/minimessage-text/src/main/java/me/minidigger/minimessage/MiniMessageSerializer.java +++ b/minimessage-text/src/main/java/me/minidigger/minimessage/MiniMessageSerializer.java @@ -25,7 +25,10 @@ import static me.minidigger.minimessage.Constants.TAG_START; import static me.minidigger.minimessage.Constants.UNDERLINED; -public class MiniMessageSerializer { +public final class MiniMessageSerializer { + + private MiniMessageSerializer(){ + } @Nonnull public static String serialize(@Nonnull Component component) { diff --git a/minimessage-text/src/test/java/minimessage/MiniMessageParserTest.java b/minimessage-text/src/test/java/minimessage/MiniMessageParserTest.java index 9ea62d160..1991f7231 100644 --- a/minimessage-text/src/test/java/minimessage/MiniMessageParserTest.java +++ b/minimessage-text/src/test/java/minimessage/MiniMessageParserTest.java @@ -21,6 +21,7 @@ public void test() { String out2 = GsonComponentSerializer.INSTANCE.serialize(MiniMessageParser.parseFormat(input2)); System.out.println(out1); System.out.println(out2); + assertEquals(out1, out2); } @Test From 658c04fc4e92afb06f120658f6ff4cd4e63fe16e Mon Sep 17 00:00:00 2001 From: MiniDigger Date: Fri, 24 Apr 2020 17:49:48 +0200 Subject: [PATCH 024/339] dont use builder --- .../minimessage/MiniMessageParser.java | 39 ++++++++++--------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/minimessage-text/src/main/java/me/minidigger/minimessage/MiniMessageParser.java b/minimessage-text/src/main/java/me/minidigger/minimessage/MiniMessageParser.java index e03ec8346..89b8111df 100644 --- a/minimessage-text/src/main/java/me/minidigger/minimessage/MiniMessageParser.java +++ b/minimessage-text/src/main/java/me/minidigger/minimessage/MiniMessageParser.java @@ -13,7 +13,7 @@ import java.util.EnumSet; import java.util.Map; import java.util.Optional; -import java.util.function.Consumer; +import java.util.function.UnaryOperator; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.annotation.Nonnull; @@ -133,7 +133,7 @@ public static Component parseFormat(@Nonnull String richMessage) { Matcher matcher = pattern.matcher(richMessage); int lastEnd = 0; while (matcher.find()) { - Builder current = null; + Component current = null; int startIndex = matcher.start(); int endIndex = matcher.end(); @@ -146,22 +146,22 @@ public static Component parseFormat(@Nonnull String richMessage) { // handle message if (msg != null && msg.length() != 0) { // append message - current = TextComponent.builder(msg); + current = TextComponent.of(msg); // set everything that is not closed yet if (!clickEvents.isEmpty()) { - current.clickEvent(clickEvents.peek()); + current = current.clickEvent(clickEvents.peek()); } if (!hoverEvents.isEmpty()) { - current.hoverEvent(hoverEvents.peek()); + current = current.hoverEvent(hoverEvents.peek()); } if (!colors.isEmpty()) { - current.color(colors.peek()); + current = current.color(colors.peek()); } if (!decorations.isEmpty()) { // no lambda because builder isn't effective final :/ for (HelperTextDecoration decor : decorations) { - decor.apply(current); + current = decor.apply(current); } } } @@ -197,11 +197,11 @@ else if ((color = resolveColor(token)).isPresent()) { colors.pop(); } else { // invalid tag - current = TextComponent.builder(TAG_START + token + TAG_END); + current = TextComponent.of(TAG_START + token + TAG_END); } if (current != null) { - parent.append(current.build()); + parent.append(current); } } @@ -209,26 +209,26 @@ else if ((color = resolveColor(token)).isPresent()) { if (richMessage.length() > lastEnd) { String msg = richMessage.substring(lastEnd); // append message - Builder current = TextComponent.builder(msg); + Component current = TextComponent.of(msg); // set everything that is not closed yet if (!clickEvents.isEmpty()) { - current.clickEvent(clickEvents.peek()); + current = current.clickEvent(clickEvents.peek()); } if (!hoverEvents.isEmpty()) { - current.hoverEvent(hoverEvents.peek()); + current = current.hoverEvent(hoverEvents.peek()); } if (!colors.isEmpty()) { - current.color(colors.peek()); + current = current.color(colors.peek()); } if (!decorations.isEmpty()) { // no lambda because builder isn't effective final :/ for (HelperTextDecoration decor : decorations) { - decor.apply(current); + current = decor.apply(current); } } - parent.append(current.build()); + parent.append(current); } // optimization, ignore empty parent @@ -285,14 +285,15 @@ enum HelperTextDecoration { STRIKETHROUGH(b -> b.decoration(TextDecoration.STRIKETHROUGH, true)), OBFUSCATED(b -> b.decoration(TextDecoration.OBFUSCATED, true)); - private final Consumer builder; + private final UnaryOperator builder; - HelperTextDecoration(@Nonnull Consumer builder) { + HelperTextDecoration(@Nonnull UnaryOperator builder) { this.builder = builder; } - public void apply(@Nonnull Builder comp) { - builder.accept(comp); + @Nonnull + public Component apply(@Nonnull Component comp) { + return builder.apply(comp); } } From 43d147d764d8a7e770a5d05302d4af2f802b1679 Mon Sep 17 00:00:00 2001 From: MiniDigger Date: Fri, 24 Apr 2020 18:04:21 +0200 Subject: [PATCH 025/339] add keybind --- .../me/minidigger/minimessage/Constants.java | 1 + .../minimessage/MiniMessageParser.java | 85 ++++++++++++------- .../minimessage/MiniMessageParserTest.java | 18 ++++ 3 files changed, 71 insertions(+), 33 deletions(-) diff --git a/minimessage-text/src/main/java/me/minidigger/minimessage/Constants.java b/minimessage-text/src/main/java/me/minidigger/minimessage/Constants.java index 1f9423479..6da0043cd 100644 --- a/minimessage-text/src/main/java/me/minidigger/minimessage/Constants.java +++ b/minimessage-text/src/main/java/me/minidigger/minimessage/Constants.java @@ -3,6 +3,7 @@ class Constants { public static final String CLICK = "click"; public static final String HOVER = "hover"; + public static final String KEYBIND = "key"; public static final String UNDERLINED = "underlined"; public static final String STRIKETHROUGH = "strikethrough"; diff --git a/minimessage-text/src/main/java/me/minidigger/minimessage/MiniMessageParser.java b/minimessage-text/src/main/java/me/minidigger/minimessage/MiniMessageParser.java index 89b8111df..1ecbebd13 100644 --- a/minimessage-text/src/main/java/me/minidigger/minimessage/MiniMessageParser.java +++ b/minimessage-text/src/main/java/me/minidigger/minimessage/MiniMessageParser.java @@ -1,6 +1,7 @@ package me.minidigger.minimessage; import net.kyori.text.Component; +import net.kyori.text.KeybindComponent; import net.kyori.text.TextComponent; import net.kyori.text.TextComponent.Builder; import net.kyori.text.event.ClickEvent; @@ -21,6 +22,7 @@ import static me.minidigger.minimessage.Constants.CLICK; import static me.minidigger.minimessage.Constants.CLOSE_TAG; import static me.minidigger.minimessage.Constants.HOVER; +import static me.minidigger.minimessage.Constants.KEYBIND; import static me.minidigger.minimessage.Constants.SEPARATOR; import static me.minidigger.minimessage.Constants.TAG_END; import static me.minidigger.minimessage.Constants.TAG_START; @@ -147,23 +149,8 @@ public static Component parseFormat(@Nonnull String richMessage) { if (msg != null && msg.length() != 0) { // append message current = TextComponent.of(msg); + current = applyFormatting(clickEvents, hoverEvents, colors, (EnumSet) decorations, current); - // set everything that is not closed yet - if (!clickEvents.isEmpty()) { - current = current.clickEvent(clickEvents.peek()); - } - if (!hoverEvents.isEmpty()) { - current = current.hoverEvent(hoverEvents.peek()); - } - if (!colors.isEmpty()) { - current = current.color(colors.peek()); - } - if (!decorations.isEmpty()) { - // no lambda because builder isn't effective final :/ - for (HelperTextDecoration decor : decorations) { - current = decor.apply(current); - } - } } String token = matcher.group(TOKEN); @@ -195,8 +182,20 @@ else if ((color = resolveColor(token)).isPresent()) { colors.push(color.get()); } else if (token.startsWith(CLOSE_TAG) && resolveColor(token.replace(CLOSE_TAG, "")).isPresent()) { colors.pop(); - } else { - // invalid tag + } + // keybind + else if (token.startsWith(KEYBIND + SEPARATOR)) { + if (current != null) { + parent.append(current); + } + current = handleKeybind(token); + current = applyFormatting(clickEvents, hoverEvents, colors, decorations, current); + } + // invalid tag + else { + if (current != null) { + parent.append(current); + } current = TextComponent.of(TAG_START + token + TAG_END); } @@ -212,21 +211,7 @@ else if ((color = resolveColor(token)).isPresent()) { Component current = TextComponent.of(msg); // set everything that is not closed yet - if (!clickEvents.isEmpty()) { - current = current.clickEvent(clickEvents.peek()); - } - if (!hoverEvents.isEmpty()) { - current = current.hoverEvent(hoverEvents.peek()); - } - if (!colors.isEmpty()) { - current = current.color(colors.peek()); - } - if (!decorations.isEmpty()) { - // no lambda because builder isn't effective final :/ - for (HelperTextDecoration decor : decorations) { - current = decor.apply(current); - } - } + current = applyFormatting(clickEvents, hoverEvents, colors, decorations, current); parent.append(current); } @@ -240,6 +225,40 @@ else if ((color = resolveColor(token)).isPresent()) { } } + @Nonnull + private static Component applyFormatting(@Nonnull Deque clickEvents, + @Nonnull Deque hoverEvents, + @Nonnull Deque colors, + @Nonnull EnumSet decorations, + @Nonnull Component current) { + // set everything that is not closed yet + if (!clickEvents.isEmpty()) { + current = current.clickEvent(clickEvents.peek()); + } + if (!hoverEvents.isEmpty()) { + current = current.hoverEvent(hoverEvents.peek()); + } + if (!colors.isEmpty()) { + current = current.color(colors.peek()); + } + if (!decorations.isEmpty()) { + // no lambda because builder isn't effective final :/ + for (HelperTextDecoration decor : decorations) { + current = decor.apply(current); + } + } + return current; + } + + @Nonnull + private static KeybindComponent handleKeybind(@Nonnull String token) { + String[] args = token.split(SEPARATOR); + if (args.length < 2) { + throw new ParseException("Can't parse keybind (too few args) " + token); + } + return KeybindComponent.of(args[1]); + } + @Nonnull private static ClickEvent handleClick(@Nonnull String token, @Nonnull String inner) { String[] args = token.split(SEPARATOR); diff --git a/minimessage-text/src/test/java/minimessage/MiniMessageParserTest.java b/minimessage-text/src/test/java/minimessage/MiniMessageParserTest.java index 1991f7231..1edaf2b01 100644 --- a/minimessage-text/src/test/java/minimessage/MiniMessageParserTest.java +++ b/minimessage-text/src/test/java/minimessage/MiniMessageParserTest.java @@ -161,6 +161,24 @@ public void testInvalidTagComplex() { test(comp, expected); } + @Test + public void testKeyBind() { + String input = "Press to jump!"; + String expected = "{\"text\":\"\",\"extra\":[{\"text\":\"Press \"},{\"keybind\":\"key.jump\"},{\"text\":\" to jump!\"}]}"; + Component comp = MiniMessageParser.parseFormat(input); + + test(comp, expected); + } + + @Test + public void testKeyBindWithColor() { + String input = "Press to jump!"; + String expected = "{\"text\":\"\",\"extra\":[{\"text\":\"Press \"},{\"keybind\":\"key.jump\",\"color\":\"red\"},{\"text\":\" to jump!\",\"color\":\"red\"}]}"; + Component comp = MiniMessageParser.parseFormat(input); + + test(comp, expected); + } + private void test(@Nonnull String input, @Nonnull String expected) { test(MiniMessageParser.parseFormat(input), expected); } From 88de4100cd183b661d189dba00421933ba5768ca Mon Sep 17 00:00:00 2001 From: MiniDigger Date: Fri, 24 Apr 2020 18:11:26 +0200 Subject: [PATCH 026/339] better handling of invalid tags --- .../main/java/me/minidigger/minimessage/MiniMessageParser.java | 1 + .../src/test/java/minimessage/MiniMessageParserTest.java | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/minimessage-text/src/main/java/me/minidigger/minimessage/MiniMessageParser.java b/minimessage-text/src/main/java/me/minidigger/minimessage/MiniMessageParser.java index 1ecbebd13..162ddbb9e 100644 --- a/minimessage-text/src/main/java/me/minidigger/minimessage/MiniMessageParser.java +++ b/minimessage-text/src/main/java/me/minidigger/minimessage/MiniMessageParser.java @@ -197,6 +197,7 @@ else if (token.startsWith(KEYBIND + SEPARATOR)) { parent.append(current); } current = TextComponent.of(TAG_START + token + TAG_END); + current = applyFormatting(clickEvents, hoverEvents, colors, decorations, current); } if (current != null) { diff --git a/minimessage-text/src/test/java/minimessage/MiniMessageParserTest.java b/minimessage-text/src/test/java/minimessage/MiniMessageParserTest.java index 1edaf2b01..22430492a 100644 --- a/minimessage-text/src/test/java/minimessage/MiniMessageParserTest.java +++ b/minimessage-text/src/test/java/minimessage/MiniMessageParserTest.java @@ -155,7 +155,7 @@ public void testInvalidTag() { @Test public void testInvalidTagComplex() { String input = " random strangerclick here to FEEL it"; - String expected = "{\"text\":\"\",\"extra\":[{\"text\":\"\\u003ctest\\u003e\"},{\"text\":\" random \",\"color\":\"yellow\"},{\"text\":\"stranger\",\"color\":\"yellow\",\"bold\":true},{\"text\":\"\\u003coof\\u003e\"},{\"text\":\"\\u003c/oof\\u003e\"},{\"text\":\"click here\",\"color\":\"red\",\"underlined\":true,\"clickEvent\":{\"action\":\"run_command\",\"value\":\"test command\"}},{\"text\":\" to \",\"color\":\"blue\",\"underlined\":true},{\"text\":\"FEEL\",\"color\":\"blue\",\"bold\":true,\"underlined\":true},{\"text\":\" it\",\"color\":\"blue\",\"bold\":true}]}"; + String expected = "{\"text\":\"\",\"extra\":[{\"text\":\"\\u003ctest\\u003e\",\"color\":\"yellow\"},{\"text\":\" random \",\"color\":\"yellow\"},{\"text\":\"stranger\",\"color\":\"yellow\",\"bold\":true},{\"text\":\"\\u003coof\\u003e\",\"color\":\"yellow\",\"clickEvent\":{\"action\":\"run_command\",\"value\":\"test command\"}},{\"text\":\"\\u003c/oof\\u003e\",\"color\":\"yellow\",\"clickEvent\":{\"action\":\"run_command\",\"value\":\"test command\"}},{\"text\":\"click here\",\"color\":\"red\",\"underlined\":true,\"clickEvent\":{\"action\":\"run_command\",\"value\":\"test command\"}},{\"text\":\" to \",\"color\":\"blue\",\"underlined\":true},{\"text\":\"FEEL\",\"color\":\"blue\",\"bold\":true,\"underlined\":true},{\"text\":\" it\",\"color\":\"blue\",\"bold\":true}]}"; Component comp = MiniMessageParser.parseFormat(input); test(comp, expected); From d5762960184599d6b0a0fd2d1c82f31d8cbba1e4 Mon Sep 17 00:00:00 2001 From: MiniDigger Date: Fri, 24 Apr 2020 18:11:43 +0200 Subject: [PATCH 027/339] readme stuff --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5d369dac8..a8eaadbd7 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,8 @@ Json: ## Usage +These examples use `minimessage-bungee`, but the api and format is the same between the two, `minimessage-text` just returns a Component instead + ```java // simple parsing String input = "Hai"; @@ -106,9 +108,9 @@ Releases get deployed to maven central. ## Upgrading to 2.0 -2.0 seperated MiniMessage into two versions, minimessage-bungee and minimessage-text. +2.0 seperated MiniMessage into two versions, `minimessage-bungee` and `minimessage-text`. The bungee version continues to use the Bungee Chat Components, like 1.x did, but the new Version uses the kyori text lib, which has support for way more features. -If you dont want to change your code, use the old dependency, but you should really consider upgrading to the new one! +If you don't want to change your code, use the old dependency, but you should really consider upgrading to the new one! ## Licence MIT From 1ee4c5c440d80dd18a15db6f564622851d468f1b Mon Sep 17 00:00:00 2001 From: MiniDigger Date: Fri, 24 Apr 2020 18:34:46 +0200 Subject: [PATCH 028/339] keybind serializer --- .../minimessage/MiniMessageSerializer.java | 13 ++++- .../MiniMessageSerializerTest.java | 47 ++++++++++++++----- 2 files changed, 47 insertions(+), 13 deletions(-) diff --git a/minimessage-text/src/main/java/me/minidigger/minimessage/MiniMessageSerializer.java b/minimessage-text/src/main/java/me/minidigger/minimessage/MiniMessageSerializer.java index 0259b0650..f193dca64 100644 --- a/minimessage-text/src/main/java/me/minidigger/minimessage/MiniMessageSerializer.java +++ b/minimessage-text/src/main/java/me/minidigger/minimessage/MiniMessageSerializer.java @@ -1,6 +1,7 @@ package me.minidigger.minimessage; import net.kyori.text.Component; +import net.kyori.text.KeybindComponent; import net.kyori.text.TextComponent; import net.kyori.text.event.ClickEvent; import net.kyori.text.event.HoverEvent; @@ -27,7 +28,7 @@ public final class MiniMessageSerializer { - private MiniMessageSerializer(){ + private MiniMessageSerializer() { } @Nonnull @@ -53,7 +54,7 @@ public static String serialize(@Nonnull Component component) { // ## color // ### white is not important - if (!TextColor.WHITE.equals(comp.color()) && comp.color() != null) { + if (!TextColor.WHITE.equals(comp.color()) && comp.color() != null && (prevComp == null || prevComp.color() != comp.color())) { sb.append(startColor(Objects.requireNonNull(comp.color()))); } @@ -92,6 +93,8 @@ public static String serialize(@Nonnull Component component) { // # append text if (comp instanceof TextComponent) { sb.append(((TextComponent) comp).content()); + } else { + handleDifferentComponent(comp, sb); } // # end tags @@ -179,4 +182,10 @@ private static String startTag(@Nonnull String content) { private static String endTag(@Nonnull String content) { return TAG_START + CLOSE_TAG + content + TAG_END; } + + private static void handleDifferentComponent(@Nonnull Component component, @Nonnull StringBuilder sb) { + if (component instanceof KeybindComponent) { + sb.append(startTag("key" + SEPARATOR + ((KeybindComponent) component).keybind())); + } + } } diff --git a/minimessage-text/src/test/java/minimessage/MiniMessageSerializerTest.java b/minimessage-text/src/test/java/minimessage/MiniMessageSerializerTest.java index 98ea01e0d..d63b028ca 100644 --- a/minimessage-text/src/test/java/minimessage/MiniMessageSerializerTest.java +++ b/minimessage-text/src/test/java/minimessage/MiniMessageSerializerTest.java @@ -1,7 +1,8 @@ package minimessage; - +import net.kyori.text.KeybindComponent; import net.kyori.text.TextComponent; +import net.kyori.text.TextComponent.Builder; import net.kyori.text.event.ClickEvent; import net.kyori.text.event.HoverEvent; import net.kyori.text.format.TextColor; @@ -21,7 +22,7 @@ public class MiniMessageSerializerTest { public void testColor() { String expected = "This is a test"; - TextComponent.Builder builder = TextComponent.builder().content("This is a test").color(TextColor.RED); + Builder builder = TextComponent.builder().content("This is a test").color(TextColor.RED); test(builder, expected); } @@ -30,7 +31,7 @@ public void testColor() { public void testColorClosing() { String expected = "This is a test"; - TextComponent.Builder builder = TextComponent.builder() + Builder builder = TextComponent.builder() .content("This is a ").color(TextColor.RED) .append("test"); @@ -41,7 +42,7 @@ public void testColorClosing() { public void testNestedColor() { String expected = "This is a blue test"; - TextComponent.Builder builder = TextComponent.builder() + Builder builder = TextComponent.builder() .content("This is a ").color(TextColor.RED) .append("blue ", TextColor.BLUE) .append("test", TextColor.RED); @@ -53,7 +54,7 @@ public void testNestedColor() { public void testDecoration() { String expected = "This is underlined, this isn't"; - TextComponent.Builder builder = TextComponent.builder() + Builder builder = TextComponent.builder() .content("This is ").decoration(TextDecoration.UNDERLINED, true) .append("underlined", b -> b.decoration(TextDecoration.UNDERLINED, true).decoration(TextDecoration.BOLD, true)) .append(", this", b -> b.decoration(TextDecoration.BOLD, true)) @@ -66,7 +67,7 @@ public void testDecoration() { public void testHover() { String expected = "Some hover that ends here"; - TextComponent.Builder builder = TextComponent.builder() + Builder builder = TextComponent.builder() .content("Some hover").hoverEvent(HoverEvent.showText(TextComponent.of("---"))) .append(" that ends here"); @@ -77,7 +78,7 @@ public void testHover() { public void testHoverWithNested() { String expected = "----\">Some hover that ends here"; - TextComponent.Builder builder = TextComponent.builder() + Builder builder = TextComponent.builder() .content("Some hover").hoverEvent(HoverEvent.showText(TextComponent.builder() .content("---").color(TextColor.RED) .append("-", TextColor.BLUE, TextDecoration.BOLD) @@ -91,7 +92,7 @@ public void testHoverWithNested() { public void testClick() { String expected = "Some click that ends here"; - TextComponent.Builder builder = TextComponent.builder() + Builder builder = TextComponent.builder() .content("Some click").clickEvent(ClickEvent.runCommand("test")) .append(" that ends here"); @@ -102,7 +103,7 @@ public void testClick() { public void testContinuedClick() { String expected = "Some click that doesn't end here"; - TextComponent.Builder builder = TextComponent.builder() + Builder builder = TextComponent.builder() .content("Some click").clickEvent(ClickEvent.runCommand("test")) // TODO figure out how to avoid repeating the click event here .append(" that doesn't end here", b -> b.color(TextColor.RED).clickEvent(ClickEvent.runCommand("test"))); @@ -114,14 +115,38 @@ public void testContinuedClick() { public void testContinuedClick2() { String expected = "Some click that doesn't end here"; - TextComponent.Builder builder = TextComponent.builder() + Builder builder = TextComponent.builder() .content("Some click").clickEvent(ClickEvent.runCommand("test")) .append(" that doesn't end here", b -> b.color(TextColor.RED).clickEvent(ClickEvent.runCommand("test"))); test(builder, expected); } - private void test(@Nonnull TextComponent.Builder builder, @Nonnull String expected) { + @Test + public void testKeyBind() { + String expected = "Press to jump!"; + + Builder builder = TextComponent.builder() + .content("Press ") + .append(KeybindComponent.of("key.jump")) + .append(" to jump!"); + + test(builder, expected); + } + + @Test + public void testKeyBindWithColor() { + String expected = "Press to jump!"; + + Builder builder = TextComponent.builder() + .content("Press ") + .append(KeybindComponent.of("key.jump").color(TextColor.RED)) + .append(" to jump!", TextColor.RED); + + test(builder, expected); + } + + private void test(@Nonnull Builder builder, @Nonnull String expected) { String string = MiniMessageSerializer.serialize(builder.build()); assertEquals(expected, string); } From 4d64b5a122795cb03f1c4dea4a790b46f3f40a3f Mon Sep 17 00:00:00 2001 From: MiniDigger Date: Fri, 24 Apr 2020 18:42:29 +0200 Subject: [PATCH 029/339] add support for translatable components --- .../me/minidigger/minimessage/Constants.java | 1 + .../minimessage/MiniMessageParser.java | 19 +++++++++++++++++++ .../minimessage/MiniMessageSerializer.java | 3 +++ .../minimessage/MiniMessageParserTest.java | 10 ++++++++++ .../MiniMessageSerializerTest.java | 13 +++++++++++++ 5 files changed, 46 insertions(+) diff --git a/minimessage-text/src/main/java/me/minidigger/minimessage/Constants.java b/minimessage-text/src/main/java/me/minidigger/minimessage/Constants.java index 6da0043cd..aa2ce5224 100644 --- a/minimessage-text/src/main/java/me/minidigger/minimessage/Constants.java +++ b/minimessage-text/src/main/java/me/minidigger/minimessage/Constants.java @@ -4,6 +4,7 @@ class Constants { public static final String CLICK = "click"; public static final String HOVER = "hover"; public static final String KEYBIND = "key"; + public static final String TRANSLATABLE = "lang"; public static final String UNDERLINED = "underlined"; public static final String STRIKETHROUGH = "strikethrough"; diff --git a/minimessage-text/src/main/java/me/minidigger/minimessage/MiniMessageParser.java b/minimessage-text/src/main/java/me/minidigger/minimessage/MiniMessageParser.java index 162ddbb9e..3046e61c4 100644 --- a/minimessage-text/src/main/java/me/minidigger/minimessage/MiniMessageParser.java +++ b/minimessage-text/src/main/java/me/minidigger/minimessage/MiniMessageParser.java @@ -4,6 +4,7 @@ import net.kyori.text.KeybindComponent; import net.kyori.text.TextComponent; import net.kyori.text.TextComponent.Builder; +import net.kyori.text.TranslatableComponent; import net.kyori.text.event.ClickEvent; import net.kyori.text.event.HoverEvent; import net.kyori.text.format.TextColor; @@ -26,6 +27,7 @@ import static me.minidigger.minimessage.Constants.SEPARATOR; import static me.minidigger.minimessage.Constants.TAG_END; import static me.minidigger.minimessage.Constants.TAG_START; +import static me.minidigger.minimessage.Constants.TRANSLATABLE; public class MiniMessageParser { @@ -191,6 +193,14 @@ else if (token.startsWith(KEYBIND + SEPARATOR)) { current = handleKeybind(token); current = applyFormatting(clickEvents, hoverEvents, colors, decorations, current); } + // translatable + else if (token.startsWith(TRANSLATABLE + SEPARATOR)) { + if (current != null) { + parent.append(current); + } + current = handleTranslatable(token); + current = applyFormatting(clickEvents, hoverEvents, colors, decorations, current); + } // invalid tag else { if (current != null) { @@ -251,6 +261,15 @@ private static Component applyFormatting(@Nonnull Deque clickEvents, return current; } + @Nonnull + private static Component handleTranslatable(@Nonnull String token) { + String[] args = token.split(SEPARATOR); + if (args.length < 2) { + throw new ParseException("Can't parse translatable (too few args) " + token); + } + return TranslatableComponent.of(args[1]); + } + @Nonnull private static KeybindComponent handleKeybind(@Nonnull String token) { String[] args = token.split(SEPARATOR); diff --git a/minimessage-text/src/main/java/me/minidigger/minimessage/MiniMessageSerializer.java b/minimessage-text/src/main/java/me/minidigger/minimessage/MiniMessageSerializer.java index f193dca64..ad3ce166f 100644 --- a/minimessage-text/src/main/java/me/minidigger/minimessage/MiniMessageSerializer.java +++ b/minimessage-text/src/main/java/me/minidigger/minimessage/MiniMessageSerializer.java @@ -3,6 +3,7 @@ import net.kyori.text.Component; import net.kyori.text.KeybindComponent; import net.kyori.text.TextComponent; +import net.kyori.text.TranslatableComponent; import net.kyori.text.event.ClickEvent; import net.kyori.text.event.HoverEvent; import net.kyori.text.format.TextColor; @@ -186,6 +187,8 @@ private static String endTag(@Nonnull String content) { private static void handleDifferentComponent(@Nonnull Component component, @Nonnull StringBuilder sb) { if (component instanceof KeybindComponent) { sb.append(startTag("key" + SEPARATOR + ((KeybindComponent) component).keybind())); + } else if (component instanceof TranslatableComponent) { + sb.append(startTag("lang" + SEPARATOR + ((TranslatableComponent) component).key())); } } } diff --git a/minimessage-text/src/test/java/minimessage/MiniMessageParserTest.java b/minimessage-text/src/test/java/minimessage/MiniMessageParserTest.java index 22430492a..7b41073e9 100644 --- a/minimessage-text/src/test/java/minimessage/MiniMessageParserTest.java +++ b/minimessage-text/src/test/java/minimessage/MiniMessageParserTest.java @@ -179,6 +179,16 @@ public void testKeyBindWithColor() { test(comp, expected); } + + @Test + public void testTranslatable() { + String input = "You should get a !"; + String expected = "{\"text\":\"\",\"extra\":[{\"text\":\"You should get a \"},{\"translate\":\"block.minecraft.diamond_block\"},{\"text\":\"!\"}]}"; + Component comp = MiniMessageParser.parseFormat(input); + + test(comp, expected); + } + private void test(@Nonnull String input, @Nonnull String expected) { test(MiniMessageParser.parseFormat(input), expected); } diff --git a/minimessage-text/src/test/java/minimessage/MiniMessageSerializerTest.java b/minimessage-text/src/test/java/minimessage/MiniMessageSerializerTest.java index d63b028ca..bfe533a7b 100644 --- a/minimessage-text/src/test/java/minimessage/MiniMessageSerializerTest.java +++ b/minimessage-text/src/test/java/minimessage/MiniMessageSerializerTest.java @@ -3,6 +3,7 @@ import net.kyori.text.KeybindComponent; import net.kyori.text.TextComponent; import net.kyori.text.TextComponent.Builder; +import net.kyori.text.TranslatableComponent; import net.kyori.text.event.ClickEvent; import net.kyori.text.event.HoverEvent; import net.kyori.text.format.TextColor; @@ -146,6 +147,18 @@ public void testKeyBindWithColor() { test(builder, expected); } + @Test + public void testTranslatable() { + String expected = "You should get a !"; + + Builder builder = TextComponent.builder() + .content("You should get a ") + .append(TranslatableComponent.of("block.minecraft.diamond_block")) + .append("!"); + + test(builder, expected); + } + private void test(@Nonnull Builder builder, @Nonnull String expected) { String string = MiniMessageSerializer.serialize(builder.build()); assertEquals(expected, string); From 1049a794e4e2fda6881dd2fb9412cb49228432c3 Mon Sep 17 00:00:00 2001 From: MiniDigger Date: Fri, 24 Apr 2020 18:43:56 +0200 Subject: [PATCH 030/339] add note that looking at the tests is a good idea --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index a8eaadbd7..682c66fd6 100644 --- a/README.md +++ b/README.md @@ -86,6 +86,8 @@ String stripped = MiniMessageParser.stripTokens(minimessage); // stripped is 'Hello You!' ``` +You should look at the parser tests for way more usage examples. The text version supports stuff like keybinds and translatable components for example! + ## Maven Artifact Artifact: From e60fd7171db96ea4749fe30b381f955759c851f4 Mon Sep 17 00:00:00 2001 From: MiniDigger Date: Fri, 24 Apr 2020 19:08:51 +0200 Subject: [PATCH 031/339] add insertion --- .../me/minidigger/minimessage/Constants.java | 1 + .../minimessage/MiniMessageParser.java | 31 ++++++++++++++++--- .../minimessage/MiniMessageSerializer.java | 16 ++++++++++ .../minimessage/MiniMessageParserTest.java | 10 +++++- .../MiniMessageSerializerTest.java | 12 +++++++ 5 files changed, 64 insertions(+), 6 deletions(-) diff --git a/minimessage-text/src/main/java/me/minidigger/minimessage/Constants.java b/minimessage-text/src/main/java/me/minidigger/minimessage/Constants.java index aa2ce5224..6f9ad71f0 100644 --- a/minimessage-text/src/main/java/me/minidigger/minimessage/Constants.java +++ b/minimessage-text/src/main/java/me/minidigger/minimessage/Constants.java @@ -5,6 +5,7 @@ class Constants { public static final String HOVER = "hover"; public static final String KEYBIND = "key"; public static final String TRANSLATABLE = "lang"; + public static final String INSERTION = "insert"; public static final String UNDERLINED = "underlined"; public static final String STRIKETHROUGH = "strikethrough"; diff --git a/minimessage-text/src/main/java/me/minidigger/minimessage/MiniMessageParser.java b/minimessage-text/src/main/java/me/minidigger/minimessage/MiniMessageParser.java index 3046e61c4..5c3a84e6f 100644 --- a/minimessage-text/src/main/java/me/minidigger/minimessage/MiniMessageParser.java +++ b/minimessage-text/src/main/java/me/minidigger/minimessage/MiniMessageParser.java @@ -23,6 +23,7 @@ import static me.minidigger.minimessage.Constants.CLICK; import static me.minidigger.minimessage.Constants.CLOSE_TAG; import static me.minidigger.minimessage.Constants.HOVER; +import static me.minidigger.minimessage.Constants.INSERTION; import static me.minidigger.minimessage.Constants.KEYBIND; import static me.minidigger.minimessage.Constants.SEPARATOR; import static me.minidigger.minimessage.Constants.TAG_END; @@ -132,6 +133,7 @@ public static Component parseFormat(@Nonnull String richMessage) { Deque clickEvents = new ArrayDeque<>(); Deque hoverEvents = new ArrayDeque<>(); Deque colors = new ArrayDeque<>(); + Deque insertions = new ArrayDeque<>(); EnumSet decorations = EnumSet.noneOf(HelperTextDecoration.class); Matcher matcher = pattern.matcher(richMessage); @@ -151,7 +153,7 @@ public static Component parseFormat(@Nonnull String richMessage) { if (msg != null && msg.length() != 0) { // append message current = TextComponent.of(msg); - current = applyFormatting(clickEvents, hoverEvents, colors, (EnumSet) decorations, current); + current = applyFormatting(clickEvents, hoverEvents, colors, insertions,decorations, current); } @@ -191,7 +193,7 @@ else if (token.startsWith(KEYBIND + SEPARATOR)) { parent.append(current); } current = handleKeybind(token); - current = applyFormatting(clickEvents, hoverEvents, colors, decorations, current); + current = applyFormatting(clickEvents, hoverEvents, colors, insertions, decorations, current); } // translatable else if (token.startsWith(TRANSLATABLE + SEPARATOR)) { @@ -199,7 +201,13 @@ else if (token.startsWith(TRANSLATABLE + SEPARATOR)) { parent.append(current); } current = handleTranslatable(token); - current = applyFormatting(clickEvents, hoverEvents, colors, decorations, current); + current = applyFormatting(clickEvents, hoverEvents, colors, insertions, decorations, current); + } + // insertion + else if (token.startsWith(INSERTION + SEPARATOR)) { + insertions.push(handleInsertion(token)); + } else if (token.startsWith(CLOSE_TAG + INSERTION)) { + insertions.pop(); } // invalid tag else { @@ -207,7 +215,7 @@ else if (token.startsWith(TRANSLATABLE + SEPARATOR)) { parent.append(current); } current = TextComponent.of(TAG_START + token + TAG_END); - current = applyFormatting(clickEvents, hoverEvents, colors, decorations, current); + current = applyFormatting(clickEvents, hoverEvents, colors, insertions, decorations, current); } if (current != null) { @@ -222,7 +230,7 @@ else if (token.startsWith(TRANSLATABLE + SEPARATOR)) { Component current = TextComponent.of(msg); // set everything that is not closed yet - current = applyFormatting(clickEvents, hoverEvents, colors, decorations, current); + current = applyFormatting(clickEvents, hoverEvents, colors, insertions, decorations, current); parent.append(current); } @@ -240,6 +248,7 @@ else if (token.startsWith(TRANSLATABLE + SEPARATOR)) { private static Component applyFormatting(@Nonnull Deque clickEvents, @Nonnull Deque hoverEvents, @Nonnull Deque colors, + @Nonnull Deque insertions, @Nonnull EnumSet decorations, @Nonnull Component current) { // set everything that is not closed yet @@ -258,9 +267,21 @@ private static Component applyFormatting(@Nonnull Deque clickEvents, current = decor.apply(current); } } + if (!insertions.isEmpty()) { + current = current.insertion(insertions.peek()); + } return current; } + @Nonnull + private static String handleInsertion(@Nonnull String token) { + String[] args = token.split(SEPARATOR); + if (args.length < 2) { + throw new ParseException("Can't parse insertion (too few args) " + token); + } + return token.replace(args[0] + SEPARATOR, ""); + } + @Nonnull private static Component handleTranslatable(@Nonnull String token) { String[] args = token.split(SEPARATOR); diff --git a/minimessage-text/src/main/java/me/minidigger/minimessage/MiniMessageSerializer.java b/minimessage-text/src/main/java/me/minidigger/minimessage/MiniMessageSerializer.java index ad3ce166f..e9db036dd 100644 --- a/minimessage-text/src/main/java/me/minidigger/minimessage/MiniMessageSerializer.java +++ b/minimessage-text/src/main/java/me/minidigger/minimessage/MiniMessageSerializer.java @@ -19,6 +19,7 @@ import static me.minidigger.minimessage.Constants.CLICK; import static me.minidigger.minimessage.Constants.CLOSE_TAG; import static me.minidigger.minimessage.Constants.HOVER; +import static me.minidigger.minimessage.Constants.INSERTION; import static me.minidigger.minimessage.Constants.ITALIC; import static me.minidigger.minimessage.Constants.OBFUSCATED; import static me.minidigger.minimessage.Constants.SEPARATOR; @@ -91,6 +92,13 @@ public static String serialize(@Nonnull Component component) { sb.append(startTag(String.format("%s" + SEPARATOR + "%s" + SEPARATOR + "\"%s\"", CLICK, click.action().name().toLowerCase(), click.value()))); } + // ## insertion + // ### only start if prevComp didn't start the same one + String insert = comp.insertion(); + if (insert != null && (prevComp == null || !insert.equals(prevComp.insertion()))) { + sb.append(startTag(INSERTION + SEPARATOR + insert)); + } + // # append text if (comp instanceof TextComponent) { sb.append(((TextComponent) comp).content()); @@ -149,6 +157,14 @@ public static String serialize(@Nonnull Component component) { sb.append(endTag(CLICK)); } } + + // ## insertion + // ### only end insertion if next tag is different + if (nextComp != null && comp.insertion() != null) { + if (!Objects.equals(comp.insertion(), nextComp.insertion())) { + sb.append(endTag(INSERTION)); + } + } } return sb.toString(); diff --git a/minimessage-text/src/test/java/minimessage/MiniMessageParserTest.java b/minimessage-text/src/test/java/minimessage/MiniMessageParserTest.java index 7b41073e9..de280e6ad 100644 --- a/minimessage-text/src/test/java/minimessage/MiniMessageParserTest.java +++ b/minimessage-text/src/test/java/minimessage/MiniMessageParserTest.java @@ -179,7 +179,6 @@ public void testKeyBindWithColor() { test(comp, expected); } - @Test public void testTranslatable() { String input = "You should get a !"; @@ -189,6 +188,15 @@ public void testTranslatable() { test(comp, expected); } + @Test + public void testInsertion() { + String input = "Click this to insert!"; + String expected = "{\"text\":\"\",\"extra\":[{\"text\":\"Click \"},{\"text\":\"this\",\"insertion\":\"test\"},{\"text\":\" to insert!\"}]}"; + Component comp = MiniMessageParser.parseFormat(input); + + test(comp, expected); + } + private void test(@Nonnull String input, @Nonnull String expected) { test(MiniMessageParser.parseFormat(input), expected); } diff --git a/minimessage-text/src/test/java/minimessage/MiniMessageSerializerTest.java b/minimessage-text/src/test/java/minimessage/MiniMessageSerializerTest.java index bfe533a7b..00c7085c9 100644 --- a/minimessage-text/src/test/java/minimessage/MiniMessageSerializerTest.java +++ b/minimessage-text/src/test/java/minimessage/MiniMessageSerializerTest.java @@ -159,6 +159,18 @@ public void testTranslatable() { test(builder, expected); } + @Test + public void testInsertion() { + String expected = "Click this to insert!"; + + Builder builder = TextComponent.builder() + .content("Click ") + .append("this", b->b.insertion("test")) + .append(" to insert!"); + + test(builder, expected); + } + private void test(@Nonnull Builder builder, @Nonnull String expected) { String string = MiniMessageSerializer.serialize(builder.build()); assertEquals(expected, string); From d7c795e510347472239c0b9015fe6af10406dddb Mon Sep 17 00:00:00 2001 From: release-bot Date: Fri, 24 Apr 2020 17:10:28 +0000 Subject: [PATCH 032/339] [maven-release-plugin] prepare release MiniMessage-2.0.3 --- minimessage-bungee/pom.xml | 2 +- minimessage-ext-markdown/pom.xml | 2 +- minimessage-text/pom.xml | 2 +- pom.xml | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/minimessage-bungee/pom.xml b/minimessage-bungee/pom.xml index 69f7b128d..494cca5b6 100644 --- a/minimessage-bungee/pom.xml +++ b/minimessage-bungee/pom.xml @@ -4,7 +4,7 @@ MiniMessage me.minidigger - 2.0.3-SNAPSHOT + 2.0.3 minimessage-bungee diff --git a/minimessage-ext-markdown/pom.xml b/minimessage-ext-markdown/pom.xml index b4c661909..f451637c4 100644 --- a/minimessage-ext-markdown/pom.xml +++ b/minimessage-ext-markdown/pom.xml @@ -3,7 +3,7 @@ MiniMessage me.minidigger - 2.0.3-SNAPSHOT + 2.0.3 4.0.0 diff --git a/minimessage-text/pom.xml b/minimessage-text/pom.xml index 6221f7c00..836f3a1ff 100644 --- a/minimessage-text/pom.xml +++ b/minimessage-text/pom.xml @@ -4,7 +4,7 @@ MiniMessage me.minidigger - 2.0.3-SNAPSHOT + 2.0.3 minimessage-text diff --git a/pom.xml b/pom.xml index e5577dfcd..3359557ae 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ me.minidigger MiniMessage pom - 2.0.3-SNAPSHOT + 2.0.3 MiniMessage Simple library that implements an easy to use textual format to send rich json messages. https://github.com/MiniDigger/MiniMessage @@ -135,7 +135,7 @@ scm:git:https://github.com/MiniDigger/MiniMessage.git scm:git:https://github.com/MiniDigger/MiniMessage.git https://github.com/MiniDigger/MiniMessage - HEAD + MiniMessage-2.0.3 From 6c9570a45cbf81342b788aed6656e133608a328b Mon Sep 17 00:00:00 2001 From: release-bot Date: Fri, 24 Apr 2020 17:10:34 +0000 Subject: [PATCH 033/339] [maven-release-plugin] prepare for next development iteration --- minimessage-bungee/pom.xml | 2 +- minimessage-ext-markdown/pom.xml | 2 +- minimessage-text/pom.xml | 2 +- pom.xml | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/minimessage-bungee/pom.xml b/minimessage-bungee/pom.xml index 494cca5b6..e9f668569 100644 --- a/minimessage-bungee/pom.xml +++ b/minimessage-bungee/pom.xml @@ -4,7 +4,7 @@ MiniMessage me.minidigger - 2.0.3 + 2.0.4-SNAPSHOT minimessage-bungee diff --git a/minimessage-ext-markdown/pom.xml b/minimessage-ext-markdown/pom.xml index f451637c4..c4e682080 100644 --- a/minimessage-ext-markdown/pom.xml +++ b/minimessage-ext-markdown/pom.xml @@ -3,7 +3,7 @@ MiniMessage me.minidigger - 2.0.3 + 2.0.4-SNAPSHOT 4.0.0 diff --git a/minimessage-text/pom.xml b/minimessage-text/pom.xml index 836f3a1ff..c9476b0d8 100644 --- a/minimessage-text/pom.xml +++ b/minimessage-text/pom.xml @@ -4,7 +4,7 @@ MiniMessage me.minidigger - 2.0.3 + 2.0.4-SNAPSHOT minimessage-text diff --git a/pom.xml b/pom.xml index 3359557ae..2064b2237 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ me.minidigger MiniMessage pom - 2.0.3 + 2.0.4-SNAPSHOT MiniMessage Simple library that implements an easy to use textual format to send rich json messages. https://github.com/MiniDigger/MiniMessage @@ -135,7 +135,7 @@ scm:git:https://github.com/MiniDigger/MiniMessage.git scm:git:https://github.com/MiniDigger/MiniMessage.git https://github.com/MiniDigger/MiniMessage - MiniMessage-2.0.3 + HEAD From d18d671cb47c0fb3ded3d7acdb7b213ffb579d8c Mon Sep 17 00:00:00 2001 From: MiniDigger Date: Fri, 24 Apr 2020 20:01:58 +0200 Subject: [PATCH 034/339] readme stuff --- .github/workflows/Publish.yml | 5 ++++- README.md | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/Publish.yml b/.github/workflows/Publish.yml index 01746aa0f..aeed9d8be 100644 --- a/.github/workflows/Publish.yml +++ b/.github/workflows/Publish.yml @@ -1,6 +1,9 @@ name: Publish -on: [push] +on: + push: + branches: + - master jobs: publish: diff --git a/README.md b/README.md index 682c66fd6..f5bfa3c56 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,7 @@ # MiniMessage +[![CodeFactor](https://www.codefactor.io/repository/github/minidigger/minimessage/badge)](https://www.codefactor.io/repository/github/minidigger/minimessage) +[![GitHub license](https://img.shields.io/github/license/MiniDigger/MiniMessage?style=flat-square)](https://github.com/MiniDigger/MiniMessage/blob/master/LICENSE) +![Maven Central](https://img.shields.io/maven-central/v/me.minidigger/MiniMessage?style=flat-square) Simple library that implements an easy to use textual format to send rich json messages. @@ -95,7 +98,7 @@ Artifact: me.minidigger MiniMessage - 1.0.0-SNAPSHOT + 2.0.3 compile ``` From bf798fbe78be7a4b5c03c0b7ae89d162b5e17055 Mon Sep 17 00:00:00 2001 From: MiniDigger Date: Fri, 24 Apr 2020 21:43:28 +0200 Subject: [PATCH 035/339] fix #GH-5 --- .../minidigger/minimessage/MiniMessageParser.java | 14 +++++++------- .../java/minimessage/MiniMessageParserTest.java | 13 +++++++++++++ 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/minimessage-text/src/main/java/me/minidigger/minimessage/MiniMessageParser.java b/minimessage-text/src/main/java/me/minidigger/minimessage/MiniMessageParser.java index 5c3a84e6f..73a970012 100644 --- a/minimessage-text/src/main/java/me/minidigger/minimessage/MiniMessageParser.java +++ b/minimessage-text/src/main/java/me/minidigger/minimessage/MiniMessageParser.java @@ -130,10 +130,10 @@ public static Component parseFormat(@Nonnull String richMessage, @Nonnull Map clickEvents = new ArrayDeque<>(); - Deque hoverEvents = new ArrayDeque<>(); - Deque colors = new ArrayDeque<>(); - Deque insertions = new ArrayDeque<>(); + ArrayDeque clickEvents = new ArrayDeque<>(); + ArrayDeque hoverEvents = new ArrayDeque<>(); + ArrayDeque colors = new ArrayDeque<>(); + ArrayDeque insertions = new ArrayDeque<>(); EnumSet decorations = EnumSet.noneOf(HelperTextDecoration.class); Matcher matcher = pattern.matcher(richMessage); @@ -167,13 +167,13 @@ public static Component parseFormat(@Nonnull String richMessage) { if (token.startsWith(CLICK + SEPARATOR)) { clickEvents.push(handleClick(token, inner)); } else if (token.equals(CLOSE_TAG + CLICK)) { - clickEvents.pop(); + clickEvents.pollFirst(); } // hover else if (token.startsWith(HOVER + SEPARATOR)) { hoverEvents.push(handleHover(token, inner)); } else if (token.equals(CLOSE_TAG + HOVER)) { - hoverEvents.pop(); + hoverEvents.pollFirst(); } // decoration else if ((deco = resolveDecoration(token)).isPresent()) { @@ -185,7 +185,7 @@ else if ((deco = resolveDecoration(token)).isPresent()) { else if ((color = resolveColor(token)).isPresent()) { colors.push(color.get()); } else if (token.startsWith(CLOSE_TAG) && resolveColor(token.replace(CLOSE_TAG, "")).isPresent()) { - colors.pop(); + colors.pollFirst(); } // keybind else if (token.startsWith(KEYBIND + SEPARATOR)) { diff --git a/minimessage-text/src/test/java/minimessage/MiniMessageParserTest.java b/minimessage-text/src/test/java/minimessage/MiniMessageParserTest.java index de280e6ad..520fedaaf 100644 --- a/minimessage-text/src/test/java/minimessage/MiniMessageParserTest.java +++ b/minimessage-text/src/test/java/minimessage/MiniMessageParserTest.java @@ -197,6 +197,19 @@ public void testInsertion() { test(comp, expected); } + @Test + public void testGH5() { + String input = "» To download it from the internet, >/!\\ install it from Options/ResourcePacks in your game\">CLICK HERE"; + String expected = "{\"text\":\"\",\"extra\":[{\"text\":\"»\",\"color\":\"dark_gray\"},{\"text\":\" To download it from the internet, \",\"color\":\"gray\"},{\"text\":\"CLICK HERE\",\"color\":\"green\",\"bold\":true,\"clickEvent\":{\"action\":\"open_url\",\"value\":\"https://www.google.com\"},\"hoverEvent\":{\"action\":\"show_text\",\"value\":{\"text\":\"/!\\\\ install it from Options/ResourcePacks in your game\",\"color\":\"green\"}}}]}"; + + // should work + Component comp1 = MiniMessageParser.parseFormat(input, "pack_url", "https://www.google.com"); + test(comp1, expected); + + // shouldnt throw an error + MiniMessageParser.parseFormat(input, "url", "https://www.google.com"); + } + private void test(@Nonnull String input, @Nonnull String expected) { test(MiniMessageParser.parseFormat(input), expected); } From 1ad017ed05d957c17f43b74a9fa392e49a2f3a8a Mon Sep 17 00:00:00 2001 From: MiniDigger Date: Sun, 26 Apr 2020 11:59:05 +0200 Subject: [PATCH 036/339] move to proper packages, deprecate old ones --- .../minimessage/MiniMessageParser.java | 314 +++--------------- .../minimessage/MiniMessageSerializer.java | 163 +-------- .../minimessage/{ => bungee}/Constants.java | 2 +- .../minimessage/bungee/MiniMessageParser.java | 306 +++++++++++++++++ .../bungee/MiniMessageSerializer.java | 169 ++++++++++ .../minimessage/{ => text}/Constants.java | 2 +- .../{ => text}/MiniMessageParser.java | 20 +- .../{ => text}/MiniMessageSerializer.java | 26 +- .../text}/MiniMessageParserTest.java | 4 +- .../text}/MiniMessageSerializerTest.java | 4 +- 10 files changed, 555 insertions(+), 455 deletions(-) rename minimessage-bungee/src/main/java/me/minidigger/minimessage/{ => bungee}/Constants.java (93%) create mode 100644 minimessage-bungee/src/main/java/me/minidigger/minimessage/bungee/MiniMessageParser.java create mode 100644 minimessage-bungee/src/main/java/me/minidigger/minimessage/bungee/MiniMessageSerializer.java rename minimessage-text/src/main/java/me/minidigger/minimessage/{ => text}/Constants.java (95%) rename minimessage-text/src/main/java/me/minidigger/minimessage/{ => text}/MiniMessageParser.java (95%) rename minimessage-text/src/main/java/me/minidigger/minimessage/{ => text}/MiniMessageSerializer.java (90%) rename minimessage-text/src/test/java/{minimessage => me/minidigger/minimessage/text}/MiniMessageParserTest.java (99%) rename minimessage-text/src/test/java/{minimessage => me/minidigger/minimessage/text}/MiniMessageSerializerTest.java (98%) diff --git a/minimessage-bungee/src/main/java/me/minidigger/minimessage/MiniMessageParser.java b/minimessage-bungee/src/main/java/me/minidigger/minimessage/MiniMessageParser.java index 85be47ebd..bfa7dcb44 100644 --- a/minimessage-bungee/src/main/java/me/minidigger/minimessage/MiniMessageParser.java +++ b/minimessage-bungee/src/main/java/me/minidigger/minimessage/MiniMessageParser.java @@ -1,306 +1,80 @@ package me.minidigger.minimessage; -import net.md_5.bungee.api.ChatColor; import net.md_5.bungee.api.chat.BaseComponent; -import net.md_5.bungee.api.chat.ClickEvent; -import net.md_5.bungee.api.chat.ComponentBuilder; -import net.md_5.bungee.api.chat.HoverEvent; -import java.util.ArrayDeque; -import java.util.Deque; -import java.util.EnumSet; import java.util.Map; -import java.util.Optional; -import java.util.function.Consumer; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import javax.annotation.Nonnull; -import static me.minidigger.minimessage.Constants.CLICK; -import static me.minidigger.minimessage.Constants.CLOSE_TAG; -import static me.minidigger.minimessage.Constants.HOVER; -import static me.minidigger.minimessage.Constants.SEPARATOR; -import static me.minidigger.minimessage.Constants.TAG_END; -import static me.minidigger.minimessage.Constants.TAG_START; - +/** + * @deprecated use me.minidigger.minimessage.bungee.MiniMessageParser instead + */ +@Deprecated public class MiniMessageParser { - // regex group names - private static final String START = "start"; - private static final String TOKEN = "token"; - private static final String INNER = "inner"; - private static final String END = "end"; - // https://regex101.com/r/8VZ7uA/5 - private static final Pattern pattern = Pattern.compile("((?<)(?([^<>]+)|([^<>]+\"(?[^\"]+)\"))(?>))+?"); + private MiniMessageParser() { + + } + /** + * @deprecated use me.minidigger.minimessage.bungee.MiniMessageParser instead + */ + @Deprecated @Nonnull public static String escapeTokens(@Nonnull String richMessage) { - StringBuilder sb = new StringBuilder(); - Matcher matcher = pattern.matcher(richMessage); - int lastEnd = 0; - while (matcher.find()) { - int startIndex = matcher.start(); - int endIndex = matcher.end(); - - if (startIndex > lastEnd) { - sb.append(richMessage, lastEnd, startIndex); - } - lastEnd = endIndex; - - String start = matcher.group(START); - String token = matcher.group(TOKEN); - String inner = matcher.group(INNER); - String end = matcher.group(END); - - // also escape inner - if (inner != null) { - token = token.replace(inner, escapeTokens(inner)); - } - - sb.append("\\").append(start).append(token).append("\\").append(end); - } - - if (richMessage.length() > lastEnd) { - sb.append(richMessage.substring(lastEnd)); - } - - return sb.toString(); + return me.minidigger.minimessage.bungee.MiniMessageParser.escapeTokens(richMessage); } + /** + * @deprecated use me.minidigger.minimessage.bungee.MiniMessageParser instead + */ + @Deprecated @Nonnull public static String stripTokens(@Nonnull String richMessage) { - StringBuilder sb = new StringBuilder(); - Matcher matcher = pattern.matcher(richMessage); - int lastEnd = 0; - while (matcher.find()) { - int startIndex = matcher.start(); - int endIndex = matcher.end(); - - if (startIndex > lastEnd) { - sb.append(richMessage, lastEnd, startIndex); - } - lastEnd = endIndex; - } - - if (richMessage.length() > lastEnd) { - sb.append(richMessage.substring(lastEnd)); - } - - return sb.toString(); + return me.minidigger.minimessage.bungee.MiniMessageParser.stripTokens(richMessage); } + /** + * @deprecated use me.minidigger.minimessage.bungee.MiniMessageParser instead + */ + @Deprecated @Nonnull public static String handlePlaceholders(@Nonnull String richMessage, @Nonnull String... placeholders) { - if (placeholders.length % 2 != 0) { - throw new ParseException( - "Invalid number placeholders defined, usage: parseFormat(format, key, value, key, value...)"); - } - for (int i = 0; i < placeholders.length; i += 2) { - richMessage = richMessage.replace(TAG_START + placeholders[i] + TAG_END, placeholders[i + 1]); - } - return richMessage; + return me.minidigger.minimessage.bungee.MiniMessageParser.handlePlaceholders(richMessage, placeholders); } + /** + * @deprecated use me.minidigger.minimessage.bungee.MiniMessageParser instead + */ + @Deprecated @Nonnull public static String handlePlaceholders(@Nonnull String richMessage, @Nonnull Map placeholders) { - for (Map.Entry entry : placeholders.entrySet()) { - richMessage = richMessage.replace(TAG_START + entry.getKey() + TAG_END, entry.getValue()); - } - return richMessage; - } - - @Nonnull - public static BaseComponent[] parseFormat(@Nonnull String richMessage, @Nonnull String... placeholders) { - return parseFormat(handlePlaceholders(richMessage, placeholders)); - } - - @Nonnull - public static BaseComponent[] parseFormat(@Nonnull String richMessage, @Nonnull Map placeholders) { - return parseFormat(handlePlaceholders(richMessage, placeholders)); + return me.minidigger.minimessage.bungee.MiniMessageParser.handlePlaceholders(richMessage, placeholders); } + /** + * @deprecated use me.minidigger.minimessage.bungee.MiniMessageParser instead + */ + @Deprecated @Nonnull public static BaseComponent[] parseFormat(@Nonnull String richMessage) { - ComponentBuilder builder = null; - - Deque clickEvents = new ArrayDeque<>(); - Deque hoverEvents = new ArrayDeque<>(); - Deque colors = new ArrayDeque<>(); - EnumSet decorations = EnumSet.noneOf(TextDecoration.class); - - Matcher matcher = pattern.matcher(richMessage); - int lastEnd = 0; - while (matcher.find()) { - int startIndex = matcher.start(); - int endIndex = matcher.end(); - - String msg = null; - if (startIndex > lastEnd) { - msg = richMessage.substring(lastEnd, startIndex); - } - lastEnd = endIndex; - - // handle message - if (msg != null && msg.length() != 0) { - // append message - if (builder == null) { - builder = new ComponentBuilder(msg); - } else { - builder.append(msg, ComponentBuilder.FormatRetention.NONE); - } - - // set everything that is not closed yet - if (!clickEvents.isEmpty()) { - builder.event(clickEvents.peek()); - } - if (!hoverEvents.isEmpty()) { - builder.event(hoverEvents.peek()); - } - if (!colors.isEmpty()) { - builder.color(colors.peek()); - } - if (!decorations.isEmpty()) { - // no lambda because builder isn't effective final :/ - for (TextDecoration decor : decorations) { - decor.apply(builder); - } - } - } - - String token = matcher.group(TOKEN); - String inner = matcher.group(INNER); - - Optional deco; - Optional color; - - // click - if (token.startsWith(CLICK + SEPARATOR)) { - clickEvents.push(handleClick(token, inner)); - } else if (token.equals(CLOSE_TAG + CLICK)) { - clickEvents.pop(); - } - // hover - else if (token.startsWith(HOVER + SEPARATOR)) { - hoverEvents.push(handleHover(token, inner)); - } else if (token.equals(CLOSE_TAG + HOVER)) { - hoverEvents.pop(); - } - // decoration - else if ((deco = resolveDecoration(token)).isPresent()) { - decorations.add(deco.get()); - } else if (token.startsWith(CLOSE_TAG) && (deco = resolveDecoration(token.replace(CLOSE_TAG, ""))).isPresent()) { - decorations.remove(deco.get()); - } - // color - else if ((color = resolveColor(token)).isPresent()) { - colors.push(color.get()); - } else if (token.startsWith(CLOSE_TAG) && resolveColor(token.replace(CLOSE_TAG, "")).isPresent()) { - colors.pop(); - } else { - // invalid tag - if (builder == null) { - builder = new ComponentBuilder(TAG_START + token + TAG_END); - } else { - builder.append(TAG_START + token + TAG_END, ComponentBuilder.FormatRetention.NONE); - } - } - } - - // handle last message part - if (richMessage.length() > lastEnd) { - String msg = richMessage.substring(lastEnd); - // append message - if (builder == null) { - builder = new ComponentBuilder(msg); - } else { - builder.append(msg, ComponentBuilder.FormatRetention.NONE); - } - - // set everything that is not closed yet - if (!clickEvents.isEmpty()) { - builder.event(clickEvents.peek()); - } - if (!hoverEvents.isEmpty()) { - builder.event(hoverEvents.peek()); - } - if (!colors.isEmpty()) { - builder.color(colors.peek()); - } - if (!decorations.isEmpty()) { - // no lambda because builder isn't effective final :/ - for (TextDecoration decor : decorations) { - decor.apply(builder); - } - } - } - - if (builder == null) { - // lets just return an empty component - builder = new ComponentBuilder(""); - } - - return builder.create(); - } - - @Nonnull - private static ClickEvent handleClick(@Nonnull String token, @Nonnull String inner) { - String[] args = token.split(SEPARATOR); - if (args.length < 2) { - throw new ParseException("Can't parse click action (too few args) " + token); - } - ClickEvent.Action action = ClickEvent.Action.valueOf(args[1].toUpperCase()); - return new ClickEvent(action, token.replace(CLICK + SEPARATOR + args[1] + SEPARATOR, "")); + return me.minidigger.minimessage.bungee.MiniMessageParser.parseFormat(richMessage); } + /** + * @deprecated use me.minidigger.minimessage.bungee.MiniMessageParser instead + */ + @Deprecated @Nonnull - private static HoverEvent handleHover(@Nonnull String token, @Nonnull String inner) { - String[] args = token.split(SEPARATOR); - if (args.length < 2) { - throw new ParseException("Can't parse hover action (too few args) " + token); - } - HoverEvent.Action action = HoverEvent.Action.valueOf(args[1].toUpperCase()); - return new HoverEvent(action, parseFormat(inner)); - } - - @Nonnull - private static Optional resolveColor(@Nonnull String token) { - try { - return Optional.of(ChatColor.valueOf(token.toUpperCase())); - } catch (IllegalArgumentException ex) { - return Optional.empty(); - } + public static BaseComponent[] parseFormat(@Nonnull String richMessage, @Nonnull String... placeholders) { + return me.minidigger.minimessage.bungee.MiniMessageParser.parseFormat(richMessage, placeholders); } + /** + * @deprecated use me.minidigger.minimessage.bungee.MiniMessageParser instead + */ + @Deprecated @Nonnull - private static Optional resolveDecoration(@Nonnull String token) { - try { - return Optional.of(TextDecoration.valueOf(token.toUpperCase())); - } catch (IllegalArgumentException ex) { - return Optional.empty(); - } - } - - enum TextDecoration { - BOLD(b -> b.bold(true)), - ITALIC(b -> b.italic(true)), - UNDERLINED(b -> b.underlined(true)), - STRIKETHROUGH(b -> b.strikethrough(true)), - OBFUSCATED(b -> b.obfuscated(true)); - - private final Consumer builder; - - TextDecoration(@Nonnull Consumer builder) { - this.builder = builder; - } - - public void apply(@Nonnull ComponentBuilder comp) { - builder.accept(comp); - } - } - - static class ParseException extends RuntimeException { - public ParseException(@Nonnull String message) { - super(message); - } + public static BaseComponent[] parseFormat(@Nonnull String richMessage, @Nonnull Map placeholders) { + return me.minidigger.minimessage.bungee.MiniMessageParser.parseFormat(richMessage, placeholders); } } diff --git a/minimessage-bungee/src/main/java/me/minidigger/minimessage/MiniMessageSerializer.java b/minimessage-bungee/src/main/java/me/minidigger/minimessage/MiniMessageSerializer.java index 78d38a524..e0f970e94 100644 --- a/minimessage-bungee/src/main/java/me/minidigger/minimessage/MiniMessageSerializer.java +++ b/minimessage-bungee/src/main/java/me/minidigger/minimessage/MiniMessageSerializer.java @@ -1,169 +1,24 @@ package me.minidigger.minimessage; -import net.md_5.bungee.api.ChatColor; import net.md_5.bungee.api.chat.BaseComponent; -import net.md_5.bungee.api.chat.ClickEvent; -import net.md_5.bungee.api.chat.HoverEvent; import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -import static me.minidigger.minimessage.Constants.BOLD; -import static me.minidigger.minimessage.Constants.CLICK; -import static me.minidigger.minimessage.Constants.CLOSE_TAG; -import static me.minidigger.minimessage.Constants.HOVER; -import static me.minidigger.minimessage.Constants.ITALIC; -import static me.minidigger.minimessage.Constants.OBFUSCATED; -import static me.minidigger.minimessage.Constants.SEPARATOR; -import static me.minidigger.minimessage.Constants.STRIKETHROUGH; -import static me.minidigger.minimessage.Constants.TAG_END; -import static me.minidigger.minimessage.Constants.TAG_START; -import static me.minidigger.minimessage.Constants.UNDERLINED; +/** + * @deprecated use me.minidigger.minimessage.bungee.MiniMessageSerializer instead + */ +@Deprecated public final class MiniMessageSerializer { private MiniMessageSerializer(){ } + /** + * @deprecated use me.minidigger.minimessage.bungee.MiniMessageSerializer instead + */ + @Deprecated @Nonnull public static String serialize(@Nonnull BaseComponent... components) { - StringBuilder sb = new StringBuilder(); - - for (int i = 0; i < components.length; i++) { - BaseComponent comp = components[i]; - - // # start tags - - // ## get prev comp - BaseComponent prevComp = null; - if (i > 0) { - prevComp = components[i - 1]; - } - - // ## color - // ### white is not important - if (!ChatColor.WHITE.equals(comp.getColor())) { - sb.append(startColor(comp.getColor())); - } - - // ## decoration - // ### only start if prevComp didn't start - if (comp.isBold() && (prevComp == null || !prevComp.isBold())) { - sb.append(startTag(BOLD)); - } - if (comp.isItalic() && (prevComp == null || !prevComp.isItalic())) { - sb.append(startTag(ITALIC)); - } - if (comp.isObfuscated() && (prevComp == null || !prevComp.isObfuscated())) { - sb.append(startTag(OBFUSCATED)); - } - if (comp.isStrikethrough() && (prevComp == null || !prevComp.isStrikethrough())) { - sb.append(startTag(STRIKETHROUGH)); - } - if (comp.isUnderlined() && (prevComp == null || !prevComp.isUnderlined())) { - sb.append(startTag(UNDERLINED)); - } - - // ## hover - // ### only start if prevComp didn't start the same one - HoverEvent hov = comp.getHoverEvent(); - if (hov != null && (prevComp == null || areDifferent(hov, prevComp.getHoverEvent()))) { - sb.append(startTag(String.format("%s" + SEPARATOR + "%s" + SEPARATOR + "\"%s\"", HOVER, hov.getAction().name().toLowerCase(), serialize(hov.getValue())))); - } - - // ## click - // ### only start if prevComp didn't start the same one - ClickEvent click = comp.getClickEvent(); - if (click != null && (prevComp == null || areDifferent(click, prevComp.getClickEvent()))) { - sb.append(startTag(String.format("%s" + SEPARATOR + "%s" + SEPARATOR + "\"%s\"", CLICK, click.getAction().name().toLowerCase(), click.getValue()))); - } - - // # append text - sb.append(comp.toPlainText()); - - // # end tags - - // ## get next comp - BaseComponent nextComp = null; - if (i + 1 < components.length) { - nextComp = components[i + 1]; - } - - // ## color - // ### only end color if next comp is white and current isn't - if (nextComp != null && comp.getColor() != ChatColor.WHITE) { - if (nextComp.getColor() == ChatColor.WHITE || nextComp.getColor() == null) { - sb.append(endColor(comp.getColor())); - } - } - - // ## decoration - // ### only end decoration if next tag is different - if (nextComp != null) { - if (comp.isBold() && !nextComp.isBold()) { - sb.append(endTag(BOLD)); - } - if (comp.isItalic() && !nextComp.isItalic()) { - sb.append(endTag(ITALIC)); - } - if (comp.isObfuscated() && !nextComp.isObfuscated()) { - sb.append(endTag(OBFUSCATED)); - } - if (comp.isStrikethrough() && !nextComp.isStrikethrough()) { - sb.append(endTag(STRIKETHROUGH)); - } - if (comp.isUnderlined() && !nextComp.isUnderlined()) { - sb.append(endTag(UNDERLINED)); - } - } - - // ## hover - // ### only end hover if next tag is different - if (nextComp != null && comp.getHoverEvent() != null) { - if (areDifferent(comp.getHoverEvent(), nextComp.getHoverEvent())) { - sb.append(endTag(HOVER)); - } - } - - // ## click - // ### only end click if next tag is different - if (nextComp != null && comp.getClickEvent() != null) { - if (areDifferent(comp.getClickEvent(), nextComp.getClickEvent())) { - sb.append(endTag(CLICK)); - } - } - } - - return sb.toString(); - } - - private static boolean areDifferent(@Nonnull ClickEvent c1, @Nullable ClickEvent c2) { - if (c2 == null) return true; - return !c1.equals(c2) && (!c1.getAction().equals(c2.getAction()) || !c1.getValue().equals(c2.getValue())); - } - - private static boolean areDifferent(@Nonnull HoverEvent h1, @Nullable HoverEvent h2) { - if (h2 == null) return true; - return !h1.equals(h2) && (!h1.getAction().equals(h2.getAction()));// TODO also compare value - } - - @Nonnull - private static String startColor(@Nonnull ChatColor color) { - return startTag(color.name().toLowerCase()); - } - - @Nonnull - private static String endColor(@Nonnull ChatColor color) { - return endTag(color.name().toLowerCase()); - } - - @Nonnull - private static String startTag(@Nonnull String content) { - return TAG_START + content + TAG_END; - } - - @Nonnull - private static String endTag(@Nonnull String content) { - return TAG_START + CLOSE_TAG + content + TAG_END; + return me.minidigger.minimessage.bungee.MiniMessageSerializer.serialize(components); } } diff --git a/minimessage-bungee/src/main/java/me/minidigger/minimessage/Constants.java b/minimessage-bungee/src/main/java/me/minidigger/minimessage/bungee/Constants.java similarity index 93% rename from minimessage-bungee/src/main/java/me/minidigger/minimessage/Constants.java rename to minimessage-bungee/src/main/java/me/minidigger/minimessage/bungee/Constants.java index 1f9423479..9a545aa5d 100644 --- a/minimessage-bungee/src/main/java/me/minidigger/minimessage/Constants.java +++ b/minimessage-bungee/src/main/java/me/minidigger/minimessage/bungee/Constants.java @@ -1,4 +1,4 @@ -package me.minidigger.minimessage; +package me.minidigger.minimessage.bungee; class Constants { public static final String CLICK = "click"; diff --git a/minimessage-bungee/src/main/java/me/minidigger/minimessage/bungee/MiniMessageParser.java b/minimessage-bungee/src/main/java/me/minidigger/minimessage/bungee/MiniMessageParser.java new file mode 100644 index 000000000..774e5a390 --- /dev/null +++ b/minimessage-bungee/src/main/java/me/minidigger/minimessage/bungee/MiniMessageParser.java @@ -0,0 +1,306 @@ +package me.minidigger.minimessage.bungee; + +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.chat.BaseComponent; +import net.md_5.bungee.api.chat.ClickEvent; +import net.md_5.bungee.api.chat.ComponentBuilder; +import net.md_5.bungee.api.chat.HoverEvent; + +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.EnumSet; +import java.util.Map; +import java.util.Optional; +import java.util.function.Consumer; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.annotation.Nonnull; + +import static me.minidigger.minimessage.bungee.Constants.CLICK; +import static me.minidigger.minimessage.bungee.Constants.CLOSE_TAG; +import static me.minidigger.minimessage.bungee.Constants.HOVER; +import static me.minidigger.minimessage.bungee.Constants.SEPARATOR; +import static me.minidigger.minimessage.bungee.Constants.TAG_END; +import static me.minidigger.minimessage.bungee.Constants.TAG_START; + +public class MiniMessageParser { + + // regex group names + private static final String START = "start"; + private static final String TOKEN = "token"; + private static final String INNER = "inner"; + private static final String END = "end"; + // https://regex101.com/r/8VZ7uA/5 + private static final Pattern pattern = Pattern.compile("((?<)(?([^<>]+)|([^<>]+\"(?[^\"]+)\"))(?>))+?"); + + @Nonnull + public static String escapeTokens(@Nonnull String richMessage) { + StringBuilder sb = new StringBuilder(); + Matcher matcher = pattern.matcher(richMessage); + int lastEnd = 0; + while (matcher.find()) { + int startIndex = matcher.start(); + int endIndex = matcher.end(); + + if (startIndex > lastEnd) { + sb.append(richMessage, lastEnd, startIndex); + } + lastEnd = endIndex; + + String start = matcher.group(START); + String token = matcher.group(TOKEN); + String inner = matcher.group(INNER); + String end = matcher.group(END); + + // also escape inner + if (inner != null) { + token = token.replace(inner, escapeTokens(inner)); + } + + sb.append("\\").append(start).append(token).append("\\").append(end); + } + + if (richMessage.length() > lastEnd) { + sb.append(richMessage.substring(lastEnd)); + } + + return sb.toString(); + } + + @Nonnull + public static String stripTokens(@Nonnull String richMessage) { + StringBuilder sb = new StringBuilder(); + Matcher matcher = pattern.matcher(richMessage); + int lastEnd = 0; + while (matcher.find()) { + int startIndex = matcher.start(); + int endIndex = matcher.end(); + + if (startIndex > lastEnd) { + sb.append(richMessage, lastEnd, startIndex); + } + lastEnd = endIndex; + } + + if (richMessage.length() > lastEnd) { + sb.append(richMessage.substring(lastEnd)); + } + + return sb.toString(); + } + + @Nonnull + public static String handlePlaceholders(@Nonnull String richMessage, @Nonnull String... placeholders) { + if (placeholders.length % 2 != 0) { + throw new ParseException( + "Invalid number placeholders defined, usage: parseFormat(format, key, value, key, value...)"); + } + for (int i = 0; i < placeholders.length; i += 2) { + richMessage = richMessage.replace(TAG_START + placeholders[i] + TAG_END, placeholders[i + 1]); + } + return richMessage; + } + + @Nonnull + public static String handlePlaceholders(@Nonnull String richMessage, @Nonnull Map placeholders) { + for (Map.Entry entry : placeholders.entrySet()) { + richMessage = richMessage.replace(TAG_START + entry.getKey() + TAG_END, entry.getValue()); + } + return richMessage; + } + + @Nonnull + public static BaseComponent[] parseFormat(@Nonnull String richMessage, @Nonnull String... placeholders) { + return parseFormat(handlePlaceholders(richMessage, placeholders)); + } + + @Nonnull + public static BaseComponent[] parseFormat(@Nonnull String richMessage, @Nonnull Map placeholders) { + return parseFormat(handlePlaceholders(richMessage, placeholders)); + } + + @Nonnull + public static BaseComponent[] parseFormat(@Nonnull String richMessage) { + ComponentBuilder builder = null; + + Deque clickEvents = new ArrayDeque<>(); + Deque hoverEvents = new ArrayDeque<>(); + Deque colors = new ArrayDeque<>(); + EnumSet decorations = EnumSet.noneOf(TextDecoration.class); + + Matcher matcher = pattern.matcher(richMessage); + int lastEnd = 0; + while (matcher.find()) { + int startIndex = matcher.start(); + int endIndex = matcher.end(); + + String msg = null; + if (startIndex > lastEnd) { + msg = richMessage.substring(lastEnd, startIndex); + } + lastEnd = endIndex; + + // handle message + if (msg != null && msg.length() != 0) { + // append message + if (builder == null) { + builder = new ComponentBuilder(msg); + } else { + builder.append(msg, ComponentBuilder.FormatRetention.NONE); + } + + // set everything that is not closed yet + if (!clickEvents.isEmpty()) { + builder.event(clickEvents.peek()); + } + if (!hoverEvents.isEmpty()) { + builder.event(hoverEvents.peek()); + } + if (!colors.isEmpty()) { + builder.color(colors.peek()); + } + if (!decorations.isEmpty()) { + // no lambda because builder isn't effective final :/ + for (TextDecoration decor : decorations) { + decor.apply(builder); + } + } + } + + String token = matcher.group(TOKEN); + String inner = matcher.group(INNER); + + Optional deco; + Optional color; + + // click + if (token.startsWith(CLICK + SEPARATOR)) { + clickEvents.push(handleClick(token, inner)); + } else if (token.equals(CLOSE_TAG + CLICK)) { + clickEvents.pop(); + } + // hover + else if (token.startsWith(HOVER + SEPARATOR)) { + hoverEvents.push(handleHover(token, inner)); + } else if (token.equals(CLOSE_TAG + HOVER)) { + hoverEvents.pop(); + } + // decoration + else if ((deco = resolveDecoration(token)).isPresent()) { + decorations.add(deco.get()); + } else if (token.startsWith(CLOSE_TAG) && (deco = resolveDecoration(token.replace(CLOSE_TAG, ""))).isPresent()) { + decorations.remove(deco.get()); + } + // color + else if ((color = resolveColor(token)).isPresent()) { + colors.push(color.get()); + } else if (token.startsWith(CLOSE_TAG) && resolveColor(token.replace(CLOSE_TAG, "")).isPresent()) { + colors.pop(); + } else { + // invalid tag + if (builder == null) { + builder = new ComponentBuilder(TAG_START + token + TAG_END); + } else { + builder.append(TAG_START + token + TAG_END, ComponentBuilder.FormatRetention.NONE); + } + } + } + + // handle last message part + if (richMessage.length() > lastEnd) { + String msg = richMessage.substring(lastEnd); + // append message + if (builder == null) { + builder = new ComponentBuilder(msg); + } else { + builder.append(msg, ComponentBuilder.FormatRetention.NONE); + } + + // set everything that is not closed yet + if (!clickEvents.isEmpty()) { + builder.event(clickEvents.peek()); + } + if (!hoverEvents.isEmpty()) { + builder.event(hoverEvents.peek()); + } + if (!colors.isEmpty()) { + builder.color(colors.peek()); + } + if (!decorations.isEmpty()) { + // no lambda because builder isn't effective final :/ + for (TextDecoration decor : decorations) { + decor.apply(builder); + } + } + } + + if (builder == null) { + // lets just return an empty component + builder = new ComponentBuilder(""); + } + + return builder.create(); + } + + @Nonnull + private static ClickEvent handleClick(@Nonnull String token, @Nonnull String inner) { + String[] args = token.split(SEPARATOR); + if (args.length < 2) { + throw new ParseException("Can't parse click action (too few args) " + token); + } + ClickEvent.Action action = ClickEvent.Action.valueOf(args[1].toUpperCase()); + return new ClickEvent(action, token.replace(CLICK + SEPARATOR + args[1] + SEPARATOR, "")); + } + + @Nonnull + private static HoverEvent handleHover(@Nonnull String token, @Nonnull String inner) { + String[] args = token.split(SEPARATOR); + if (args.length < 2) { + throw new ParseException("Can't parse hover action (too few args) " + token); + } + HoverEvent.Action action = HoverEvent.Action.valueOf(args[1].toUpperCase()); + return new HoverEvent(action, parseFormat(inner)); + } + + @Nonnull + private static Optional resolveColor(@Nonnull String token) { + try { + return Optional.of(ChatColor.valueOf(token.toUpperCase())); + } catch (IllegalArgumentException ex) { + return Optional.empty(); + } + } + + @Nonnull + private static Optional resolveDecoration(@Nonnull String token) { + try { + return Optional.of(TextDecoration.valueOf(token.toUpperCase())); + } catch (IllegalArgumentException ex) { + return Optional.empty(); + } + } + + enum TextDecoration { + BOLD(b -> b.bold(true)), + ITALIC(b -> b.italic(true)), + UNDERLINED(b -> b.underlined(true)), + STRIKETHROUGH(b -> b.strikethrough(true)), + OBFUSCATED(b -> b.obfuscated(true)); + + private final Consumer builder; + + TextDecoration(@Nonnull Consumer builder) { + this.builder = builder; + } + + public void apply(@Nonnull ComponentBuilder comp) { + builder.accept(comp); + } + } + + static class ParseException extends RuntimeException { + public ParseException(@Nonnull String message) { + super(message); + } + } +} diff --git a/minimessage-bungee/src/main/java/me/minidigger/minimessage/bungee/MiniMessageSerializer.java b/minimessage-bungee/src/main/java/me/minidigger/minimessage/bungee/MiniMessageSerializer.java new file mode 100644 index 000000000..56817bb8a --- /dev/null +++ b/minimessage-bungee/src/main/java/me/minidigger/minimessage/bungee/MiniMessageSerializer.java @@ -0,0 +1,169 @@ +package me.minidigger.minimessage.bungee; + +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.chat.BaseComponent; +import net.md_5.bungee.api.chat.ClickEvent; +import net.md_5.bungee.api.chat.HoverEvent; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import static me.minidigger.minimessage.bungee.Constants.BOLD; +import static me.minidigger.minimessage.bungee.Constants.CLICK; +import static me.minidigger.minimessage.bungee.Constants.CLOSE_TAG; +import static me.minidigger.minimessage.bungee.Constants.HOVER; +import static me.minidigger.minimessage.bungee.Constants.ITALIC; +import static me.minidigger.minimessage.bungee.Constants.OBFUSCATED; +import static me.minidigger.minimessage.bungee.Constants.SEPARATOR; +import static me.minidigger.minimessage.bungee.Constants.STRIKETHROUGH; +import static me.minidigger.minimessage.bungee.Constants.TAG_END; +import static me.minidigger.minimessage.bungee.Constants.TAG_START; +import static me.minidigger.minimessage.bungee.Constants.UNDERLINED; + +public final class MiniMessageSerializer { + + private MiniMessageSerializer(){ + } + + @Nonnull + public static String serialize(@Nonnull BaseComponent... components) { + StringBuilder sb = new StringBuilder(); + + for (int i = 0; i < components.length; i++) { + BaseComponent comp = components[i]; + + // # start tags + + // ## get prev comp + BaseComponent prevComp = null; + if (i > 0) { + prevComp = components[i - 1]; + } + + // ## color + // ### white is not important + if (!ChatColor.WHITE.equals(comp.getColor())) { + sb.append(startColor(comp.getColor())); + } + + // ## decoration + // ### only start if prevComp didn't start + if (comp.isBold() && (prevComp == null || !prevComp.isBold())) { + sb.append(startTag(BOLD)); + } + if (comp.isItalic() && (prevComp == null || !prevComp.isItalic())) { + sb.append(startTag(ITALIC)); + } + if (comp.isObfuscated() && (prevComp == null || !prevComp.isObfuscated())) { + sb.append(startTag(OBFUSCATED)); + } + if (comp.isStrikethrough() && (prevComp == null || !prevComp.isStrikethrough())) { + sb.append(startTag(STRIKETHROUGH)); + } + if (comp.isUnderlined() && (prevComp == null || !prevComp.isUnderlined())) { + sb.append(startTag(UNDERLINED)); + } + + // ## hover + // ### only start if prevComp didn't start the same one + HoverEvent hov = comp.getHoverEvent(); + if (hov != null && (prevComp == null || areDifferent(hov, prevComp.getHoverEvent()))) { + sb.append(startTag(String.format("%s" + SEPARATOR + "%s" + SEPARATOR + "\"%s\"", HOVER, hov.getAction().name().toLowerCase(), serialize(hov.getValue())))); + } + + // ## click + // ### only start if prevComp didn't start the same one + ClickEvent click = comp.getClickEvent(); + if (click != null && (prevComp == null || areDifferent(click, prevComp.getClickEvent()))) { + sb.append(startTag(String.format("%s" + SEPARATOR + "%s" + SEPARATOR + "\"%s\"", CLICK, click.getAction().name().toLowerCase(), click.getValue()))); + } + + // # append text + sb.append(comp.toPlainText()); + + // # end tags + + // ## get next comp + BaseComponent nextComp = null; + if (i + 1 < components.length) { + nextComp = components[i + 1]; + } + + // ## color + // ### only end color if next comp is white and current isn't + if (nextComp != null && comp.getColor() != ChatColor.WHITE) { + if (nextComp.getColor() == ChatColor.WHITE || nextComp.getColor() == null) { + sb.append(endColor(comp.getColor())); + } + } + + // ## decoration + // ### only end decoration if next tag is different + if (nextComp != null) { + if (comp.isBold() && !nextComp.isBold()) { + sb.append(endTag(BOLD)); + } + if (comp.isItalic() && !nextComp.isItalic()) { + sb.append(endTag(ITALIC)); + } + if (comp.isObfuscated() && !nextComp.isObfuscated()) { + sb.append(endTag(OBFUSCATED)); + } + if (comp.isStrikethrough() && !nextComp.isStrikethrough()) { + sb.append(endTag(STRIKETHROUGH)); + } + if (comp.isUnderlined() && !nextComp.isUnderlined()) { + sb.append(endTag(UNDERLINED)); + } + } + + // ## hover + // ### only end hover if next tag is different + if (nextComp != null && comp.getHoverEvent() != null) { + if (areDifferent(comp.getHoverEvent(), nextComp.getHoverEvent())) { + sb.append(endTag(HOVER)); + } + } + + // ## click + // ### only end click if next tag is different + if (nextComp != null && comp.getClickEvent() != null) { + if (areDifferent(comp.getClickEvent(), nextComp.getClickEvent())) { + sb.append(endTag(CLICK)); + } + } + } + + return sb.toString(); + } + + private static boolean areDifferent(@Nonnull ClickEvent c1, @Nullable ClickEvent c2) { + if (c2 == null) return true; + return !c1.equals(c2) && (!c1.getAction().equals(c2.getAction()) || !c1.getValue().equals(c2.getValue())); + } + + private static boolean areDifferent(@Nonnull HoverEvent h1, @Nullable HoverEvent h2) { + if (h2 == null) return true; + return !h1.equals(h2) && (!h1.getAction().equals(h2.getAction()));// TODO also compare value + } + + @Nonnull + private static String startColor(@Nonnull ChatColor color) { + return startTag(color.name().toLowerCase()); + } + + @Nonnull + private static String endColor(@Nonnull ChatColor color) { + return endTag(color.name().toLowerCase()); + } + + @Nonnull + private static String startTag(@Nonnull String content) { + return TAG_START + content + TAG_END; + } + + @Nonnull + private static String endTag(@Nonnull String content) { + return TAG_START + CLOSE_TAG + content + TAG_END; + } +} diff --git a/minimessage-text/src/main/java/me/minidigger/minimessage/Constants.java b/minimessage-text/src/main/java/me/minidigger/minimessage/text/Constants.java similarity index 95% rename from minimessage-text/src/main/java/me/minidigger/minimessage/Constants.java rename to minimessage-text/src/main/java/me/minidigger/minimessage/text/Constants.java index 6f9ad71f0..76012f6ae 100644 --- a/minimessage-text/src/main/java/me/minidigger/minimessage/Constants.java +++ b/minimessage-text/src/main/java/me/minidigger/minimessage/text/Constants.java @@ -1,4 +1,4 @@ -package me.minidigger.minimessage; +package me.minidigger.minimessage.text; class Constants { public static final String CLICK = "click"; diff --git a/minimessage-text/src/main/java/me/minidigger/minimessage/MiniMessageParser.java b/minimessage-text/src/main/java/me/minidigger/minimessage/text/MiniMessageParser.java similarity index 95% rename from minimessage-text/src/main/java/me/minidigger/minimessage/MiniMessageParser.java rename to minimessage-text/src/main/java/me/minidigger/minimessage/text/MiniMessageParser.java index 73a970012..27de80182 100644 --- a/minimessage-text/src/main/java/me/minidigger/minimessage/MiniMessageParser.java +++ b/minimessage-text/src/main/java/me/minidigger/minimessage/text/MiniMessageParser.java @@ -1,4 +1,4 @@ -package me.minidigger.minimessage; +package me.minidigger.minimessage.text; import net.kyori.text.Component; import net.kyori.text.KeybindComponent; @@ -20,15 +20,15 @@ import java.util.regex.Pattern; import javax.annotation.Nonnull; -import static me.minidigger.minimessage.Constants.CLICK; -import static me.minidigger.minimessage.Constants.CLOSE_TAG; -import static me.minidigger.minimessage.Constants.HOVER; -import static me.minidigger.minimessage.Constants.INSERTION; -import static me.minidigger.minimessage.Constants.KEYBIND; -import static me.minidigger.minimessage.Constants.SEPARATOR; -import static me.minidigger.minimessage.Constants.TAG_END; -import static me.minidigger.minimessage.Constants.TAG_START; -import static me.minidigger.minimessage.Constants.TRANSLATABLE; +import static me.minidigger.minimessage.text.Constants.CLICK; +import static me.minidigger.minimessage.text.Constants.CLOSE_TAG; +import static me.minidigger.minimessage.text.Constants.HOVER; +import static me.minidigger.minimessage.text.Constants.INSERTION; +import static me.minidigger.minimessage.text.Constants.KEYBIND; +import static me.minidigger.minimessage.text.Constants.SEPARATOR; +import static me.minidigger.minimessage.text.Constants.TAG_END; +import static me.minidigger.minimessage.text.Constants.TAG_START; +import static me.minidigger.minimessage.text.Constants.TRANSLATABLE; public class MiniMessageParser { diff --git a/minimessage-text/src/main/java/me/minidigger/minimessage/MiniMessageSerializer.java b/minimessage-text/src/main/java/me/minidigger/minimessage/text/MiniMessageSerializer.java similarity index 90% rename from minimessage-text/src/main/java/me/minidigger/minimessage/MiniMessageSerializer.java rename to minimessage-text/src/main/java/me/minidigger/minimessage/text/MiniMessageSerializer.java index e9db036dd..a6eb3db61 100644 --- a/minimessage-text/src/main/java/me/minidigger/minimessage/MiniMessageSerializer.java +++ b/minimessage-text/src/main/java/me/minidigger/minimessage/text/MiniMessageSerializer.java @@ -1,4 +1,4 @@ -package me.minidigger.minimessage; +package me.minidigger.minimessage.text; import net.kyori.text.Component; import net.kyori.text.KeybindComponent; @@ -15,18 +15,18 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; -import static me.minidigger.minimessage.Constants.BOLD; -import static me.minidigger.minimessage.Constants.CLICK; -import static me.minidigger.minimessage.Constants.CLOSE_TAG; -import static me.minidigger.minimessage.Constants.HOVER; -import static me.minidigger.minimessage.Constants.INSERTION; -import static me.minidigger.minimessage.Constants.ITALIC; -import static me.minidigger.minimessage.Constants.OBFUSCATED; -import static me.minidigger.minimessage.Constants.SEPARATOR; -import static me.minidigger.minimessage.Constants.STRIKETHROUGH; -import static me.minidigger.minimessage.Constants.TAG_END; -import static me.minidigger.minimessage.Constants.TAG_START; -import static me.minidigger.minimessage.Constants.UNDERLINED; +import static me.minidigger.minimessage.text.Constants.BOLD; +import static me.minidigger.minimessage.text.Constants.CLICK; +import static me.minidigger.minimessage.text.Constants.CLOSE_TAG; +import static me.minidigger.minimessage.text.Constants.HOVER; +import static me.minidigger.minimessage.text.Constants.INSERTION; +import static me.minidigger.minimessage.text.Constants.ITALIC; +import static me.minidigger.minimessage.text.Constants.OBFUSCATED; +import static me.minidigger.minimessage.text.Constants.SEPARATOR; +import static me.minidigger.minimessage.text.Constants.STRIKETHROUGH; +import static me.minidigger.minimessage.text.Constants.TAG_END; +import static me.minidigger.minimessage.text.Constants.TAG_START; +import static me.minidigger.minimessage.text.Constants.UNDERLINED; public final class MiniMessageSerializer { diff --git a/minimessage-text/src/test/java/minimessage/MiniMessageParserTest.java b/minimessage-text/src/test/java/me/minidigger/minimessage/text/MiniMessageParserTest.java similarity index 99% rename from minimessage-text/src/test/java/minimessage/MiniMessageParserTest.java rename to minimessage-text/src/test/java/me/minidigger/minimessage/text/MiniMessageParserTest.java index 520fedaaf..379d781e1 100644 --- a/minimessage-text/src/test/java/minimessage/MiniMessageParserTest.java +++ b/minimessage-text/src/test/java/me/minidigger/minimessage/text/MiniMessageParserTest.java @@ -1,4 +1,4 @@ -package minimessage; +package me.minidigger.minimessage.text; import net.kyori.text.Component; import net.kyori.text.serializer.gson.GsonComponentSerializer; @@ -7,8 +7,6 @@ import javax.annotation.Nonnull; -import me.minidigger.minimessage.MiniMessageParser; - import static org.junit.Assert.assertEquals; public class MiniMessageParserTest { diff --git a/minimessage-text/src/test/java/minimessage/MiniMessageSerializerTest.java b/minimessage-text/src/test/java/me/minidigger/minimessage/text/MiniMessageSerializerTest.java similarity index 98% rename from minimessage-text/src/test/java/minimessage/MiniMessageSerializerTest.java rename to minimessage-text/src/test/java/me/minidigger/minimessage/text/MiniMessageSerializerTest.java index 00c7085c9..f5248b031 100644 --- a/minimessage-text/src/test/java/minimessage/MiniMessageSerializerTest.java +++ b/minimessage-text/src/test/java/me/minidigger/minimessage/text/MiniMessageSerializerTest.java @@ -1,4 +1,4 @@ -package minimessage; +package me.minidigger.minimessage.text; import net.kyori.text.KeybindComponent; import net.kyori.text.TextComponent; @@ -13,8 +13,6 @@ import javax.annotation.Nonnull; -import me.minidigger.minimessage.MiniMessageSerializer; - import static org.junit.Assert.assertEquals; public class MiniMessageSerializerTest { From 707c4b2141bd43ba8d2fd7593b6f0605a660aaac Mon Sep 17 00:00:00 2001 From: MiniDigger Date: Fri, 24 Apr 2020 20:01:58 +0200 Subject: [PATCH 037/339] readme stuff --- .github/workflows/Publish.yml | 5 ++++- README.md | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/Publish.yml b/.github/workflows/Publish.yml index 01746aa0f..aeed9d8be 100644 --- a/.github/workflows/Publish.yml +++ b/.github/workflows/Publish.yml @@ -1,6 +1,9 @@ name: Publish -on: [push] +on: + push: + branches: + - master jobs: publish: diff --git a/README.md b/README.md index 682c66fd6..f5bfa3c56 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,7 @@ # MiniMessage +[![CodeFactor](https://www.codefactor.io/repository/github/minidigger/minimessage/badge)](https://www.codefactor.io/repository/github/minidigger/minimessage) +[![GitHub license](https://img.shields.io/github/license/MiniDigger/MiniMessage?style=flat-square)](https://github.com/MiniDigger/MiniMessage/blob/master/LICENSE) +![Maven Central](https://img.shields.io/maven-central/v/me.minidigger/MiniMessage?style=flat-square) Simple library that implements an easy to use textual format to send rich json messages. @@ -95,7 +98,7 @@ Artifact: me.minidigger MiniMessage - 1.0.0-SNAPSHOT + 2.0.3 compile ``` From 1690f28cc59af1f5c35c0ec0c43851830585d89d Mon Sep 17 00:00:00 2001 From: MiniDigger Date: Fri, 24 Apr 2020 21:43:28 +0200 Subject: [PATCH 038/339] fix #GH-5 --- .../minidigger/minimessage/MiniMessageParser.java | 14 +++++++------- .../java/minimessage/MiniMessageParserTest.java | 13 +++++++++++++ 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/minimessage-text/src/main/java/me/minidigger/minimessage/MiniMessageParser.java b/minimessage-text/src/main/java/me/minidigger/minimessage/MiniMessageParser.java index 5c3a84e6f..73a970012 100644 --- a/minimessage-text/src/main/java/me/minidigger/minimessage/MiniMessageParser.java +++ b/minimessage-text/src/main/java/me/minidigger/minimessage/MiniMessageParser.java @@ -130,10 +130,10 @@ public static Component parseFormat(@Nonnull String richMessage, @Nonnull Map clickEvents = new ArrayDeque<>(); - Deque hoverEvents = new ArrayDeque<>(); - Deque colors = new ArrayDeque<>(); - Deque insertions = new ArrayDeque<>(); + ArrayDeque clickEvents = new ArrayDeque<>(); + ArrayDeque hoverEvents = new ArrayDeque<>(); + ArrayDeque colors = new ArrayDeque<>(); + ArrayDeque insertions = new ArrayDeque<>(); EnumSet decorations = EnumSet.noneOf(HelperTextDecoration.class); Matcher matcher = pattern.matcher(richMessage); @@ -167,13 +167,13 @@ public static Component parseFormat(@Nonnull String richMessage) { if (token.startsWith(CLICK + SEPARATOR)) { clickEvents.push(handleClick(token, inner)); } else if (token.equals(CLOSE_TAG + CLICK)) { - clickEvents.pop(); + clickEvents.pollFirst(); } // hover else if (token.startsWith(HOVER + SEPARATOR)) { hoverEvents.push(handleHover(token, inner)); } else if (token.equals(CLOSE_TAG + HOVER)) { - hoverEvents.pop(); + hoverEvents.pollFirst(); } // decoration else if ((deco = resolveDecoration(token)).isPresent()) { @@ -185,7 +185,7 @@ else if ((deco = resolveDecoration(token)).isPresent()) { else if ((color = resolveColor(token)).isPresent()) { colors.push(color.get()); } else if (token.startsWith(CLOSE_TAG) && resolveColor(token.replace(CLOSE_TAG, "")).isPresent()) { - colors.pop(); + colors.pollFirst(); } // keybind else if (token.startsWith(KEYBIND + SEPARATOR)) { diff --git a/minimessage-text/src/test/java/minimessage/MiniMessageParserTest.java b/minimessage-text/src/test/java/minimessage/MiniMessageParserTest.java index de280e6ad..520fedaaf 100644 --- a/minimessage-text/src/test/java/minimessage/MiniMessageParserTest.java +++ b/minimessage-text/src/test/java/minimessage/MiniMessageParserTest.java @@ -197,6 +197,19 @@ public void testInsertion() { test(comp, expected); } + @Test + public void testGH5() { + String input = "» To download it from the internet, >/!\\ install it from Options/ResourcePacks in your game\">CLICK HERE"; + String expected = "{\"text\":\"\",\"extra\":[{\"text\":\"»\",\"color\":\"dark_gray\"},{\"text\":\" To download it from the internet, \",\"color\":\"gray\"},{\"text\":\"CLICK HERE\",\"color\":\"green\",\"bold\":true,\"clickEvent\":{\"action\":\"open_url\",\"value\":\"https://www.google.com\"},\"hoverEvent\":{\"action\":\"show_text\",\"value\":{\"text\":\"/!\\\\ install it from Options/ResourcePacks in your game\",\"color\":\"green\"}}}]}"; + + // should work + Component comp1 = MiniMessageParser.parseFormat(input, "pack_url", "https://www.google.com"); + test(comp1, expected); + + // shouldnt throw an error + MiniMessageParser.parseFormat(input, "url", "https://www.google.com"); + } + private void test(@Nonnull String input, @Nonnull String expected) { test(MiniMessageParser.parseFormat(input), expected); } From c0265bd8f94bea92e141ffd632a711381c4b9d34 Mon Sep 17 00:00:00 2001 From: MiniDigger Date: Sun, 26 Apr 2020 11:59:05 +0200 Subject: [PATCH 039/339] move to proper packages, deprecate old ones --- .../minimessage/MiniMessageParser.java | 314 +++--------------- .../minimessage/MiniMessageSerializer.java | 163 +-------- .../minimessage/{ => bungee}/Constants.java | 2 +- .../minimessage/bungee/MiniMessageParser.java | 306 +++++++++++++++++ .../bungee/MiniMessageSerializer.java | 169 ++++++++++ .../minimessage/{ => text}/Constants.java | 2 +- .../{ => text}/MiniMessageParser.java | 20 +- .../{ => text}/MiniMessageSerializer.java | 26 +- .../text}/MiniMessageParserTest.java | 4 +- .../text}/MiniMessageSerializerTest.java | 4 +- 10 files changed, 555 insertions(+), 455 deletions(-) rename minimessage-bungee/src/main/java/me/minidigger/minimessage/{ => bungee}/Constants.java (93%) create mode 100644 minimessage-bungee/src/main/java/me/minidigger/minimessage/bungee/MiniMessageParser.java create mode 100644 minimessage-bungee/src/main/java/me/minidigger/minimessage/bungee/MiniMessageSerializer.java rename minimessage-text/src/main/java/me/minidigger/minimessage/{ => text}/Constants.java (95%) rename minimessage-text/src/main/java/me/minidigger/minimessage/{ => text}/MiniMessageParser.java (95%) rename minimessage-text/src/main/java/me/minidigger/minimessage/{ => text}/MiniMessageSerializer.java (90%) rename minimessage-text/src/test/java/{minimessage => me/minidigger/minimessage/text}/MiniMessageParserTest.java (99%) rename minimessage-text/src/test/java/{minimessage => me/minidigger/minimessage/text}/MiniMessageSerializerTest.java (98%) diff --git a/minimessage-bungee/src/main/java/me/minidigger/minimessage/MiniMessageParser.java b/minimessage-bungee/src/main/java/me/minidigger/minimessage/MiniMessageParser.java index 85be47ebd..bfa7dcb44 100644 --- a/minimessage-bungee/src/main/java/me/minidigger/minimessage/MiniMessageParser.java +++ b/minimessage-bungee/src/main/java/me/minidigger/minimessage/MiniMessageParser.java @@ -1,306 +1,80 @@ package me.minidigger.minimessage; -import net.md_5.bungee.api.ChatColor; import net.md_5.bungee.api.chat.BaseComponent; -import net.md_5.bungee.api.chat.ClickEvent; -import net.md_5.bungee.api.chat.ComponentBuilder; -import net.md_5.bungee.api.chat.HoverEvent; -import java.util.ArrayDeque; -import java.util.Deque; -import java.util.EnumSet; import java.util.Map; -import java.util.Optional; -import java.util.function.Consumer; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import javax.annotation.Nonnull; -import static me.minidigger.minimessage.Constants.CLICK; -import static me.minidigger.minimessage.Constants.CLOSE_TAG; -import static me.minidigger.minimessage.Constants.HOVER; -import static me.minidigger.minimessage.Constants.SEPARATOR; -import static me.minidigger.minimessage.Constants.TAG_END; -import static me.minidigger.minimessage.Constants.TAG_START; - +/** + * @deprecated use me.minidigger.minimessage.bungee.MiniMessageParser instead + */ +@Deprecated public class MiniMessageParser { - // regex group names - private static final String START = "start"; - private static final String TOKEN = "token"; - private static final String INNER = "inner"; - private static final String END = "end"; - // https://regex101.com/r/8VZ7uA/5 - private static final Pattern pattern = Pattern.compile("((?<)(?([^<>]+)|([^<>]+\"(?[^\"]+)\"))(?>))+?"); + private MiniMessageParser() { + + } + /** + * @deprecated use me.minidigger.minimessage.bungee.MiniMessageParser instead + */ + @Deprecated @Nonnull public static String escapeTokens(@Nonnull String richMessage) { - StringBuilder sb = new StringBuilder(); - Matcher matcher = pattern.matcher(richMessage); - int lastEnd = 0; - while (matcher.find()) { - int startIndex = matcher.start(); - int endIndex = matcher.end(); - - if (startIndex > lastEnd) { - sb.append(richMessage, lastEnd, startIndex); - } - lastEnd = endIndex; - - String start = matcher.group(START); - String token = matcher.group(TOKEN); - String inner = matcher.group(INNER); - String end = matcher.group(END); - - // also escape inner - if (inner != null) { - token = token.replace(inner, escapeTokens(inner)); - } - - sb.append("\\").append(start).append(token).append("\\").append(end); - } - - if (richMessage.length() > lastEnd) { - sb.append(richMessage.substring(lastEnd)); - } - - return sb.toString(); + return me.minidigger.minimessage.bungee.MiniMessageParser.escapeTokens(richMessage); } + /** + * @deprecated use me.minidigger.minimessage.bungee.MiniMessageParser instead + */ + @Deprecated @Nonnull public static String stripTokens(@Nonnull String richMessage) { - StringBuilder sb = new StringBuilder(); - Matcher matcher = pattern.matcher(richMessage); - int lastEnd = 0; - while (matcher.find()) { - int startIndex = matcher.start(); - int endIndex = matcher.end(); - - if (startIndex > lastEnd) { - sb.append(richMessage, lastEnd, startIndex); - } - lastEnd = endIndex; - } - - if (richMessage.length() > lastEnd) { - sb.append(richMessage.substring(lastEnd)); - } - - return sb.toString(); + return me.minidigger.minimessage.bungee.MiniMessageParser.stripTokens(richMessage); } + /** + * @deprecated use me.minidigger.minimessage.bungee.MiniMessageParser instead + */ + @Deprecated @Nonnull public static String handlePlaceholders(@Nonnull String richMessage, @Nonnull String... placeholders) { - if (placeholders.length % 2 != 0) { - throw new ParseException( - "Invalid number placeholders defined, usage: parseFormat(format, key, value, key, value...)"); - } - for (int i = 0; i < placeholders.length; i += 2) { - richMessage = richMessage.replace(TAG_START + placeholders[i] + TAG_END, placeholders[i + 1]); - } - return richMessage; + return me.minidigger.minimessage.bungee.MiniMessageParser.handlePlaceholders(richMessage, placeholders); } + /** + * @deprecated use me.minidigger.minimessage.bungee.MiniMessageParser instead + */ + @Deprecated @Nonnull public static String handlePlaceholders(@Nonnull String richMessage, @Nonnull Map placeholders) { - for (Map.Entry entry : placeholders.entrySet()) { - richMessage = richMessage.replace(TAG_START + entry.getKey() + TAG_END, entry.getValue()); - } - return richMessage; - } - - @Nonnull - public static BaseComponent[] parseFormat(@Nonnull String richMessage, @Nonnull String... placeholders) { - return parseFormat(handlePlaceholders(richMessage, placeholders)); - } - - @Nonnull - public static BaseComponent[] parseFormat(@Nonnull String richMessage, @Nonnull Map placeholders) { - return parseFormat(handlePlaceholders(richMessage, placeholders)); + return me.minidigger.minimessage.bungee.MiniMessageParser.handlePlaceholders(richMessage, placeholders); } + /** + * @deprecated use me.minidigger.minimessage.bungee.MiniMessageParser instead + */ + @Deprecated @Nonnull public static BaseComponent[] parseFormat(@Nonnull String richMessage) { - ComponentBuilder builder = null; - - Deque clickEvents = new ArrayDeque<>(); - Deque hoverEvents = new ArrayDeque<>(); - Deque colors = new ArrayDeque<>(); - EnumSet decorations = EnumSet.noneOf(TextDecoration.class); - - Matcher matcher = pattern.matcher(richMessage); - int lastEnd = 0; - while (matcher.find()) { - int startIndex = matcher.start(); - int endIndex = matcher.end(); - - String msg = null; - if (startIndex > lastEnd) { - msg = richMessage.substring(lastEnd, startIndex); - } - lastEnd = endIndex; - - // handle message - if (msg != null && msg.length() != 0) { - // append message - if (builder == null) { - builder = new ComponentBuilder(msg); - } else { - builder.append(msg, ComponentBuilder.FormatRetention.NONE); - } - - // set everything that is not closed yet - if (!clickEvents.isEmpty()) { - builder.event(clickEvents.peek()); - } - if (!hoverEvents.isEmpty()) { - builder.event(hoverEvents.peek()); - } - if (!colors.isEmpty()) { - builder.color(colors.peek()); - } - if (!decorations.isEmpty()) { - // no lambda because builder isn't effective final :/ - for (TextDecoration decor : decorations) { - decor.apply(builder); - } - } - } - - String token = matcher.group(TOKEN); - String inner = matcher.group(INNER); - - Optional deco; - Optional color; - - // click - if (token.startsWith(CLICK + SEPARATOR)) { - clickEvents.push(handleClick(token, inner)); - } else if (token.equals(CLOSE_TAG + CLICK)) { - clickEvents.pop(); - } - // hover - else if (token.startsWith(HOVER + SEPARATOR)) { - hoverEvents.push(handleHover(token, inner)); - } else if (token.equals(CLOSE_TAG + HOVER)) { - hoverEvents.pop(); - } - // decoration - else if ((deco = resolveDecoration(token)).isPresent()) { - decorations.add(deco.get()); - } else if (token.startsWith(CLOSE_TAG) && (deco = resolveDecoration(token.replace(CLOSE_TAG, ""))).isPresent()) { - decorations.remove(deco.get()); - } - // color - else if ((color = resolveColor(token)).isPresent()) { - colors.push(color.get()); - } else if (token.startsWith(CLOSE_TAG) && resolveColor(token.replace(CLOSE_TAG, "")).isPresent()) { - colors.pop(); - } else { - // invalid tag - if (builder == null) { - builder = new ComponentBuilder(TAG_START + token + TAG_END); - } else { - builder.append(TAG_START + token + TAG_END, ComponentBuilder.FormatRetention.NONE); - } - } - } - - // handle last message part - if (richMessage.length() > lastEnd) { - String msg = richMessage.substring(lastEnd); - // append message - if (builder == null) { - builder = new ComponentBuilder(msg); - } else { - builder.append(msg, ComponentBuilder.FormatRetention.NONE); - } - - // set everything that is not closed yet - if (!clickEvents.isEmpty()) { - builder.event(clickEvents.peek()); - } - if (!hoverEvents.isEmpty()) { - builder.event(hoverEvents.peek()); - } - if (!colors.isEmpty()) { - builder.color(colors.peek()); - } - if (!decorations.isEmpty()) { - // no lambda because builder isn't effective final :/ - for (TextDecoration decor : decorations) { - decor.apply(builder); - } - } - } - - if (builder == null) { - // lets just return an empty component - builder = new ComponentBuilder(""); - } - - return builder.create(); - } - - @Nonnull - private static ClickEvent handleClick(@Nonnull String token, @Nonnull String inner) { - String[] args = token.split(SEPARATOR); - if (args.length < 2) { - throw new ParseException("Can't parse click action (too few args) " + token); - } - ClickEvent.Action action = ClickEvent.Action.valueOf(args[1].toUpperCase()); - return new ClickEvent(action, token.replace(CLICK + SEPARATOR + args[1] + SEPARATOR, "")); + return me.minidigger.minimessage.bungee.MiniMessageParser.parseFormat(richMessage); } + /** + * @deprecated use me.minidigger.minimessage.bungee.MiniMessageParser instead + */ + @Deprecated @Nonnull - private static HoverEvent handleHover(@Nonnull String token, @Nonnull String inner) { - String[] args = token.split(SEPARATOR); - if (args.length < 2) { - throw new ParseException("Can't parse hover action (too few args) " + token); - } - HoverEvent.Action action = HoverEvent.Action.valueOf(args[1].toUpperCase()); - return new HoverEvent(action, parseFormat(inner)); - } - - @Nonnull - private static Optional resolveColor(@Nonnull String token) { - try { - return Optional.of(ChatColor.valueOf(token.toUpperCase())); - } catch (IllegalArgumentException ex) { - return Optional.empty(); - } + public static BaseComponent[] parseFormat(@Nonnull String richMessage, @Nonnull String... placeholders) { + return me.minidigger.minimessage.bungee.MiniMessageParser.parseFormat(richMessage, placeholders); } + /** + * @deprecated use me.minidigger.minimessage.bungee.MiniMessageParser instead + */ + @Deprecated @Nonnull - private static Optional resolveDecoration(@Nonnull String token) { - try { - return Optional.of(TextDecoration.valueOf(token.toUpperCase())); - } catch (IllegalArgumentException ex) { - return Optional.empty(); - } - } - - enum TextDecoration { - BOLD(b -> b.bold(true)), - ITALIC(b -> b.italic(true)), - UNDERLINED(b -> b.underlined(true)), - STRIKETHROUGH(b -> b.strikethrough(true)), - OBFUSCATED(b -> b.obfuscated(true)); - - private final Consumer builder; - - TextDecoration(@Nonnull Consumer builder) { - this.builder = builder; - } - - public void apply(@Nonnull ComponentBuilder comp) { - builder.accept(comp); - } - } - - static class ParseException extends RuntimeException { - public ParseException(@Nonnull String message) { - super(message); - } + public static BaseComponent[] parseFormat(@Nonnull String richMessage, @Nonnull Map placeholders) { + return me.minidigger.minimessage.bungee.MiniMessageParser.parseFormat(richMessage, placeholders); } } diff --git a/minimessage-bungee/src/main/java/me/minidigger/minimessage/MiniMessageSerializer.java b/minimessage-bungee/src/main/java/me/minidigger/minimessage/MiniMessageSerializer.java index 78d38a524..e0f970e94 100644 --- a/minimessage-bungee/src/main/java/me/minidigger/minimessage/MiniMessageSerializer.java +++ b/minimessage-bungee/src/main/java/me/minidigger/minimessage/MiniMessageSerializer.java @@ -1,169 +1,24 @@ package me.minidigger.minimessage; -import net.md_5.bungee.api.ChatColor; import net.md_5.bungee.api.chat.BaseComponent; -import net.md_5.bungee.api.chat.ClickEvent; -import net.md_5.bungee.api.chat.HoverEvent; import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -import static me.minidigger.minimessage.Constants.BOLD; -import static me.minidigger.minimessage.Constants.CLICK; -import static me.minidigger.minimessage.Constants.CLOSE_TAG; -import static me.minidigger.minimessage.Constants.HOVER; -import static me.minidigger.minimessage.Constants.ITALIC; -import static me.minidigger.minimessage.Constants.OBFUSCATED; -import static me.minidigger.minimessage.Constants.SEPARATOR; -import static me.minidigger.minimessage.Constants.STRIKETHROUGH; -import static me.minidigger.minimessage.Constants.TAG_END; -import static me.minidigger.minimessage.Constants.TAG_START; -import static me.minidigger.minimessage.Constants.UNDERLINED; +/** + * @deprecated use me.minidigger.minimessage.bungee.MiniMessageSerializer instead + */ +@Deprecated public final class MiniMessageSerializer { private MiniMessageSerializer(){ } + /** + * @deprecated use me.minidigger.minimessage.bungee.MiniMessageSerializer instead + */ + @Deprecated @Nonnull public static String serialize(@Nonnull BaseComponent... components) { - StringBuilder sb = new StringBuilder(); - - for (int i = 0; i < components.length; i++) { - BaseComponent comp = components[i]; - - // # start tags - - // ## get prev comp - BaseComponent prevComp = null; - if (i > 0) { - prevComp = components[i - 1]; - } - - // ## color - // ### white is not important - if (!ChatColor.WHITE.equals(comp.getColor())) { - sb.append(startColor(comp.getColor())); - } - - // ## decoration - // ### only start if prevComp didn't start - if (comp.isBold() && (prevComp == null || !prevComp.isBold())) { - sb.append(startTag(BOLD)); - } - if (comp.isItalic() && (prevComp == null || !prevComp.isItalic())) { - sb.append(startTag(ITALIC)); - } - if (comp.isObfuscated() && (prevComp == null || !prevComp.isObfuscated())) { - sb.append(startTag(OBFUSCATED)); - } - if (comp.isStrikethrough() && (prevComp == null || !prevComp.isStrikethrough())) { - sb.append(startTag(STRIKETHROUGH)); - } - if (comp.isUnderlined() && (prevComp == null || !prevComp.isUnderlined())) { - sb.append(startTag(UNDERLINED)); - } - - // ## hover - // ### only start if prevComp didn't start the same one - HoverEvent hov = comp.getHoverEvent(); - if (hov != null && (prevComp == null || areDifferent(hov, prevComp.getHoverEvent()))) { - sb.append(startTag(String.format("%s" + SEPARATOR + "%s" + SEPARATOR + "\"%s\"", HOVER, hov.getAction().name().toLowerCase(), serialize(hov.getValue())))); - } - - // ## click - // ### only start if prevComp didn't start the same one - ClickEvent click = comp.getClickEvent(); - if (click != null && (prevComp == null || areDifferent(click, prevComp.getClickEvent()))) { - sb.append(startTag(String.format("%s" + SEPARATOR + "%s" + SEPARATOR + "\"%s\"", CLICK, click.getAction().name().toLowerCase(), click.getValue()))); - } - - // # append text - sb.append(comp.toPlainText()); - - // # end tags - - // ## get next comp - BaseComponent nextComp = null; - if (i + 1 < components.length) { - nextComp = components[i + 1]; - } - - // ## color - // ### only end color if next comp is white and current isn't - if (nextComp != null && comp.getColor() != ChatColor.WHITE) { - if (nextComp.getColor() == ChatColor.WHITE || nextComp.getColor() == null) { - sb.append(endColor(comp.getColor())); - } - } - - // ## decoration - // ### only end decoration if next tag is different - if (nextComp != null) { - if (comp.isBold() && !nextComp.isBold()) { - sb.append(endTag(BOLD)); - } - if (comp.isItalic() && !nextComp.isItalic()) { - sb.append(endTag(ITALIC)); - } - if (comp.isObfuscated() && !nextComp.isObfuscated()) { - sb.append(endTag(OBFUSCATED)); - } - if (comp.isStrikethrough() && !nextComp.isStrikethrough()) { - sb.append(endTag(STRIKETHROUGH)); - } - if (comp.isUnderlined() && !nextComp.isUnderlined()) { - sb.append(endTag(UNDERLINED)); - } - } - - // ## hover - // ### only end hover if next tag is different - if (nextComp != null && comp.getHoverEvent() != null) { - if (areDifferent(comp.getHoverEvent(), nextComp.getHoverEvent())) { - sb.append(endTag(HOVER)); - } - } - - // ## click - // ### only end click if next tag is different - if (nextComp != null && comp.getClickEvent() != null) { - if (areDifferent(comp.getClickEvent(), nextComp.getClickEvent())) { - sb.append(endTag(CLICK)); - } - } - } - - return sb.toString(); - } - - private static boolean areDifferent(@Nonnull ClickEvent c1, @Nullable ClickEvent c2) { - if (c2 == null) return true; - return !c1.equals(c2) && (!c1.getAction().equals(c2.getAction()) || !c1.getValue().equals(c2.getValue())); - } - - private static boolean areDifferent(@Nonnull HoverEvent h1, @Nullable HoverEvent h2) { - if (h2 == null) return true; - return !h1.equals(h2) && (!h1.getAction().equals(h2.getAction()));// TODO also compare value - } - - @Nonnull - private static String startColor(@Nonnull ChatColor color) { - return startTag(color.name().toLowerCase()); - } - - @Nonnull - private static String endColor(@Nonnull ChatColor color) { - return endTag(color.name().toLowerCase()); - } - - @Nonnull - private static String startTag(@Nonnull String content) { - return TAG_START + content + TAG_END; - } - - @Nonnull - private static String endTag(@Nonnull String content) { - return TAG_START + CLOSE_TAG + content + TAG_END; + return me.minidigger.minimessage.bungee.MiniMessageSerializer.serialize(components); } } diff --git a/minimessage-bungee/src/main/java/me/minidigger/minimessage/Constants.java b/minimessage-bungee/src/main/java/me/minidigger/minimessage/bungee/Constants.java similarity index 93% rename from minimessage-bungee/src/main/java/me/minidigger/minimessage/Constants.java rename to minimessage-bungee/src/main/java/me/minidigger/minimessage/bungee/Constants.java index 1f9423479..9a545aa5d 100644 --- a/minimessage-bungee/src/main/java/me/minidigger/minimessage/Constants.java +++ b/minimessage-bungee/src/main/java/me/minidigger/minimessage/bungee/Constants.java @@ -1,4 +1,4 @@ -package me.minidigger.minimessage; +package me.minidigger.minimessage.bungee; class Constants { public static final String CLICK = "click"; diff --git a/minimessage-bungee/src/main/java/me/minidigger/minimessage/bungee/MiniMessageParser.java b/minimessage-bungee/src/main/java/me/minidigger/minimessage/bungee/MiniMessageParser.java new file mode 100644 index 000000000..774e5a390 --- /dev/null +++ b/minimessage-bungee/src/main/java/me/minidigger/minimessage/bungee/MiniMessageParser.java @@ -0,0 +1,306 @@ +package me.minidigger.minimessage.bungee; + +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.chat.BaseComponent; +import net.md_5.bungee.api.chat.ClickEvent; +import net.md_5.bungee.api.chat.ComponentBuilder; +import net.md_5.bungee.api.chat.HoverEvent; + +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.EnumSet; +import java.util.Map; +import java.util.Optional; +import java.util.function.Consumer; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.annotation.Nonnull; + +import static me.minidigger.minimessage.bungee.Constants.CLICK; +import static me.minidigger.minimessage.bungee.Constants.CLOSE_TAG; +import static me.minidigger.minimessage.bungee.Constants.HOVER; +import static me.minidigger.minimessage.bungee.Constants.SEPARATOR; +import static me.minidigger.minimessage.bungee.Constants.TAG_END; +import static me.minidigger.minimessage.bungee.Constants.TAG_START; + +public class MiniMessageParser { + + // regex group names + private static final String START = "start"; + private static final String TOKEN = "token"; + private static final String INNER = "inner"; + private static final String END = "end"; + // https://regex101.com/r/8VZ7uA/5 + private static final Pattern pattern = Pattern.compile("((?<)(?([^<>]+)|([^<>]+\"(?[^\"]+)\"))(?>))+?"); + + @Nonnull + public static String escapeTokens(@Nonnull String richMessage) { + StringBuilder sb = new StringBuilder(); + Matcher matcher = pattern.matcher(richMessage); + int lastEnd = 0; + while (matcher.find()) { + int startIndex = matcher.start(); + int endIndex = matcher.end(); + + if (startIndex > lastEnd) { + sb.append(richMessage, lastEnd, startIndex); + } + lastEnd = endIndex; + + String start = matcher.group(START); + String token = matcher.group(TOKEN); + String inner = matcher.group(INNER); + String end = matcher.group(END); + + // also escape inner + if (inner != null) { + token = token.replace(inner, escapeTokens(inner)); + } + + sb.append("\\").append(start).append(token).append("\\").append(end); + } + + if (richMessage.length() > lastEnd) { + sb.append(richMessage.substring(lastEnd)); + } + + return sb.toString(); + } + + @Nonnull + public static String stripTokens(@Nonnull String richMessage) { + StringBuilder sb = new StringBuilder(); + Matcher matcher = pattern.matcher(richMessage); + int lastEnd = 0; + while (matcher.find()) { + int startIndex = matcher.start(); + int endIndex = matcher.end(); + + if (startIndex > lastEnd) { + sb.append(richMessage, lastEnd, startIndex); + } + lastEnd = endIndex; + } + + if (richMessage.length() > lastEnd) { + sb.append(richMessage.substring(lastEnd)); + } + + return sb.toString(); + } + + @Nonnull + public static String handlePlaceholders(@Nonnull String richMessage, @Nonnull String... placeholders) { + if (placeholders.length % 2 != 0) { + throw new ParseException( + "Invalid number placeholders defined, usage: parseFormat(format, key, value, key, value...)"); + } + for (int i = 0; i < placeholders.length; i += 2) { + richMessage = richMessage.replace(TAG_START + placeholders[i] + TAG_END, placeholders[i + 1]); + } + return richMessage; + } + + @Nonnull + public static String handlePlaceholders(@Nonnull String richMessage, @Nonnull Map placeholders) { + for (Map.Entry entry : placeholders.entrySet()) { + richMessage = richMessage.replace(TAG_START + entry.getKey() + TAG_END, entry.getValue()); + } + return richMessage; + } + + @Nonnull + public static BaseComponent[] parseFormat(@Nonnull String richMessage, @Nonnull String... placeholders) { + return parseFormat(handlePlaceholders(richMessage, placeholders)); + } + + @Nonnull + public static BaseComponent[] parseFormat(@Nonnull String richMessage, @Nonnull Map placeholders) { + return parseFormat(handlePlaceholders(richMessage, placeholders)); + } + + @Nonnull + public static BaseComponent[] parseFormat(@Nonnull String richMessage) { + ComponentBuilder builder = null; + + Deque clickEvents = new ArrayDeque<>(); + Deque hoverEvents = new ArrayDeque<>(); + Deque colors = new ArrayDeque<>(); + EnumSet decorations = EnumSet.noneOf(TextDecoration.class); + + Matcher matcher = pattern.matcher(richMessage); + int lastEnd = 0; + while (matcher.find()) { + int startIndex = matcher.start(); + int endIndex = matcher.end(); + + String msg = null; + if (startIndex > lastEnd) { + msg = richMessage.substring(lastEnd, startIndex); + } + lastEnd = endIndex; + + // handle message + if (msg != null && msg.length() != 0) { + // append message + if (builder == null) { + builder = new ComponentBuilder(msg); + } else { + builder.append(msg, ComponentBuilder.FormatRetention.NONE); + } + + // set everything that is not closed yet + if (!clickEvents.isEmpty()) { + builder.event(clickEvents.peek()); + } + if (!hoverEvents.isEmpty()) { + builder.event(hoverEvents.peek()); + } + if (!colors.isEmpty()) { + builder.color(colors.peek()); + } + if (!decorations.isEmpty()) { + // no lambda because builder isn't effective final :/ + for (TextDecoration decor : decorations) { + decor.apply(builder); + } + } + } + + String token = matcher.group(TOKEN); + String inner = matcher.group(INNER); + + Optional deco; + Optional color; + + // click + if (token.startsWith(CLICK + SEPARATOR)) { + clickEvents.push(handleClick(token, inner)); + } else if (token.equals(CLOSE_TAG + CLICK)) { + clickEvents.pop(); + } + // hover + else if (token.startsWith(HOVER + SEPARATOR)) { + hoverEvents.push(handleHover(token, inner)); + } else if (token.equals(CLOSE_TAG + HOVER)) { + hoverEvents.pop(); + } + // decoration + else if ((deco = resolveDecoration(token)).isPresent()) { + decorations.add(deco.get()); + } else if (token.startsWith(CLOSE_TAG) && (deco = resolveDecoration(token.replace(CLOSE_TAG, ""))).isPresent()) { + decorations.remove(deco.get()); + } + // color + else if ((color = resolveColor(token)).isPresent()) { + colors.push(color.get()); + } else if (token.startsWith(CLOSE_TAG) && resolveColor(token.replace(CLOSE_TAG, "")).isPresent()) { + colors.pop(); + } else { + // invalid tag + if (builder == null) { + builder = new ComponentBuilder(TAG_START + token + TAG_END); + } else { + builder.append(TAG_START + token + TAG_END, ComponentBuilder.FormatRetention.NONE); + } + } + } + + // handle last message part + if (richMessage.length() > lastEnd) { + String msg = richMessage.substring(lastEnd); + // append message + if (builder == null) { + builder = new ComponentBuilder(msg); + } else { + builder.append(msg, ComponentBuilder.FormatRetention.NONE); + } + + // set everything that is not closed yet + if (!clickEvents.isEmpty()) { + builder.event(clickEvents.peek()); + } + if (!hoverEvents.isEmpty()) { + builder.event(hoverEvents.peek()); + } + if (!colors.isEmpty()) { + builder.color(colors.peek()); + } + if (!decorations.isEmpty()) { + // no lambda because builder isn't effective final :/ + for (TextDecoration decor : decorations) { + decor.apply(builder); + } + } + } + + if (builder == null) { + // lets just return an empty component + builder = new ComponentBuilder(""); + } + + return builder.create(); + } + + @Nonnull + private static ClickEvent handleClick(@Nonnull String token, @Nonnull String inner) { + String[] args = token.split(SEPARATOR); + if (args.length < 2) { + throw new ParseException("Can't parse click action (too few args) " + token); + } + ClickEvent.Action action = ClickEvent.Action.valueOf(args[1].toUpperCase()); + return new ClickEvent(action, token.replace(CLICK + SEPARATOR + args[1] + SEPARATOR, "")); + } + + @Nonnull + private static HoverEvent handleHover(@Nonnull String token, @Nonnull String inner) { + String[] args = token.split(SEPARATOR); + if (args.length < 2) { + throw new ParseException("Can't parse hover action (too few args) " + token); + } + HoverEvent.Action action = HoverEvent.Action.valueOf(args[1].toUpperCase()); + return new HoverEvent(action, parseFormat(inner)); + } + + @Nonnull + private static Optional resolveColor(@Nonnull String token) { + try { + return Optional.of(ChatColor.valueOf(token.toUpperCase())); + } catch (IllegalArgumentException ex) { + return Optional.empty(); + } + } + + @Nonnull + private static Optional resolveDecoration(@Nonnull String token) { + try { + return Optional.of(TextDecoration.valueOf(token.toUpperCase())); + } catch (IllegalArgumentException ex) { + return Optional.empty(); + } + } + + enum TextDecoration { + BOLD(b -> b.bold(true)), + ITALIC(b -> b.italic(true)), + UNDERLINED(b -> b.underlined(true)), + STRIKETHROUGH(b -> b.strikethrough(true)), + OBFUSCATED(b -> b.obfuscated(true)); + + private final Consumer builder; + + TextDecoration(@Nonnull Consumer builder) { + this.builder = builder; + } + + public void apply(@Nonnull ComponentBuilder comp) { + builder.accept(comp); + } + } + + static class ParseException extends RuntimeException { + public ParseException(@Nonnull String message) { + super(message); + } + } +} diff --git a/minimessage-bungee/src/main/java/me/minidigger/minimessage/bungee/MiniMessageSerializer.java b/minimessage-bungee/src/main/java/me/minidigger/minimessage/bungee/MiniMessageSerializer.java new file mode 100644 index 000000000..56817bb8a --- /dev/null +++ b/minimessage-bungee/src/main/java/me/minidigger/minimessage/bungee/MiniMessageSerializer.java @@ -0,0 +1,169 @@ +package me.minidigger.minimessage.bungee; + +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.chat.BaseComponent; +import net.md_5.bungee.api.chat.ClickEvent; +import net.md_5.bungee.api.chat.HoverEvent; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import static me.minidigger.minimessage.bungee.Constants.BOLD; +import static me.minidigger.minimessage.bungee.Constants.CLICK; +import static me.minidigger.minimessage.bungee.Constants.CLOSE_TAG; +import static me.minidigger.minimessage.bungee.Constants.HOVER; +import static me.minidigger.minimessage.bungee.Constants.ITALIC; +import static me.minidigger.minimessage.bungee.Constants.OBFUSCATED; +import static me.minidigger.minimessage.bungee.Constants.SEPARATOR; +import static me.minidigger.minimessage.bungee.Constants.STRIKETHROUGH; +import static me.minidigger.minimessage.bungee.Constants.TAG_END; +import static me.minidigger.minimessage.bungee.Constants.TAG_START; +import static me.minidigger.minimessage.bungee.Constants.UNDERLINED; + +public final class MiniMessageSerializer { + + private MiniMessageSerializer(){ + } + + @Nonnull + public static String serialize(@Nonnull BaseComponent... components) { + StringBuilder sb = new StringBuilder(); + + for (int i = 0; i < components.length; i++) { + BaseComponent comp = components[i]; + + // # start tags + + // ## get prev comp + BaseComponent prevComp = null; + if (i > 0) { + prevComp = components[i - 1]; + } + + // ## color + // ### white is not important + if (!ChatColor.WHITE.equals(comp.getColor())) { + sb.append(startColor(comp.getColor())); + } + + // ## decoration + // ### only start if prevComp didn't start + if (comp.isBold() && (prevComp == null || !prevComp.isBold())) { + sb.append(startTag(BOLD)); + } + if (comp.isItalic() && (prevComp == null || !prevComp.isItalic())) { + sb.append(startTag(ITALIC)); + } + if (comp.isObfuscated() && (prevComp == null || !prevComp.isObfuscated())) { + sb.append(startTag(OBFUSCATED)); + } + if (comp.isStrikethrough() && (prevComp == null || !prevComp.isStrikethrough())) { + sb.append(startTag(STRIKETHROUGH)); + } + if (comp.isUnderlined() && (prevComp == null || !prevComp.isUnderlined())) { + sb.append(startTag(UNDERLINED)); + } + + // ## hover + // ### only start if prevComp didn't start the same one + HoverEvent hov = comp.getHoverEvent(); + if (hov != null && (prevComp == null || areDifferent(hov, prevComp.getHoverEvent()))) { + sb.append(startTag(String.format("%s" + SEPARATOR + "%s" + SEPARATOR + "\"%s\"", HOVER, hov.getAction().name().toLowerCase(), serialize(hov.getValue())))); + } + + // ## click + // ### only start if prevComp didn't start the same one + ClickEvent click = comp.getClickEvent(); + if (click != null && (prevComp == null || areDifferent(click, prevComp.getClickEvent()))) { + sb.append(startTag(String.format("%s" + SEPARATOR + "%s" + SEPARATOR + "\"%s\"", CLICK, click.getAction().name().toLowerCase(), click.getValue()))); + } + + // # append text + sb.append(comp.toPlainText()); + + // # end tags + + // ## get next comp + BaseComponent nextComp = null; + if (i + 1 < components.length) { + nextComp = components[i + 1]; + } + + // ## color + // ### only end color if next comp is white and current isn't + if (nextComp != null && comp.getColor() != ChatColor.WHITE) { + if (nextComp.getColor() == ChatColor.WHITE || nextComp.getColor() == null) { + sb.append(endColor(comp.getColor())); + } + } + + // ## decoration + // ### only end decoration if next tag is different + if (nextComp != null) { + if (comp.isBold() && !nextComp.isBold()) { + sb.append(endTag(BOLD)); + } + if (comp.isItalic() && !nextComp.isItalic()) { + sb.append(endTag(ITALIC)); + } + if (comp.isObfuscated() && !nextComp.isObfuscated()) { + sb.append(endTag(OBFUSCATED)); + } + if (comp.isStrikethrough() && !nextComp.isStrikethrough()) { + sb.append(endTag(STRIKETHROUGH)); + } + if (comp.isUnderlined() && !nextComp.isUnderlined()) { + sb.append(endTag(UNDERLINED)); + } + } + + // ## hover + // ### only end hover if next tag is different + if (nextComp != null && comp.getHoverEvent() != null) { + if (areDifferent(comp.getHoverEvent(), nextComp.getHoverEvent())) { + sb.append(endTag(HOVER)); + } + } + + // ## click + // ### only end click if next tag is different + if (nextComp != null && comp.getClickEvent() != null) { + if (areDifferent(comp.getClickEvent(), nextComp.getClickEvent())) { + sb.append(endTag(CLICK)); + } + } + } + + return sb.toString(); + } + + private static boolean areDifferent(@Nonnull ClickEvent c1, @Nullable ClickEvent c2) { + if (c2 == null) return true; + return !c1.equals(c2) && (!c1.getAction().equals(c2.getAction()) || !c1.getValue().equals(c2.getValue())); + } + + private static boolean areDifferent(@Nonnull HoverEvent h1, @Nullable HoverEvent h2) { + if (h2 == null) return true; + return !h1.equals(h2) && (!h1.getAction().equals(h2.getAction()));// TODO also compare value + } + + @Nonnull + private static String startColor(@Nonnull ChatColor color) { + return startTag(color.name().toLowerCase()); + } + + @Nonnull + private static String endColor(@Nonnull ChatColor color) { + return endTag(color.name().toLowerCase()); + } + + @Nonnull + private static String startTag(@Nonnull String content) { + return TAG_START + content + TAG_END; + } + + @Nonnull + private static String endTag(@Nonnull String content) { + return TAG_START + CLOSE_TAG + content + TAG_END; + } +} diff --git a/minimessage-text/src/main/java/me/minidigger/minimessage/Constants.java b/minimessage-text/src/main/java/me/minidigger/minimessage/text/Constants.java similarity index 95% rename from minimessage-text/src/main/java/me/minidigger/minimessage/Constants.java rename to minimessage-text/src/main/java/me/minidigger/minimessage/text/Constants.java index 6f9ad71f0..76012f6ae 100644 --- a/minimessage-text/src/main/java/me/minidigger/minimessage/Constants.java +++ b/minimessage-text/src/main/java/me/minidigger/minimessage/text/Constants.java @@ -1,4 +1,4 @@ -package me.minidigger.minimessage; +package me.minidigger.minimessage.text; class Constants { public static final String CLICK = "click"; diff --git a/minimessage-text/src/main/java/me/minidigger/minimessage/MiniMessageParser.java b/minimessage-text/src/main/java/me/minidigger/minimessage/text/MiniMessageParser.java similarity index 95% rename from minimessage-text/src/main/java/me/minidigger/minimessage/MiniMessageParser.java rename to minimessage-text/src/main/java/me/minidigger/minimessage/text/MiniMessageParser.java index 73a970012..27de80182 100644 --- a/minimessage-text/src/main/java/me/minidigger/minimessage/MiniMessageParser.java +++ b/minimessage-text/src/main/java/me/minidigger/minimessage/text/MiniMessageParser.java @@ -1,4 +1,4 @@ -package me.minidigger.minimessage; +package me.minidigger.minimessage.text; import net.kyori.text.Component; import net.kyori.text.KeybindComponent; @@ -20,15 +20,15 @@ import java.util.regex.Pattern; import javax.annotation.Nonnull; -import static me.minidigger.minimessage.Constants.CLICK; -import static me.minidigger.minimessage.Constants.CLOSE_TAG; -import static me.minidigger.minimessage.Constants.HOVER; -import static me.minidigger.minimessage.Constants.INSERTION; -import static me.minidigger.minimessage.Constants.KEYBIND; -import static me.minidigger.minimessage.Constants.SEPARATOR; -import static me.minidigger.minimessage.Constants.TAG_END; -import static me.minidigger.minimessage.Constants.TAG_START; -import static me.minidigger.minimessage.Constants.TRANSLATABLE; +import static me.minidigger.minimessage.text.Constants.CLICK; +import static me.minidigger.minimessage.text.Constants.CLOSE_TAG; +import static me.minidigger.minimessage.text.Constants.HOVER; +import static me.minidigger.minimessage.text.Constants.INSERTION; +import static me.minidigger.minimessage.text.Constants.KEYBIND; +import static me.minidigger.minimessage.text.Constants.SEPARATOR; +import static me.minidigger.minimessage.text.Constants.TAG_END; +import static me.minidigger.minimessage.text.Constants.TAG_START; +import static me.minidigger.minimessage.text.Constants.TRANSLATABLE; public class MiniMessageParser { diff --git a/minimessage-text/src/main/java/me/minidigger/minimessage/MiniMessageSerializer.java b/minimessage-text/src/main/java/me/minidigger/minimessage/text/MiniMessageSerializer.java similarity index 90% rename from minimessage-text/src/main/java/me/minidigger/minimessage/MiniMessageSerializer.java rename to minimessage-text/src/main/java/me/minidigger/minimessage/text/MiniMessageSerializer.java index e9db036dd..a6eb3db61 100644 --- a/minimessage-text/src/main/java/me/minidigger/minimessage/MiniMessageSerializer.java +++ b/minimessage-text/src/main/java/me/minidigger/minimessage/text/MiniMessageSerializer.java @@ -1,4 +1,4 @@ -package me.minidigger.minimessage; +package me.minidigger.minimessage.text; import net.kyori.text.Component; import net.kyori.text.KeybindComponent; @@ -15,18 +15,18 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; -import static me.minidigger.minimessage.Constants.BOLD; -import static me.minidigger.minimessage.Constants.CLICK; -import static me.minidigger.minimessage.Constants.CLOSE_TAG; -import static me.minidigger.minimessage.Constants.HOVER; -import static me.minidigger.minimessage.Constants.INSERTION; -import static me.minidigger.minimessage.Constants.ITALIC; -import static me.minidigger.minimessage.Constants.OBFUSCATED; -import static me.minidigger.minimessage.Constants.SEPARATOR; -import static me.minidigger.minimessage.Constants.STRIKETHROUGH; -import static me.minidigger.minimessage.Constants.TAG_END; -import static me.minidigger.minimessage.Constants.TAG_START; -import static me.minidigger.minimessage.Constants.UNDERLINED; +import static me.minidigger.minimessage.text.Constants.BOLD; +import static me.minidigger.minimessage.text.Constants.CLICK; +import static me.minidigger.minimessage.text.Constants.CLOSE_TAG; +import static me.minidigger.minimessage.text.Constants.HOVER; +import static me.minidigger.minimessage.text.Constants.INSERTION; +import static me.minidigger.minimessage.text.Constants.ITALIC; +import static me.minidigger.minimessage.text.Constants.OBFUSCATED; +import static me.minidigger.minimessage.text.Constants.SEPARATOR; +import static me.minidigger.minimessage.text.Constants.STRIKETHROUGH; +import static me.minidigger.minimessage.text.Constants.TAG_END; +import static me.minidigger.minimessage.text.Constants.TAG_START; +import static me.minidigger.minimessage.text.Constants.UNDERLINED; public final class MiniMessageSerializer { diff --git a/minimessage-text/src/test/java/minimessage/MiniMessageParserTest.java b/minimessage-text/src/test/java/me/minidigger/minimessage/text/MiniMessageParserTest.java similarity index 99% rename from minimessage-text/src/test/java/minimessage/MiniMessageParserTest.java rename to minimessage-text/src/test/java/me/minidigger/minimessage/text/MiniMessageParserTest.java index 520fedaaf..379d781e1 100644 --- a/minimessage-text/src/test/java/minimessage/MiniMessageParserTest.java +++ b/minimessage-text/src/test/java/me/minidigger/minimessage/text/MiniMessageParserTest.java @@ -1,4 +1,4 @@ -package minimessage; +package me.minidigger.minimessage.text; import net.kyori.text.Component; import net.kyori.text.serializer.gson.GsonComponentSerializer; @@ -7,8 +7,6 @@ import javax.annotation.Nonnull; -import me.minidigger.minimessage.MiniMessageParser; - import static org.junit.Assert.assertEquals; public class MiniMessageParserTest { diff --git a/minimessage-text/src/test/java/minimessage/MiniMessageSerializerTest.java b/minimessage-text/src/test/java/me/minidigger/minimessage/text/MiniMessageSerializerTest.java similarity index 98% rename from minimessage-text/src/test/java/minimessage/MiniMessageSerializerTest.java rename to minimessage-text/src/test/java/me/minidigger/minimessage/text/MiniMessageSerializerTest.java index 00c7085c9..f5248b031 100644 --- a/minimessage-text/src/test/java/minimessage/MiniMessageSerializerTest.java +++ b/minimessage-text/src/test/java/me/minidigger/minimessage/text/MiniMessageSerializerTest.java @@ -1,4 +1,4 @@ -package minimessage; +package me.minidigger.minimessage.text; import net.kyori.text.KeybindComponent; import net.kyori.text.TextComponent; @@ -13,8 +13,6 @@ import javax.annotation.Nonnull; -import me.minidigger.minimessage.MiniMessageSerializer; - import static org.junit.Assert.assertEquals; public class MiniMessageSerializerTest { From 385c01f73ba666a5b4a7d483836daf2f6f742064 Mon Sep 17 00:00:00 2001 From: release-bot Date: Sun, 26 Apr 2020 10:11:46 +0000 Subject: [PATCH 040/339] [maven-release-plugin] prepare release MiniMessage-2.0.4 --- minimessage-bungee/pom.xml | 2 +- minimessage-ext-markdown/pom.xml | 2 +- minimessage-text/pom.xml | 2 +- pom.xml | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/minimessage-bungee/pom.xml b/minimessage-bungee/pom.xml index e9f668569..8523c2477 100644 --- a/minimessage-bungee/pom.xml +++ b/minimessage-bungee/pom.xml @@ -4,7 +4,7 @@ MiniMessage me.minidigger - 2.0.4-SNAPSHOT + 2.0.4 minimessage-bungee diff --git a/minimessage-ext-markdown/pom.xml b/minimessage-ext-markdown/pom.xml index c4e682080..8dc9cc48e 100644 --- a/minimessage-ext-markdown/pom.xml +++ b/minimessage-ext-markdown/pom.xml @@ -3,7 +3,7 @@ MiniMessage me.minidigger - 2.0.4-SNAPSHOT + 2.0.4 4.0.0 diff --git a/minimessage-text/pom.xml b/minimessage-text/pom.xml index c9476b0d8..ac64e57fb 100644 --- a/minimessage-text/pom.xml +++ b/minimessage-text/pom.xml @@ -4,7 +4,7 @@ MiniMessage me.minidigger - 2.0.4-SNAPSHOT + 2.0.4 minimessage-text diff --git a/pom.xml b/pom.xml index 2064b2237..6d9b113ae 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ me.minidigger MiniMessage pom - 2.0.4-SNAPSHOT + 2.0.4 MiniMessage Simple library that implements an easy to use textual format to send rich json messages. https://github.com/MiniDigger/MiniMessage @@ -135,7 +135,7 @@ scm:git:https://github.com/MiniDigger/MiniMessage.git scm:git:https://github.com/MiniDigger/MiniMessage.git https://github.com/MiniDigger/MiniMessage - HEAD + MiniMessage-2.0.4 From 0387230fc6afe95d64e7cbc44077c3248e0f209b Mon Sep 17 00:00:00 2001 From: release-bot Date: Sun, 26 Apr 2020 10:11:53 +0000 Subject: [PATCH 041/339] [maven-release-plugin] prepare for next development iteration --- minimessage-bungee/pom.xml | 2 +- minimessage-ext-markdown/pom.xml | 2 +- minimessage-text/pom.xml | 2 +- pom.xml | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/minimessage-bungee/pom.xml b/minimessage-bungee/pom.xml index 8523c2477..77f6bc2e7 100644 --- a/minimessage-bungee/pom.xml +++ b/minimessage-bungee/pom.xml @@ -4,7 +4,7 @@ MiniMessage me.minidigger - 2.0.4 + 2.0.5-SNAPSHOT minimessage-bungee diff --git a/minimessage-ext-markdown/pom.xml b/minimessage-ext-markdown/pom.xml index 8dc9cc48e..6546af02e 100644 --- a/minimessage-ext-markdown/pom.xml +++ b/minimessage-ext-markdown/pom.xml @@ -3,7 +3,7 @@ MiniMessage me.minidigger - 2.0.4 + 2.0.5-SNAPSHOT 4.0.0 diff --git a/minimessage-text/pom.xml b/minimessage-text/pom.xml index ac64e57fb..7cd20093d 100644 --- a/minimessage-text/pom.xml +++ b/minimessage-text/pom.xml @@ -4,7 +4,7 @@ MiniMessage me.minidigger - 2.0.4 + 2.0.5-SNAPSHOT minimessage-text diff --git a/pom.xml b/pom.xml index 6d9b113ae..318dd2734 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ me.minidigger MiniMessage pom - 2.0.4 + 2.0.5-SNAPSHOT MiniMessage Simple library that implements an easy to use textual format to send rich json messages. https://github.com/MiniDigger/MiniMessage @@ -135,7 +135,7 @@ scm:git:https://github.com/MiniDigger/MiniMessage.git scm:git:https://github.com/MiniDigger/MiniMessage.git https://github.com/MiniDigger/MiniMessage - MiniMessage-2.0.4 + HEAD From ab8a31556d0ad59e27410c4dc6b58ee8a57c8e71 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Mon, 27 Apr 2020 15:59:48 +0000 Subject: [PATCH 042/339] Update dependency net.kyori:text-api to v3.0.4 --- minimessage-text/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/minimessage-text/pom.xml b/minimessage-text/pom.xml index 7cd20093d..f692583ac 100644 --- a/minimessage-text/pom.xml +++ b/minimessage-text/pom.xml @@ -14,7 +14,7 @@ net.kyori text-api - 3.0.3 + 3.0.4 provided From e80de598f68941bd4e400cda34584f7ed0c297e3 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Mon, 27 Apr 2020 15:59:54 +0000 Subject: [PATCH 043/339] Update dependency net.kyori:text-serializer-gson to v3.0.4 --- minimessage-text/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/minimessage-text/pom.xml b/minimessage-text/pom.xml index f692583ac..462322a5d 100644 --- a/minimessage-text/pom.xml +++ b/minimessage-text/pom.xml @@ -20,7 +20,7 @@ net.kyori text-serializer-gson - 3.0.3 + 3.0.4 test From e51ea28bc1413f1db19ed8d54867ae88cc82c795 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Mon, 27 Apr 2020 16:31:34 +0000 Subject: [PATCH 044/339] Update dependency org.apache.maven.plugins:maven-javadoc-plugin to v3.2.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 318dd2734..bfbebdfb9 100644 --- a/pom.xml +++ b/pom.xml @@ -86,7 +86,7 @@ org.apache.maven.plugins maven-javadoc-plugin - 3.1.1 + 3.2.0 attach-javadocs From af2a609445798ed195ea75a7fdf21d44e0ed33c8 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Mon, 27 Apr 2020 16:31:41 +0000 Subject: [PATCH 045/339] Update dependency com.google.code.findbugs:jsr305 to v3 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index bfbebdfb9..71da068da 100644 --- a/pom.xml +++ b/pom.xml @@ -33,7 +33,7 @@ com.google.code.findbugs jsr305 - 1.3.9 + 3.0.2 compile From f78cfda235921aa338f557c4b317961892441c7a Mon Sep 17 00:00:00 2001 From: MiniDigger Date: Sat, 9 May 2020 17:43:06 +0200 Subject: [PATCH 046/339] clarify command example, see #13 --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f5bfa3c56..e69420b66 100644 --- a/README.md +++ b/README.md @@ -49,13 +49,13 @@ Json: ``` MiniMessage: -`TEST` +`TEST` Json: ```json { "clickEvent": { "action": "run_command", - "value": "test command" + "value": "/test command" }, "text": "TEST" } From ec80d8626be4eae2fa33151497cb7c32de5ff4da Mon Sep 17 00:00:00 2001 From: release-bot Date: Sat, 9 May 2020 15:44:35 +0000 Subject: [PATCH 047/339] [maven-release-plugin] prepare release MiniMessage-2.0.5 --- minimessage-bungee/pom.xml | 2 +- minimessage-ext-markdown/pom.xml | 2 +- minimessage-text/pom.xml | 2 +- pom.xml | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/minimessage-bungee/pom.xml b/minimessage-bungee/pom.xml index 77f6bc2e7..544af6455 100644 --- a/minimessage-bungee/pom.xml +++ b/minimessage-bungee/pom.xml @@ -4,7 +4,7 @@ MiniMessage me.minidigger - 2.0.5-SNAPSHOT + 2.0.5 minimessage-bungee diff --git a/minimessage-ext-markdown/pom.xml b/minimessage-ext-markdown/pom.xml index 6546af02e..bc229cd15 100644 --- a/minimessage-ext-markdown/pom.xml +++ b/minimessage-ext-markdown/pom.xml @@ -3,7 +3,7 @@ MiniMessage me.minidigger - 2.0.5-SNAPSHOT + 2.0.5 4.0.0 diff --git a/minimessage-text/pom.xml b/minimessage-text/pom.xml index 462322a5d..eab8b5708 100644 --- a/minimessage-text/pom.xml +++ b/minimessage-text/pom.xml @@ -4,7 +4,7 @@ MiniMessage me.minidigger - 2.0.5-SNAPSHOT + 2.0.5 minimessage-text diff --git a/pom.xml b/pom.xml index 71da068da..ce4a11178 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ me.minidigger MiniMessage pom - 2.0.5-SNAPSHOT + 2.0.5 MiniMessage Simple library that implements an easy to use textual format to send rich json messages. https://github.com/MiniDigger/MiniMessage @@ -135,7 +135,7 @@ scm:git:https://github.com/MiniDigger/MiniMessage.git scm:git:https://github.com/MiniDigger/MiniMessage.git https://github.com/MiniDigger/MiniMessage - HEAD + MiniMessage-2.0.5 From 09d4e89b2bdf9a1d0fd95c302911780fec6e0825 Mon Sep 17 00:00:00 2001 From: release-bot Date: Sat, 9 May 2020 15:44:41 +0000 Subject: [PATCH 048/339] [maven-release-plugin] prepare for next development iteration --- minimessage-bungee/pom.xml | 2 +- minimessage-ext-markdown/pom.xml | 2 +- minimessage-text/pom.xml | 2 +- pom.xml | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/minimessage-bungee/pom.xml b/minimessage-bungee/pom.xml index 544af6455..6c03470e2 100644 --- a/minimessage-bungee/pom.xml +++ b/minimessage-bungee/pom.xml @@ -4,7 +4,7 @@ MiniMessage me.minidigger - 2.0.5 + 2.0.6-SNAPSHOT minimessage-bungee diff --git a/minimessage-ext-markdown/pom.xml b/minimessage-ext-markdown/pom.xml index bc229cd15..246e81c3c 100644 --- a/minimessage-ext-markdown/pom.xml +++ b/minimessage-ext-markdown/pom.xml @@ -3,7 +3,7 @@ MiniMessage me.minidigger - 2.0.5 + 2.0.6-SNAPSHOT 4.0.0 diff --git a/minimessage-text/pom.xml b/minimessage-text/pom.xml index eab8b5708..f6bf82452 100644 --- a/minimessage-text/pom.xml +++ b/minimessage-text/pom.xml @@ -4,7 +4,7 @@ MiniMessage me.minidigger - 2.0.5 + 2.0.6-SNAPSHOT minimessage-text diff --git a/pom.xml b/pom.xml index ce4a11178..fb1ccb93e 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ me.minidigger MiniMessage pom - 2.0.5 + 2.0.6-SNAPSHOT MiniMessage Simple library that implements an easy to use textual format to send rich json messages. https://github.com/MiniDigger/MiniMessage @@ -135,7 +135,7 @@ scm:git:https://github.com/MiniDigger/MiniMessage.git scm:git:https://github.com/MiniDigger/MiniMessage.git https://github.com/MiniDigger/MiniMessage - MiniMessage-2.0.5 + HEAD From 93cc653fa2c0398593868b65fc70e538e62952b7 Mon Sep 17 00:00:00 2001 From: MiniDigger Date: Sat, 6 Jun 2020 16:12:01 +0200 Subject: [PATCH 049/339] implement GH-14 --- .../me/minidigger/minimessage/text/MiniMessageParser.java | 4 ++-- .../minimessage/text/MiniMessageParserTest.java | 8 ++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/minimessage-text/src/main/java/me/minidigger/minimessage/text/MiniMessageParser.java b/minimessage-text/src/main/java/me/minidigger/minimessage/text/MiniMessageParser.java index 27de80182..db7a699e6 100644 --- a/minimessage-text/src/main/java/me/minidigger/minimessage/text/MiniMessageParser.java +++ b/minimessage-text/src/main/java/me/minidigger/minimessage/text/MiniMessageParser.java @@ -37,8 +37,8 @@ public class MiniMessageParser { private static final String TOKEN = "token"; private static final String INNER = "inner"; private static final String END = "end"; - // https://regex101.com/r/8VZ7uA/5 - private static final Pattern pattern = Pattern.compile("((?<)(?([^<>]+)|([^<>]+\"(?[^\"]+)\"))(?>))+?"); + // https://regex101.com/r/8VZ7uA/6 + private static final Pattern pattern = Pattern.compile("((?<)(?([^<>]+)|([^<>]+[\"'](?[^\"']+)[\"']))(?>))+?"); @Nonnull public static String escapeTokens(@Nonnull String richMessage) { diff --git a/minimessage-text/src/test/java/me/minidigger/minimessage/text/MiniMessageParserTest.java b/minimessage-text/src/test/java/me/minidigger/minimessage/text/MiniMessageParserTest.java index 379d781e1..b089afeed 100644 --- a/minimessage-text/src/test/java/me/minidigger/minimessage/text/MiniMessageParserTest.java +++ b/minimessage-text/src/test/java/me/minidigger/minimessage/text/MiniMessageParserTest.java @@ -115,6 +115,14 @@ public void testHover() { test(input, expected); } + @Test + public void testHover2() { + String input = "test'>TEST"; + String expected = "{\"text\":\"TEST\",\"hoverEvent\":{\"action\":\"show_text\",\"value\":{\"text\":\"test\",\"color\":\"red\"}}}"; + + test(input, expected); + } + @Test public void testHoverWithColon() { String input = "test:TEST\">TEST"; From 262484a4caf029c0c05073a94d90cad4df0614b3 Mon Sep 17 00:00:00 2001 From: Mark Vainomaa Date: Fri, 12 Jun 2020 10:46:49 +0200 Subject: [PATCH 050/339] Bungee counterpart toUpper/LowerCase fixes Also use ChatColor#getName instead of toLowerCase where possible Signed-off-by: MiniDigger --- .../minimessage/bungee/MiniMessageParser.java | 9 +++++---- .../minimessage/bungee/MiniMessageSerializer.java | 10 ++++++---- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/minimessage-bungee/src/main/java/me/minidigger/minimessage/bungee/MiniMessageParser.java b/minimessage-bungee/src/main/java/me/minidigger/minimessage/bungee/MiniMessageParser.java index 774e5a390..bd7d16f0f 100644 --- a/minimessage-bungee/src/main/java/me/minidigger/minimessage/bungee/MiniMessageParser.java +++ b/minimessage-bungee/src/main/java/me/minidigger/minimessage/bungee/MiniMessageParser.java @@ -9,6 +9,7 @@ import java.util.ArrayDeque; import java.util.Deque; import java.util.EnumSet; +import java.util.Locale; import java.util.Map; import java.util.Optional; import java.util.function.Consumer; @@ -248,7 +249,7 @@ private static ClickEvent handleClick(@Nonnull String token, @Nonnull String inn if (args.length < 2) { throw new ParseException("Can't parse click action (too few args) " + token); } - ClickEvent.Action action = ClickEvent.Action.valueOf(args[1].toUpperCase()); + ClickEvent.Action action = ClickEvent.Action.valueOf(args[1].toUpperCase(Locale.ROOT)); return new ClickEvent(action, token.replace(CLICK + SEPARATOR + args[1] + SEPARATOR, "")); } @@ -258,14 +259,14 @@ private static HoverEvent handleHover(@Nonnull String token, @Nonnull String inn if (args.length < 2) { throw new ParseException("Can't parse hover action (too few args) " + token); } - HoverEvent.Action action = HoverEvent.Action.valueOf(args[1].toUpperCase()); + HoverEvent.Action action = HoverEvent.Action.valueOf(args[1].toUpperCase(Locale.ROOT)); return new HoverEvent(action, parseFormat(inner)); } @Nonnull private static Optional resolveColor(@Nonnull String token) { try { - return Optional.of(ChatColor.valueOf(token.toUpperCase())); + return Optional.of(ChatColor.valueOf(token.toUpperCase(Locale.ROOT))); } catch (IllegalArgumentException ex) { return Optional.empty(); } @@ -274,7 +275,7 @@ private static Optional resolveColor(@Nonnull String token) { @Nonnull private static Optional resolveDecoration(@Nonnull String token) { try { - return Optional.of(TextDecoration.valueOf(token.toUpperCase())); + return Optional.of(TextDecoration.valueOf(token.toUpperCase(Locale.ROOT))); } catch (IllegalArgumentException ex) { return Optional.empty(); } diff --git a/minimessage-bungee/src/main/java/me/minidigger/minimessage/bungee/MiniMessageSerializer.java b/minimessage-bungee/src/main/java/me/minidigger/minimessage/bungee/MiniMessageSerializer.java index 56817bb8a..fa4440e47 100644 --- a/minimessage-bungee/src/main/java/me/minidigger/minimessage/bungee/MiniMessageSerializer.java +++ b/minimessage-bungee/src/main/java/me/minidigger/minimessage/bungee/MiniMessageSerializer.java @@ -8,6 +8,8 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; +import java.util.Locale; + import static me.minidigger.minimessage.bungee.Constants.BOLD; import static me.minidigger.minimessage.bungee.Constants.CLICK; import static me.minidigger.minimessage.bungee.Constants.CLOSE_TAG; @@ -68,14 +70,14 @@ public static String serialize(@Nonnull BaseComponent... components) { // ### only start if prevComp didn't start the same one HoverEvent hov = comp.getHoverEvent(); if (hov != null && (prevComp == null || areDifferent(hov, prevComp.getHoverEvent()))) { - sb.append(startTag(String.format("%s" + SEPARATOR + "%s" + SEPARATOR + "\"%s\"", HOVER, hov.getAction().name().toLowerCase(), serialize(hov.getValue())))); + sb.append(startTag(String.format("%s" + SEPARATOR + "%s" + SEPARATOR + "\"%s\"", HOVER, hov.getAction().name().toLowerCase(Locale.ROOT), serialize(hov.getValue())))); } // ## click // ### only start if prevComp didn't start the same one ClickEvent click = comp.getClickEvent(); if (click != null && (prevComp == null || areDifferent(click, prevComp.getClickEvent()))) { - sb.append(startTag(String.format("%s" + SEPARATOR + "%s" + SEPARATOR + "\"%s\"", CLICK, click.getAction().name().toLowerCase(), click.getValue()))); + sb.append(startTag(String.format("%s" + SEPARATOR + "%s" + SEPARATOR + "\"%s\"", CLICK, click.getAction().name().toLowerCase(Locale.ROOT), click.getValue()))); } // # append text @@ -149,12 +151,12 @@ private static boolean areDifferent(@Nonnull HoverEvent h1, @Nullable HoverEvent @Nonnull private static String startColor(@Nonnull ChatColor color) { - return startTag(color.name().toLowerCase()); + return startTag(color.getName()); } @Nonnull private static String endColor(@Nonnull ChatColor color) { - return endTag(color.name().toLowerCase()); + return endTag(color.getName()); } @Nonnull From e85befebfcb41451159ff01df28ccdfa757a0390 Mon Sep 17 00:00:00 2001 From: Mark Vainomaa Date: Fri, 12 Jun 2020 10:47:16 +0200 Subject: [PATCH 051/339] Use Kyori's NameMap instead of valueOf and fix toLower/UpperCase usage String#toLower/UpperCase() is dangerous to use without specifying a Locale because of its non-deterministic behaviour between different systems with different configurations Signed-off-by: MiniDigger --- .../minimessage/text/MiniMessageParser.java | 15 +++++++-------- .../minimessage/text/MiniMessageSerializer.java | 8 ++++---- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/minimessage-text/src/main/java/me/minidigger/minimessage/text/MiniMessageParser.java b/minimessage-text/src/main/java/me/minidigger/minimessage/text/MiniMessageParser.java index db7a699e6..7ae7905d4 100644 --- a/minimessage-text/src/main/java/me/minidigger/minimessage/text/MiniMessageParser.java +++ b/minimessage-text/src/main/java/me/minidigger/minimessage/text/MiniMessageParser.java @@ -13,6 +13,7 @@ import java.util.ArrayDeque; import java.util.Deque; import java.util.EnumSet; +import java.util.Locale; import java.util.Map; import java.util.Optional; import java.util.function.UnaryOperator; @@ -306,7 +307,8 @@ private static ClickEvent handleClick(@Nonnull String token, @Nonnull String inn if (args.length < 2) { throw new ParseException("Can't parse click action (too few args) " + token); } - ClickEvent.Action action = ClickEvent.Action.valueOf(args[1].toUpperCase()); + ClickEvent.Action action = ClickEvent.Action.NAMES.value(args[1].toLowerCase(Locale.ROOT)) + .orElseThrow(() -> new ParseException("Can't parse click action (invalid action) " + token)); return ClickEvent.of(action, token.replace(CLICK + SEPARATOR + args[1] + SEPARATOR, "")); } @@ -316,23 +318,20 @@ private static HoverEvent handleHover(@Nonnull String token, @Nonnull String inn if (args.length < 2) { throw new ParseException("Can't parse hover action (too few args) " + token); } - HoverEvent.Action action = HoverEvent.Action.valueOf(args[1].toUpperCase()); + HoverEvent.Action action = HoverEvent.Action.NAMES.value(args[1].toLowerCase(Locale.ROOT)) + .orElseThrow(() -> new ParseException("Can't parse hover action (invalid action) " + token)); return HoverEvent.of(action, parseFormat(inner)); } @Nonnull private static Optional resolveColor(@Nonnull String token) { - try { - return Optional.of(TextColor.valueOf(token.toUpperCase())); - } catch (IllegalArgumentException ex) { - return Optional.empty(); - } + return TextColor.NAMES.value(token.toLowerCase(Locale.ROOT)); } @Nonnull private static Optional resolveDecoration(@Nonnull String token) { try { - return Optional.of(HelperTextDecoration.valueOf(token.toUpperCase())); + return Optional.of(HelperTextDecoration.valueOf(token.toUpperCase(Locale.ROOT))); } catch (IllegalArgumentException ex) { return Optional.empty(); } diff --git a/minimessage-text/src/main/java/me/minidigger/minimessage/text/MiniMessageSerializer.java b/minimessage-text/src/main/java/me/minidigger/minimessage/text/MiniMessageSerializer.java index a6eb3db61..e3e37afd5 100644 --- a/minimessage-text/src/main/java/me/minidigger/minimessage/text/MiniMessageSerializer.java +++ b/minimessage-text/src/main/java/me/minidigger/minimessage/text/MiniMessageSerializer.java @@ -82,14 +82,14 @@ public static String serialize(@Nonnull Component component) { // ### only start if prevComp didn't start the same one HoverEvent hov = comp.hoverEvent(); if (hov != null && (prevComp == null || areDifferent(hov, prevComp.hoverEvent()))) { - sb.append(startTag(String.format("%s" + SEPARATOR + "%s" + SEPARATOR + "\"%s\"", HOVER, hov.action().name().toLowerCase(), serialize(hov.value())))); + sb.append(startTag(String.format("%s" + SEPARATOR + "%s" + SEPARATOR + "\"%s\"", HOVER, HoverEvent.Action.NAMES.name(hov.action()), serialize(hov.value())))); } // ## click // ### only start if prevComp didn't start the same one ClickEvent click = comp.clickEvent(); if (click != null && (prevComp == null || areDifferent(click, prevComp.clickEvent()))) { - sb.append(startTag(String.format("%s" + SEPARATOR + "%s" + SEPARATOR + "\"%s\"", CLICK, click.action().name().toLowerCase(), click.value()))); + sb.append(startTag(String.format("%s" + SEPARATOR + "%s" + SEPARATOR + "\"%s\"", CLICK, ClickEvent.Action.NAMES.name(click.action()), click.value()))); } // ## insertion @@ -182,12 +182,12 @@ private static boolean areDifferent(@Nonnull HoverEvent h1, @Nullable HoverEvent @Nonnull private static String startColor(@Nonnull TextColor color) { - return startTag(color.name().toLowerCase()); + return startTag(TextColor.NAMES.name(color)); } @Nonnull private static String endColor(@Nonnull TextColor color) { - return endTag(color.name().toLowerCase()); + return endTag(TextColor.NAMES.name(color)); } @Nonnull From b5a587dd590439821dea1bc054590c071f8991d4 Mon Sep 17 00:00:00 2001 From: MiniDigger Date: Fri, 12 Jun 2020 11:07:53 +0200 Subject: [PATCH 052/339] Add support for reset, closes #16 Signed-off-by: MiniDigger --- .../me/minidigger/minimessage/text/Constants.java | 1 + .../minimessage/text/MiniMessageParser.java | 11 ++++++++++- .../minimessage/text/MiniMessageParserTest.java | 9 +++++++++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/minimessage-text/src/main/java/me/minidigger/minimessage/text/Constants.java b/minimessage-text/src/main/java/me/minidigger/minimessage/text/Constants.java index 76012f6ae..4be0f0cef 100644 --- a/minimessage-text/src/main/java/me/minidigger/minimessage/text/Constants.java +++ b/minimessage-text/src/main/java/me/minidigger/minimessage/text/Constants.java @@ -12,6 +12,7 @@ class Constants { public static final String OBFUSCATED = "obfuscated"; public static final String ITALIC = "italic"; public static final String BOLD = "bold"; + public static final String RESET = "reset"; public static final String TAG_START = "<"; public static final String TAG_END = ">"; diff --git a/minimessage-text/src/main/java/me/minidigger/minimessage/text/MiniMessageParser.java b/minimessage-text/src/main/java/me/minidigger/minimessage/text/MiniMessageParser.java index 7ae7905d4..1721189df 100644 --- a/minimessage-text/src/main/java/me/minidigger/minimessage/text/MiniMessageParser.java +++ b/minimessage-text/src/main/java/me/minidigger/minimessage/text/MiniMessageParser.java @@ -26,6 +26,7 @@ import static me.minidigger.minimessage.text.Constants.HOVER; import static me.minidigger.minimessage.text.Constants.INSERTION; import static me.minidigger.minimessage.text.Constants.KEYBIND; +import static me.minidigger.minimessage.text.Constants.RESET; import static me.minidigger.minimessage.text.Constants.SEPARATOR; import static me.minidigger.minimessage.text.Constants.TAG_END; import static me.minidigger.minimessage.text.Constants.TAG_START; @@ -154,7 +155,7 @@ public static Component parseFormat(@Nonnull String richMessage) { if (msg != null && msg.length() != 0) { // append message current = TextComponent.of(msg); - current = applyFormatting(clickEvents, hoverEvents, colors, insertions,decorations, current); + current = applyFormatting(clickEvents, hoverEvents, colors, insertions, decorations, current); } @@ -210,6 +211,14 @@ else if (token.startsWith(INSERTION + SEPARATOR)) { } else if (token.startsWith(CLOSE_TAG + INSERTION)) { insertions.pop(); } + // reset + else if (token.startsWith(RESET)) { + clickEvents.clear(); + hoverEvents.clear(); + colors.clear(); + insertions.clear(); + decorations.clear(); + } // invalid tag else { if (current != null) { diff --git a/minimessage-text/src/test/java/me/minidigger/minimessage/text/MiniMessageParserTest.java b/minimessage-text/src/test/java/me/minidigger/minimessage/text/MiniMessageParserTest.java index b089afeed..a466a471f 100644 --- a/minimessage-text/src/test/java/me/minidigger/minimessage/text/MiniMessageParserTest.java +++ b/minimessage-text/src/test/java/me/minidigger/minimessage/text/MiniMessageParserTest.java @@ -216,6 +216,15 @@ public void testGH5() { MiniMessageParser.parseFormat(input, "url", "https://www.google.com"); } + @Test + public void testReset() { + String input = "Click this to insert!"; + String expected = "{\"text\":\"\",\"extra\":[{\"text\":\"Click \"},{\"text\":\"this\",\"color\":\"yellow\",\"insertion\":\"test\"},{\"text\":\" to insert!\"}]}"; + Component comp = MiniMessageParser.parseFormat(input); + + test(comp, expected); + } + private void test(@Nonnull String input, @Nonnull String expected) { test(MiniMessageParser.parseFormat(input), expected); } From 08be7cad7150a592b0502ad88f1270138eb09a6a Mon Sep 17 00:00:00 2001 From: MiniDigger Date: Fri, 12 Jun 2020 11:20:04 +0200 Subject: [PATCH 053/339] Support pre tags, closes #19 Signed-off-by: MiniDigger --- .../minimessage/text/Constants.java | 1 + .../minimessage/text/MiniMessageParser.java | 24 +++++++++++++++++++ .../text/MiniMessageParserTest.java | 9 +++++++ 3 files changed, 34 insertions(+) diff --git a/minimessage-text/src/main/java/me/minidigger/minimessage/text/Constants.java b/minimessage-text/src/main/java/me/minidigger/minimessage/text/Constants.java index 4be0f0cef..299e9e9f3 100644 --- a/minimessage-text/src/main/java/me/minidigger/minimessage/text/Constants.java +++ b/minimessage-text/src/main/java/me/minidigger/minimessage/text/Constants.java @@ -13,6 +13,7 @@ class Constants { public static final String ITALIC = "italic"; public static final String BOLD = "bold"; public static final String RESET = "reset"; + public static final String PRE = "pre"; public static final String TAG_START = "<"; public static final String TAG_END = ">"; diff --git a/minimessage-text/src/main/java/me/minidigger/minimessage/text/MiniMessageParser.java b/minimessage-text/src/main/java/me/minidigger/minimessage/text/MiniMessageParser.java index 1721189df..8467cf7f8 100644 --- a/minimessage-text/src/main/java/me/minidigger/minimessage/text/MiniMessageParser.java +++ b/minimessage-text/src/main/java/me/minidigger/minimessage/text/MiniMessageParser.java @@ -26,6 +26,7 @@ import static me.minidigger.minimessage.text.Constants.HOVER; import static me.minidigger.minimessage.text.Constants.INSERTION; import static me.minidigger.minimessage.text.Constants.KEYBIND; +import static me.minidigger.minimessage.text.Constants.PRE; import static me.minidigger.minimessage.text.Constants.RESET; import static me.minidigger.minimessage.text.Constants.SEPARATOR; import static me.minidigger.minimessage.text.Constants.TAG_END; @@ -137,6 +138,7 @@ public static Component parseFormat(@Nonnull String richMessage) { ArrayDeque colors = new ArrayDeque<>(); ArrayDeque insertions = new ArrayDeque<>(); EnumSet decorations = EnumSet.noneOf(HelperTextDecoration.class); + boolean isPreformatted = false; Matcher matcher = pattern.matcher(richMessage); int lastEnd = 0; @@ -165,6 +167,24 @@ public static Component parseFormat(@Nonnull String richMessage) { Optional deco; Optional color; + // handle pre + if (isPreformatted) { + if (token.startsWith(CLOSE_TAG + PRE)) { + isPreformatted = false; + if (current != null) { + parent.append(current); + } + } else { + if (current != null) { + parent.append(current); + } + current = TextComponent.of(TAG_START + token + TAG_END); + current = applyFormatting(clickEvents, hoverEvents, colors, insertions, decorations, current); + parent.append(current); + } + continue; + } + // click if (token.startsWith(CLICK + SEPARATOR)) { clickEvents.push(handleClick(token, inner)); @@ -219,6 +239,10 @@ else if (token.startsWith(RESET)) { insertions.clear(); decorations.clear(); } + // pre + else if (token.startsWith(PRE)) { + isPreformatted = true; + } // invalid tag else { if (current != null) { diff --git a/minimessage-text/src/test/java/me/minidigger/minimessage/text/MiniMessageParserTest.java b/minimessage-text/src/test/java/me/minidigger/minimessage/text/MiniMessageParserTest.java index a466a471f..4e7e7307f 100644 --- a/minimessage-text/src/test/java/me/minidigger/minimessage/text/MiniMessageParserTest.java +++ b/minimessage-text/src/test/java/me/minidigger/minimessage/text/MiniMessageParserTest.java @@ -225,6 +225,15 @@ public void testReset() { test(comp, expected); } + @Test + public void testPre() { + String input = "Click
this
to insert!"; + String expected = "{\"text\":\"\",\"extra\":[{\"text\":\"Click \"},{\"text\":\"\\u003cinsert:test\\u003e\",\"color\":\"yellow\"},{\"text\":\"this\",\"color\":\"yellow\"},{\"text\":\" to \",\"color\":\"yellow\"},{\"text\":\"insert!\",\"color\":\"red\"}]}"; + Component comp = MiniMessageParser.parseFormat(input); + + test(comp, expected); + } + private void test(@Nonnull String input, @Nonnull String expected) { test(MiniMessageParser.parseFormat(input), expected); } From f80f1112f8b5600ac61467ec7f5a8f461218d63a Mon Sep 17 00:00:00 2001 From: MiniDigger Date: Fri, 12 Jun 2020 12:36:07 +0200 Subject: [PATCH 054/339] Add a bit more docs --- DOCS.md | 89 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 2 ++ 2 files changed, 91 insertions(+) create mode 100644 DOCS.md diff --git a/DOCS.md b/DOCS.md new file mode 100644 index 000000000..7efd3913c --- /dev/null +++ b/DOCS.md @@ -0,0 +1,89 @@ +# Documentation + +> Note: this page mainly covers minimessage-text, although the principles should also apply to the legacy bungee module, just with way fewer features. + +## General + +First let me start with a few core principles. + +This library uses tags. Everything you do will be defined with tags. Tags have a start tag and an end tag (the `` tag is an exception here) +Start tags are mandatory (obviously), end tags aren't. +`Hello World!` and `Hello World!` and even `Hello World!` all do the same. + +Some tags have inner tags. Those look like this: `stuff`. For example: `test:TEST">TEST` or `TEST` +As you can see, those sometimes contain components, sometimes just strings. Refer to the details docs below. + + +## Components + +### Color + +Color the next parts + +Tag: `<_colorname_>` +Arguments: All minecraft color constants (check [here](https://github.com/KyoriPowered/adventure/blob/master/api/src/main/java/net/kyori/text/format/TextColor.java)) +Examples: +* `Hello World!` ![https://i.imgur.com/wB32YpZ.png](https://i.imgur.com/wB32YpZ.png) +* `This is a test!` ![https://i.imgur.com/vsN3OHa.png](https://i.imgur.com/vsN3OHa.png) + +### Decoration + +Decorate the next parts + +Tag: `<_decorationname_>` +Arguments: All minecraft decorations (check [here](https://github.com/KyoriPowered/adventure/blob/master/api/src/main/java/net/kyori/text/format/TextDecoration.java)) +Examples: +* `This is important!` ![https://i.imgur.com/hREGXQy.png](https://i.imgur.com/hREGXQy.png) + +### Reset + +Reset all colors, decorations, hovers etc. Doesn't have a close tag + +Tag: `` +Arguments: non +Examples: +* `Hello world!` ![https://i.imgur.com/bjInUhj.png](https://i.imgur.com/bjInUhj.png) + +### Click + +TODO + +### Hover + +TODO + +### Keybind + +Allows displaying the configured key for actions + +Tag: `` +Arguments: `_key_`, the minecraft key of the action +Examples: +* `Press to jump!` ![https://i.imgur.com/iQmNDF6.png](https://i.imgur.com/iQmNDF6.png) + +### Translatable + +Allows displaying minecraft messages using the player locale + +Tag: `` +Arguments: `_key_`, the translation key +Examples: +* `You should get a !` ![https://i.imgur.com/mpdDMF6.png](https://i.imgur.com/mpdDMF6.png) + +### Insertion + +Allow insertion of text into chat via shift click + +Tag: `` +Arguments: `_text_`, the text to insert +Examples: +* `Click this to insert!` ![https://i.imgur.com/Imhom84.png](https://i.imgur.com/Imhom84.png) + +### Pre + +Tags within this tag will not be parsed, useful for player input for example + +Tag: `
`  
+Arguments: non  
+Examples: 
+* `<> 
` ![https://i.imgur.com/pQqaJnD.png](https://i.imgur.com/pQqaJnD.png) diff --git a/README.md b/README.md index f5bfa3c56..58a1c684d 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,8 @@ Simple library that implements an easy to use textual format to send rich json m ## Examples +You can find a full breakdown of all supported components [here](DOCS.md). + MiniMessage: `TESTnestedTest` Json: From a18dc9f2bdf7d119ff25381121596fad89fb19ad Mon Sep 17 00:00:00 2001 From: Mark Vainomaa Date: Fri, 12 Jun 2020 18:40:07 +0200 Subject: [PATCH 055/339] Add '' tag support Signed-off-by: MiniDigger --- DOCS.md | 18 +++++++++++---- .../minimessage/text/Constants.java | 1 + .../minimessage/text/MiniMessageParser.java | 22 +++++++++++++++++++ .../text/MiniMessageParserTest.java | 10 +++++++++ 4 files changed, 47 insertions(+), 4 deletions(-) diff --git a/DOCS.md b/DOCS.md index 7efd3913c..433e3a96f 100644 --- a/DOCS.md +++ b/DOCS.md @@ -21,17 +21,27 @@ As you can see, those sometimes contain components, sometimes just strings. Refe Color the next parts Tag: `<_colorname_>` -Arguments: All minecraft color constants (check [here](https://github.com/KyoriPowered/adventure/blob/master/api/src/main/java/net/kyori/text/format/TextColor.java)) +Arguments: `_colorname_`, all minecraft color constants (check [here](https://github.com/KyoriPowered/adventure/blob/master/api/src/main/java/net/kyori/text/format/TextColor.java)) Examples: * `Hello World!` ![https://i.imgur.com/wB32YpZ.png](https://i.imgur.com/wB32YpZ.png) * `This is a test!` ![https://i.imgur.com/vsN3OHa.png](https://i.imgur.com/vsN3OHa.png) +### Color (2) + +A different, more flexible way (supports hex colors!) for colors looks like this + +Tag: `` +Arguments: `_colorNameOrHex_`, can be all the values from above, or hex colors (in 1.16) +Examples: +* `Hello World!` ![https://i.imgur.com/wB32YpZ.png](https://i.imgur.com/wB32YpZ.png) +* `This is a test!` ![https://i.imgur.com/vsN3OHa.png](https://i.imgur.com/vsN3OHa.png) + ### Decoration Decorate the next parts Tag: `<_decorationname_>` -Arguments: All minecraft decorations (check [here](https://github.com/KyoriPowered/adventure/blob/master/api/src/main/java/net/kyori/text/format/TextDecoration.java)) +Arguments: `_decorationname_` , all minecraft decorations (check [here](https://github.com/KyoriPowered/adventure/blob/master/api/src/main/java/net/kyori/text/format/TextDecoration.java)) Examples: * `This is important!` ![https://i.imgur.com/hREGXQy.png](https://i.imgur.com/hREGXQy.png) @@ -56,8 +66,8 @@ TODO Allows displaying the configured key for actions -Tag: `` -Arguments: `_key_`, the minecraft key of the action +Tag: `` +Arguments: `_key_`, the minecraft key of the action Examples: * `Press to jump!` ![https://i.imgur.com/iQmNDF6.png](https://i.imgur.com/iQmNDF6.png) diff --git a/minimessage-text/src/main/java/me/minidigger/minimessage/text/Constants.java b/minimessage-text/src/main/java/me/minidigger/minimessage/text/Constants.java index 299e9e9f3..c8f8c0f69 100644 --- a/minimessage-text/src/main/java/me/minidigger/minimessage/text/Constants.java +++ b/minimessage-text/src/main/java/me/minidigger/minimessage/text/Constants.java @@ -6,6 +6,7 @@ class Constants { public static final String KEYBIND = "key"; public static final String TRANSLATABLE = "lang"; public static final String INSERTION = "insert"; + public static final String COLOR = "color"; public static final String UNDERLINED = "underlined"; public static final String STRIKETHROUGH = "strikethrough"; diff --git a/minimessage-text/src/main/java/me/minidigger/minimessage/text/MiniMessageParser.java b/minimessage-text/src/main/java/me/minidigger/minimessage/text/MiniMessageParser.java index 8467cf7f8..dc473255d 100644 --- a/minimessage-text/src/main/java/me/minidigger/minimessage/text/MiniMessageParser.java +++ b/minimessage-text/src/main/java/me/minidigger/minimessage/text/MiniMessageParser.java @@ -23,6 +23,7 @@ import static me.minidigger.minimessage.text.Constants.CLICK; import static me.minidigger.minimessage.text.Constants.CLOSE_TAG; +import static me.minidigger.minimessage.text.Constants.COLOR; import static me.minidigger.minimessage.text.Constants.HOVER; import static me.minidigger.minimessage.text.Constants.INSERTION; import static me.minidigger.minimessage.text.Constants.KEYBIND; @@ -209,6 +210,12 @@ else if ((color = resolveColor(token)).isPresent()) { } else if (token.startsWith(CLOSE_TAG) && resolveColor(token.replace(CLOSE_TAG, "")).isPresent()) { colors.pollFirst(); } + // color; hex or named syntax + else if (token.startsWith(COLOR + SEPARATOR) && (color = resolveColorNew(token)).isPresent()) { + colors.push(color.get()); + } else if (token.startsWith(CLOSE_TAG + COLOR) && resolveColorNew(token.replace(CLOSE_TAG, "")).isPresent()) { + colors.pollFirst(); + } // keybind else if (token.startsWith(KEYBIND + SEPARATOR)) { if (current != null) { @@ -361,6 +368,21 @@ private static Optional resolveColor(@Nonnull String token) { return TextColor.NAMES.value(token.toLowerCase(Locale.ROOT)); } + @Nonnull + private static Optional resolveColorNew(@Nonnull String token) { + String[] args = token.split(SEPARATOR); + if (args.length < 2) { + throw new ParseException("Can't parse color (too few args) " + token); + } + + // TODO: hex colors + if (args[1].charAt(0) == '#') { + throw new ParseException("Hex color codes are not supported yet " + token); + } else { + return TextColor.NAMES.value(args[1].toLowerCase(Locale.ROOT)); + } + } + @Nonnull private static Optional resolveDecoration(@Nonnull String token) { try { diff --git a/minimessage-text/src/test/java/me/minidigger/minimessage/text/MiniMessageParserTest.java b/minimessage-text/src/test/java/me/minidigger/minimessage/text/MiniMessageParserTest.java index 4e7e7307f..2d2ba5c1a 100644 --- a/minimessage-text/src/test/java/me/minidigger/minimessage/text/MiniMessageParserTest.java +++ b/minimessage-text/src/test/java/me/minidigger/minimessage/text/MiniMessageParserTest.java @@ -22,6 +22,16 @@ public void test() { assertEquals(out1, out2); } + @Test + public void testNewColor() { + String input1 = "TEST nestedTest"; + String input2 = "TEST nestedTest"; + String out1 = GsonComponentSerializer.INSTANCE.serialize(MiniMessageParser.parseFormat(input1)); + String out2 = GsonComponentSerializer.INSTANCE.serialize(MiniMessageParser.parseFormat(input2)); + + assertEquals(out1, out2); + } + @Test public void testStripSimple() { String input = "TEST nestedTest"; From a1d35bfaa5d2d57e9888e459376f043053baea5d Mon Sep 17 00:00:00 2001 From: MiniDigger Date: Fri, 12 Jun 2020 22:14:35 +0200 Subject: [PATCH 056/339] Implement lang components with placeholders, finish docs This what a fucking pain in the ass Fixes #15 Signed-off-by: MiniDigger --- DOCS.md | 63 ++++++++++++++++--- .../minimessage/text/MiniMessageParser.java | 27 ++++++-- .../text/MiniMessageParserTest.java | 47 +++++++++++++- 3 files changed, 122 insertions(+), 15 deletions(-) diff --git a/DOCS.md b/DOCS.md index 433e3a96f..89109568e 100644 --- a/DOCS.md +++ b/DOCS.md @@ -13,6 +13,20 @@ Start tags are mandatory (obviously), end tags aren't. Some tags have inner tags. Those look like this: `stuff`. For example: `test:TEST">TEST` or `TEST` As you can see, those sometimes contain components, sometimes just strings. Refer to the details docs below. +Single (`'`) and double (`"`) quotes can be used interchangeably, but please stay consistent. + +The components try to represent vanilla as closely as possible. +It might to helpful to use [the minecraft wiki](https://minecraft.gamepedia.com/Raw_JSON_text_format) as a reference, especially for stuff like the actions and values of click and hover events. + +### A note on inner components + +Some components (like hover and translate) support nested/inner components. This feature is a total mess. It's best to assume that it only works because of luck. +Following things are known to be broken in inner components and should not be used: +* Colons (`:`) +* Quotation marks (both single `'` and double `"`), altho you may have luck with escaping them like this `\"` + +Please don't open issues about such cases, I don't think that I'll able to fix them. PRs are welcome tho! +There are two `@Ignore`'d unit cases that are disabled due to these limitations. ## Components @@ -21,7 +35,9 @@ As you can see, those sometimes contain components, sometimes just strings. Refe Color the next parts Tag: `<_colorname_>` -Arguments: `_colorname_`, all minecraft color constants (check [here](https://github.com/KyoriPowered/adventure/blob/master/api/src/main/java/net/kyori/text/format/TextColor.java)) +Arguments: +* `_colorname_`, all minecraft color constants (check [here](https://github.com/KyoriPowered/adventure/blob/master/api/src/main/java/net/kyori/text/format/TextColor.java)) + Examples: * `Hello World!` ![https://i.imgur.com/wB32YpZ.png](https://i.imgur.com/wB32YpZ.png) * `This is a test!` ![https://i.imgur.com/vsN3OHa.png](https://i.imgur.com/vsN3OHa.png) @@ -31,7 +47,9 @@ Examples: A different, more flexible way (supports hex colors!) for colors looks like this Tag: `` -Arguments: `_colorNameOrHex_`, can be all the values from above, or hex colors (in 1.16) +Arguments: +* `_colorNameOrHex_`, can be all the values from above, or hex colors (in 1.16) + Examples: * `Hello World!` ![https://i.imgur.com/wB32YpZ.png](https://i.imgur.com/wB32YpZ.png) * `This is a test!` ![https://i.imgur.com/vsN3OHa.png](https://i.imgur.com/vsN3OHa.png) @@ -41,7 +59,9 @@ Examples: Decorate the next parts Tag: `<_decorationname_>` -Arguments: `_decorationname_` , all minecraft decorations (check [here](https://github.com/KyoriPowered/adventure/blob/master/api/src/main/java/net/kyori/text/format/TextDecoration.java)) +Arguments: +* `_decorationname_` , all minecraft decorations (check [here](https://github.com/KyoriPowered/adventure/blob/master/api/src/main/java/net/kyori/text/format/TextDecoration.java)) + Examples: * `This is important!` ![https://i.imgur.com/hREGXQy.png](https://i.imgur.com/hREGXQy.png) @@ -56,18 +76,37 @@ Examples: ### Click -TODO +Allows doing multiple things when clicking on the component. + +Tag: `` +Arguments: +* `_action_`, the type of click event, one of [this list](https://github.com/KyoriPowered/adventure/blob/master/api/src/main/java/net/kyori/text/event/ClickEvent.java#L181) +* `_value_`, the argument for that particular event, refer to [the minecraft wiki](https://minecraft.gamepedia.com/Raw_JSON_text_format) + +Examples: +* `Click to say hello` ![https://i.imgur.com/J82qOHn.png](https://i.imgur.com/J82qOHn.png) +* `Click this to copy your score!` ### Hover -TODO +Allows doing multiple things when hovering on the component. + +Tag: `test'>TEST` ![https://i.imgur.com/VsHDPTI.png](https://i.imgur.com/VsHDPTI.png) ### Keybind Allows displaying the configured key for actions Tag: `` -Arguments: `_key_`, the minecraft key of the action +Arguments: +* `_key_`, the minecraft key of the action + Examples: * `Press to jump!` ![https://i.imgur.com/iQmNDF6.png](https://i.imgur.com/iQmNDF6.png) @@ -75,17 +114,23 @@ Examples: Allows displaying minecraft messages using the player locale -Tag: `` -Arguments: `_key_`, the translation key +Tag: `` +Arguments: +* `_key_`, the translation key +* `_valueX_`, optional values that are used for placeholders in the key (they will end up in the `with` tag in the json) + Examples: * `You should get a !` ![https://i.imgur.com/mpdDMF6.png](https://i.imgur.com/mpdDMF6.png) +* `1':'Stone'>!` ![https://i.imgur.com/esWpnxm.png](https://i.imgur.com/esWpnxm.png) ### Insertion Allow insertion of text into chat via shift click Tag: `` -Arguments: `_text_`, the text to insert +Arguments: +* `_text_`, the text to insert + Examples: * `Click this to insert!` ![https://i.imgur.com/Imhom84.png](https://i.imgur.com/Imhom84.png) diff --git a/minimessage-text/src/main/java/me/minidigger/minimessage/text/MiniMessageParser.java b/minimessage-text/src/main/java/me/minidigger/minimessage/text/MiniMessageParser.java index dc473255d..b435fb282 100644 --- a/minimessage-text/src/main/java/me/minidigger/minimessage/text/MiniMessageParser.java +++ b/minimessage-text/src/main/java/me/minidigger/minimessage/text/MiniMessageParser.java @@ -11,8 +11,10 @@ import net.kyori.text.format.TextDecoration; import java.util.ArrayDeque; +import java.util.ArrayList; import java.util.Deque; import java.util.EnumSet; +import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Optional; @@ -42,7 +44,9 @@ public class MiniMessageParser { private static final String INNER = "inner"; private static final String END = "end"; // https://regex101.com/r/8VZ7uA/6 - private static final Pattern pattern = Pattern.compile("((?<)(?([^<>]+)|([^<>]+[\"'](?[^\"']+)[\"']))(?>))+?"); + private static final Pattern pattern = Pattern.compile("((?<)(?[^<>]+(:(?['\"]?([^'\"](\\\\['\"])?)+['\"]?))*)(?>))+?"); + + private static final Pattern dumSplitPattern = Pattern.compile("['\"]:['\"]"); @Nonnull public static String escapeTokens(@Nonnull String richMessage) { @@ -229,7 +233,7 @@ else if (token.startsWith(TRANSLATABLE + SEPARATOR)) { if (current != null) { parent.append(current); } - current = handleTranslatable(token); + current = handleTranslatable(token, inner); current = applyFormatting(clickEvents, hoverEvents, colors, insertions, decorations, current); } // insertion @@ -324,12 +328,22 @@ private static String handleInsertion(@Nonnull String token) { } @Nonnull - private static Component handleTranslatable(@Nonnull String token) { + private static Component handleTranslatable(@Nonnull String token, String inner) { String[] args = token.split(SEPARATOR); if (args.length < 2) { throw new ParseException("Can't parse translatable (too few args) " + token); } - return TranslatableComponent.of(args[1]); + if (inner == null) { + return TranslatableComponent.of(args[1]); + } else { + List inners = new ArrayList<>(); + String toSplit = token.replace(args[0] + ":" + args[1] + ":", ""); + String[] split = dumSplitPattern.split(cleanInner(toSplit)); + for (String someInner : split) { + inners.add(parseFormat(someInner)); + } + return TranslatableComponent.of(args[1], inners); + } } @Nonnull @@ -358,6 +372,7 @@ private static HoverEvent handleHover(@Nonnull String token, @Nonnull String inn if (args.length < 2) { throw new ParseException("Can't parse hover action (too few args) " + token); } + inner = cleanInner(inner); HoverEvent.Action action = HoverEvent.Action.NAMES.value(args[1].toLowerCase(Locale.ROOT)) .orElseThrow(() -> new ParseException("Can't parse hover action (invalid action) " + token)); return HoverEvent.of(action, parseFormat(inner)); @@ -392,6 +407,10 @@ private static Optional resolveDecoration(@Nonnull String } } + private static String cleanInner(String inner) { + return inner.substring(1).substring(0, inner.length() - 2); // cut off first and last "/' + } + enum HelperTextDecoration { BOLD(b -> b.decoration(TextDecoration.BOLD, true)), ITALIC(b -> b.decoration(TextDecoration.ITALIC, true)), diff --git a/minimessage-text/src/test/java/me/minidigger/minimessage/text/MiniMessageParserTest.java b/minimessage-text/src/test/java/me/minidigger/minimessage/text/MiniMessageParserTest.java index 2d2ba5c1a..7d8d2aea6 100644 --- a/minimessage-text/src/test/java/me/minidigger/minimessage/text/MiniMessageParserTest.java +++ b/minimessage-text/src/test/java/me/minidigger/minimessage/text/MiniMessageParserTest.java @@ -3,6 +3,7 @@ import net.kyori.text.Component; import net.kyori.text.serializer.gson.GsonComponentSerializer; +import org.junit.Ignore; import org.junit.Test; import javax.annotation.Nonnull; @@ -151,8 +152,8 @@ public void testClick() { @Test public void testClickExtendedCommand() { - String input = "TEST"; - String expected = "{\"text\":\"TEST\",\"clickEvent\":{\"action\":\"run_command\",\"value\":\"test command\"}}"; + String input = "TEST"; + String expected = "{\"text\":\"TEST\",\"clickEvent\":{\"action\":\"run_command\",\"value\":\"/test command\"}}"; test(input, expected); } @@ -204,6 +205,34 @@ public void testTranslatable() { test(comp, expected); } + @Test + public void testTranslatableWith() { + String input = "Test: 1':'Stone'>!"; + String expected = "{\"text\":\"\",\"extra\":[{\"text\":\"Test: \"},{\"translate\":\"commands.drop.success.single\",\"with\":[{\"text\":\"1\",\"color\":\"red\"},{\"text\":\"Stone\",\"color\":\"blue\"}]},{\"text\":\"!\"}]}"; + Component comp = MiniMessageParser.parseFormat(input); + + test(comp, expected); + } + + @Test + @Ignore // TODO implement inner with ' or " + public void testTranslatableWithHover() { + String input = "Test: 1dum'>':'Stone'>!"; + String expected = "{\"text\":\"\",\"extra\":[{\"text\":\"Test: \"},{\"translate\":\"commands.drop.success.single\",\"with\":[{\"text\":\"1\",\"color\":\"red\"},{\"text\":\"Stone\",\"color\":\"blue\"}]},{\"text\":\"!\"}]}"; + Component comp = MiniMessageParser.parseFormat(input); + + test(comp, expected); + } + + @Test + public void testKingAlter() { + String input = "Ahoy mates!'>"; + String expected = "{\"text\":\"\",\"extra\":[{\"text\":\"Ahoy \"},{\"translate\":\"offset.-40\",\"with\":[{\"text\":\"mates!\",\"color\":\"red\"}]}]}"; + Component comp = MiniMessageParser.parseFormat(input); + + test(comp, expected); + } + @Test public void testInsertion() { String input = "Click this to insert!"; @@ -226,6 +255,20 @@ public void testGH5() { MiniMessageParser.parseFormat(input, "url", "https://www.google.com"); } + @Test + @Ignore // TODO implement inner with ' or " + public void testGH5Modified() { + String input = "» To download it from the internet, >/!\\ install it from 'Options/ResourcePacks' in your game\">CLICK HERE"; + String expected = "{\"text\":\"\",\"extra\":[{\"text\":\"»\",\"color\":\"dark_gray\"},{\"text\":\" To download it from the internet, \",\"color\":\"gray\"},{\"text\":\"CLICK HERE\",\"color\":\"green\",\"bold\":true,\"clickEvent\":{\"action\":\"open_url\",\"value\":\"https://www.google.com\"},\"hoverEvent\":{\"action\":\"show_text\",\"value\":{\"text\":\"/!\\\\ install it from 'Options/ResourcePacks' in your game\",\"color\":\"green\"}}}]}"; + + // should work + Component comp1 = MiniMessageParser.parseFormat(input, "pack_url", "https://www.google.com"); + test(comp1, expected); + + // shouldnt throw an error + MiniMessageParser.parseFormat(input, "url", "https://www.google.com"); + } + @Test public void testReset() { String input = "Click this to insert!"; From 5e2964cb1ecd65528d738206294dea877ab7bc16 Mon Sep 17 00:00:00 2001 From: MiniDigger Date: Fri, 12 Jun 2020 22:21:51 +0200 Subject: [PATCH 057/339] bump version, add changelog Signed-off-by: MiniDigger --- CHANGELOG.md | 14 ++++++++++++++ README.md | 4 ++++ minimessage-bungee/pom.xml | 2 +- minimessage-ext-markdown/pom.xml | 2 +- minimessage-text/pom.xml | 2 +- pom.xml | 2 +- 6 files changed, 22 insertions(+), 4 deletions(-) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000..13e4327b9 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,14 @@ +# Changelog + +# 2.1.0 (12.06.2020) + +* Move packages around to avoid confusion between bungee and text impls +* Allow the usage of single quotes to define inner components +* Use localized toLower/Uppercase methods (@mikroskeem) +* Use map lookup over valueOf when resolving stuff (@mikroskeem) +* Add new color tag syntax (in preparation for 1.16 hex colors) (@mikroskeem) +* Support reset tags +* Support pre tags +* Support placeholders in lang tags +* Add [docs](/DOCS.md) +* Add changelog (this, lol) diff --git a/README.md b/README.md index 58a1c684d..bc038b050 100644 --- a/README.md +++ b/README.md @@ -113,6 +113,10 @@ Repo (for snapshots): ``` Releases get deployed to maven central. +## Changelog + +You can find the changelog [here](CHANGELOG.md). + ## Upgrading to 2.0 2.0 seperated MiniMessage into two versions, `minimessage-bungee` and `minimessage-text`. diff --git a/minimessage-bungee/pom.xml b/minimessage-bungee/pom.xml index e9f668569..85ca307d7 100644 --- a/minimessage-bungee/pom.xml +++ b/minimessage-bungee/pom.xml @@ -4,7 +4,7 @@ MiniMessage me.minidigger - 2.0.4-SNAPSHOT + 2.1.0-SNAPSHOT minimessage-bungee diff --git a/minimessage-ext-markdown/pom.xml b/minimessage-ext-markdown/pom.xml index c4e682080..0252df714 100644 --- a/minimessage-ext-markdown/pom.xml +++ b/minimessage-ext-markdown/pom.xml @@ -3,7 +3,7 @@ MiniMessage me.minidigger - 2.0.4-SNAPSHOT + 2.1.0-SNAPSHOT 4.0.0 diff --git a/minimessage-text/pom.xml b/minimessage-text/pom.xml index c9476b0d8..1e0cb1f66 100644 --- a/minimessage-text/pom.xml +++ b/minimessage-text/pom.xml @@ -4,7 +4,7 @@ MiniMessage me.minidigger - 2.0.4-SNAPSHOT + 2.1.0-SNAPSHOT minimessage-text diff --git a/pom.xml b/pom.xml index 2064b2237..b300701a3 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ me.minidigger MiniMessage pom - 2.0.4-SNAPSHOT + 2.1.0-SNAPSHOT MiniMessage Simple library that implements an easy to use textual format to send rich json messages. https://github.com/MiniDigger/MiniMessage From a6b216b696e85828b920eee3226f0104831c20a4 Mon Sep 17 00:00:00 2001 From: MiniDigger Date: Fri, 12 Jun 2020 22:22:44 +0200 Subject: [PATCH 058/339] make sure latest regex101 is linked Signed-off-by: MiniDigger --- .../java/me/minidigger/minimessage/text/MiniMessageParser.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/minimessage-text/src/main/java/me/minidigger/minimessage/text/MiniMessageParser.java b/minimessage-text/src/main/java/me/minidigger/minimessage/text/MiniMessageParser.java index b435fb282..711e5c1b6 100644 --- a/minimessage-text/src/main/java/me/minidigger/minimessage/text/MiniMessageParser.java +++ b/minimessage-text/src/main/java/me/minidigger/minimessage/text/MiniMessageParser.java @@ -43,7 +43,7 @@ public class MiniMessageParser { private static final String TOKEN = "token"; private static final String INNER = "inner"; private static final String END = "end"; - // https://regex101.com/r/8VZ7uA/6 + // https://regex101.com/r/8VZ7uA/10 private static final Pattern pattern = Pattern.compile("((?<)(?[^<>]+(:(?['\"]?([^'\"](\\\\['\"])?)+['\"]?))*)(?>))+?"); private static final Pattern dumSplitPattern = Pattern.compile("['\"]:['\"]"); From c05d2515b362131b3a5e122a87daf11fcd51d745 Mon Sep 17 00:00:00 2001 From: release-bot Date: Fri, 12 Jun 2020 20:31:30 +0000 Subject: [PATCH 059/339] [maven-release-plugin] prepare release MiniMessage-2.1.0 --- minimessage-bungee/pom.xml | 2 +- minimessage-ext-markdown/pom.xml | 2 +- minimessage-text/pom.xml | 2 +- pom.xml | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/minimessage-bungee/pom.xml b/minimessage-bungee/pom.xml index 85ca307d7..0b17b340d 100644 --- a/minimessage-bungee/pom.xml +++ b/minimessage-bungee/pom.xml @@ -4,7 +4,7 @@ MiniMessage me.minidigger - 2.1.0-SNAPSHOT + 2.1.0 minimessage-bungee diff --git a/minimessage-ext-markdown/pom.xml b/minimessage-ext-markdown/pom.xml index 0252df714..c7365177d 100644 --- a/minimessage-ext-markdown/pom.xml +++ b/minimessage-ext-markdown/pom.xml @@ -3,7 +3,7 @@ MiniMessage me.minidigger - 2.1.0-SNAPSHOT + 2.1.0 4.0.0 diff --git a/minimessage-text/pom.xml b/minimessage-text/pom.xml index 1278ae53f..a6f5fdeda 100644 --- a/minimessage-text/pom.xml +++ b/minimessage-text/pom.xml @@ -4,7 +4,7 @@ MiniMessage me.minidigger - 2.1.0-SNAPSHOT + 2.1.0 minimessage-text diff --git a/pom.xml b/pom.xml index cf2dc7a3b..d7dafafe7 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ me.minidigger MiniMessage pom - 2.1.0-SNAPSHOT + 2.1.0 MiniMessage Simple library that implements an easy to use textual format to send rich json messages. https://github.com/MiniDigger/MiniMessage @@ -135,7 +135,7 @@ scm:git:https://github.com/MiniDigger/MiniMessage.git scm:git:https://github.com/MiniDigger/MiniMessage.git https://github.com/MiniDigger/MiniMessage - HEAD + MiniMessage-2.1.0 From 6f8db5dc7f2f45f5a51c99817d6dcec4aa1e9b7c Mon Sep 17 00:00:00 2001 From: release-bot Date: Fri, 12 Jun 2020 20:31:36 +0000 Subject: [PATCH 060/339] [maven-release-plugin] prepare for next development iteration --- minimessage-bungee/pom.xml | 2 +- minimessage-ext-markdown/pom.xml | 2 +- minimessage-text/pom.xml | 2 +- pom.xml | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/minimessage-bungee/pom.xml b/minimessage-bungee/pom.xml index 0b17b340d..211153b6b 100644 --- a/minimessage-bungee/pom.xml +++ b/minimessage-bungee/pom.xml @@ -4,7 +4,7 @@ MiniMessage me.minidigger - 2.1.0 + 2.1.1-SNAPSHOT minimessage-bungee diff --git a/minimessage-ext-markdown/pom.xml b/minimessage-ext-markdown/pom.xml index c7365177d..9632a5e73 100644 --- a/minimessage-ext-markdown/pom.xml +++ b/minimessage-ext-markdown/pom.xml @@ -3,7 +3,7 @@ MiniMessage me.minidigger - 2.1.0 + 2.1.1-SNAPSHOT 4.0.0 diff --git a/minimessage-text/pom.xml b/minimessage-text/pom.xml index a6f5fdeda..f5ffb1145 100644 --- a/minimessage-text/pom.xml +++ b/minimessage-text/pom.xml @@ -4,7 +4,7 @@ MiniMessage me.minidigger - 2.1.0 + 2.1.1-SNAPSHOT minimessage-text diff --git a/pom.xml b/pom.xml index d7dafafe7..b4aeb83bb 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ me.minidigger MiniMessage pom - 2.1.0 + 2.1.1-SNAPSHOT MiniMessage Simple library that implements an easy to use textual format to send rich json messages. https://github.com/MiniDigger/MiniMessage @@ -135,7 +135,7 @@ scm:git:https://github.com/MiniDigger/MiniMessage.git scm:git:https://github.com/MiniDigger/MiniMessage.git https://github.com/MiniDigger/MiniMessage - MiniMessage-2.1.0 + HEAD From aa47b6e0f676ab09ec5bf7c64e2c2dfddb741ad1 Mon Sep 17 00:00:00 2001 From: MiniDigger Date: Fri, 12 Jun 2020 22:33:51 +0200 Subject: [PATCH 061/339] update version in readme Signed-off-by: MiniDigger --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e017c5b12..8657adaca 100644 --- a/README.md +++ b/README.md @@ -100,7 +100,7 @@ Artifact: me.minidigger MiniMessage - 2.0.3 + 2.1.0 compile ``` From 9b10c166de12a5aeec53c27a57cc251f19a9f380 Mon Sep 17 00:00:00 2001 From: MiniDigger Date: Wed, 24 Jun 2020 17:52:05 +0200 Subject: [PATCH 062/339] Port to adventure/text 4.x --- minimessage-text/pom.xml | 8 +-- .../minimessage/text/MiniMessageParser.java | 31 ++++++------ .../text/MiniMessageSerializer.java | 43 ++++++++++------ .../text/MiniMessageParserTest.java | 22 +++++--- .../text/MiniMessageSerializerTest.java | 50 ++++++++++++------- 5 files changed, 93 insertions(+), 61 deletions(-) diff --git a/minimessage-text/pom.xml b/minimessage-text/pom.xml index f5ffb1145..7a049c5a7 100644 --- a/minimessage-text/pom.xml +++ b/minimessage-text/pom.xml @@ -13,14 +13,14 @@ net.kyori - text-api - 3.0.4 + adventure-api + 4.0.0-SNAPSHOT provided net.kyori - text-serializer-gson - 3.0.4 + adventure-text-serializer-gson + 4.0.0-SNAPSHOT test diff --git a/minimessage-text/src/main/java/me/minidigger/minimessage/text/MiniMessageParser.java b/minimessage-text/src/main/java/me/minidigger/minimessage/text/MiniMessageParser.java index 711e5c1b6..877c18f97 100644 --- a/minimessage-text/src/main/java/me/minidigger/minimessage/text/MiniMessageParser.java +++ b/minimessage-text/src/main/java/me/minidigger/minimessage/text/MiniMessageParser.java @@ -1,14 +1,14 @@ package me.minidigger.minimessage.text; -import net.kyori.text.Component; -import net.kyori.text.KeybindComponent; -import net.kyori.text.TextComponent; -import net.kyori.text.TextComponent.Builder; -import net.kyori.text.TranslatableComponent; -import net.kyori.text.event.ClickEvent; -import net.kyori.text.event.HoverEvent; -import net.kyori.text.format.TextColor; -import net.kyori.text.format.TextDecoration; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.KeybindComponent; +import net.kyori.adventure.text.TextComponent; +import net.kyori.adventure.text.TranslatableComponent; +import net.kyori.adventure.text.event.ClickEvent; +import net.kyori.adventure.text.event.HoverEvent; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.TextColor; +import net.kyori.adventure.text.format.TextDecoration; import java.util.ArrayDeque; import java.util.ArrayList; @@ -136,7 +136,7 @@ public static Component parseFormat(@Nonnull String richMessage, @Nonnull Map clickEvents = new ArrayDeque<>(); ArrayDeque hoverEvents = new ArrayDeque<>(); @@ -361,7 +361,7 @@ private static ClickEvent handleClick(@Nonnull String token, @Nonnull String inn if (args.length < 2) { throw new ParseException("Can't parse click action (too few args) " + token); } - ClickEvent.Action action = ClickEvent.Action.NAMES.value(args[1].toLowerCase(Locale.ROOT)) + ClickEvent.Action action = Optional.ofNullable(ClickEvent.Action.NAMES.value(args[1].toLowerCase(Locale.ROOT))) .orElseThrow(() -> new ParseException("Can't parse click action (invalid action) " + token)); return ClickEvent.of(action, token.replace(CLICK + SEPARATOR + args[1] + SEPARATOR, "")); } @@ -373,14 +373,14 @@ private static HoverEvent handleHover(@Nonnull String token, @Nonnull String inn throw new ParseException("Can't parse hover action (too few args) " + token); } inner = cleanInner(inner); - HoverEvent.Action action = HoverEvent.Action.NAMES.value(args[1].toLowerCase(Locale.ROOT)) + HoverEvent.Action action = Optional.ofNullable(HoverEvent.Action.NAMES.value(args[1].toLowerCase(Locale.ROOT))) .orElseThrow(() -> new ParseException("Can't parse hover action (invalid action) " + token)); return HoverEvent.of(action, parseFormat(inner)); } @Nonnull private static Optional resolveColor(@Nonnull String token) { - return TextColor.NAMES.value(token.toLowerCase(Locale.ROOT)); + return Optional.ofNullable(NamedTextColor.NAMES.value(token.toLowerCase(Locale.ROOT))); } @Nonnull @@ -390,11 +390,10 @@ private static Optional resolveColorNew(@Nonnull String token) { throw new ParseException("Can't parse color (too few args) " + token); } - // TODO: hex colors if (args[1].charAt(0) == '#') { - throw new ParseException("Hex color codes are not supported yet " + token); + return Optional.ofNullable(TextColor.fromHexString(args[1])); } else { - return TextColor.NAMES.value(args[1].toLowerCase(Locale.ROOT)); + return Optional.ofNullable(NamedTextColor.NAMES.value(args[1].toLowerCase(Locale.ROOT))); } } diff --git a/minimessage-text/src/main/java/me/minidigger/minimessage/text/MiniMessageSerializer.java b/minimessage-text/src/main/java/me/minidigger/minimessage/text/MiniMessageSerializer.java index e3e37afd5..c0d2ae6a4 100644 --- a/minimessage-text/src/main/java/me/minidigger/minimessage/text/MiniMessageSerializer.java +++ b/minimessage-text/src/main/java/me/minidigger/minimessage/text/MiniMessageSerializer.java @@ -1,13 +1,14 @@ package me.minidigger.minimessage.text; -import net.kyori.text.Component; -import net.kyori.text.KeybindComponent; -import net.kyori.text.TextComponent; -import net.kyori.text.TranslatableComponent; -import net.kyori.text.event.ClickEvent; -import net.kyori.text.event.HoverEvent; -import net.kyori.text.format.TextColor; -import net.kyori.text.format.TextDecoration; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.KeybindComponent; +import net.kyori.adventure.text.TextComponent; +import net.kyori.adventure.text.TranslatableComponent; +import net.kyori.adventure.text.event.ClickEvent; +import net.kyori.adventure.text.event.HoverEvent; +import net.kyori.adventure.text.format.TextColor; +import net.kyori.adventure.text.format.TextDecoration; import java.util.ArrayList; import java.util.List; @@ -18,6 +19,7 @@ import static me.minidigger.minimessage.text.Constants.BOLD; import static me.minidigger.minimessage.text.Constants.CLICK; import static me.minidigger.minimessage.text.Constants.CLOSE_TAG; +import static me.minidigger.minimessage.text.Constants.COLOR; import static me.minidigger.minimessage.text.Constants.HOVER; import static me.minidigger.minimessage.text.Constants.INSERTION; import static me.minidigger.minimessage.text.Constants.ITALIC; @@ -56,7 +58,7 @@ public static String serialize(@Nonnull Component component) { // ## color // ### white is not important - if (!TextColor.WHITE.equals(comp.color()) && comp.color() != null && (prevComp == null || prevComp.color() != comp.color())) { + if (!NamedTextColor.WHITE.equals(comp.color()) && comp.color() != null && (prevComp == null || prevComp.color() != comp.color())) { sb.append(startColor(Objects.requireNonNull(comp.color()))); } @@ -80,16 +82,17 @@ public static String serialize(@Nonnull Component component) { // ## hover // ### only start if prevComp didn't start the same one - HoverEvent hov = comp.hoverEvent(); + HoverEvent hov = comp.hoverEvent(); if (hov != null && (prevComp == null || areDifferent(hov, prevComp.hoverEvent()))) { - sb.append(startTag(String.format("%s" + SEPARATOR + "%s" + SEPARATOR + "\"%s\"", HOVER, HoverEvent.Action.NAMES.name(hov.action()), serialize(hov.value())))); + // TODO make sure the value cast is right + sb.append(startTag(String.format("%s" + SEPARATOR + "%s" + SEPARATOR + "\"%s\"", HOVER, HoverEvent.Action.NAMES.key(hov.action()), serialize((Component) hov.value())))); } // ## click // ### only start if prevComp didn't start the same one ClickEvent click = comp.clickEvent(); if (click != null && (prevComp == null || areDifferent(click, prevComp.clickEvent()))) { - sb.append(startTag(String.format("%s" + SEPARATOR + "%s" + SEPARATOR + "\"%s\"", CLICK, ClickEvent.Action.NAMES.name(click.action()), click.value()))); + sb.append(startTag(String.format("%s" + SEPARATOR + "%s" + SEPARATOR + "\"%s\"", CLICK, ClickEvent.Action.NAMES.key(click.action()), click.value()))); } // ## insertion @@ -116,8 +119,8 @@ public static String serialize(@Nonnull Component component) { // ## color // ### only end color if next comp is white and current isn't - if (nextComp != null && comp.color() != TextColor.WHITE && comp.color() != null) { - if (nextComp.color() == TextColor.WHITE || nextComp.color() == null) { + if (nextComp != null && comp.color() != NamedTextColor.WHITE && comp.color() != null) { + if (nextComp.color() == NamedTextColor.WHITE || nextComp.color() == null) { sb.append(endColor(Objects.requireNonNull(comp.color()))); } } @@ -182,12 +185,20 @@ private static boolean areDifferent(@Nonnull HoverEvent h1, @Nullable HoverEvent @Nonnull private static String startColor(@Nonnull TextColor color) { - return startTag(TextColor.NAMES.name(color)); + if (color instanceof NamedTextColor) { + return startTag(Objects.requireNonNull(NamedTextColor.NAMES.key((NamedTextColor) color))); + } else { + return startTag(COLOR + SEPARATOR + color.asHexString()); + } } @Nonnull private static String endColor(@Nonnull TextColor color) { - return endTag(TextColor.NAMES.name(color)); + if (color instanceof NamedTextColor) { + return endTag(Objects.requireNonNull(NamedTextColor.NAMES.key((NamedTextColor) color))); + } else { + return endTag(COLOR + SEPARATOR + color.asHexString()); + } } @Nonnull diff --git a/minimessage-text/src/test/java/me/minidigger/minimessage/text/MiniMessageParserTest.java b/minimessage-text/src/test/java/me/minidigger/minimessage/text/MiniMessageParserTest.java index 7d8d2aea6..4e3015531 100644 --- a/minimessage-text/src/test/java/me/minidigger/minimessage/text/MiniMessageParserTest.java +++ b/minimessage-text/src/test/java/me/minidigger/minimessage/text/MiniMessageParserTest.java @@ -1,7 +1,7 @@ package me.minidigger.minimessage.text; -import net.kyori.text.Component; -import net.kyori.text.serializer.gson.GsonComponentSerializer; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; import org.junit.Ignore; import org.junit.Test; @@ -33,6 +33,16 @@ public void testNewColor() { assertEquals(out1, out2); } + @Test + public void testHexColor() { + String input1 = "TEST nestedTest"; + String input2 = "TEST nestedTest"; + String out1 = GsonComponentSerializer.INSTANCE.serialize(MiniMessageParser.parseFormat(input1)); + String out2 = GsonComponentSerializer.INSTANCE.serialize(MiniMessageParser.parseFormat(input2)); + + assertEquals(out1, out2); + } + @Test public void testStripSimple() { String input = "TEST nestedTest"; @@ -121,7 +131,7 @@ public void testColorNotNested() { @Test public void testHover() { String input = "test\">TEST"; - String expected = "{\"text\":\"TEST\",\"hoverEvent\":{\"action\":\"show_text\",\"value\":{\"text\":\"test\",\"color\":\"red\"}}}"; + String expected = "{\"text\":\"TEST\",\"hoverEvent\":{\"action\":\"show_text\",\"contents\":{\"text\":\"test\",\"color\":\"red\"}}}"; test(input, expected); } @@ -129,7 +139,7 @@ public void testHover() { @Test public void testHover2() { String input = "test'>TEST"; - String expected = "{\"text\":\"TEST\",\"hoverEvent\":{\"action\":\"show_text\",\"value\":{\"text\":\"test\",\"color\":\"red\"}}}"; + String expected = "{\"text\":\"TEST\",\"hoverEvent\":{\"action\":\"show_text\",\"contents\":{\"text\":\"test\",\"color\":\"red\"}}}"; test(input, expected); } @@ -137,7 +147,7 @@ public void testHover2() { @Test public void testHoverWithColon() { String input = "test:TEST\">TEST"; - String expected = "{\"text\":\"TEST\",\"hoverEvent\":{\"action\":\"show_text\",\"value\":{\"text\":\"test:TEST\",\"color\":\"red\"}}}"; + String expected = "{\"text\":\"TEST\",\"hoverEvent\":{\"action\":\"show_text\",\"contents\":{\"text\":\"test:TEST\",\"color\":\"red\"}}}"; test(input, expected); } @@ -245,7 +255,7 @@ public void testInsertion() { @Test public void testGH5() { String input = "» To download it from the internet, >/!\\ install it from Options/ResourcePacks in your game\">CLICK HERE"; - String expected = "{\"text\":\"\",\"extra\":[{\"text\":\"»\",\"color\":\"dark_gray\"},{\"text\":\" To download it from the internet, \",\"color\":\"gray\"},{\"text\":\"CLICK HERE\",\"color\":\"green\",\"bold\":true,\"clickEvent\":{\"action\":\"open_url\",\"value\":\"https://www.google.com\"},\"hoverEvent\":{\"action\":\"show_text\",\"value\":{\"text\":\"/!\\\\ install it from Options/ResourcePacks in your game\",\"color\":\"green\"}}}]}"; + String expected = "{\"text\":\"\",\"extra\":[{\"text\":\"»\",\"color\":\"dark_gray\"},{\"text\":\" To download it from the internet, \",\"color\":\"gray\"},{\"text\":\"CLICK HERE\",\"color\":\"green\",\"bold\":true,\"clickEvent\":{\"action\":\"open_url\",\"value\":\"https://www.google.com\"},\"hoverEvent\":{\"action\":\"show_text\",\"contents\":{\"text\":\"/!\\\\ install it from Options/ResourcePacks in your game\",\"color\":\"green\"}}}]}"; // should work Component comp1 = MiniMessageParser.parseFormat(input, "pack_url", "https://www.google.com"); diff --git a/minimessage-text/src/test/java/me/minidigger/minimessage/text/MiniMessageSerializerTest.java b/minimessage-text/src/test/java/me/minidigger/minimessage/text/MiniMessageSerializerTest.java index f5248b031..1035b262e 100644 --- a/minimessage-text/src/test/java/me/minidigger/minimessage/text/MiniMessageSerializerTest.java +++ b/minimessage-text/src/test/java/me/minidigger/minimessage/text/MiniMessageSerializerTest.java @@ -1,13 +1,14 @@ package me.minidigger.minimessage.text; -import net.kyori.text.KeybindComponent; -import net.kyori.text.TextComponent; -import net.kyori.text.TextComponent.Builder; -import net.kyori.text.TranslatableComponent; -import net.kyori.text.event.ClickEvent; -import net.kyori.text.event.HoverEvent; -import net.kyori.text.format.TextColor; -import net.kyori.text.format.TextDecoration; +import net.kyori.adventure.text.KeybindComponent; +import net.kyori.adventure.text.TextComponent; +import net.kyori.adventure.text.TextComponent.Builder; +import net.kyori.adventure.text.TranslatableComponent; +import net.kyori.adventure.text.event.ClickEvent; +import net.kyori.adventure.text.event.HoverEvent; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.TextColor; +import net.kyori.adventure.text.format.TextDecoration; import org.junit.Test; @@ -21,7 +22,7 @@ public class MiniMessageSerializerTest { public void testColor() { String expected = "This is a test"; - Builder builder = TextComponent.builder().content("This is a test").color(TextColor.RED); + Builder builder = TextComponent.builder().content("This is a test").color(NamedTextColor.RED); test(builder, expected); } @@ -31,7 +32,7 @@ public void testColorClosing() { String expected = "This is a test"; Builder builder = TextComponent.builder() - .content("This is a ").color(TextColor.RED) + .content("This is a ").color(NamedTextColor.RED) .append("test"); test(builder, expected); @@ -42,9 +43,9 @@ public void testNestedColor() { String expected = "This is a blue test"; Builder builder = TextComponent.builder() - .content("This is a ").color(TextColor.RED) - .append("blue ", TextColor.BLUE) - .append("test", TextColor.RED); + .content("This is a ").color(NamedTextColor.RED) + .append("blue ", NamedTextColor.BLUE) + .append("test", NamedTextColor.RED); test(builder, expected); } @@ -79,8 +80,8 @@ public void testHoverWithNested() { Builder builder = TextComponent.builder() .content("Some hover").hoverEvent(HoverEvent.showText(TextComponent.builder() - .content("---").color(TextColor.RED) - .append("-", TextColor.BLUE, TextDecoration.BOLD) + .content("---").color(NamedTextColor.RED) + .append("-", NamedTextColor.BLUE, TextDecoration.BOLD) .build())) .append(" that ends here"); @@ -105,7 +106,7 @@ public void testContinuedClick() { Builder builder = TextComponent.builder() .content("Some click").clickEvent(ClickEvent.runCommand("test")) // TODO figure out how to avoid repeating the click event here - .append(" that doesn't end here", b -> b.color(TextColor.RED).clickEvent(ClickEvent.runCommand("test"))); + .append(" that doesn't end here", b -> b.color(NamedTextColor.RED).clickEvent(ClickEvent.runCommand("test"))); test(builder, expected); } @@ -116,7 +117,7 @@ public void testContinuedClick2() { Builder builder = TextComponent.builder() .content("Some click").clickEvent(ClickEvent.runCommand("test")) - .append(" that doesn't end here", b -> b.color(TextColor.RED).clickEvent(ClickEvent.runCommand("test"))); + .append(" that doesn't end here", b -> b.color(NamedTextColor.RED).clickEvent(ClickEvent.runCommand("test"))); test(builder, expected); } @@ -139,8 +140,8 @@ public void testKeyBindWithColor() { Builder builder = TextComponent.builder() .content("Press ") - .append(KeybindComponent.of("key.jump").color(TextColor.RED)) - .append(" to jump!", TextColor.RED); + .append(KeybindComponent.of("key.jump").color(NamedTextColor.RED)) + .append(" to jump!", NamedTextColor.RED); test(builder, expected); } @@ -169,6 +170,17 @@ public void testInsertion() { test(builder, expected); } + @Test + public void testHexColor() { + String expected = "This is a test"; + + Builder builder = TextComponent.builder() + .content("This is a ").color(TextColor.fromHexString("#ff0000")) + .append("test"); + + test(builder, expected); + } + private void test(@Nonnull Builder builder, @Nonnull String expected) { String string = MiniMessageSerializer.serialize(builder.build()); assertEquals(expected, string); From b46232482283af4cc2b834c031218c5fb05a4aba Mon Sep 17 00:00:00 2001 From: MiniDigger Date: Wed, 24 Jun 2020 20:03:41 +0200 Subject: [PATCH 063/339] add rainbow tags Signed-off-by: MiniDigger --- DOCS.md | 10 +++ .../minimessage/text/Constants.java | 6 ++ .../text/HelperTextDecoration.java | 26 ++++++ .../minimessage/text/MiniMessageParser.java | 85 ++++++++++++------- .../text/MiniMessageSerializer.java | 2 +- .../minidigger/minimessage/text/Rainbow.java | 41 +++++++++ .../text/MiniMessageParserTest.java | 18 ++++ 7 files changed, 156 insertions(+), 32 deletions(-) create mode 100644 minimessage-text/src/main/java/me/minidigger/minimessage/text/HelperTextDecoration.java create mode 100644 minimessage-text/src/main/java/me/minidigger/minimessage/text/Rainbow.java diff --git a/DOCS.md b/DOCS.md index 89109568e..81b9752f6 100644 --- a/DOCS.md +++ b/DOCS.md @@ -142,3 +142,13 @@ Tag: `
`
 Arguments: non  
 Examples: 
 * `<> 
` ![https://i.imgur.com/pQqaJnD.png](https://i.imgur.com/pQqaJnD.png) + +### Rainbow + +Rainbow colored text?! + +Tag: `` +Arguments: phase, optional +Examples: +* `Woo: ||||||||||||||||||||||||!` +* `Woo: ||||||||||||||||||||||||!` ![https://i.imgur.com/uNbyoYk.png](https://i.imgur.com/uNbyoYk.png) diff --git a/minimessage-text/src/main/java/me/minidigger/minimessage/text/Constants.java b/minimessage-text/src/main/java/me/minidigger/minimessage/text/Constants.java index c8f8c0f69..e36b1ca15 100644 --- a/minimessage-text/src/main/java/me/minidigger/minimessage/text/Constants.java +++ b/minimessage-text/src/main/java/me/minidigger/minimessage/text/Constants.java @@ -1,6 +1,7 @@ package me.minidigger.minimessage.text; class Constants { + // vanilla components public static final String CLICK = "click"; public static final String HOVER = "hover"; public static final String KEYBIND = "key"; @@ -8,6 +9,7 @@ class Constants { public static final String INSERTION = "insert"; public static final String COLOR = "color"; + // vanilla decoration public static final String UNDERLINED = "underlined"; public static final String STRIKETHROUGH = "strikethrough"; public static final String OBFUSCATED = "obfuscated"; @@ -16,6 +18,10 @@ class Constants { public static final String RESET = "reset"; public static final String PRE = "pre"; + // minimessage components + public static final String RAINBOW = "rainbow"; + + // minimessage tags public static final String TAG_START = "<"; public static final String TAG_END = ">"; public static final String CLOSE_TAG = "/"; diff --git a/minimessage-text/src/main/java/me/minidigger/minimessage/text/HelperTextDecoration.java b/minimessage-text/src/main/java/me/minidigger/minimessage/text/HelperTextDecoration.java new file mode 100644 index 000000000..d2270bf7d --- /dev/null +++ b/minimessage-text/src/main/java/me/minidigger/minimessage/text/HelperTextDecoration.java @@ -0,0 +1,26 @@ +package me.minidigger.minimessage.text; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.TextDecoration; + +import java.util.function.UnaryOperator; +import javax.annotation.Nonnull; + +enum HelperTextDecoration { + BOLD(b -> b.decoration(TextDecoration.BOLD, true)), + ITALIC(b -> b.decoration(TextDecoration.ITALIC, true)), + UNDERLINED(b -> b.decoration(TextDecoration.UNDERLINED, true)), + STRIKETHROUGH(b -> b.decoration(TextDecoration.STRIKETHROUGH, true)), + OBFUSCATED(b -> b.decoration(TextDecoration.OBFUSCATED, true)); + + private final UnaryOperator builder; + + HelperTextDecoration(@Nonnull UnaryOperator builder) { + this.builder = builder; + } + + @Nonnull + public Component apply(@Nonnull Component comp) { + return builder.apply(comp); + } +} diff --git a/minimessage-text/src/main/java/me/minidigger/minimessage/text/MiniMessageParser.java b/minimessage-text/src/main/java/me/minidigger/minimessage/text/MiniMessageParser.java index 877c18f97..ea935ea71 100644 --- a/minimessage-text/src/main/java/me/minidigger/minimessage/text/MiniMessageParser.java +++ b/minimessage-text/src/main/java/me/minidigger/minimessage/text/MiniMessageParser.java @@ -8,7 +8,6 @@ import net.kyori.adventure.text.event.HoverEvent; import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.TextColor; -import net.kyori.adventure.text.format.TextDecoration; import java.util.ArrayDeque; import java.util.ArrayList; @@ -18,7 +17,6 @@ import java.util.Locale; import java.util.Map; import java.util.Optional; -import java.util.function.UnaryOperator; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.annotation.Nonnull; @@ -30,6 +28,7 @@ import static me.minidigger.minimessage.text.Constants.INSERTION; import static me.minidigger.minimessage.text.Constants.KEYBIND; import static me.minidigger.minimessage.text.Constants.PRE; +import static me.minidigger.minimessage.text.Constants.RAINBOW; import static me.minidigger.minimessage.text.Constants.RESET; import static me.minidigger.minimessage.text.Constants.SEPARATOR; import static me.minidigger.minimessage.text.Constants.TAG_END; @@ -139,11 +138,12 @@ public static Component parseFormat(@Nonnull String richMessage) { TextComponent.Builder parent = TextComponent.builder(""); ArrayDeque clickEvents = new ArrayDeque<>(); - ArrayDeque hoverEvents = new ArrayDeque<>(); + ArrayDeque> hoverEvents = new ArrayDeque<>(); ArrayDeque colors = new ArrayDeque<>(); ArrayDeque insertions = new ArrayDeque<>(); EnumSet decorations = EnumSet.noneOf(HelperTextDecoration.class); boolean isPreformatted = false; + Rainbow rainbow = null; Matcher matcher = pattern.matcher(richMessage); int lastEnd = 0; @@ -162,7 +162,7 @@ public static Component parseFormat(@Nonnull String richMessage) { if (msg != null && msg.length() != 0) { // append message current = TextComponent.of(msg); - current = applyFormatting(clickEvents, hoverEvents, colors, insertions, decorations, current); + current = applyFormatting(clickEvents, hoverEvents, colors, insertions, decorations, current, rainbow); } @@ -184,7 +184,7 @@ public static Component parseFormat(@Nonnull String richMessage) { parent.append(current); } current = TextComponent.of(TAG_START + token + TAG_END); - current = applyFormatting(clickEvents, hoverEvents, colors, insertions, decorations, current); + current = applyFormatting(clickEvents, hoverEvents, colors, insertions, decorations, current, rainbow); parent.append(current); } continue; @@ -226,7 +226,7 @@ else if (token.startsWith(KEYBIND + SEPARATOR)) { parent.append(current); } current = handleKeybind(token); - current = applyFormatting(clickEvents, hoverEvents, colors, insertions, decorations, current); + current = applyFormatting(clickEvents, hoverEvents, colors, insertions, decorations, current, rainbow); } // translatable else if (token.startsWith(TRANSLATABLE + SEPARATOR)) { @@ -234,7 +234,7 @@ else if (token.startsWith(TRANSLATABLE + SEPARATOR)) { parent.append(current); } current = handleTranslatable(token, inner); - current = applyFormatting(clickEvents, hoverEvents, colors, insertions, decorations, current); + current = applyFormatting(clickEvents, hoverEvents, colors, insertions, decorations, current, rainbow); } // insertion else if (token.startsWith(INSERTION + SEPARATOR)) { @@ -254,13 +254,19 @@ else if (token.startsWith(RESET)) { else if (token.startsWith(PRE)) { isPreformatted = true; } + // rainbow + else if (token.startsWith(RAINBOW)) { + rainbow = handleRainbow(token); + } else if (token.startsWith(CLOSE_TAG + RAINBOW)) { + rainbow = null; + } // invalid tag else { if (current != null) { parent.append(current); } current = TextComponent.of(TAG_START + token + TAG_END); - current = applyFormatting(clickEvents, hoverEvents, colors, insertions, decorations, current); + current = applyFormatting(clickEvents, hoverEvents, colors, insertions, decorations, current, rainbow); } if (current != null) { @@ -275,7 +281,7 @@ else if (token.startsWith(PRE)) { Component current = TextComponent.of(msg); // set everything that is not closed yet - current = applyFormatting(clickEvents, hoverEvents, colors, insertions, decorations, current); + current = applyFormatting(clickEvents, hoverEvents, colors, insertions, decorations, current, rainbow); parent.append(current); } @@ -291,11 +297,11 @@ else if (token.startsWith(PRE)) { @Nonnull private static Component applyFormatting(@Nonnull Deque clickEvents, - @Nonnull Deque hoverEvents, + @Nonnull Deque> hoverEvents, @Nonnull Deque colors, @Nonnull Deque insertions, @Nonnull EnumSet decorations, - @Nonnull Component current) { + @Nonnull Component current, Rainbow rainbow) { // set everything that is not closed yet if (!clickEvents.isEmpty()) { current = current.clickEvent(clickEvents.peek()); @@ -315,9 +321,45 @@ private static Component applyFormatting(@Nonnull Deque clickEvents, if (!insertions.isEmpty()) { current = current.insertion(insertions.peek()); } + + if (rainbow != null && current instanceof TextComponent) { + Component parent = null; + TextComponent bigComponent = (TextComponent) current; + rainbow.init(bigComponent.content().length()); + // split into multiple components + for (int i = 0; i < bigComponent.content().length(); i++) { + Component smallComponent = TextComponent.of(bigComponent.content().charAt(i)); + // apply formatting + smallComponent = applyFormatting(clickEvents, hoverEvents, colors, insertions, decorations, smallComponent, null); + smallComponent = rainbow.apply(smallComponent); + // append + if (parent == null) { + parent = smallComponent; + } else { + parent = parent.append(smallComponent); + } + } + if (parent != null) { + current = parent; + } + } + return current; } + @Nonnull + private static Rainbow handleRainbow(String token) { + if (token.contains(SEPARATOR)) { + String phase = token.substring(token.indexOf(SEPARATOR) + 1); + try { + return new Rainbow(Integer.parseInt(phase)); + } catch (NumberFormatException ex) { + throw new ParseException("Can't parse rainbow phase (not a int) " + token); + } + } + return new Rainbow(); + } + @Nonnull private static String handleInsertion(@Nonnull String token) { String[] args = token.split(SEPARATOR); @@ -367,7 +409,7 @@ private static ClickEvent handleClick(@Nonnull String token, @Nonnull String inn } @Nonnull - private static HoverEvent handleHover(@Nonnull String token, @Nonnull String inner) { + private static HoverEvent handleHover(@Nonnull String token, @Nonnull String inner) { String[] args = token.split(SEPARATOR); if (args.length < 2) { throw new ParseException("Can't parse hover action (too few args) " + token); @@ -410,25 +452,6 @@ private static String cleanInner(String inner) { return inner.substring(1).substring(0, inner.length() - 2); // cut off first and last "/' } - enum HelperTextDecoration { - BOLD(b -> b.decoration(TextDecoration.BOLD, true)), - ITALIC(b -> b.decoration(TextDecoration.ITALIC, true)), - UNDERLINED(b -> b.decoration(TextDecoration.UNDERLINED, true)), - STRIKETHROUGH(b -> b.decoration(TextDecoration.STRIKETHROUGH, true)), - OBFUSCATED(b -> b.decoration(TextDecoration.OBFUSCATED, true)); - - private final UnaryOperator builder; - - HelperTextDecoration(@Nonnull UnaryOperator builder) { - this.builder = builder; - } - - @Nonnull - public Component apply(@Nonnull Component comp) { - return builder.apply(comp); - } - } - static class ParseException extends RuntimeException { public ParseException(@Nonnull String message) { super(message); diff --git a/minimessage-text/src/main/java/me/minidigger/minimessage/text/MiniMessageSerializer.java b/minimessage-text/src/main/java/me/minidigger/minimessage/text/MiniMessageSerializer.java index c0d2ae6a4..b845dbde7 100644 --- a/minimessage-text/src/main/java/me/minidigger/minimessage/text/MiniMessageSerializer.java +++ b/minimessage-text/src/main/java/me/minidigger/minimessage/text/MiniMessageSerializer.java @@ -178,7 +178,7 @@ private static boolean areDifferent(@Nonnull ClickEvent c1, @Nullable ClickEvent return !c1.equals(c2) && (!c1.action().equals(c2.action()) || !c1.value().equals(c2.value())); } - private static boolean areDifferent(@Nonnull HoverEvent h1, @Nullable HoverEvent h2) { + private static boolean areDifferent(@Nonnull HoverEvent h1, @Nullable HoverEvent h2) { if (h2 == null) return true; return !h1.equals(h2) && (!h1.action().equals(h2.action()));// TODO also compare value } diff --git a/minimessage-text/src/main/java/me/minidigger/minimessage/text/Rainbow.java b/minimessage-text/src/main/java/me/minidigger/minimessage/text/Rainbow.java new file mode 100644 index 000000000..0711d3aec --- /dev/null +++ b/minimessage-text/src/main/java/me/minidigger/minimessage/text/Rainbow.java @@ -0,0 +1,41 @@ +package me.minidigger.minimessage.text; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.TextColor; + +public class Rainbow { + + private int colorIndex = 0; + + private float center = 128; + private float width = 127; + private double frequency = 1; + + private int phase; + + public Rainbow() { + this(0); + } + + public Rainbow(int phase) { + this.phase = phase; + } + + public void init(int steps) { + center = 128; + width = 127; + frequency = Math.PI * 2 / steps; + } + + public Component apply(Component current) { + return current.color(getColor(phase)); + } + + private TextColor getColor(float phase) { + int index = colorIndex++; + int red = (int) (Math.sin(frequency * index + 2 + phase) * width + center); + int green = (int) (Math.sin(frequency * index + 0 + phase) * width + center); + int blue = (int) (Math.sin(frequency * index + 4 + phase) * width + center); + return TextColor.of(red, green, blue); + } +} diff --git a/minimessage-text/src/test/java/me/minidigger/minimessage/text/MiniMessageParserTest.java b/minimessage-text/src/test/java/me/minidigger/minimessage/text/MiniMessageParserTest.java index 4e3015531..3468b8cf7 100644 --- a/minimessage-text/src/test/java/me/minidigger/minimessage/text/MiniMessageParserTest.java +++ b/minimessage-text/src/test/java/me/minidigger/minimessage/text/MiniMessageParserTest.java @@ -297,6 +297,24 @@ public void testPre() { test(comp, expected); } + @Test + public void testRainbow() { + String input = "Woo: ||||||||||||||||||||||||!"; + String expected = "{\"text\":\"\",\"extra\":[{\"text\":\"Woo: \",\"color\":\"yellow\"},{\"text\":\"|\",\"extra\":[{\"text\":\"|\",\"color\":\"#e1a00d\"},{\"text\":\"|\",\"color\":\"#c9bf03\"},{\"text\":\"|\",\"color\":\"#acd901\"},{\"text\":\"|\",\"color\":\"#8bed08\"},{\"text\":\"|\",\"color\":\"#6afa16\"},{\"text\":\"|\",\"color\":\"#4bff2c\"},{\"text\":\"|\",\"color\":\"#2ffa48\"},{\"text\":\"|\",\"color\":\"#18ed68\"},{\"text\":\"|\",\"color\":\"#08d989\"},{\"text\":\"|\",\"color\":\"#01bfa9\"},{\"text\":\"|\",\"color\":\"#02a0c7\"},{\"text\":\"|\",\"color\":\"#0c80e0\"},{\"text\":\"|\",\"color\":\"#1e5ff2\"},{\"text\":\"|\",\"color\":\"#3640fc\"},{\"text\":\"|\",\"color\":\"#5326fe\"},{\"text\":\"|\",\"color\":\"#7412f7\"},{\"text\":\"|\",\"color\":\"#9505e9\"},{\"text\":\"|\",\"color\":\"#b401d3\"},{\"text\":\"|\",\"color\":\"#d005b7\"},{\"text\":\"|\",\"color\":\"#e71297\"},{\"text\":\"|\",\"color\":\"#f72676\"},{\"text\":\"|\",\"color\":\"#fe4056\"},{\"text\":\"|\",\"color\":\"#fd5f38\"}],\"color\":\"#f3801f\"},{\"text\":\"!\",\"color\":\"yellow\"}]}"; + Component comp = MiniMessageParser.parseFormat(input); + + test(comp, expected); + } + + @Test + public void testRainbowPhase() { + String input = "Woo: ||||||||||||||||||||||||!"; + String expected = "{\"text\":\"\",\"extra\":[{\"text\":\"Woo: \",\"color\":\"yellow\"},{\"text\":\"|\",\"extra\":[{\"text\":\"|\",\"color\":\"#e1a00d\"},{\"text\":\"|\",\"color\":\"#c9bf03\"},{\"text\":\"|\",\"color\":\"#acd901\"},{\"text\":\"|\",\"color\":\"#8bed08\"},{\"text\":\"|\",\"color\":\"#6afa16\"},{\"text\":\"|\",\"color\":\"#4bff2c\"},{\"text\":\"|\",\"color\":\"#2ffa48\"},{\"text\":\"|\",\"color\":\"#18ed68\"},{\"text\":\"|\",\"color\":\"#08d989\"},{\"text\":\"|\",\"color\":\"#01bfa9\"},{\"text\":\"|\",\"color\":\"#02a0c7\"},{\"text\":\"|\",\"color\":\"#0c80e0\"},{\"text\":\"|\",\"color\":\"#1e5ff2\"},{\"text\":\"|\",\"color\":\"#3640fc\"},{\"text\":\"|\",\"color\":\"#5326fe\"},{\"text\":\"|\",\"color\":\"#7412f7\"},{\"text\":\"|\",\"color\":\"#9505e9\"},{\"text\":\"|\",\"color\":\"#b401d3\"},{\"text\":\"|\",\"color\":\"#d005b7\"},{\"text\":\"|\",\"color\":\"#e71297\"},{\"text\":\"|\",\"color\":\"#f72676\"},{\"text\":\"|\",\"color\":\"#fe4056\"},{\"text\":\"|\",\"color\":\"#fd5f38\"}],\"color\":\"#f3801f\"},{\"text\":\"!\",\"color\":\"yellow\"}]}"; + Component comp = MiniMessageParser.parseFormat(input); + + test(comp, expected); + } + private void test(@Nonnull String input, @Nonnull String expected) { test(MiniMessageParser.parseFormat(input), expected); } From 923922baf44dd3a8fd69eda34aeab755d34b682e Mon Sep 17 00:00:00 2001 From: MiniDigger Date: Wed, 24 Jun 2020 21:13:46 +0200 Subject: [PATCH 064/339] add gradient tags Signed-off-by: MiniDigger --- DOCS.md | 10 +++ .../minimessage/text/Constants.java | 1 + .../minimessage/text/MiniMessageParser.java | 72 ++++++++++++++----- .../minimessage/text/fancy/Fancy.java | 10 +++ .../minimessage/text/fancy/Gradient.java | 45 ++++++++++++ .../minimessage/text/{ => fancy}/Rainbow.java | 12 ++-- .../text/MiniMessageParserTest.java | 29 +++++++- 7 files changed, 156 insertions(+), 23 deletions(-) create mode 100644 minimessage-text/src/main/java/me/minidigger/minimessage/text/fancy/Fancy.java create mode 100644 minimessage-text/src/main/java/me/minidigger/minimessage/text/fancy/Gradient.java rename minimessage-text/src/main/java/me/minidigger/minimessage/text/{ => fancy}/Rainbow.java (80%) diff --git a/DOCS.md b/DOCS.md index 81b9752f6..3e6bee818 100644 --- a/DOCS.md +++ b/DOCS.md @@ -152,3 +152,13 @@ Arguments: phase, optional Examples: * `Woo: ||||||||||||||||||||||||!` * `Woo: ||||||||||||||||||||||||!` ![https://i.imgur.com/uNbyoYk.png](https://i.imgur.com/uNbyoYk.png) + +### Gradient + +Gradient colored text +Tag: `` +Arguments: color1 and 2, either hex or named colors +Examples: +* `Woo: ||||||||||||||||||||||||!` +* `Woo: ||||||||||||||||||||||||!` +* `Woo: ||||||||||||||||||||||||!` ![https://i.imgur.com/8qYHCWk.png](https://i.imgur.com/8qYHCWk.png) diff --git a/minimessage-text/src/main/java/me/minidigger/minimessage/text/Constants.java b/minimessage-text/src/main/java/me/minidigger/minimessage/text/Constants.java index e36b1ca15..5f7ec2aaa 100644 --- a/minimessage-text/src/main/java/me/minidigger/minimessage/text/Constants.java +++ b/minimessage-text/src/main/java/me/minidigger/minimessage/text/Constants.java @@ -20,6 +20,7 @@ class Constants { // minimessage components public static final String RAINBOW = "rainbow"; + public static final String GRADIENT = "gradient"; // minimessage tags public static final String TAG_START = "<"; diff --git a/minimessage-text/src/main/java/me/minidigger/minimessage/text/MiniMessageParser.java b/minimessage-text/src/main/java/me/minidigger/minimessage/text/MiniMessageParser.java index ea935ea71..af3bd1b1d 100644 --- a/minimessage-text/src/main/java/me/minidigger/minimessage/text/MiniMessageParser.java +++ b/minimessage-text/src/main/java/me/minidigger/minimessage/text/MiniMessageParser.java @@ -13,6 +13,8 @@ import java.util.ArrayList; import java.util.Deque; import java.util.EnumSet; +import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; import java.util.Map; @@ -21,9 +23,14 @@ import java.util.regex.Pattern; import javax.annotation.Nonnull; +import me.minidigger.minimessage.text.fancy.Fancy; +import me.minidigger.minimessage.text.fancy.Gradient; +import me.minidigger.minimessage.text.fancy.Rainbow; + import static me.minidigger.minimessage.text.Constants.CLICK; import static me.minidigger.minimessage.text.Constants.CLOSE_TAG; import static me.minidigger.minimessage.text.Constants.COLOR; +import static me.minidigger.minimessage.text.Constants.GRADIENT; import static me.minidigger.minimessage.text.Constants.HOVER; import static me.minidigger.minimessage.text.Constants.INSERTION; import static me.minidigger.minimessage.text.Constants.KEYBIND; @@ -47,6 +54,8 @@ public class MiniMessageParser { private static final Pattern dumSplitPattern = Pattern.compile("['\"]:['\"]"); + private static final Map, Fancy> empty = new HashMap<>(); + @Nonnull public static String escapeTokens(@Nonnull String richMessage) { StringBuilder sb = new StringBuilder(); @@ -143,7 +152,7 @@ public static Component parseFormat(@Nonnull String richMessage) { ArrayDeque insertions = new ArrayDeque<>(); EnumSet decorations = EnumSet.noneOf(HelperTextDecoration.class); boolean isPreformatted = false; - Rainbow rainbow = null; + Map, Fancy> fancy = new LinkedHashMap<>(); Matcher matcher = pattern.matcher(richMessage); int lastEnd = 0; @@ -162,7 +171,7 @@ public static Component parseFormat(@Nonnull String richMessage) { if (msg != null && msg.length() != 0) { // append message current = TextComponent.of(msg); - current = applyFormatting(clickEvents, hoverEvents, colors, insertions, decorations, current, rainbow); + current = applyFormatting(clickEvents, hoverEvents, colors, insertions, decorations, current, fancy); } @@ -184,7 +193,7 @@ public static Component parseFormat(@Nonnull String richMessage) { parent.append(current); } current = TextComponent.of(TAG_START + token + TAG_END); - current = applyFormatting(clickEvents, hoverEvents, colors, insertions, decorations, current, rainbow); + current = applyFormatting(clickEvents, hoverEvents, colors, insertions, decorations, current, fancy); parent.append(current); } continue; @@ -226,7 +235,7 @@ else if (token.startsWith(KEYBIND + SEPARATOR)) { parent.append(current); } current = handleKeybind(token); - current = applyFormatting(clickEvents, hoverEvents, colors, insertions, decorations, current, rainbow); + current = applyFormatting(clickEvents, hoverEvents, colors, insertions, decorations, current, fancy); } // translatable else if (token.startsWith(TRANSLATABLE + SEPARATOR)) { @@ -234,7 +243,7 @@ else if (token.startsWith(TRANSLATABLE + SEPARATOR)) { parent.append(current); } current = handleTranslatable(token, inner); - current = applyFormatting(clickEvents, hoverEvents, colors, insertions, decorations, current, rainbow); + current = applyFormatting(clickEvents, hoverEvents, colors, insertions, decorations, current, fancy); } // insertion else if (token.startsWith(INSERTION + SEPARATOR)) { @@ -256,9 +265,15 @@ else if (token.startsWith(PRE)) { } // rainbow else if (token.startsWith(RAINBOW)) { - rainbow = handleRainbow(token); + fancy.put(Rainbow.class, handleRainbow(token)); } else if (token.startsWith(CLOSE_TAG + RAINBOW)) { - rainbow = null; + fancy.remove(Rainbow.class); + } + // gradient + else if (token.startsWith(GRADIENT)) { + fancy.put(Gradient.class, handleGradient(token)); + } else if (token.startsWith(CLOSE_TAG + GRADIENT)) { + fancy.remove(Gradient.class); } // invalid tag else { @@ -266,7 +281,7 @@ else if (token.startsWith(RAINBOW)) { parent.append(current); } current = TextComponent.of(TAG_START + token + TAG_END); - current = applyFormatting(clickEvents, hoverEvents, colors, insertions, decorations, current, rainbow); + current = applyFormatting(clickEvents, hoverEvents, colors, insertions, decorations, current, fancy); } if (current != null) { @@ -281,7 +296,7 @@ else if (token.startsWith(RAINBOW)) { Component current = TextComponent.of(msg); // set everything that is not closed yet - current = applyFormatting(clickEvents, hoverEvents, colors, insertions, decorations, current, rainbow); + current = applyFormatting(clickEvents, hoverEvents, colors, insertions, decorations, current, fancy); parent.append(current); } @@ -301,7 +316,8 @@ private static Component applyFormatting(@Nonnull Deque clickEvents, @Nonnull Deque colors, @Nonnull Deque insertions, @Nonnull EnumSet decorations, - @Nonnull Component current, Rainbow rainbow) { + @Nonnull Component current, + @Nonnull Map, Fancy> fancy) { // set everything that is not closed yet if (!clickEvents.isEmpty()) { current = current.clickEvent(clickEvents.peek()); @@ -322,16 +338,18 @@ private static Component applyFormatting(@Nonnull Deque clickEvents, current = current.insertion(insertions.peek()); } - if (rainbow != null && current instanceof TextComponent) { + if (current instanceof TextComponent && fancy.size() != 0) { Component parent = null; TextComponent bigComponent = (TextComponent) current; - rainbow.init(bigComponent.content().length()); + + Fancy next = fancy.entrySet().iterator().next().getValue(); + next.init(bigComponent.content().length()); // split into multiple components for (int i = 0; i < bigComponent.content().length(); i++) { Component smallComponent = TextComponent.of(bigComponent.content().charAt(i)); // apply formatting - smallComponent = applyFormatting(clickEvents, hoverEvents, colors, insertions, decorations, smallComponent, null); - smallComponent = rainbow.apply(smallComponent); + smallComponent = applyFormatting(clickEvents, hoverEvents, colors, insertions, decorations, smallComponent, empty); + smallComponent = next.apply(smallComponent); // append if (parent == null) { parent = smallComponent; @@ -360,6 +378,21 @@ private static Rainbow handleRainbow(String token) { return new Rainbow(); } + @Nonnull + private static Gradient handleGradient(String token) { + if (token.contains(SEPARATOR)) { + String[] split = token.split(":"); + if (split.length == 3) { + TextColor c1 = parseColor(split[1]).orElseThrow(() -> new ParseException("Can't parse gradient phase (not a color 1) " + token)); + TextColor c2 = parseColor(split[2]).orElseThrow(() -> new ParseException("Can't parse gradient phase (not a color 2) " + token)); + return new Gradient(c1, c2); + } else { + throw new ParseException("Can't parse gradient (wrong args) " + token); + } + } + return new Gradient(); + } + @Nonnull private static String handleInsertion(@Nonnull String token) { String[] args = token.split(SEPARATOR); @@ -432,10 +465,15 @@ private static Optional resolveColorNew(@Nonnull String token) { throw new ParseException("Can't parse color (too few args) " + token); } - if (args[1].charAt(0) == '#') { - return Optional.ofNullable(TextColor.fromHexString(args[1])); + return parseColor(args[1]); + } + + @Nonnull + private static Optional parseColor(String color) { + if (color.charAt(0) == '#') { + return Optional.ofNullable(TextColor.fromHexString(color)); } else { - return Optional.ofNullable(NamedTextColor.NAMES.value(args[1].toLowerCase(Locale.ROOT))); + return Optional.ofNullable(NamedTextColor.NAMES.value(color.toLowerCase(Locale.ROOT))); } } diff --git a/minimessage-text/src/main/java/me/minidigger/minimessage/text/fancy/Fancy.java b/minimessage-text/src/main/java/me/minidigger/minimessage/text/fancy/Fancy.java new file mode 100644 index 000000000..3332e7b87 --- /dev/null +++ b/minimessage-text/src/main/java/me/minidigger/minimessage/text/fancy/Fancy.java @@ -0,0 +1,10 @@ +package me.minidigger.minimessage.text.fancy; + +import net.kyori.adventure.text.Component; + +import java.util.function.Function; + +public interface Fancy extends Function { + + void init(int size); +} diff --git a/minimessage-text/src/main/java/me/minidigger/minimessage/text/fancy/Gradient.java b/minimessage-text/src/main/java/me/minidigger/minimessage/text/fancy/Gradient.java new file mode 100644 index 000000000..d8e691f4f --- /dev/null +++ b/minimessage-text/src/main/java/me/minidigger/minimessage/text/fancy/Gradient.java @@ -0,0 +1,45 @@ +package me.minidigger.minimessage.text.fancy; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.TextColor; + +public class Gradient implements Fancy { + + private int colorIndex = 0; + + private float factorStep = 0.5f; + private final TextColor color1; + private final TextColor color2; + + public Gradient() { + this(TextColor.fromHexString("#ffffff"), TextColor.fromHexString("#000000")); + } + + public Gradient(TextColor c1, TextColor c2) { + this.color1 = c1; + this.color2 = c2; + } + + @Override + public void init(int size) { + this.factorStep = (float) (1. / (size - 1)); + } + + @Override + public Component apply(Component current) { + return current.color(getColor()); + } + + private TextColor getColor() { + float factor = factorStep * colorIndex++; + return interpolate(color1, color2, factor); + } + + private TextColor interpolate(TextColor color1, TextColor color2, float factor) { + return TextColor.of( + Math.round(color1.red() + factor * (color2.red() - color1.red())), + Math.round(color1.green() + factor * (color2.green() - color1.green())), + Math.round(color1.blue() + factor * (color2.blue() - color1.blue())) + ); + } +} diff --git a/minimessage-text/src/main/java/me/minidigger/minimessage/text/Rainbow.java b/minimessage-text/src/main/java/me/minidigger/minimessage/text/fancy/Rainbow.java similarity index 80% rename from minimessage-text/src/main/java/me/minidigger/minimessage/text/Rainbow.java rename to minimessage-text/src/main/java/me/minidigger/minimessage/text/fancy/Rainbow.java index 0711d3aec..db10458f2 100644 --- a/minimessage-text/src/main/java/me/minidigger/minimessage/text/Rainbow.java +++ b/minimessage-text/src/main/java/me/minidigger/minimessage/text/fancy/Rainbow.java @@ -1,9 +1,9 @@ -package me.minidigger.minimessage.text; +package me.minidigger.minimessage.text.fancy; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.TextColor; -public class Rainbow { +public class Rainbow implements Fancy { private int colorIndex = 0; @@ -11,7 +11,7 @@ public class Rainbow { private float width = 127; private double frequency = 1; - private int phase; + private final int phase; public Rainbow() { this(0); @@ -21,12 +21,14 @@ public Rainbow(int phase) { this.phase = phase; } - public void init(int steps) { + @Override + public void init(int size) { center = 128; width = 127; - frequency = Math.PI * 2 / steps; + frequency = Math.PI * 2 / size; } + @Override public Component apply(Component current) { return current.color(getColor(phase)); } diff --git a/minimessage-text/src/test/java/me/minidigger/minimessage/text/MiniMessageParserTest.java b/minimessage-text/src/test/java/me/minidigger/minimessage/text/MiniMessageParserTest.java index 3468b8cf7..884a68b59 100644 --- a/minimessage-text/src/test/java/me/minidigger/minimessage/text/MiniMessageParserTest.java +++ b/minimessage-text/src/test/java/me/minidigger/minimessage/text/MiniMessageParserTest.java @@ -309,7 +309,34 @@ public void testRainbow() { @Test public void testRainbowPhase() { String input = "Woo: ||||||||||||||||||||||||!"; - String expected = "{\"text\":\"\",\"extra\":[{\"text\":\"Woo: \",\"color\":\"yellow\"},{\"text\":\"|\",\"extra\":[{\"text\":\"|\",\"color\":\"#e1a00d\"},{\"text\":\"|\",\"color\":\"#c9bf03\"},{\"text\":\"|\",\"color\":\"#acd901\"},{\"text\":\"|\",\"color\":\"#8bed08\"},{\"text\":\"|\",\"color\":\"#6afa16\"},{\"text\":\"|\",\"color\":\"#4bff2c\"},{\"text\":\"|\",\"color\":\"#2ffa48\"},{\"text\":\"|\",\"color\":\"#18ed68\"},{\"text\":\"|\",\"color\":\"#08d989\"},{\"text\":\"|\",\"color\":\"#01bfa9\"},{\"text\":\"|\",\"color\":\"#02a0c7\"},{\"text\":\"|\",\"color\":\"#0c80e0\"},{\"text\":\"|\",\"color\":\"#1e5ff2\"},{\"text\":\"|\",\"color\":\"#3640fc\"},{\"text\":\"|\",\"color\":\"#5326fe\"},{\"text\":\"|\",\"color\":\"#7412f7\"},{\"text\":\"|\",\"color\":\"#9505e9\"},{\"text\":\"|\",\"color\":\"#b401d3\"},{\"text\":\"|\",\"color\":\"#d005b7\"},{\"text\":\"|\",\"color\":\"#e71297\"},{\"text\":\"|\",\"color\":\"#f72676\"},{\"text\":\"|\",\"color\":\"#fe4056\"},{\"text\":\"|\",\"color\":\"#fd5f38\"}],\"color\":\"#f3801f\"},{\"text\":\"!\",\"color\":\"yellow\"}]}"; + String expected = "{\"text\":\"\",\"extra\":[{\"text\":\"Woo: \",\"color\":\"yellow\"},{\"text\":\"|\",\"extra\":[{\"text\":\"|\",\"color\":\"#0de17d\"},{\"text\":\"|\",\"color\":\"#03c99e\"},{\"text\":\"|\",\"color\":\"#01acbd\"},{\"text\":\"|\",\"color\":\"#088bd7\"},{\"text\":\"|\",\"color\":\"#166aec\"},{\"text\":\"|\",\"color\":\"#2c4bf9\"},{\"text\":\"|\",\"color\":\"#482ffe\"},{\"text\":\"|\",\"color\":\"#6818fb\"},{\"text\":\"|\",\"color\":\"#8908ef\"},{\"text\":\"|\",\"color\":\"#a901db\"},{\"text\":\"|\",\"color\":\"#c702c1\"},{\"text\":\"|\",\"color\":\"#e00ca3\"},{\"text\":\"|\",\"color\":\"#f21e82\"},{\"text\":\"|\",\"color\":\"#fc3661\"},{\"text\":\"|\",\"color\":\"#fe5342\"},{\"text\":\"|\",\"color\":\"#f77428\"},{\"text\":\"|\",\"color\":\"#e99513\"},{\"text\":\"|\",\"color\":\"#d3b406\"},{\"text\":\"|\",\"color\":\"#b7d001\"},{\"text\":\"|\",\"color\":\"#97e704\"},{\"text\":\"|\",\"color\":\"#76f710\"},{\"text\":\"|\",\"color\":\"#56fe24\"},{\"text\":\"|\",\"color\":\"#38fd3e\"}],\"color\":\"#1ff35c\"},{\"text\":\"!\",\"color\":\"yellow\"}]}"; + Component comp = MiniMessageParser.parseFormat(input); + + test(comp, expected); + } + + @Test + public void testGradient() { + String input = "Woo: ||||||||||||||||||||||||!"; + String expected = "{\"text\":\"\",\"extra\":[{\"text\":\"Woo: \",\"color\":\"yellow\"},{\"text\":\"|\",\"extra\":[{\"text\":\"|\",\"color\":\"#f4f4f4\"},{\"text\":\"|\",\"color\":\"#e9e9e9\"},{\"text\":\"|\",\"color\":\"#dedede\"},{\"text\":\"|\",\"color\":\"#d3d3d3\"},{\"text\":\"|\",\"color\":\"#c8c8c8\"},{\"text\":\"|\",\"color\":\"#bcbcbc\"},{\"text\":\"|\",\"color\":\"#b1b1b1\"},{\"text\":\"|\",\"color\":\"#a6a6a6\"},{\"text\":\"|\",\"color\":\"#9b9b9b\"},{\"text\":\"|\",\"color\":\"#909090\"},{\"text\":\"|\",\"color\":\"#858585\"},{\"text\":\"|\",\"color\":\"#7a7a7a\"},{\"text\":\"|\",\"color\":\"#6f6f6f\"},{\"text\":\"|\",\"color\":\"#646464\"},{\"text\":\"|\",\"color\":\"#595959\"},{\"text\":\"|\",\"color\":\"#4e4e4e\"},{\"text\":\"|\",\"color\":\"#434343\"},{\"text\":\"|\",\"color\":\"#373737\"},{\"text\":\"|\",\"color\":\"#2c2c2c\"},{\"text\":\"|\",\"color\":\"#212121\"},{\"text\":\"|\",\"color\":\"#161616\"},{\"text\":\"|\",\"color\":\"#0b0b0b\"},{\"text\":\"|\",\"color\":\"black\"}],\"color\":\"white\"},{\"text\":\"!\",\"color\":\"yellow\"}]}"; + Component comp = MiniMessageParser.parseFormat(input); + + test(comp, expected); + } + + @Test + public void testGradient2() { + String input = "Woo: ||||||||||||||||||||||||!"; + String expected = "{\"text\":\"\",\"extra\":[{\"text\":\"Woo: \",\"color\":\"yellow\"},{\"text\":\"|\",\"extra\":[{\"text\":\"|\",\"color\":\"#65529f\"},{\"text\":\"|\",\"color\":\"#6b559c\"},{\"text\":\"|\",\"color\":\"#725898\"},{\"text\":\"|\",\"color\":\"#795b95\"},{\"text\":\"|\",\"color\":\"#7f5e92\"},{\"text\":\"|\",\"color\":\"#86618f\"},{\"text\":\"|\",\"color\":\"#8d648c\"},{\"text\":\"|\",\"color\":\"#936789\"},{\"text\":\"|\",\"color\":\"#9a6a85\"},{\"text\":\"|\",\"color\":\"#a16d82\"},{\"text\":\"|\",\"color\":\"#a7707f\"},{\"text\":\"|\",\"color\":\"#ae737c\"},{\"text\":\"|\",\"color\":\"#b47679\"},{\"text\":\"|\",\"color\":\"#bb7976\"},{\"text\":\"|\",\"color\":\"#c27c72\"},{\"text\":\"|\",\"color\":\"#c87f6f\"},{\"text\":\"|\",\"color\":\"#cf826c\"},{\"text\":\"|\",\"color\":\"#d68569\"},{\"text\":\"|\",\"color\":\"#dc8866\"},{\"text\":\"|\",\"color\":\"#e38b63\"},{\"text\":\"|\",\"color\":\"#ea8e5f\"},{\"text\":\"|\",\"color\":\"#f0915c\"},{\"text\":\"|\",\"color\":\"#f79459\"}],\"color\":\"#5e4fa2\"},{\"text\":\"!\",\"color\":\"yellow\"}]}"; + Component comp = MiniMessageParser.parseFormat(input); + + test(comp, expected); + } + + @Test + public void testGradient3() { + String input = "Woo: ||||||||||||||||||||||||!"; + String expected = "{\"text\":\"\",\"extra\":[{\"text\":\"Woo: \",\"color\":\"yellow\"},{\"text\":\"|\",\"extra\":[{\"text\":\"|\",\"color\":\"#55f85c\"},{\"text\":\"|\",\"color\":\"#55f064\"},{\"text\":\"|\",\"color\":\"#55e96b\"},{\"text\":\"|\",\"color\":\"#55e173\"},{\"text\":\"|\",\"color\":\"#55da7a\"},{\"text\":\"|\",\"color\":\"#55d381\"},{\"text\":\"|\",\"color\":\"#55cb89\"},{\"text\":\"|\",\"color\":\"#55c490\"},{\"text\":\"|\",\"color\":\"#55bc98\"},{\"text\":\"|\",\"color\":\"#55b59f\"},{\"text\":\"|\",\"color\":\"#55aea6\"},{\"text\":\"|\",\"color\":\"#55a6ae\"},{\"text\":\"|\",\"color\":\"#559fb5\"},{\"text\":\"|\",\"color\":\"#5598bc\"},{\"text\":\"|\",\"color\":\"#5590c4\"},{\"text\":\"|\",\"color\":\"#5589cb\"},{\"text\":\"|\",\"color\":\"#5581d3\"},{\"text\":\"|\",\"color\":\"#557ada\"},{\"text\":\"|\",\"color\":\"#5573e1\"},{\"text\":\"|\",\"color\":\"#556be9\"},{\"text\":\"|\",\"color\":\"#5564f0\"},{\"text\":\"|\",\"color\":\"#555cf8\"},{\"text\":\"|\",\"color\":\"blue\"}],\"color\":\"green\"},{\"text\":\"!\",\"color\":\"yellow\"}]}"; Component comp = MiniMessageParser.parseFormat(input); test(comp, expected); From a8c0df3804d9d12d766a1db8fa665c950bccc2a9 Mon Sep 17 00:00:00 2001 From: MiniDigger Date: Wed, 24 Jun 2020 22:24:29 +0200 Subject: [PATCH 065/339] add plugin to test shit Signed-off-by: MiniDigger --- minimessage-plugin/pom.xml | 75 +++++++++++++++++++ .../minimessage/plugin/MiniMessagePlugin.java | 36 +++++++++ .../src/main/resources/plugin.yml | 7 ++ .../minimessage/text/MiniMessageParser.java | 5 -- .../minimessage/text/ParseException.java | 9 +++ 5 files changed, 127 insertions(+), 5 deletions(-) create mode 100644 minimessage-plugin/pom.xml create mode 100644 minimessage-plugin/src/main/java/me/minidigger/minimessage/plugin/MiniMessagePlugin.java create mode 100644 minimessage-plugin/src/main/resources/plugin.yml create mode 100644 minimessage-text/src/main/java/me/minidigger/minimessage/text/ParseException.java diff --git a/minimessage-plugin/pom.xml b/minimessage-plugin/pom.xml new file mode 100644 index 000000000..29692d7c7 --- /dev/null +++ b/minimessage-plugin/pom.xml @@ -0,0 +1,75 @@ + + + 4.0.0 + + MiniMessage + me.minidigger + 2.1.1-SNAPSHOT + + minimessage-plugin + 1.0-SNAPSHOT + jar + Minimessage Plugin + + + + + org.apache.maven.plugins + maven-shade-plugin + 3.2.2 + + + package + + shade + + + false + + + + + + + + src/main/resources + true + + + + + + + papermc-repo + https://papermc.io/repo/repository/maven-public/ + + + + + + com.destroystokyo.paper + paper-api + 1.15.2-R0.1-SNAPSHOT + provided + + + me.minidigger + minimessage-text + 2.1.1-SNAPSHOT + + + net.kyori + adventure-api + 4.0.0-SNAPSHOT + compile + + + net.kyori + adventure-platform-bukkit + 4.0.0-SNAPSHOT + compile + + + diff --git a/minimessage-plugin/src/main/java/me/minidigger/minimessage/plugin/MiniMessagePlugin.java b/minimessage-plugin/src/main/java/me/minidigger/minimessage/plugin/MiniMessagePlugin.java new file mode 100644 index 000000000..387cdec7f --- /dev/null +++ b/minimessage-plugin/src/main/java/me/minidigger/minimessage/plugin/MiniMessagePlugin.java @@ -0,0 +1,36 @@ +package me.minidigger.minimessage.plugin; + +import net.kyori.adventure.platform.bukkit.BukkitPlatform; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; +import net.md_5.bungee.api.chat.BaseComponent; +import net.md_5.bungee.chat.ComponentSerializer; + +import org.bukkit.ChatColor; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.plugin.java.JavaPlugin; + +import me.minidigger.minimessage.text.MiniMessageParser; +import me.minidigger.minimessage.text.ParseException; + +public final class MiniMessagePlugin extends JavaPlugin { + + private BukkitPlatform platform; + + @Override + public void onEnable() { + platform = BukkitPlatform.of(this); + } + + @Override + public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { + try { + Component component = MiniMessageParser.parseFormat(String.join(" ", args)); + platform.audience(sender).sendMessage(component); + } catch (ParseException ex) { + sender.sendMessage(ChatColor.RED + "Error: " + ex.getMessage()); + } + return true; + } +} diff --git a/minimessage-plugin/src/main/resources/plugin.yml b/minimessage-plugin/src/main/resources/plugin.yml new file mode 100644 index 000000000..4e36891e3 --- /dev/null +++ b/minimessage-plugin/src/main/resources/plugin.yml @@ -0,0 +1,7 @@ +name: MiniMessage +version: ${project.version} +main: me.minidigger.minimessage.plugin.MiniMessagePlugin +api-version: 1.15 +authors: [MiniDigger] +commands: + minimessage: diff --git a/minimessage-text/src/main/java/me/minidigger/minimessage/text/MiniMessageParser.java b/minimessage-text/src/main/java/me/minidigger/minimessage/text/MiniMessageParser.java index af3bd1b1d..fd8c5050a 100644 --- a/minimessage-text/src/main/java/me/minidigger/minimessage/text/MiniMessageParser.java +++ b/minimessage-text/src/main/java/me/minidigger/minimessage/text/MiniMessageParser.java @@ -490,9 +490,4 @@ private static String cleanInner(String inner) { return inner.substring(1).substring(0, inner.length() - 2); // cut off first and last "/' } - static class ParseException extends RuntimeException { - public ParseException(@Nonnull String message) { - super(message); - } - } } diff --git a/minimessage-text/src/main/java/me/minidigger/minimessage/text/ParseException.java b/minimessage-text/src/main/java/me/minidigger/minimessage/text/ParseException.java new file mode 100644 index 000000000..594423a6b --- /dev/null +++ b/minimessage-text/src/main/java/me/minidigger/minimessage/text/ParseException.java @@ -0,0 +1,9 @@ +package me.minidigger.minimessage.text; + +import javax.annotation.Nonnull; + +public class ParseException extends RuntimeException { + public ParseException(@Nonnull String message) { + super(message); + } +} From 51d393e11569bf2aeca6600686a96ca7c935f313 Mon Sep 17 00:00:00 2001 From: MiniDigger Date: Wed, 24 Jun 2020 23:00:57 +0200 Subject: [PATCH 066/339] I hate myself for this Signed-off-by: MiniDigger --- .../minimessage/plugin/MiniMessagePlugin.java | 69 +++++++++++++++++-- .../src/main/resources/plugin.yml | 2 + 2 files changed, 65 insertions(+), 6 deletions(-) diff --git a/minimessage-plugin/src/main/java/me/minidigger/minimessage/plugin/MiniMessagePlugin.java b/minimessage-plugin/src/main/java/me/minidigger/minimessage/plugin/MiniMessagePlugin.java index 387cdec7f..9cb1695a8 100644 --- a/minimessage-plugin/src/main/java/me/minidigger/minimessage/plugin/MiniMessagePlugin.java +++ b/minimessage-plugin/src/main/java/me/minidigger/minimessage/plugin/MiniMessagePlugin.java @@ -1,15 +1,21 @@ package me.minidigger.minimessage.plugin; +import net.kyori.adventure.audience.Audience; +import net.kyori.adventure.bossbar.BossBar; import net.kyori.adventure.platform.bukkit.BukkitPlatform; import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; -import net.md_5.bungee.api.chat.BaseComponent; -import net.md_5.bungee.chat.ComponentSerializer; +import net.kyori.adventure.title.Title; +import java.time.Duration; +import java.time.temporal.ChronoUnit; +import java.time.temporal.TemporalUnit; + +import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.command.Command; import org.bukkit.command.CommandSender; import org.bukkit.plugin.java.JavaPlugin; +import org.bukkit.scheduler.BukkitTask; import me.minidigger.minimessage.text.MiniMessageParser; import me.minidigger.minimessage.text.ParseException; @@ -18,19 +24,70 @@ public final class MiniMessagePlugin extends JavaPlugin { private BukkitPlatform platform; + private BukkitTask task; + private int progress = 0; + @Override public void onEnable() { platform = BukkitPlatform.of(this); } @Override - public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { + public boolean onCommand(CommandSender sender, Command command, String label, String[] a) { + String args = String.join(" ", a); + switch (command.getName()) { + case "minimessage": + minimessage(sender, args); + break; + case "minibossbar": + minibossbar(sender); + break; + case "minispam": + minispam(sender); + break; + default: + return false; + } + return true; + } + + private void minispam(CommandSender sender) { + BossBar bar = BossBar.of(getWithProgress(0), 1, BossBar.Color.BLUE, BossBar.Overlay.PROGRESS); + if (task != null) { + task.cancel(); + } + Audience audience = platform.audience(sender); + task = Bukkit.getScheduler().runTaskTimer(this, () -> { + Component message = getWithProgress(progress++); + bar.name(message); + audience.sendActionBar(message); + audience.sendMessage(message); + audience.showTitle(Title.of(message, message, Duration.ZERO, Duration.of(20, ChronoUnit.MILLIS), Duration.ZERO)); + }, 1, 1); + audience.showBossBar(bar); + } + + private void minibossbar(CommandSender sender) { + BossBar bar = BossBar.of(getWithProgress(0), 1, BossBar.Color.BLUE, BossBar.Overlay.PROGRESS); + if (task != null) { + task.cancel(); + } + task = Bukkit.getScheduler().runTaskTimer(this, () -> { + bar.name(getWithProgress(progress++)); + }, 1, 1); + platform.audience(sender).showBossBar(bar); + } + + private void minimessage(CommandSender sender, String args) { try { - Component component = MiniMessageParser.parseFormat(String.join(" ", args)); + Component component = MiniMessageParser.parseFormat(args); platform.audience(sender).sendMessage(component); } catch (ParseException ex) { sender.sendMessage(ChatColor.RED + "Error: " + ex.getMessage()); } - return true; + } + + private Component getWithProgress(int phase) { + return MiniMessageParser.parseFormat("|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||"); } } diff --git a/minimessage-plugin/src/main/resources/plugin.yml b/minimessage-plugin/src/main/resources/plugin.yml index 4e36891e3..c87ce7e53 100644 --- a/minimessage-plugin/src/main/resources/plugin.yml +++ b/minimessage-plugin/src/main/resources/plugin.yml @@ -5,3 +5,5 @@ api-version: 1.15 authors: [MiniDigger] commands: minimessage: + minibossbar: + minispam: From c080f9b2ae71880edd00be972952c4e6a016cfe0 Mon Sep 17 00:00:00 2001 From: MiniDigger Date: Wed, 24 Jun 2020 23:18:50 +0200 Subject: [PATCH 067/339] make to cancelable Signed-off-by: MiniDigger --- .../minimessage/plugin/MiniMessagePlugin.java | 33 ++++++++++--------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/minimessage-plugin/src/main/java/me/minidigger/minimessage/plugin/MiniMessagePlugin.java b/minimessage-plugin/src/main/java/me/minidigger/minimessage/plugin/MiniMessagePlugin.java index 9cb1695a8..b3580f4ba 100644 --- a/minimessage-plugin/src/main/java/me/minidigger/minimessage/plugin/MiniMessagePlugin.java +++ b/minimessage-plugin/src/main/java/me/minidigger/minimessage/plugin/MiniMessagePlugin.java @@ -8,7 +8,6 @@ import java.time.Duration; import java.time.temporal.ChronoUnit; -import java.time.temporal.TemporalUnit; import org.bukkit.Bukkit; import org.bukkit.ChatColor; @@ -52,30 +51,32 @@ public boolean onCommand(CommandSender sender, Command command, String label, St } private void minispam(CommandSender sender) { - BossBar bar = BossBar.of(getWithProgress(0), 1, BossBar.Color.BLUE, BossBar.Overlay.PROGRESS); if (task != null) { task.cancel(); + } else { + BossBar bar = BossBar.of(getWithProgress(0), 1, BossBar.Color.BLUE, BossBar.Overlay.PROGRESS); + Audience audience = platform.audience(sender); + task = Bukkit.getScheduler().runTaskTimer(this, () -> { + Component message = getWithProgress(progress++); + bar.name(message); + audience.sendActionBar(message); + audience.sendMessage(message); + audience.showTitle(Title.of(message, message, Duration.ZERO, Duration.of(20, ChronoUnit.MILLIS), Duration.ZERO)); + }, 1, 1); + audience.showBossBar(bar); } - Audience audience = platform.audience(sender); - task = Bukkit.getScheduler().runTaskTimer(this, () -> { - Component message = getWithProgress(progress++); - bar.name(message); - audience.sendActionBar(message); - audience.sendMessage(message); - audience.showTitle(Title.of(message, message, Duration.ZERO, Duration.of(20, ChronoUnit.MILLIS), Duration.ZERO)); - }, 1, 1); - audience.showBossBar(bar); } private void minibossbar(CommandSender sender) { - BossBar bar = BossBar.of(getWithProgress(0), 1, BossBar.Color.BLUE, BossBar.Overlay.PROGRESS); if (task != null) { task.cancel(); + } else { + BossBar bar = BossBar.of(getWithProgress(0), 1, BossBar.Color.BLUE, BossBar.Overlay.PROGRESS); + task = Bukkit.getScheduler().runTaskTimer(this, () -> { + bar.name(getWithProgress(progress++)); + }, 1, 1); + platform.audience(sender).showBossBar(bar); } - task = Bukkit.getScheduler().runTaskTimer(this, () -> { - bar.name(getWithProgress(progress++)); - }, 1, 1); - platform.audience(sender).showBossBar(bar); } private void minimessage(CommandSender sender, String args) { From 9a7b7c4fedbea42cb2ab302a461de7d806d15a7b Mon Sep 17 00:00:00 2001 From: MiniDigger Date: Thu, 25 Jun 2020 14:54:40 +0200 Subject: [PATCH 068/339] fix gradient resetting (closes #23) Signed-off-by: MiniDigger --- .../java/me/minidigger/minimessage/text/fancy/Gradient.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/minimessage-text/src/main/java/me/minidigger/minimessage/text/fancy/Gradient.java b/minimessage-text/src/main/java/me/minidigger/minimessage/text/fancy/Gradient.java index d8e691f4f..251a48ace 100644 --- a/minimessage-text/src/main/java/me/minidigger/minimessage/text/fancy/Gradient.java +++ b/minimessage-text/src/main/java/me/minidigger/minimessage/text/fancy/Gradient.java @@ -7,7 +7,7 @@ public class Gradient implements Fancy { private int colorIndex = 0; - private float factorStep = 0.5f; + private float factorStep = 0; private final TextColor color1; private final TextColor color2; @@ -22,7 +22,8 @@ public Gradient(TextColor c1, TextColor c2) { @Override public void init(int size) { - this.factorStep = (float) (1. / (size - 1)); + this.factorStep = (float) (1. / (size + this.colorIndex - 1)); + this.colorIndex = 0; } @Override From b430606fa213c308d4835c6f2c5e688df7d99af1 Mon Sep 17 00:00:00 2001 From: MiniDigger Date: Sat, 27 Jun 2020 10:36:27 +0200 Subject: [PATCH 069/339] shift some dependencies around, update adventure Signed-off-by: MiniDigger --- minimessage-plugin/pom.xml | 8 +--- .../minimessage/plugin/MiniMessagePlugin.java | 37 ++++++++++++++++--- .../src/main/resources/plugin.yml | 1 + minimessage-text/pom.xml | 9 ++++- .../text/MiniMessageParserTest.java | 14 +++---- pom.xml | 1 + 6 files changed, 49 insertions(+), 21 deletions(-) diff --git a/minimessage-plugin/pom.xml b/minimessage-plugin/pom.xml index 29692d7c7..fd08b228b 100644 --- a/minimessage-plugin/pom.xml +++ b/minimessage-plugin/pom.xml @@ -51,7 +51,7 @@ com.destroystokyo.paper paper-api - 1.15.2-R0.1-SNAPSHOT + 1.16.1-R0.1-SNAPSHOT provided @@ -59,12 +59,6 @@ minimessage-text 2.1.1-SNAPSHOT - - net.kyori - adventure-api - 4.0.0-SNAPSHOT - compile - net.kyori adventure-platform-bukkit diff --git a/minimessage-plugin/src/main/java/me/minidigger/minimessage/plugin/MiniMessagePlugin.java b/minimessage-plugin/src/main/java/me/minidigger/minimessage/plugin/MiniMessagePlugin.java index b3580f4ba..1a096fd50 100644 --- a/minimessage-plugin/src/main/java/me/minidigger/minimessage/plugin/MiniMessagePlugin.java +++ b/minimessage-plugin/src/main/java/me/minidigger/minimessage/plugin/MiniMessagePlugin.java @@ -13,22 +13,26 @@ import org.bukkit.ChatColor; import org.bukkit.command.Command; import org.bukkit.command.CommandSender; +import org.bukkit.event.Listener; import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.scheduler.BukkitTask; import me.minidigger.minimessage.text.MiniMessageParser; import me.minidigger.minimessage.text.ParseException; -public final class MiniMessagePlugin extends JavaPlugin { +public final class MiniMessagePlugin extends JavaPlugin implements Listener { private BukkitPlatform platform; private BukkitTask task; private int progress = 0; + private BossBar bar; + private Audience audience; @Override public void onEnable() { platform = BukkitPlatform.of(this); + getServer().getPluginManager().registerEvents(this, this); } @Override @@ -38,6 +42,9 @@ public boolean onCommand(CommandSender sender, Command command, String label, St case "minimessage": minimessage(sender, args); break; + case "minibroadcast": + minibroadcast(sender, args); + break; case "minibossbar": minibossbar(sender); break; @@ -51,31 +58,40 @@ public boolean onCommand(CommandSender sender, Command command, String label, St } private void minispam(CommandSender sender) { + if (audience == null) { + audience = Audience.of(platform.audience(sender)); + } else { + audience = Audience.of(audience, platform.audience(sender)); + } if (task != null) { task.cancel(); + task = null; + audience.hideBossBar(bar); + audience.clearTitle(); } else { - BossBar bar = BossBar.of(getWithProgress(0), 1, BossBar.Color.BLUE, BossBar.Overlay.PROGRESS); - Audience audience = platform.audience(sender); + bar = BossBar.of(getWithProgress(0), 1, BossBar.Color.BLUE, BossBar.Overlay.PROGRESS); task = Bukkit.getScheduler().runTaskTimer(this, () -> { Component message = getWithProgress(progress++); bar.name(message); audience.sendActionBar(message); audience.sendMessage(message); - audience.showTitle(Title.of(message, message, Duration.ZERO, Duration.of(20, ChronoUnit.MILLIS), Duration.ZERO)); + audience.showTitle(Title.of(message, message, Duration.ZERO, Duration.of(50, ChronoUnit.MILLIS), Duration.ZERO)); }, 1, 1); audience.showBossBar(bar); } } private void minibossbar(CommandSender sender) { + Audience audience = platform.audience(sender); if (task != null) { task.cancel(); + audience.hideBossBar(bar); } else { - BossBar bar = BossBar.of(getWithProgress(0), 1, BossBar.Color.BLUE, BossBar.Overlay.PROGRESS); + bar = BossBar.of(getWithProgress(0), 1, BossBar.Color.BLUE, BossBar.Overlay.PROGRESS); task = Bukkit.getScheduler().runTaskTimer(this, () -> { bar.name(getWithProgress(progress++)); }, 1, 1); - platform.audience(sender).showBossBar(bar); + audience.showBossBar(bar); } } @@ -88,6 +104,15 @@ private void minimessage(CommandSender sender, String args) { } } + private void minibroadcast(CommandSender sender, String args) { + try { + Component component = MiniMessageParser.parseFormat(args); + platform.players().sendMessage(component); + } catch (ParseException ex) { + sender.sendMessage(ChatColor.RED + "Error: " + ex.getMessage()); + } + } + private Component getWithProgress(int phase) { return MiniMessageParser.parseFormat("|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||"); } diff --git a/minimessage-plugin/src/main/resources/plugin.yml b/minimessage-plugin/src/main/resources/plugin.yml index c87ce7e53..bd8666e1f 100644 --- a/minimessage-plugin/src/main/resources/plugin.yml +++ b/minimessage-plugin/src/main/resources/plugin.yml @@ -5,5 +5,6 @@ api-version: 1.15 authors: [MiniDigger] commands: minimessage: + minibroadcast: minibossbar: minispam: diff --git a/minimessage-text/pom.xml b/minimessage-text/pom.xml index 7a049c5a7..be38ecbe7 100644 --- a/minimessage-text/pom.xml +++ b/minimessage-text/pom.xml @@ -10,12 +10,19 @@ minimessage-text MiniMessage Text + + + snapshots-repo + https://oss.sonatype.org/content/repositories/snapshots + + + net.kyori adventure-api 4.0.0-SNAPSHOT - provided + compile net.kyori diff --git a/minimessage-text/src/test/java/me/minidigger/minimessage/text/MiniMessageParserTest.java b/minimessage-text/src/test/java/me/minidigger/minimessage/text/MiniMessageParserTest.java index 884a68b59..d57fcd60f 100644 --- a/minimessage-text/src/test/java/me/minidigger/minimessage/text/MiniMessageParserTest.java +++ b/minimessage-text/src/test/java/me/minidigger/minimessage/text/MiniMessageParserTest.java @@ -16,8 +16,8 @@ public class MiniMessageParserTest { public void test() { String input1 = "TEST nestedTest"; String input2 = "TEST nestedTest"; - String out1 = GsonComponentSerializer.INSTANCE.serialize(MiniMessageParser.parseFormat(input1)); - String out2 = GsonComponentSerializer.INSTANCE.serialize(MiniMessageParser.parseFormat(input2)); + String out1 = GsonComponentSerializer.gson().serialize(MiniMessageParser.parseFormat(input1)); + String out2 = GsonComponentSerializer.gson().serialize(MiniMessageParser.parseFormat(input2)); System.out.println(out1); System.out.println(out2); assertEquals(out1, out2); @@ -27,8 +27,8 @@ public void test() { public void testNewColor() { String input1 = "TEST nestedTest"; String input2 = "TEST nestedTest"; - String out1 = GsonComponentSerializer.INSTANCE.serialize(MiniMessageParser.parseFormat(input1)); - String out2 = GsonComponentSerializer.INSTANCE.serialize(MiniMessageParser.parseFormat(input2)); + String out1 = GsonComponentSerializer.gson().serialize(MiniMessageParser.parseFormat(input1)); + String out2 = GsonComponentSerializer.gson().serialize(MiniMessageParser.parseFormat(input2)); assertEquals(out1, out2); } @@ -37,8 +37,8 @@ public void testNewColor() { public void testHexColor() { String input1 = "TEST nestedTest"; String input2 = "TEST nestedTest"; - String out1 = GsonComponentSerializer.INSTANCE.serialize(MiniMessageParser.parseFormat(input1)); - String out2 = GsonComponentSerializer.INSTANCE.serialize(MiniMessageParser.parseFormat(input2)); + String out1 = GsonComponentSerializer.gson().serialize(MiniMessageParser.parseFormat(input1)); + String out2 = GsonComponentSerializer.gson().serialize(MiniMessageParser.parseFormat(input2)); assertEquals(out1, out2); } @@ -347,6 +347,6 @@ private void test(@Nonnull String input, @Nonnull String expected) { } private void test(@Nonnull Component comp, @Nonnull String expected) { - assertEquals(expected, GsonComponentSerializer.INSTANCE.serialize(comp)); + assertEquals(expected, GsonComponentSerializer.gson().serialize(comp)); } } diff --git a/pom.xml b/pom.xml index b4aeb83bb..dd50a7569 100644 --- a/pom.xml +++ b/pom.xml @@ -20,6 +20,7 @@ minimessage-text minimessage-bungee minimessage-ext-markdown + minimessage-plugin From e185820dd7419cf169273e891fdd343c283d393e Mon Sep 17 00:00:00 2001 From: MiniDigger Date: Tue, 30 Jun 2020 22:03:28 +0200 Subject: [PATCH 070/339] Move MiniMessage to kyori \o/ See changelog, but summery, removed bungee, remerged markdown into main project, added new template system, added hex colors, gradients and rainbows and did a big refactor --- .github/workflows/Build.yml | 16 - .github/workflows/Publish.yml | 36 -- .gitignore | 9 +- .travis.sh | 7 + .travis.yml | 17 + CHANGELOG.md | 11 + DOCS.md | 164 ------ README.md | 2 + build.gradle | 131 +++++ gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 58910 bytes gradle/wrapper/gradle-wrapper.properties | 5 + gradlew | 185 ++++++ gradlew.bat | 104 ++++ header.txt | 21 + LICENSE => license.txt | 2 +- minimessage-bungee/pom.xml | 22 - .../minimessage/MiniMessageParser.java | 80 --- .../minimessage/MiniMessageSerializer.java | 24 - .../minimessage/bungee/Constants.java | 20 - .../minimessage/bungee/MiniMessageParser.java | 307 ---------- .../bungee/MiniMessageSerializer.java | 171 ------ .../minimessage/MiniMessageParserTest.java | 169 ------ .../MiniMessageSerializerTest.java | 124 ---- minimessage-ext-markdown/pom.xml | 20 - .../me/minidigger/minimessage/Constants.java | 15 - .../minimessage/MiniMarkdownParser.java | 167 ------ .../minimessage/MiniMarkdownParserTest.java | 188 ------ minimessage-plugin/pom.xml | 69 --- .../minimessage/plugin/MiniMessagePlugin.java | 119 ---- .../src/main/resources/plugin.yml | 10 - minimessage-text/pom.xml | 34 -- .../minimessage/text/Constants.java | 33 -- .../text/HelperTextDecoration.java | 26 - .../minimessage/text/MiniMessageParser.java | 493 ---------------- .../text/MiniMessageSerializer.java | 221 ------- .../minimessage/text/ParseException.java | 9 - .../minimessage/text/fancy/Fancy.java | 10 - .../minimessage/text/fancy/Gradient.java | 46 -- .../minimessage/text/fancy/Rainbow.java | 43 -- .../text/MiniMessageParserTest.java | 352 ------------ .../text/MiniMessageSerializerTest.java | 188 ------ pom.xml | 169 ------ renovate.json | 5 - settings.gradle | 1 + .../text/minimessage/MiniMarkdownParser.java | 199 +++++++ .../text/minimessage/MiniMessage.java | 150 +++++ .../text/minimessage/MiniMessageImpl.java | 130 +++++ .../text/minimessage/MiniMessageParser.java | 543 ++++++++++++++++++ .../minimessage/MiniMessageSerializer.java | 245 ++++++++ .../text/minimessage/ParseException.java | 38 ++ .../adventure/text/minimessage/Template.java | 92 +++ .../adventure/text/minimessage/Tokens.java | 61 ++ .../text/minimessage/fancy/Fancy.java | 33 ++ .../text/minimessage/fancy/Gradient.java | 69 +++ .../text/minimessage/fancy/Rainbow.java | 66 +++ .../minimessage/MiniMarkdownParserTest.java | 211 +++++++ .../minimessage/MiniMessageParserTest.java | 374 ++++++++++++ .../MiniMessageSerializerTest.java | 210 +++++++ .../text/minimessage/MiniMessageTest.java | 142 +++++ 59 files changed, 3054 insertions(+), 3354 deletions(-) delete mode 100644 .github/workflows/Build.yml delete mode 100644 .github/workflows/Publish.yml create mode 100644 .travis.sh create mode 100644 .travis.yml delete mode 100644 DOCS.md create mode 100644 build.gradle create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100644 gradlew create mode 100644 gradlew.bat create mode 100644 header.txt rename LICENSE => license.txt (96%) delete mode 100644 minimessage-bungee/pom.xml delete mode 100644 minimessage-bungee/src/main/java/me/minidigger/minimessage/MiniMessageParser.java delete mode 100644 minimessage-bungee/src/main/java/me/minidigger/minimessage/MiniMessageSerializer.java delete mode 100644 minimessage-bungee/src/main/java/me/minidigger/minimessage/bungee/Constants.java delete mode 100644 minimessage-bungee/src/main/java/me/minidigger/minimessage/bungee/MiniMessageParser.java delete mode 100644 minimessage-bungee/src/main/java/me/minidigger/minimessage/bungee/MiniMessageSerializer.java delete mode 100644 minimessage-bungee/src/test/java/me/minidigger/minimessage/MiniMessageParserTest.java delete mode 100644 minimessage-bungee/src/test/java/me/minidigger/minimessage/MiniMessageSerializerTest.java delete mode 100644 minimessage-ext-markdown/pom.xml delete mode 100644 minimessage-ext-markdown/src/main/java/me/minidigger/minimessage/Constants.java delete mode 100644 minimessage-ext-markdown/src/main/java/me/minidigger/minimessage/MiniMarkdownParser.java delete mode 100644 minimessage-ext-markdown/src/test/java/me/minidigger/minimessage/MiniMarkdownParserTest.java delete mode 100644 minimessage-plugin/pom.xml delete mode 100644 minimessage-plugin/src/main/java/me/minidigger/minimessage/plugin/MiniMessagePlugin.java delete mode 100644 minimessage-plugin/src/main/resources/plugin.yml delete mode 100644 minimessage-text/pom.xml delete mode 100644 minimessage-text/src/main/java/me/minidigger/minimessage/text/Constants.java delete mode 100644 minimessage-text/src/main/java/me/minidigger/minimessage/text/HelperTextDecoration.java delete mode 100644 minimessage-text/src/main/java/me/minidigger/minimessage/text/MiniMessageParser.java delete mode 100644 minimessage-text/src/main/java/me/minidigger/minimessage/text/MiniMessageSerializer.java delete mode 100644 minimessage-text/src/main/java/me/minidigger/minimessage/text/ParseException.java delete mode 100644 minimessage-text/src/main/java/me/minidigger/minimessage/text/fancy/Fancy.java delete mode 100644 minimessage-text/src/main/java/me/minidigger/minimessage/text/fancy/Gradient.java delete mode 100644 minimessage-text/src/main/java/me/minidigger/minimessage/text/fancy/Rainbow.java delete mode 100644 minimessage-text/src/test/java/me/minidigger/minimessage/text/MiniMessageParserTest.java delete mode 100644 minimessage-text/src/test/java/me/minidigger/minimessage/text/MiniMessageSerializerTest.java delete mode 100644 pom.xml delete mode 100644 renovate.json create mode 100644 settings.gradle create mode 100644 src/main/java/net/kyori/adventure/text/minimessage/MiniMarkdownParser.java create mode 100644 src/main/java/net/kyori/adventure/text/minimessage/MiniMessage.java create mode 100644 src/main/java/net/kyori/adventure/text/minimessage/MiniMessageImpl.java create mode 100644 src/main/java/net/kyori/adventure/text/minimessage/MiniMessageParser.java create mode 100644 src/main/java/net/kyori/adventure/text/minimessage/MiniMessageSerializer.java create mode 100644 src/main/java/net/kyori/adventure/text/minimessage/ParseException.java create mode 100644 src/main/java/net/kyori/adventure/text/minimessage/Template.java create mode 100644 src/main/java/net/kyori/adventure/text/minimessage/Tokens.java create mode 100644 src/main/java/net/kyori/adventure/text/minimessage/fancy/Fancy.java create mode 100644 src/main/java/net/kyori/adventure/text/minimessage/fancy/Gradient.java create mode 100644 src/main/java/net/kyori/adventure/text/minimessage/fancy/Rainbow.java create mode 100644 src/test/java/net/kyori/adventure/text/minimessage/MiniMarkdownParserTest.java create mode 100644 src/test/java/net/kyori/adventure/text/minimessage/MiniMessageParserTest.java create mode 100644 src/test/java/net/kyori/adventure/text/minimessage/MiniMessageSerializerTest.java create mode 100644 src/test/java/net/kyori/adventure/text/minimessage/MiniMessageTest.java diff --git a/.github/workflows/Build.yml b/.github/workflows/Build.yml deleted file mode 100644 index f07465b54..000000000 --- a/.github/workflows/Build.yml +++ /dev/null @@ -1,16 +0,0 @@ -name: Build - -on: [pull_request] - -jobs: - build: - name: Build - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Set up JDK - uses: actions/setup-java@v1 - with: - java-version: 11 - - name: Build with Maven - run: mvn package diff --git a/.github/workflows/Publish.yml b/.github/workflows/Publish.yml deleted file mode 100644 index aeed9d8be..000000000 --- a/.github/workflows/Publish.yml +++ /dev/null @@ -1,36 +0,0 @@ -name: Publish - -on: - push: - branches: - - master - -jobs: - publish: - name: Publish - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@master - - - name: Set up JDK - uses: actions/setup-java@v1 - with: - java-version: 11 - - - name: Release - uses: MiniDigger/github-actions-maven-release@master - with: - release-branch-name: "master" - - gpg-key: ${{ secrets.GITHUB_GPG_KEY }} - - maven-repo-server-id: ${{ secrets.MVN_REPO_PRIVATE_REPO_ID }} - maven-repo-server-username: ${{ secrets.MVN_REPO_PRIVATE_REPO_USER }} - maven-repo-server-password: ${{ secrets.MVN_REPO_PRIVATE_REPO_PASSWORD }} - - git-release-bot-name: "release-bot" - git-release-bot-email: "release-bot@minidigger.me" - - access-token: ${{ secrets.GITHUB_ACCESS_TOKEN }} - - maven-args: ${{ '-Dgpg.passphrase=' }}${{ secrets.MVN_REPO_PRIVATE_REPO_PASSWORD }} diff --git a/.gitignore b/.gitignore index 7466b121e..8c7d4598f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ -.idea -*.iml -target/ +# General IDE + build files +/.gradle/ +/.idea/ +/run/ +/*.iml +/build/ diff --git a/.travis.sh b/.travis.sh new file mode 100644 index 000000000..6a34931fd --- /dev/null +++ b/.travis.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +if [ ${TRAVIS_PULL_REQUEST} = 'false' ] && [ ${TRAVIS_BRANCH} = 'master' ]; then + ./gradlew -PsonatypeUsername="${SONATYPE_USERNAME}" -PsonatypePassword="${SONATYPE_PASSWORD}" build uploadArchives +else + ./gradlew build +fi diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..330adee23 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,17 @@ +dist: 'trusty' +sudo: false + +language: 'java' +jdk: +- 'oraclejdk8' + +install: true +script: './.travis.sh' + +before_cache: +- 'rm -f $HOME/.gradle/caches/modules-2/modules-2.lock' +- 'rm -fr $HOME/.gradle/caches/*/plugin-resolution/' +cache: + directories: + - '$HOME/.gradle/caches/' + - '$HOME/.gradle/wrapper/' diff --git a/CHANGELOG.md b/CHANGELOG.md index 13e4327b9..4a127c0eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +# 3.0.0 (xx.07.2020) + +* MiniMessage is now part of the Kyori organization! +* Add support for hex colors, rainbows and gradients +* Add templates, a new type of placeholder that works with components +* Dropped bungee impl +* Moved markdown ext into the main project +* Refactored the project, MiniMessage is now the main api +* Moved docs to the adventure docs page https://adventure.docs.kyori.net/ +* Moved the project to gradle to align with other kyori projects + # 2.1.0 (12.06.2020) * Move packages around to avoid confusion between bungee and text impls diff --git a/DOCS.md b/DOCS.md deleted file mode 100644 index 3e6bee818..000000000 --- a/DOCS.md +++ /dev/null @@ -1,164 +0,0 @@ -# Documentation - -> Note: this page mainly covers minimessage-text, although the principles should also apply to the legacy bungee module, just with way fewer features. - -## General - -First let me start with a few core principles. - -This library uses tags. Everything you do will be defined with tags. Tags have a start tag and an end tag (the `` tag is an exception here) -Start tags are mandatory (obviously), end tags aren't. -`Hello World!` and `Hello World!` and even `Hello World!` all do the same. - -Some tags have inner tags. Those look like this: `stuff`. For example: `test:TEST">TEST` or `TEST` -As you can see, those sometimes contain components, sometimes just strings. Refer to the details docs below. - -Single (`'`) and double (`"`) quotes can be used interchangeably, but please stay consistent. - -The components try to represent vanilla as closely as possible. -It might to helpful to use [the minecraft wiki](https://minecraft.gamepedia.com/Raw_JSON_text_format) as a reference, especially for stuff like the actions and values of click and hover events. - -### A note on inner components - -Some components (like hover and translate) support nested/inner components. This feature is a total mess. It's best to assume that it only works because of luck. -Following things are known to be broken in inner components and should not be used: -* Colons (`:`) -* Quotation marks (both single `'` and double `"`), altho you may have luck with escaping them like this `\"` - -Please don't open issues about such cases, I don't think that I'll able to fix them. PRs are welcome tho! -There are two `@Ignore`'d unit cases that are disabled due to these limitations. - -## Components - -### Color - -Color the next parts - -Tag: `<_colorname_>` -Arguments: -* `_colorname_`, all minecraft color constants (check [here](https://github.com/KyoriPowered/adventure/blob/master/api/src/main/java/net/kyori/text/format/TextColor.java)) - -Examples: -* `Hello World!` ![https://i.imgur.com/wB32YpZ.png](https://i.imgur.com/wB32YpZ.png) -* `This is a test!` ![https://i.imgur.com/vsN3OHa.png](https://i.imgur.com/vsN3OHa.png) - -### Color (2) - -A different, more flexible way (supports hex colors!) for colors looks like this - -Tag: `` -Arguments: -* `_colorNameOrHex_`, can be all the values from above, or hex colors (in 1.16) - -Examples: -* `Hello World!` ![https://i.imgur.com/wB32YpZ.png](https://i.imgur.com/wB32YpZ.png) -* `This is a test!` ![https://i.imgur.com/vsN3OHa.png](https://i.imgur.com/vsN3OHa.png) - -### Decoration - -Decorate the next parts - -Tag: `<_decorationname_>` -Arguments: -* `_decorationname_` , all minecraft decorations (check [here](https://github.com/KyoriPowered/adventure/blob/master/api/src/main/java/net/kyori/text/format/TextDecoration.java)) - -Examples: -* `This is important!` ![https://i.imgur.com/hREGXQy.png](https://i.imgur.com/hREGXQy.png) - -### Reset - -Reset all colors, decorations, hovers etc. Doesn't have a close tag - -Tag: `` -Arguments: non -Examples: -* `Hello world!` ![https://i.imgur.com/bjInUhj.png](https://i.imgur.com/bjInUhj.png) - -### Click - -Allows doing multiple things when clicking on the component. - -Tag: `` -Arguments: -* `_action_`, the type of click event, one of [this list](https://github.com/KyoriPowered/adventure/blob/master/api/src/main/java/net/kyori/text/event/ClickEvent.java#L181) -* `_value_`, the argument for that particular event, refer to [the minecraft wiki](https://minecraft.gamepedia.com/Raw_JSON_text_format) - -Examples: -* `Click to say hello` ![https://i.imgur.com/J82qOHn.png](https://i.imgur.com/J82qOHn.png) -* `Click this to copy your score!` - -### Hover - -Allows doing multiple things when hovering on the component. - -Tag: `test'>TEST` ![https://i.imgur.com/VsHDPTI.png](https://i.imgur.com/VsHDPTI.png) - -### Keybind - -Allows displaying the configured key for actions - -Tag: `` -Arguments: -* `_key_`, the minecraft key of the action - -Examples: -* `Press to jump!` ![https://i.imgur.com/iQmNDF6.png](https://i.imgur.com/iQmNDF6.png) - -### Translatable - -Allows displaying minecraft messages using the player locale - -Tag: `` -Arguments: -* `_key_`, the translation key -* `_valueX_`, optional values that are used for placeholders in the key (they will end up in the `with` tag in the json) - -Examples: -* `You should get a !` ![https://i.imgur.com/mpdDMF6.png](https://i.imgur.com/mpdDMF6.png) -* `1':'Stone'>!` ![https://i.imgur.com/esWpnxm.png](https://i.imgur.com/esWpnxm.png) - -### Insertion - -Allow insertion of text into chat via shift click - -Tag: `` -Arguments: -* `_text_`, the text to insert - -Examples: -* `Click this to insert!` ![https://i.imgur.com/Imhom84.png](https://i.imgur.com/Imhom84.png) - -### Pre - -Tags within this tag will not be parsed, useful for player input for example - -Tag: `
`  
-Arguments: non  
-Examples: 
-* `<> 
` ![https://i.imgur.com/pQqaJnD.png](https://i.imgur.com/pQqaJnD.png) - -### Rainbow - -Rainbow colored text?! - -Tag: `` -Arguments: phase, optional -Examples: -* `Woo: ||||||||||||||||||||||||!` -* `Woo: ||||||||||||||||||||||||!` ![https://i.imgur.com/uNbyoYk.png](https://i.imgur.com/uNbyoYk.png) - -### Gradient - -Gradient colored text -Tag: `` -Arguments: color1 and 2, either hex or named colors -Examples: -* `Woo: ||||||||||||||||||||||||!` -* `Woo: ||||||||||||||||||||||||!` -* `Woo: ||||||||||||||||||||||||!` ![https://i.imgur.com/8qYHCWk.png](https://i.imgur.com/8qYHCWk.png) diff --git a/README.md b/README.md index 8657adaca..447feb5be 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,8 @@ Simple library that implements an easy to use textual format to send rich json messages. +## TODO Update readme, this is still for 2.x and even 1.x + ## Examples You can find a full breakdown of all supported components [here](DOCS.md). diff --git a/build.gradle b/build.gradle new file mode 100644 index 000000000..6fa0c87bf --- /dev/null +++ b/build.gradle @@ -0,0 +1,131 @@ +plugins { + id 'net.minecrell.licenser' version '0.4.1' apply false +} + +// Adventure version +ext.adventure = "4.0.0-SNAPSHOT" + +apply plugin: 'java-library' +apply plugin: 'maven' +apply plugin: 'signing' +apply plugin: 'net.minecrell.licenser' + +group 'net.kyori' +version '3.0.0-SNAPSHOT' + +sourceCompatibility = 1.8 +targetCompatibility = 1.8 + +dependencies { + api 'net.kyori:adventure-api:4.0.0-SNAPSHOT' + testCompile 'net.kyori:adventure-text-serializer-gson:4.0.0-SNAPSHOT' +} + +tasks.withType(JavaCompile) { + options.compilerArgs += ['-Xlint:all', '-Xlint:-path', '-parameters'] + options.deprecation = true + options.encoding = 'UTF-8' +} + +tasks.withType(Test) { + useJUnitPlatform() +} + +license { + header rootProject.file('header.txt') + include '**/*.java' + newLine false +} + +repositories { + mavenLocal() + mavenCentral() + maven { + url 'https://oss.sonatype.org/content/groups/public/' + } + maven { + name = 'viaversion' + url = 'https://repo.viaversion.com' + } +} + +dependencies { + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.3.0-M1' + testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.3.0-M1' +} + +task javadocJar(type: Jar) { + classifier 'javadoc' + from javadoc +} + +task sourcesJar(type: Jar) { + classifier 'sources' + from sourceSets.main.allSource +} + +artifacts { + archives javadocJar + archives sourcesJar +} + +signing { + required { !project.version.endsWith('-SNAPSHOT') && gradle.taskGraph.hasTask(':uploadArchives') && project.hasProperty('signing.keyId') } + sign configurations.archives +} + +jar { + manifest.attributes( + 'Automatic-Module-Name': 'net.kyori.adventure.text.minimessage' + ) +} + +if (project.hasProperty('sonatypeUsername') && project.hasProperty('sonatypePassword')) { + uploadArchives { + enabled = System.getenv('TRAVIS') == null || project.version.endsWith('-SNAPSHOT') + + repositories { + mavenDeployer { + beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) } + + repository(url: 'https://oss.sonatype.org/service/local/staging/deploy/maven2/') { + authentication(userName: sonatypeUsername, password: sonatypePassword) + } + + snapshotRepository(url: 'https://oss.sonatype.org/content/repositories/snapshots/') { + authentication(userName: sonatypeUsername, password: sonatypePassword) + } + + pom.project { + description 'Textual representation for components.' + name project.name + url 'https://github.com/KyoriPowered/MiniMessage/' + + developers { + developer { + name 'Luck' + } + } + + issueManagement { + system 'GitHub Issues' + url 'https://github.com/KyoriPowered/MiniMessage/issues' + } + + licenses { + license { + name 'MIT License' + url 'https://opensource.org/licenses/MIT' + } + } + + scm { + connection 'scm:git@github.com:KyoriPowered/MiniMessage.git' + developerConnection 'scm:git@github.com:KyoriPowered/MiniMessage.git' + url 'https://github.com/KyoriPowered/MiniMessage/' + } + } + } + } + } +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..62d4c053550b91381bbd28b1afc82d634bf73a8a GIT binary patch literal 58910 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 literal 0 HcmV?d00001 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..622ab64a3 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100644 index 000000000..fbd7c5158 --- /dev/null +++ b/gradlew @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 000000000..5093609d5 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,104 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/header.txt b/header.txt new file mode 100644 index 000000000..af107733c --- /dev/null +++ b/header.txt @@ -0,0 +1,21 @@ +This file is part of adventure-text-minimessage, licensed under the MIT License. + +Copyright (c) 2018-2020 KyoriPowered + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/LICENSE b/license.txt similarity index 96% rename from LICENSE rename to license.txt index 5fe04da45..eb5a20c42 100644 --- a/LICENSE +++ b/license.txt @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019 MiniDigger +Copyright (c) 2018-2020 KyoriPowered Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/minimessage-bungee/pom.xml b/minimessage-bungee/pom.xml deleted file mode 100644 index 211153b6b..000000000 --- a/minimessage-bungee/pom.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - 4.0.0 - - MiniMessage - me.minidigger - 2.1.1-SNAPSHOT - - - minimessage-bungee - MiniMessage Bungee - - - - net.md-5 - bungeecord-chat - 1.15-SNAPSHOT - jar - provided - - - diff --git a/minimessage-bungee/src/main/java/me/minidigger/minimessage/MiniMessageParser.java b/minimessage-bungee/src/main/java/me/minidigger/minimessage/MiniMessageParser.java deleted file mode 100644 index bfa7dcb44..000000000 --- a/minimessage-bungee/src/main/java/me/minidigger/minimessage/MiniMessageParser.java +++ /dev/null @@ -1,80 +0,0 @@ -package me.minidigger.minimessage; - -import net.md_5.bungee.api.chat.BaseComponent; - -import java.util.Map; -import javax.annotation.Nonnull; - -/** - * @deprecated use me.minidigger.minimessage.bungee.MiniMessageParser instead - */ -@Deprecated -public class MiniMessageParser { - - private MiniMessageParser() { - - } - - /** - * @deprecated use me.minidigger.minimessage.bungee.MiniMessageParser instead - */ - @Deprecated - @Nonnull - public static String escapeTokens(@Nonnull String richMessage) { - return me.minidigger.minimessage.bungee.MiniMessageParser.escapeTokens(richMessage); - } - - /** - * @deprecated use me.minidigger.minimessage.bungee.MiniMessageParser instead - */ - @Deprecated - @Nonnull - public static String stripTokens(@Nonnull String richMessage) { - return me.minidigger.minimessage.bungee.MiniMessageParser.stripTokens(richMessage); - } - - /** - * @deprecated use me.minidigger.minimessage.bungee.MiniMessageParser instead - */ - @Deprecated - @Nonnull - public static String handlePlaceholders(@Nonnull String richMessage, @Nonnull String... placeholders) { - return me.minidigger.minimessage.bungee.MiniMessageParser.handlePlaceholders(richMessage, placeholders); - } - - /** - * @deprecated use me.minidigger.minimessage.bungee.MiniMessageParser instead - */ - @Deprecated - @Nonnull - public static String handlePlaceholders(@Nonnull String richMessage, @Nonnull Map placeholders) { - return me.minidigger.minimessage.bungee.MiniMessageParser.handlePlaceholders(richMessage, placeholders); - } - - /** - * @deprecated use me.minidigger.minimessage.bungee.MiniMessageParser instead - */ - @Deprecated - @Nonnull - public static BaseComponent[] parseFormat(@Nonnull String richMessage) { - return me.minidigger.minimessage.bungee.MiniMessageParser.parseFormat(richMessage); - } - - /** - * @deprecated use me.minidigger.minimessage.bungee.MiniMessageParser instead - */ - @Deprecated - @Nonnull - public static BaseComponent[] parseFormat(@Nonnull String richMessage, @Nonnull String... placeholders) { - return me.minidigger.minimessage.bungee.MiniMessageParser.parseFormat(richMessage, placeholders); - } - - /** - * @deprecated use me.minidigger.minimessage.bungee.MiniMessageParser instead - */ - @Deprecated - @Nonnull - public static BaseComponent[] parseFormat(@Nonnull String richMessage, @Nonnull Map placeholders) { - return me.minidigger.minimessage.bungee.MiniMessageParser.parseFormat(richMessage, placeholders); - } -} diff --git a/minimessage-bungee/src/main/java/me/minidigger/minimessage/MiniMessageSerializer.java b/minimessage-bungee/src/main/java/me/minidigger/minimessage/MiniMessageSerializer.java deleted file mode 100644 index e0f970e94..000000000 --- a/minimessage-bungee/src/main/java/me/minidigger/minimessage/MiniMessageSerializer.java +++ /dev/null @@ -1,24 +0,0 @@ -package me.minidigger.minimessage; - -import net.md_5.bungee.api.chat.BaseComponent; - -import javax.annotation.Nonnull; - -/** - * @deprecated use me.minidigger.minimessage.bungee.MiniMessageSerializer instead - */ -@Deprecated -public final class MiniMessageSerializer { - - private MiniMessageSerializer(){ - } - - /** - * @deprecated use me.minidigger.minimessage.bungee.MiniMessageSerializer instead - */ - @Deprecated - @Nonnull - public static String serialize(@Nonnull BaseComponent... components) { - return me.minidigger.minimessage.bungee.MiniMessageSerializer.serialize(components); - } -} diff --git a/minimessage-bungee/src/main/java/me/minidigger/minimessage/bungee/Constants.java b/minimessage-bungee/src/main/java/me/minidigger/minimessage/bungee/Constants.java deleted file mode 100644 index 9a545aa5d..000000000 --- a/minimessage-bungee/src/main/java/me/minidigger/minimessage/bungee/Constants.java +++ /dev/null @@ -1,20 +0,0 @@ -package me.minidigger.minimessage.bungee; - -class Constants { - public static final String CLICK = "click"; - public static final String HOVER = "hover"; - - public static final String UNDERLINED = "underlined"; - public static final String STRIKETHROUGH = "strikethrough"; - public static final String OBFUSCATED = "obfuscated"; - public static final String ITALIC = "italic"; - public static final String BOLD = "bold"; - - public static final String TAG_START = "<"; - public static final String TAG_END = ">"; - public static final String CLOSE_TAG = "/"; - public static final String SEPARATOR = ":"; - - private Constants(){ - } -} diff --git a/minimessage-bungee/src/main/java/me/minidigger/minimessage/bungee/MiniMessageParser.java b/minimessage-bungee/src/main/java/me/minidigger/minimessage/bungee/MiniMessageParser.java deleted file mode 100644 index bd7d16f0f..000000000 --- a/minimessage-bungee/src/main/java/me/minidigger/minimessage/bungee/MiniMessageParser.java +++ /dev/null @@ -1,307 +0,0 @@ -package me.minidigger.minimessage.bungee; - -import net.md_5.bungee.api.ChatColor; -import net.md_5.bungee.api.chat.BaseComponent; -import net.md_5.bungee.api.chat.ClickEvent; -import net.md_5.bungee.api.chat.ComponentBuilder; -import net.md_5.bungee.api.chat.HoverEvent; - -import java.util.ArrayDeque; -import java.util.Deque; -import java.util.EnumSet; -import java.util.Locale; -import java.util.Map; -import java.util.Optional; -import java.util.function.Consumer; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import javax.annotation.Nonnull; - -import static me.minidigger.minimessage.bungee.Constants.CLICK; -import static me.minidigger.minimessage.bungee.Constants.CLOSE_TAG; -import static me.minidigger.minimessage.bungee.Constants.HOVER; -import static me.minidigger.minimessage.bungee.Constants.SEPARATOR; -import static me.minidigger.minimessage.bungee.Constants.TAG_END; -import static me.minidigger.minimessage.bungee.Constants.TAG_START; - -public class MiniMessageParser { - - // regex group names - private static final String START = "start"; - private static final String TOKEN = "token"; - private static final String INNER = "inner"; - private static final String END = "end"; - // https://regex101.com/r/8VZ7uA/5 - private static final Pattern pattern = Pattern.compile("((?<)(?([^<>]+)|([^<>]+\"(?[^\"]+)\"))(?>))+?"); - - @Nonnull - public static String escapeTokens(@Nonnull String richMessage) { - StringBuilder sb = new StringBuilder(); - Matcher matcher = pattern.matcher(richMessage); - int lastEnd = 0; - while (matcher.find()) { - int startIndex = matcher.start(); - int endIndex = matcher.end(); - - if (startIndex > lastEnd) { - sb.append(richMessage, lastEnd, startIndex); - } - lastEnd = endIndex; - - String start = matcher.group(START); - String token = matcher.group(TOKEN); - String inner = matcher.group(INNER); - String end = matcher.group(END); - - // also escape inner - if (inner != null) { - token = token.replace(inner, escapeTokens(inner)); - } - - sb.append("\\").append(start).append(token).append("\\").append(end); - } - - if (richMessage.length() > lastEnd) { - sb.append(richMessage.substring(lastEnd)); - } - - return sb.toString(); - } - - @Nonnull - public static String stripTokens(@Nonnull String richMessage) { - StringBuilder sb = new StringBuilder(); - Matcher matcher = pattern.matcher(richMessage); - int lastEnd = 0; - while (matcher.find()) { - int startIndex = matcher.start(); - int endIndex = matcher.end(); - - if (startIndex > lastEnd) { - sb.append(richMessage, lastEnd, startIndex); - } - lastEnd = endIndex; - } - - if (richMessage.length() > lastEnd) { - sb.append(richMessage.substring(lastEnd)); - } - - return sb.toString(); - } - - @Nonnull - public static String handlePlaceholders(@Nonnull String richMessage, @Nonnull String... placeholders) { - if (placeholders.length % 2 != 0) { - throw new ParseException( - "Invalid number placeholders defined, usage: parseFormat(format, key, value, key, value...)"); - } - for (int i = 0; i < placeholders.length; i += 2) { - richMessage = richMessage.replace(TAG_START + placeholders[i] + TAG_END, placeholders[i + 1]); - } - return richMessage; - } - - @Nonnull - public static String handlePlaceholders(@Nonnull String richMessage, @Nonnull Map placeholders) { - for (Map.Entry entry : placeholders.entrySet()) { - richMessage = richMessage.replace(TAG_START + entry.getKey() + TAG_END, entry.getValue()); - } - return richMessage; - } - - @Nonnull - public static BaseComponent[] parseFormat(@Nonnull String richMessage, @Nonnull String... placeholders) { - return parseFormat(handlePlaceholders(richMessage, placeholders)); - } - - @Nonnull - public static BaseComponent[] parseFormat(@Nonnull String richMessage, @Nonnull Map placeholders) { - return parseFormat(handlePlaceholders(richMessage, placeholders)); - } - - @Nonnull - public static BaseComponent[] parseFormat(@Nonnull String richMessage) { - ComponentBuilder builder = null; - - Deque clickEvents = new ArrayDeque<>(); - Deque hoverEvents = new ArrayDeque<>(); - Deque colors = new ArrayDeque<>(); - EnumSet decorations = EnumSet.noneOf(TextDecoration.class); - - Matcher matcher = pattern.matcher(richMessage); - int lastEnd = 0; - while (matcher.find()) { - int startIndex = matcher.start(); - int endIndex = matcher.end(); - - String msg = null; - if (startIndex > lastEnd) { - msg = richMessage.substring(lastEnd, startIndex); - } - lastEnd = endIndex; - - // handle message - if (msg != null && msg.length() != 0) { - // append message - if (builder == null) { - builder = new ComponentBuilder(msg); - } else { - builder.append(msg, ComponentBuilder.FormatRetention.NONE); - } - - // set everything that is not closed yet - if (!clickEvents.isEmpty()) { - builder.event(clickEvents.peek()); - } - if (!hoverEvents.isEmpty()) { - builder.event(hoverEvents.peek()); - } - if (!colors.isEmpty()) { - builder.color(colors.peek()); - } - if (!decorations.isEmpty()) { - // no lambda because builder isn't effective final :/ - for (TextDecoration decor : decorations) { - decor.apply(builder); - } - } - } - - String token = matcher.group(TOKEN); - String inner = matcher.group(INNER); - - Optional deco; - Optional color; - - // click - if (token.startsWith(CLICK + SEPARATOR)) { - clickEvents.push(handleClick(token, inner)); - } else if (token.equals(CLOSE_TAG + CLICK)) { - clickEvents.pop(); - } - // hover - else if (token.startsWith(HOVER + SEPARATOR)) { - hoverEvents.push(handleHover(token, inner)); - } else if (token.equals(CLOSE_TAG + HOVER)) { - hoverEvents.pop(); - } - // decoration - else if ((deco = resolveDecoration(token)).isPresent()) { - decorations.add(deco.get()); - } else if (token.startsWith(CLOSE_TAG) && (deco = resolveDecoration(token.replace(CLOSE_TAG, ""))).isPresent()) { - decorations.remove(deco.get()); - } - // color - else if ((color = resolveColor(token)).isPresent()) { - colors.push(color.get()); - } else if (token.startsWith(CLOSE_TAG) && resolveColor(token.replace(CLOSE_TAG, "")).isPresent()) { - colors.pop(); - } else { - // invalid tag - if (builder == null) { - builder = new ComponentBuilder(TAG_START + token + TAG_END); - } else { - builder.append(TAG_START + token + TAG_END, ComponentBuilder.FormatRetention.NONE); - } - } - } - - // handle last message part - if (richMessage.length() > lastEnd) { - String msg = richMessage.substring(lastEnd); - // append message - if (builder == null) { - builder = new ComponentBuilder(msg); - } else { - builder.append(msg, ComponentBuilder.FormatRetention.NONE); - } - - // set everything that is not closed yet - if (!clickEvents.isEmpty()) { - builder.event(clickEvents.peek()); - } - if (!hoverEvents.isEmpty()) { - builder.event(hoverEvents.peek()); - } - if (!colors.isEmpty()) { - builder.color(colors.peek()); - } - if (!decorations.isEmpty()) { - // no lambda because builder isn't effective final :/ - for (TextDecoration decor : decorations) { - decor.apply(builder); - } - } - } - - if (builder == null) { - // lets just return an empty component - builder = new ComponentBuilder(""); - } - - return builder.create(); - } - - @Nonnull - private static ClickEvent handleClick(@Nonnull String token, @Nonnull String inner) { - String[] args = token.split(SEPARATOR); - if (args.length < 2) { - throw new ParseException("Can't parse click action (too few args) " + token); - } - ClickEvent.Action action = ClickEvent.Action.valueOf(args[1].toUpperCase(Locale.ROOT)); - return new ClickEvent(action, token.replace(CLICK + SEPARATOR + args[1] + SEPARATOR, "")); - } - - @Nonnull - private static HoverEvent handleHover(@Nonnull String token, @Nonnull String inner) { - String[] args = token.split(SEPARATOR); - if (args.length < 2) { - throw new ParseException("Can't parse hover action (too few args) " + token); - } - HoverEvent.Action action = HoverEvent.Action.valueOf(args[1].toUpperCase(Locale.ROOT)); - return new HoverEvent(action, parseFormat(inner)); - } - - @Nonnull - private static Optional resolveColor(@Nonnull String token) { - try { - return Optional.of(ChatColor.valueOf(token.toUpperCase(Locale.ROOT))); - } catch (IllegalArgumentException ex) { - return Optional.empty(); - } - } - - @Nonnull - private static Optional resolveDecoration(@Nonnull String token) { - try { - return Optional.of(TextDecoration.valueOf(token.toUpperCase(Locale.ROOT))); - } catch (IllegalArgumentException ex) { - return Optional.empty(); - } - } - - enum TextDecoration { - BOLD(b -> b.bold(true)), - ITALIC(b -> b.italic(true)), - UNDERLINED(b -> b.underlined(true)), - STRIKETHROUGH(b -> b.strikethrough(true)), - OBFUSCATED(b -> b.obfuscated(true)); - - private final Consumer builder; - - TextDecoration(@Nonnull Consumer builder) { - this.builder = builder; - } - - public void apply(@Nonnull ComponentBuilder comp) { - builder.accept(comp); - } - } - - static class ParseException extends RuntimeException { - public ParseException(@Nonnull String message) { - super(message); - } - } -} diff --git a/minimessage-bungee/src/main/java/me/minidigger/minimessage/bungee/MiniMessageSerializer.java b/minimessage-bungee/src/main/java/me/minidigger/minimessage/bungee/MiniMessageSerializer.java deleted file mode 100644 index fa4440e47..000000000 --- a/minimessage-bungee/src/main/java/me/minidigger/minimessage/bungee/MiniMessageSerializer.java +++ /dev/null @@ -1,171 +0,0 @@ -package me.minidigger.minimessage.bungee; - -import net.md_5.bungee.api.ChatColor; -import net.md_5.bungee.api.chat.BaseComponent; -import net.md_5.bungee.api.chat.ClickEvent; -import net.md_5.bungee.api.chat.HoverEvent; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -import java.util.Locale; - -import static me.minidigger.minimessage.bungee.Constants.BOLD; -import static me.minidigger.minimessage.bungee.Constants.CLICK; -import static me.minidigger.minimessage.bungee.Constants.CLOSE_TAG; -import static me.minidigger.minimessage.bungee.Constants.HOVER; -import static me.minidigger.minimessage.bungee.Constants.ITALIC; -import static me.minidigger.minimessage.bungee.Constants.OBFUSCATED; -import static me.minidigger.minimessage.bungee.Constants.SEPARATOR; -import static me.minidigger.minimessage.bungee.Constants.STRIKETHROUGH; -import static me.minidigger.minimessage.bungee.Constants.TAG_END; -import static me.minidigger.minimessage.bungee.Constants.TAG_START; -import static me.minidigger.minimessage.bungee.Constants.UNDERLINED; - -public final class MiniMessageSerializer { - - private MiniMessageSerializer(){ - } - - @Nonnull - public static String serialize(@Nonnull BaseComponent... components) { - StringBuilder sb = new StringBuilder(); - - for (int i = 0; i < components.length; i++) { - BaseComponent comp = components[i]; - - // # start tags - - // ## get prev comp - BaseComponent prevComp = null; - if (i > 0) { - prevComp = components[i - 1]; - } - - // ## color - // ### white is not important - if (!ChatColor.WHITE.equals(comp.getColor())) { - sb.append(startColor(comp.getColor())); - } - - // ## decoration - // ### only start if prevComp didn't start - if (comp.isBold() && (prevComp == null || !prevComp.isBold())) { - sb.append(startTag(BOLD)); - } - if (comp.isItalic() && (prevComp == null || !prevComp.isItalic())) { - sb.append(startTag(ITALIC)); - } - if (comp.isObfuscated() && (prevComp == null || !prevComp.isObfuscated())) { - sb.append(startTag(OBFUSCATED)); - } - if (comp.isStrikethrough() && (prevComp == null || !prevComp.isStrikethrough())) { - sb.append(startTag(STRIKETHROUGH)); - } - if (comp.isUnderlined() && (prevComp == null || !prevComp.isUnderlined())) { - sb.append(startTag(UNDERLINED)); - } - - // ## hover - // ### only start if prevComp didn't start the same one - HoverEvent hov = comp.getHoverEvent(); - if (hov != null && (prevComp == null || areDifferent(hov, prevComp.getHoverEvent()))) { - sb.append(startTag(String.format("%s" + SEPARATOR + "%s" + SEPARATOR + "\"%s\"", HOVER, hov.getAction().name().toLowerCase(Locale.ROOT), serialize(hov.getValue())))); - } - - // ## click - // ### only start if prevComp didn't start the same one - ClickEvent click = comp.getClickEvent(); - if (click != null && (prevComp == null || areDifferent(click, prevComp.getClickEvent()))) { - sb.append(startTag(String.format("%s" + SEPARATOR + "%s" + SEPARATOR + "\"%s\"", CLICK, click.getAction().name().toLowerCase(Locale.ROOT), click.getValue()))); - } - - // # append text - sb.append(comp.toPlainText()); - - // # end tags - - // ## get next comp - BaseComponent nextComp = null; - if (i + 1 < components.length) { - nextComp = components[i + 1]; - } - - // ## color - // ### only end color if next comp is white and current isn't - if (nextComp != null && comp.getColor() != ChatColor.WHITE) { - if (nextComp.getColor() == ChatColor.WHITE || nextComp.getColor() == null) { - sb.append(endColor(comp.getColor())); - } - } - - // ## decoration - // ### only end decoration if next tag is different - if (nextComp != null) { - if (comp.isBold() && !nextComp.isBold()) { - sb.append(endTag(BOLD)); - } - if (comp.isItalic() && !nextComp.isItalic()) { - sb.append(endTag(ITALIC)); - } - if (comp.isObfuscated() && !nextComp.isObfuscated()) { - sb.append(endTag(OBFUSCATED)); - } - if (comp.isStrikethrough() && !nextComp.isStrikethrough()) { - sb.append(endTag(STRIKETHROUGH)); - } - if (comp.isUnderlined() && !nextComp.isUnderlined()) { - sb.append(endTag(UNDERLINED)); - } - } - - // ## hover - // ### only end hover if next tag is different - if (nextComp != null && comp.getHoverEvent() != null) { - if (areDifferent(comp.getHoverEvent(), nextComp.getHoverEvent())) { - sb.append(endTag(HOVER)); - } - } - - // ## click - // ### only end click if next tag is different - if (nextComp != null && comp.getClickEvent() != null) { - if (areDifferent(comp.getClickEvent(), nextComp.getClickEvent())) { - sb.append(endTag(CLICK)); - } - } - } - - return sb.toString(); - } - - private static boolean areDifferent(@Nonnull ClickEvent c1, @Nullable ClickEvent c2) { - if (c2 == null) return true; - return !c1.equals(c2) && (!c1.getAction().equals(c2.getAction()) || !c1.getValue().equals(c2.getValue())); - } - - private static boolean areDifferent(@Nonnull HoverEvent h1, @Nullable HoverEvent h2) { - if (h2 == null) return true; - return !h1.equals(h2) && (!h1.getAction().equals(h2.getAction()));// TODO also compare value - } - - @Nonnull - private static String startColor(@Nonnull ChatColor color) { - return startTag(color.getName()); - } - - @Nonnull - private static String endColor(@Nonnull ChatColor color) { - return endTag(color.getName()); - } - - @Nonnull - private static String startTag(@Nonnull String content) { - return TAG_START + content + TAG_END; - } - - @Nonnull - private static String endTag(@Nonnull String content) { - return TAG_START + CLOSE_TAG + content + TAG_END; - } -} diff --git a/minimessage-bungee/src/test/java/me/minidigger/minimessage/MiniMessageParserTest.java b/minimessage-bungee/src/test/java/me/minidigger/minimessage/MiniMessageParserTest.java deleted file mode 100644 index 4879173f9..000000000 --- a/minimessage-bungee/src/test/java/me/minidigger/minimessage/MiniMessageParserTest.java +++ /dev/null @@ -1,169 +0,0 @@ -package me.minidigger.minimessage; - -import net.md_5.bungee.api.chat.BaseComponent; -import net.md_5.bungee.chat.ComponentSerializer; - -import org.junit.Test; - -import javax.annotation.Nonnull; - -import static org.junit.Assert.assertEquals; - -public class MiniMessageParserTest { - - @Test - public void test() { - String input1 = "TEST nestedTest"; - String input2 = "TEST nestedTest"; - String out1 = ComponentSerializer.toString(MiniMessageParser.parseFormat(input1)); - String out2 = ComponentSerializer.toString(MiniMessageParser.parseFormat(input2)); - System.out.println(out1); - System.out.println(out2); - assertEquals(out1, out2); - } - - @Test - public void testStripSimple() { - String input = "TEST nestedTest"; - String expected = "TEST nestedTest"; - assertEquals(expected, MiniMessageParser.stripTokens(input)); - } - - @Test - public void testStripComplex() { - String input = " random strangerclick here to FEEL it"; - String expected = " random strangerclick here to FEEL it"; - assertEquals(expected, MiniMessageParser.stripTokens(input)); - } - - @Test - public void testStripInner() { - String input = "test:TEST\">TEST"; - String expected = "TEST"; - assertEquals(expected, MiniMessageParser.stripTokens(input)); - } - - @Test - public void testEscapeSimple() { - String input = "TEST nestedTest"; - String expected = "\\TEST\\ nested\\Test"; - assertEquals(expected, MiniMessageParser.escapeTokens(input)); - } - - @Test - public void testEscapeComplex() { - String input = " random strangerclick here to FEEL it"; - String expected = "\\\\ random \\stranger\\\\\\\\click here\\\\ to \\FEEL\\ it"; - assertEquals(expected, MiniMessageParser.escapeTokens(input)); - } - - @Test - public void testEscapeInner() { - String input = "test:TEST\">TEST"; - String expected = "\\test:TEST\"\\>TEST"; - assertEquals(expected, MiniMessageParser.escapeTokens(input)); - } - - - @Test - public void checkPlaceholder() { - String input = ""; - String expected = "{\"text\":\"Hello!\"}"; - BaseComponent[] comp = MiniMessageParser.parseFormat(input, "test", "Hello!"); - - test(comp, expected); - } - - @Test - public void testNiceMix() { - String input = " random strangerclick here to FEEL it"; - String expected = "{\"extra\":[{\"color\":\"yellow\",\"text\":\"Hello! random \"},{\"color\":\"yellow\",\"bold\":true,\"text\":\"stranger\"},{\"color\":\"red\",\"underlined\":true,\"clickEvent\":{\"action\":\"run_command\",\"value\":\"test command\"},\"text\":\"click here\"},{\"color\":\"blue\",\"underlined\":true,\"text\":\" to \"},{\"color\":\"blue\",\"bold\":true,\"underlined\":true,\"text\":\"FEEL\"},{\"color\":\"blue\",\"bold\":true,\"text\":\" it\"}],\"text\":\"\"}"; - BaseComponent[] comp = MiniMessageParser.parseFormat(input, "test", "Hello!"); - - test(comp, expected); - } - - @Test - public void testColorSimple() { - String input = "TEST"; - String expected = "{\"color\":\"yellow\",\"text\":\"TEST\"}"; - - test(input, expected); - } - - @Test - public void testColorNested() { - String input = "TESTnestedTest"; - String expected = "{\"extra\":[{\"color\":\"yellow\",\"text\":\"TEST\"},{\"color\":\"green\",\"text\":\"nested\"},{\"color\":\"yellow\",\"text\":\"Test\"}],\"text\":\"\"}"; - - test(input, expected); - } - - @Test - public void testColorNotNested() { - String input = "TESTnestedTest"; - String expected = "{\"extra\":[{\"color\":\"yellow\",\"text\":\"TEST\"},{\"color\":\"green\",\"text\":\"nested\"},{\"text\":\"Test\"}],\"text\":\"\"}"; - - test(input, expected); - } - - @Test - public void testHover() { - String input = "test\">TEST"; - String expected = "{\"hoverEvent\":{\"action\":\"show_text\",\"value\":[{\"color\":\"red\",\"text\":\"test\"}]},\"text\":\"TEST\"}"; - - test(input, expected); - } - - @Test - public void testHoverWithColon() { - String input = "test:TEST\">TEST"; - String expected = "{\"hoverEvent\":{\"action\":\"show_text\",\"value\":[{\"color\":\"red\",\"text\":\"test:TEST\"}]},\"text\":\"TEST\"}"; - - test(input, expected); - } - - @Test - public void testClick() { - String input = "TEST"; - String expected = "{\"clickEvent\":{\"action\":\"run_command\",\"value\":\"test\"},\"text\":\"TEST\"}"; - - test(input, expected); - } - - @Test - public void testClickExtendedCommand() { - String input = "TEST"; - String expected = "{\"clickEvent\":{\"action\":\"run_command\",\"value\":\"test command\"},\"text\":\"TEST\"}"; - - test(input, expected); - } - - @Test - public void testInvalidTag() { - String input = ""; - String expected = "{\"text\":\"\\u003ctest\\u003e\"}"; // gson makes it html save - BaseComponent[] comp = MiniMessageParser.parseFormat(input); - - test(comp, expected); - - // TODO am not totally happy about this yet, invalid tags arent getting colored for example, but good enough for now - } - - @Test - public void testInvalidTagComplex() { - String input = " random strangerclick here to FEEL it"; - String expected = "{\"extra\":[{\"text\":\"\\u003ctest\\u003e\"},{\"color\":\"yellow\",\"text\":\" random \"},{\"color\":\"yellow\",\"bold\":true,\"text\":\"stranger\"},{\"text\":\"\\u003coof\\u003e\"},{\"text\":\"\\u003c/oof\\u003e\"},{\"color\":\"red\",\"underlined\":true,\"clickEvent\":{\"action\":\"run_command\",\"value\":\"test command\"},\"text\":\"click here\"},{\"color\":\"blue\",\"underlined\":true,\"text\":\" to \"},{\"color\":\"blue\",\"bold\":true,\"underlined\":true,\"text\":\"FEEL\"},{\"color\":\"blue\",\"bold\":true,\"text\":\" it\"}],\"text\":\"\"}"; - BaseComponent[] comp = MiniMessageParser.parseFormat(input); - - test(comp, expected); - } - - private void test(@Nonnull String input, @Nonnull String expected) { - test(MiniMessageParser.parseFormat(input), expected); - } - - private void test(@Nonnull BaseComponent[] comp, @Nonnull String expected) { - assertEquals(expected, ComponentSerializer.toString(comp)); - } -} diff --git a/minimessage-bungee/src/test/java/me/minidigger/minimessage/MiniMessageSerializerTest.java b/minimessage-bungee/src/test/java/me/minidigger/minimessage/MiniMessageSerializerTest.java deleted file mode 100644 index 329cdc9e6..000000000 --- a/minimessage-bungee/src/test/java/me/minidigger/minimessage/MiniMessageSerializerTest.java +++ /dev/null @@ -1,124 +0,0 @@ -package me.minidigger.minimessage; - -import net.md_5.bungee.api.ChatColor; -import net.md_5.bungee.api.chat.ClickEvent; -import net.md_5.bungee.api.chat.ComponentBuilder; -import net.md_5.bungee.api.chat.ComponentBuilder.FormatRetention; -import net.md_5.bungee.api.chat.HoverEvent; -import net.md_5.bungee.api.chat.HoverEvent.Action; - -import org.junit.Test; - -import javax.annotation.Nonnull; - -import static org.junit.Assert.assertEquals; - -public class MiniMessageSerializerTest { - - @Test - public void testColor() { - String expected = "This is a test"; - - ComponentBuilder builder = new ComponentBuilder("This is a test"); - builder.color(ChatColor.RED); - - test(builder, expected); - } - - @Test - public void testColorClosing() { - String expected = "This is a test"; - - ComponentBuilder builder = new ComponentBuilder("This is a "); - builder.color(ChatColor.RED); - builder.append("test", FormatRetention.NONE); - - test(builder, expected); - } - - @Test - public void testNestedColor() { - String expected = "This is a blue test"; - - ComponentBuilder builder = new ComponentBuilder("This is a ").color(ChatColor.RED)// - .append("blue ", FormatRetention.NONE).color(ChatColor.BLUE)// - .append("test", FormatRetention.NONE).color(ChatColor.RED); - - test(builder, expected); - } - - @Test - public void testDecoration() { - String expected = "This is underlined, this isn't"; - - ComponentBuilder builder = new ComponentBuilder("This is ").underlined(true)// - .append("underlined", FormatRetention.NONE).bold(true).underlined(true)// - .append(", this", FormatRetention.NONE).bold(true)// - .append(" isn't", FormatRetention.NONE); - - test(builder, expected); - } - - @Test - public void testHover() { - String expected = "Some hover that ends here"; - - ComponentBuilder builder = new ComponentBuilder("Some hover") - .event(new HoverEvent(Action.SHOW_TEXT, new ComponentBuilder("---").create()))// - .append(" that ends here", FormatRetention.NONE); - - test(builder, expected); - } - - @Test - public void testHoverWithNested() { - String expected = "----\">Some hover that ends here"; - - ComponentBuilder builder = new ComponentBuilder("Some hover").event(new HoverEvent(Action.SHOW_TEXT, // - new ComponentBuilder("---").color(ChatColor.RED)// - .append("-").color(ChatColor.BLUE).bold(true).create()))// - .append(" that ends here", FormatRetention.NONE); - - test(builder, expected); - } - - @Test - public void testClick() { - String expected = "Some click that ends here"; - - ComponentBuilder builder = new ComponentBuilder("Some click") - .event(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "test"))// - .append(" that ends here", FormatRetention.NONE); - - test(builder, expected); - } - - @Test - public void testContinuedClick() { - String expected = "Some click that doesn't ends here"; - - ComponentBuilder builder = new ComponentBuilder("Some click") - .event(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "test"))// - .append(" that doesn't ends here", FormatRetention.EVENTS).color(ChatColor.RED); - - test(builder, expected); - } - - @Test - public void testContinuedClick2() { - String expected = "Some click that doesn't ends here"; - - ComponentBuilder builder = new ComponentBuilder("Some click") - .event(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "test"))// - .append(" that doesn't ends here", FormatRetention.NONE) - .event(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "test")) - .color(ChatColor.RED); - - test(builder, expected); - } - - private void test(@Nonnull ComponentBuilder builder, @Nonnull String expected) { - String string = MiniMessageSerializer.serialize(builder.create()); - assertEquals(expected, string); - } -} diff --git a/minimessage-ext-markdown/pom.xml b/minimessage-ext-markdown/pom.xml deleted file mode 100644 index 9632a5e73..000000000 --- a/minimessage-ext-markdown/pom.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - MiniMessage - me.minidigger - 2.1.1-SNAPSHOT - - 4.0.0 - - minimessage-ext-markdown - - - - com.destroystokyo.paper - paper-api - 1.15.2-R0.1-SNAPSHOT - provided - - - diff --git a/minimessage-ext-markdown/src/main/java/me/minidigger/minimessage/Constants.java b/minimessage-ext-markdown/src/main/java/me/minidigger/minimessage/Constants.java deleted file mode 100644 index cae1fb97d..000000000 --- a/minimessage-ext-markdown/src/main/java/me/minidigger/minimessage/Constants.java +++ /dev/null @@ -1,15 +0,0 @@ -package me.minidigger.minimessage; - -class Constants { - - public static final char EMPHASIS_1 = '*'; - public static final char EMPHASIS_2 = '_'; - public static final char UNDERLINE = '~'; - - public static final String ITALIC_TAG = "italic"; - public static final String BOLD_TAG = "bold"; - public static final String UNDERLINE_TAG = "underline"; - - private Constants() { - } -} diff --git a/minimessage-ext-markdown/src/main/java/me/minidigger/minimessage/MiniMarkdownParser.java b/minimessage-ext-markdown/src/main/java/me/minidigger/minimessage/MiniMarkdownParser.java deleted file mode 100644 index 1e6446d44..000000000 --- a/minimessage-ext-markdown/src/main/java/me/minidigger/minimessage/MiniMarkdownParser.java +++ /dev/null @@ -1,167 +0,0 @@ -package me.minidigger.minimessage; - -import java.util.ArrayList; -import java.util.Comparator; -import java.util.List; -import java.util.StringJoiner; -import javax.annotation.Nonnull; - -public class MiniMarkdownParser { - - private MiniMarkdownParser() { - } - - @Nonnull - public static String stripMarkdown(@Nonnull String input) { - return handle(input, true); - } - - @Nonnull - public static String parse(@Nonnull String input) { - return handle(input, false); - } - - @Nonnull - private static String handle(@Nonnull String input, boolean strip) { - StringBuilder sb = new StringBuilder(); - - int bold = -1; - Insert boldSkip = null; - int italic = -1; - Insert italicSkip = null; - int underline = -1; - Insert underlineSkip = null; - - List inserts = new ArrayList<>(); - int skip = 0; - for (int i = 0; i + skip < input.length(); i++) { - int currIndex = i + skip; - char c = input.charAt(currIndex); - - boolean shouldSkip = false; - if (c == Constants.EMPHASIS_1) { - char n = next(currIndex, input); - if (n == Constants.EMPHASIS_1) { - if (bold == -1) { - bold = sb.length(); - boldSkip = new Insert(sb.length(), c + ""); - } else { - inserts.add(new Insert(bold, "<" + Constants.BOLD_TAG + ">")); - inserts.add(new Insert(sb.length(), "")); - bold = -1; - } - skip++; - } else { - if (italic == -1) { - italic = sb.length(); - italicSkip = new Insert(sb.length(), c + ""); - } else { - inserts.add(new Insert(italic, "<" + Constants.ITALIC_TAG + ">")); - inserts.add(new Insert(sb.length(), "")); - italic = -1; - } - } - shouldSkip = true; - } else if (c == Constants.EMPHASIS_2) { - char n = next(currIndex, input); - if (n == Constants.EMPHASIS_2) { - if (bold == -1) { - bold = sb.length(); - boldSkip = new Insert(sb.length(), c + ""); - } else { - inserts.add(new Insert(bold, "<" + Constants.BOLD_TAG + ">")); - inserts.add(new Insert(sb.length(), "")); - bold = -1; - } - skip++; - } else { - if (italic == -1) { - italic = currIndex; - italicSkip = new Insert(sb.length(), c + ""); - } else { - inserts.add(new Insert(italic, "<" + Constants.ITALIC_TAG + ">")); - inserts.add(new Insert(currIndex - 1, "")); - italic = -1; - } - } - shouldSkip = true; - } else if (c == Constants.UNDERLINE) { - char n = next(currIndex, input); - if (n == Constants.UNDERLINE) { - if (underline == -1) { - underline = sb.length(); - underlineSkip = new Insert(sb.length(), c + ""); - } else { - inserts.add(new Insert(underline, "<" + Constants.UNDERLINE_TAG + ">")); - inserts.add(new Insert(sb.length(), "")); - underline = -1; - } - skip++; - shouldSkip = true; - } - } - - if (!shouldSkip) { - sb.append(c); - } - } - - if (strip) { - inserts.clear(); - } else { - inserts.sort(Comparator.comparing(Insert::getPos).thenComparing(Insert::getValue).reversed()); - } - - if (underline != -1) { - inserts.add(underlineSkip); - } - if (bold != -1) { - inserts.add(boldSkip); - } - if (italic != -1) { - inserts.add(italicSkip); - } - - for (Insert el : inserts) { - sb.insert(el.getPos(), el.getValue()); - } - - return sb.toString(); - } - - private static char next(int index, @Nonnull String input) { - if (index < input.length() - 1) { - return input.charAt(index + 1); - } else { - return ' '; - } - } - - static class Insert { - private final int pos; - private final String value; - - public int getPos() { - return pos; - } - - @Nonnull - public String getValue() { - return value; - } - - public Insert(int pos, @Nonnull String value) { - this.pos = pos; - this.value = value; - } - - @Override - @Nonnull - public String toString() { - return new StringJoiner(", ", Insert.class.getSimpleName() + "[", "]") - .add("pos=" + pos) - .add("value='" + value + "'") - .toString(); - } - } -} diff --git a/minimessage-ext-markdown/src/test/java/me/minidigger/minimessage/MiniMarkdownParserTest.java b/minimessage-ext-markdown/src/test/java/me/minidigger/minimessage/MiniMarkdownParserTest.java deleted file mode 100644 index 0151bf389..000000000 --- a/minimessage-ext-markdown/src/test/java/me/minidigger/minimessage/MiniMarkdownParserTest.java +++ /dev/null @@ -1,188 +0,0 @@ -package me.minidigger.minimessage; - -import org.junit.Test; - -import static org.junit.Assert.assertEquals; - -public class MiniMarkdownParserTest { - - @Test - public void testBold() { - String input = "**bold**"; - String expected = "bold"; - - String output = MiniMarkdownParser.parse(input); - - assertEquals(expected, output); - } - - @Test - public void testBold2() { - String input = "__bold__"; - String expected = "bold"; - - String output = MiniMarkdownParser.parse(input); - - assertEquals(expected, output); - } - - @Test - public void testItalic() { - String input = "*italic*"; - String expected = "italic"; - - String output = MiniMarkdownParser.parse(input); - - assertEquals(expected, output); - } - - @Test - public void testItalic2() { - String input = "_italic_"; - String expected = "italic"; - - String output = MiniMarkdownParser.parse(input); - - assertEquals(expected, output); - } - - @Test - public void testUnderline() { - String input = "~~underline~~"; - String expected = "underline"; - - String output = MiniMarkdownParser.parse(input); - - assertEquals(expected, output); - } - - @Test - public void testBoldWithSpaces() { - String input = "AaA** bold **AaA"; - String expected = "AaA bold AaA"; - - String output = MiniMarkdownParser.parse(input); - - assertEquals(expected, output); - } - - @Test - public void testMixed() { - String input = "*italic*~~underline~~**bold**"; - String expected = "italicunderlinebold"; - - String output = MiniMarkdownParser.parse(input); - - assertEquals(expected, output); - } - - @Test - public void testMushedTogether() { - String input = "*a***a**"; - String expected = "aa"; - - String output = MiniMarkdownParser.parse(input); - - assertEquals(expected, output); - } - - @Test - public void testWithFake() { - String input = "*a~*a~"; - String expected = "a~a~"; - - String output = MiniMarkdownParser.parse(input); - - assertEquals(expected, output); - } - - @Test - public void testCrossed() { - String input = "*a~~*a~~"; - String expected = "aa"; - - String output = MiniMarkdownParser.parse(input); - - assertEquals(expected, output); - } - - @Test - public void testCrossedWithSpace() { - String input = "*a~~ *a~~"; - String expected = "a a"; - - String output = MiniMarkdownParser.parse(input); - - assertEquals(expected, output); - } - - @Test - public void testNoEnd() { - String input = "*a"; - String expected = "*a"; - - String output = MiniMarkdownParser.parse(input); - - assertEquals(expected, output); - } - - @Test - public void testStripMixed() { - String input = "*italic*~~underline~~**bold**"; - String expected = "italicunderlinebold"; - - String output = MiniMarkdownParser.stripMarkdown(input); - - assertEquals(expected, output); - } - - @Test - public void testStripMushedTogether() { - String input = "*a***a**"; - String expected = "aa"; - - String output = MiniMarkdownParser.stripMarkdown(input); - - assertEquals(expected, output); - } - - @Test - public void testStripWithFake() { - String input = "*a~*a~"; - String expected = "a~a~"; - - String output = MiniMarkdownParser.stripMarkdown(input); - - assertEquals(expected, output); - } - - @Test - public void testStripCrossed() { - String input = "*a~~*a~~"; - String expected = "aa"; - - String output = MiniMarkdownParser.stripMarkdown(input); - - assertEquals(expected, output); - } - - @Test - public void testStripCrossedWithSpace() { - String input = "*a~~ *a~~"; - String expected = "a a"; - - String output = MiniMarkdownParser.stripMarkdown(input); - - assertEquals(expected, output); - } - - @Test - public void testStripNoEnd() { - String input = "*a"; - String expected = "*a"; - - String output = MiniMarkdownParser.stripMarkdown(input); - - assertEquals(expected, output); - } -} diff --git a/minimessage-plugin/pom.xml b/minimessage-plugin/pom.xml deleted file mode 100644 index fd08b228b..000000000 --- a/minimessage-plugin/pom.xml +++ /dev/null @@ -1,69 +0,0 @@ - - - 4.0.0 - - MiniMessage - me.minidigger - 2.1.1-SNAPSHOT - - minimessage-plugin - 1.0-SNAPSHOT - jar - Minimessage Plugin - - - - - org.apache.maven.plugins - maven-shade-plugin - 3.2.2 - - - package - - shade - - - false - - - - - - - - src/main/resources - true - - - - - - - papermc-repo - https://papermc.io/repo/repository/maven-public/ - - - - - - com.destroystokyo.paper - paper-api - 1.16.1-R0.1-SNAPSHOT - provided - - - me.minidigger - minimessage-text - 2.1.1-SNAPSHOT - - - net.kyori - adventure-platform-bukkit - 4.0.0-SNAPSHOT - compile - - - diff --git a/minimessage-plugin/src/main/java/me/minidigger/minimessage/plugin/MiniMessagePlugin.java b/minimessage-plugin/src/main/java/me/minidigger/minimessage/plugin/MiniMessagePlugin.java deleted file mode 100644 index 1a096fd50..000000000 --- a/minimessage-plugin/src/main/java/me/minidigger/minimessage/plugin/MiniMessagePlugin.java +++ /dev/null @@ -1,119 +0,0 @@ -package me.minidigger.minimessage.plugin; - -import net.kyori.adventure.audience.Audience; -import net.kyori.adventure.bossbar.BossBar; -import net.kyori.adventure.platform.bukkit.BukkitPlatform; -import net.kyori.adventure.text.Component; -import net.kyori.adventure.title.Title; - -import java.time.Duration; -import java.time.temporal.ChronoUnit; - -import org.bukkit.Bukkit; -import org.bukkit.ChatColor; -import org.bukkit.command.Command; -import org.bukkit.command.CommandSender; -import org.bukkit.event.Listener; -import org.bukkit.plugin.java.JavaPlugin; -import org.bukkit.scheduler.BukkitTask; - -import me.minidigger.minimessage.text.MiniMessageParser; -import me.minidigger.minimessage.text.ParseException; - -public final class MiniMessagePlugin extends JavaPlugin implements Listener { - - private BukkitPlatform platform; - - private BukkitTask task; - private int progress = 0; - private BossBar bar; - private Audience audience; - - @Override - public void onEnable() { - platform = BukkitPlatform.of(this); - getServer().getPluginManager().registerEvents(this, this); - } - - @Override - public boolean onCommand(CommandSender sender, Command command, String label, String[] a) { - String args = String.join(" ", a); - switch (command.getName()) { - case "minimessage": - minimessage(sender, args); - break; - case "minibroadcast": - minibroadcast(sender, args); - break; - case "minibossbar": - minibossbar(sender); - break; - case "minispam": - minispam(sender); - break; - default: - return false; - } - return true; - } - - private void minispam(CommandSender sender) { - if (audience == null) { - audience = Audience.of(platform.audience(sender)); - } else { - audience = Audience.of(audience, platform.audience(sender)); - } - if (task != null) { - task.cancel(); - task = null; - audience.hideBossBar(bar); - audience.clearTitle(); - } else { - bar = BossBar.of(getWithProgress(0), 1, BossBar.Color.BLUE, BossBar.Overlay.PROGRESS); - task = Bukkit.getScheduler().runTaskTimer(this, () -> { - Component message = getWithProgress(progress++); - bar.name(message); - audience.sendActionBar(message); - audience.sendMessage(message); - audience.showTitle(Title.of(message, message, Duration.ZERO, Duration.of(50, ChronoUnit.MILLIS), Duration.ZERO)); - }, 1, 1); - audience.showBossBar(bar); - } - } - - private void minibossbar(CommandSender sender) { - Audience audience = platform.audience(sender); - if (task != null) { - task.cancel(); - audience.hideBossBar(bar); - } else { - bar = BossBar.of(getWithProgress(0), 1, BossBar.Color.BLUE, BossBar.Overlay.PROGRESS); - task = Bukkit.getScheduler().runTaskTimer(this, () -> { - bar.name(getWithProgress(progress++)); - }, 1, 1); - audience.showBossBar(bar); - } - } - - private void minimessage(CommandSender sender, String args) { - try { - Component component = MiniMessageParser.parseFormat(args); - platform.audience(sender).sendMessage(component); - } catch (ParseException ex) { - sender.sendMessage(ChatColor.RED + "Error: " + ex.getMessage()); - } - } - - private void minibroadcast(CommandSender sender, String args) { - try { - Component component = MiniMessageParser.parseFormat(args); - platform.players().sendMessage(component); - } catch (ParseException ex) { - sender.sendMessage(ChatColor.RED + "Error: " + ex.getMessage()); - } - } - - private Component getWithProgress(int phase) { - return MiniMessageParser.parseFormat("|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||"); - } -} diff --git a/minimessage-plugin/src/main/resources/plugin.yml b/minimessage-plugin/src/main/resources/plugin.yml deleted file mode 100644 index bd8666e1f..000000000 --- a/minimessage-plugin/src/main/resources/plugin.yml +++ /dev/null @@ -1,10 +0,0 @@ -name: MiniMessage -version: ${project.version} -main: me.minidigger.minimessage.plugin.MiniMessagePlugin -api-version: 1.15 -authors: [MiniDigger] -commands: - minimessage: - minibroadcast: - minibossbar: - minispam: diff --git a/minimessage-text/pom.xml b/minimessage-text/pom.xml deleted file mode 100644 index be38ecbe7..000000000 --- a/minimessage-text/pom.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - 4.0.0 - - MiniMessage - me.minidigger - 2.1.1-SNAPSHOT - - - minimessage-text - MiniMessage Text - - - - snapshots-repo - https://oss.sonatype.org/content/repositories/snapshots - - - - - - net.kyori - adventure-api - 4.0.0-SNAPSHOT - compile - - - net.kyori - adventure-text-serializer-gson - 4.0.0-SNAPSHOT - test - - - diff --git a/minimessage-text/src/main/java/me/minidigger/minimessage/text/Constants.java b/minimessage-text/src/main/java/me/minidigger/minimessage/text/Constants.java deleted file mode 100644 index 5f7ec2aaa..000000000 --- a/minimessage-text/src/main/java/me/minidigger/minimessage/text/Constants.java +++ /dev/null @@ -1,33 +0,0 @@ -package me.minidigger.minimessage.text; - -class Constants { - // vanilla components - public static final String CLICK = "click"; - public static final String HOVER = "hover"; - public static final String KEYBIND = "key"; - public static final String TRANSLATABLE = "lang"; - public static final String INSERTION = "insert"; - public static final String COLOR = "color"; - - // vanilla decoration - public static final String UNDERLINED = "underlined"; - public static final String STRIKETHROUGH = "strikethrough"; - public static final String OBFUSCATED = "obfuscated"; - public static final String ITALIC = "italic"; - public static final String BOLD = "bold"; - public static final String RESET = "reset"; - public static final String PRE = "pre"; - - // minimessage components - public static final String RAINBOW = "rainbow"; - public static final String GRADIENT = "gradient"; - - // minimessage tags - public static final String TAG_START = "<"; - public static final String TAG_END = ">"; - public static final String CLOSE_TAG = "/"; - public static final String SEPARATOR = ":"; - - private Constants(){ - } -} diff --git a/minimessage-text/src/main/java/me/minidigger/minimessage/text/HelperTextDecoration.java b/minimessage-text/src/main/java/me/minidigger/minimessage/text/HelperTextDecoration.java deleted file mode 100644 index d2270bf7d..000000000 --- a/minimessage-text/src/main/java/me/minidigger/minimessage/text/HelperTextDecoration.java +++ /dev/null @@ -1,26 +0,0 @@ -package me.minidigger.minimessage.text; - -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.format.TextDecoration; - -import java.util.function.UnaryOperator; -import javax.annotation.Nonnull; - -enum HelperTextDecoration { - BOLD(b -> b.decoration(TextDecoration.BOLD, true)), - ITALIC(b -> b.decoration(TextDecoration.ITALIC, true)), - UNDERLINED(b -> b.decoration(TextDecoration.UNDERLINED, true)), - STRIKETHROUGH(b -> b.decoration(TextDecoration.STRIKETHROUGH, true)), - OBFUSCATED(b -> b.decoration(TextDecoration.OBFUSCATED, true)); - - private final UnaryOperator builder; - - HelperTextDecoration(@Nonnull UnaryOperator builder) { - this.builder = builder; - } - - @Nonnull - public Component apply(@Nonnull Component comp) { - return builder.apply(comp); - } -} diff --git a/minimessage-text/src/main/java/me/minidigger/minimessage/text/MiniMessageParser.java b/minimessage-text/src/main/java/me/minidigger/minimessage/text/MiniMessageParser.java deleted file mode 100644 index fd8c5050a..000000000 --- a/minimessage-text/src/main/java/me/minidigger/minimessage/text/MiniMessageParser.java +++ /dev/null @@ -1,493 +0,0 @@ -package me.minidigger.minimessage.text; - -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.KeybindComponent; -import net.kyori.adventure.text.TextComponent; -import net.kyori.adventure.text.TranslatableComponent; -import net.kyori.adventure.text.event.ClickEvent; -import net.kyori.adventure.text.event.HoverEvent; -import net.kyori.adventure.text.format.NamedTextColor; -import net.kyori.adventure.text.format.TextColor; - -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Deque; -import java.util.EnumSet; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Optional; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import javax.annotation.Nonnull; - -import me.minidigger.minimessage.text.fancy.Fancy; -import me.minidigger.minimessage.text.fancy.Gradient; -import me.minidigger.minimessage.text.fancy.Rainbow; - -import static me.minidigger.minimessage.text.Constants.CLICK; -import static me.minidigger.minimessage.text.Constants.CLOSE_TAG; -import static me.minidigger.minimessage.text.Constants.COLOR; -import static me.minidigger.minimessage.text.Constants.GRADIENT; -import static me.minidigger.minimessage.text.Constants.HOVER; -import static me.minidigger.minimessage.text.Constants.INSERTION; -import static me.minidigger.minimessage.text.Constants.KEYBIND; -import static me.minidigger.minimessage.text.Constants.PRE; -import static me.minidigger.minimessage.text.Constants.RAINBOW; -import static me.minidigger.minimessage.text.Constants.RESET; -import static me.minidigger.minimessage.text.Constants.SEPARATOR; -import static me.minidigger.minimessage.text.Constants.TAG_END; -import static me.minidigger.minimessage.text.Constants.TAG_START; -import static me.minidigger.minimessage.text.Constants.TRANSLATABLE; - -public class MiniMessageParser { - - // regex group names - private static final String START = "start"; - private static final String TOKEN = "token"; - private static final String INNER = "inner"; - private static final String END = "end"; - // https://regex101.com/r/8VZ7uA/10 - private static final Pattern pattern = Pattern.compile("((?<)(?[^<>]+(:(?['\"]?([^'\"](\\\\['\"])?)+['\"]?))*)(?>))+?"); - - private static final Pattern dumSplitPattern = Pattern.compile("['\"]:['\"]"); - - private static final Map, Fancy> empty = new HashMap<>(); - - @Nonnull - public static String escapeTokens(@Nonnull String richMessage) { - StringBuilder sb = new StringBuilder(); - Matcher matcher = pattern.matcher(richMessage); - int lastEnd = 0; - while (matcher.find()) { - int startIndex = matcher.start(); - int endIndex = matcher.end(); - - if (startIndex > lastEnd) { - sb.append(richMessage, lastEnd, startIndex); - } - lastEnd = endIndex; - - String start = matcher.group(START); - String token = matcher.group(TOKEN); - String inner = matcher.group(INNER); - String end = matcher.group(END); - - // also escape inner - if (inner != null) { - token = token.replace(inner, escapeTokens(inner)); - } - - sb.append("\\").append(start).append(token).append("\\").append(end); - } - - if (richMessage.length() > lastEnd) { - sb.append(richMessage.substring(lastEnd)); - } - - return sb.toString(); - } - - @Nonnull - public static String stripTokens(@Nonnull String richMessage) { - StringBuilder sb = new StringBuilder(); - Matcher matcher = pattern.matcher(richMessage); - int lastEnd = 0; - while (matcher.find()) { - int startIndex = matcher.start(); - int endIndex = matcher.end(); - - if (startIndex > lastEnd) { - sb.append(richMessage, lastEnd, startIndex); - } - lastEnd = endIndex; - } - - if (richMessage.length() > lastEnd) { - sb.append(richMessage.substring(lastEnd)); - } - - return sb.toString(); - } - - @Nonnull - public static String handlePlaceholders(@Nonnull String richMessage, @Nonnull String... placeholders) { - if (placeholders.length % 2 != 0) { - throw new ParseException( - "Invalid number placeholders defined, usage: parseFormat(format, key, value, key, value...)"); - } - for (int i = 0; i < placeholders.length; i += 2) { - richMessage = richMessage.replace(TAG_START + placeholders[i] + TAG_END, placeholders[i + 1]); - } - return richMessage; - } - - @Nonnull - public static String handlePlaceholders(@Nonnull String richMessage, @Nonnull Map placeholders) { - for (Map.Entry entry : placeholders.entrySet()) { - richMessage = richMessage.replace(TAG_START + entry.getKey() + TAG_END, entry.getValue()); - } - return richMessage; - } - - @Nonnull - public static Component parseFormat(@Nonnull String richMessage, @Nonnull String... placeholders) { - return parseFormat(handlePlaceholders(richMessage, placeholders)); - } - - @Nonnull - public static Component parseFormat(@Nonnull String richMessage, @Nonnull Map placeholders) { - return parseFormat(handlePlaceholders(richMessage, placeholders)); - } - - @Nonnull - public static Component parseFormat(@Nonnull String richMessage) { - TextComponent.Builder parent = TextComponent.builder(""); - - ArrayDeque clickEvents = new ArrayDeque<>(); - ArrayDeque> hoverEvents = new ArrayDeque<>(); - ArrayDeque colors = new ArrayDeque<>(); - ArrayDeque insertions = new ArrayDeque<>(); - EnumSet decorations = EnumSet.noneOf(HelperTextDecoration.class); - boolean isPreformatted = false; - Map, Fancy> fancy = new LinkedHashMap<>(); - - Matcher matcher = pattern.matcher(richMessage); - int lastEnd = 0; - while (matcher.find()) { - Component current = null; - int startIndex = matcher.start(); - int endIndex = matcher.end(); - - String msg = null; - if (startIndex > lastEnd) { - msg = richMessage.substring(lastEnd, startIndex); - } - lastEnd = endIndex; - - // handle message - if (msg != null && msg.length() != 0) { - // append message - current = TextComponent.of(msg); - current = applyFormatting(clickEvents, hoverEvents, colors, insertions, decorations, current, fancy); - - } - - String token = matcher.group(TOKEN); - String inner = matcher.group(INNER); - - Optional deco; - Optional color; - - // handle pre - if (isPreformatted) { - if (token.startsWith(CLOSE_TAG + PRE)) { - isPreformatted = false; - if (current != null) { - parent.append(current); - } - } else { - if (current != null) { - parent.append(current); - } - current = TextComponent.of(TAG_START + token + TAG_END); - current = applyFormatting(clickEvents, hoverEvents, colors, insertions, decorations, current, fancy); - parent.append(current); - } - continue; - } - - // click - if (token.startsWith(CLICK + SEPARATOR)) { - clickEvents.push(handleClick(token, inner)); - } else if (token.equals(CLOSE_TAG + CLICK)) { - clickEvents.pollFirst(); - } - // hover - else if (token.startsWith(HOVER + SEPARATOR)) { - hoverEvents.push(handleHover(token, inner)); - } else if (token.equals(CLOSE_TAG + HOVER)) { - hoverEvents.pollFirst(); - } - // decoration - else if ((deco = resolveDecoration(token)).isPresent()) { - decorations.add(deco.get()); - } else if (token.startsWith(CLOSE_TAG) && (deco = resolveDecoration(token.replace(CLOSE_TAG, ""))).isPresent()) { - decorations.remove(deco.get()); - } - // color - else if ((color = resolveColor(token)).isPresent()) { - colors.push(color.get()); - } else if (token.startsWith(CLOSE_TAG) && resolveColor(token.replace(CLOSE_TAG, "")).isPresent()) { - colors.pollFirst(); - } - // color; hex or named syntax - else if (token.startsWith(COLOR + SEPARATOR) && (color = resolveColorNew(token)).isPresent()) { - colors.push(color.get()); - } else if (token.startsWith(CLOSE_TAG + COLOR) && resolveColorNew(token.replace(CLOSE_TAG, "")).isPresent()) { - colors.pollFirst(); - } - // keybind - else if (token.startsWith(KEYBIND + SEPARATOR)) { - if (current != null) { - parent.append(current); - } - current = handleKeybind(token); - current = applyFormatting(clickEvents, hoverEvents, colors, insertions, decorations, current, fancy); - } - // translatable - else if (token.startsWith(TRANSLATABLE + SEPARATOR)) { - if (current != null) { - parent.append(current); - } - current = handleTranslatable(token, inner); - current = applyFormatting(clickEvents, hoverEvents, colors, insertions, decorations, current, fancy); - } - // insertion - else if (token.startsWith(INSERTION + SEPARATOR)) { - insertions.push(handleInsertion(token)); - } else if (token.startsWith(CLOSE_TAG + INSERTION)) { - insertions.pop(); - } - // reset - else if (token.startsWith(RESET)) { - clickEvents.clear(); - hoverEvents.clear(); - colors.clear(); - insertions.clear(); - decorations.clear(); - } - // pre - else if (token.startsWith(PRE)) { - isPreformatted = true; - } - // rainbow - else if (token.startsWith(RAINBOW)) { - fancy.put(Rainbow.class, handleRainbow(token)); - } else if (token.startsWith(CLOSE_TAG + RAINBOW)) { - fancy.remove(Rainbow.class); - } - // gradient - else if (token.startsWith(GRADIENT)) { - fancy.put(Gradient.class, handleGradient(token)); - } else if (token.startsWith(CLOSE_TAG + GRADIENT)) { - fancy.remove(Gradient.class); - } - // invalid tag - else { - if (current != null) { - parent.append(current); - } - current = TextComponent.of(TAG_START + token + TAG_END); - current = applyFormatting(clickEvents, hoverEvents, colors, insertions, decorations, current, fancy); - } - - if (current != null) { - parent.append(current); - } - } - - // handle last message part - if (richMessage.length() > lastEnd) { - String msg = richMessage.substring(lastEnd); - // append message - Component current = TextComponent.of(msg); - - // set everything that is not closed yet - current = applyFormatting(clickEvents, hoverEvents, colors, insertions, decorations, current, fancy); - - parent.append(current); - } - - // optimization, ignore empty parent - TextComponent comp = parent.build(); - if (comp.content().equals("") && comp.children().size() == 1) { - return comp.children().get(0); - } else { - return comp; - } - } - - @Nonnull - private static Component applyFormatting(@Nonnull Deque clickEvents, - @Nonnull Deque> hoverEvents, - @Nonnull Deque colors, - @Nonnull Deque insertions, - @Nonnull EnumSet decorations, - @Nonnull Component current, - @Nonnull Map, Fancy> fancy) { - // set everything that is not closed yet - if (!clickEvents.isEmpty()) { - current = current.clickEvent(clickEvents.peek()); - } - if (!hoverEvents.isEmpty()) { - current = current.hoverEvent(hoverEvents.peek()); - } - if (!colors.isEmpty()) { - current = current.color(colors.peek()); - } - if (!decorations.isEmpty()) { - // no lambda because builder isn't effective final :/ - for (HelperTextDecoration decor : decorations) { - current = decor.apply(current); - } - } - if (!insertions.isEmpty()) { - current = current.insertion(insertions.peek()); - } - - if (current instanceof TextComponent && fancy.size() != 0) { - Component parent = null; - TextComponent bigComponent = (TextComponent) current; - - Fancy next = fancy.entrySet().iterator().next().getValue(); - next.init(bigComponent.content().length()); - // split into multiple components - for (int i = 0; i < bigComponent.content().length(); i++) { - Component smallComponent = TextComponent.of(bigComponent.content().charAt(i)); - // apply formatting - smallComponent = applyFormatting(clickEvents, hoverEvents, colors, insertions, decorations, smallComponent, empty); - smallComponent = next.apply(smallComponent); - // append - if (parent == null) { - parent = smallComponent; - } else { - parent = parent.append(smallComponent); - } - } - if (parent != null) { - current = parent; - } - } - - return current; - } - - @Nonnull - private static Rainbow handleRainbow(String token) { - if (token.contains(SEPARATOR)) { - String phase = token.substring(token.indexOf(SEPARATOR) + 1); - try { - return new Rainbow(Integer.parseInt(phase)); - } catch (NumberFormatException ex) { - throw new ParseException("Can't parse rainbow phase (not a int) " + token); - } - } - return new Rainbow(); - } - - @Nonnull - private static Gradient handleGradient(String token) { - if (token.contains(SEPARATOR)) { - String[] split = token.split(":"); - if (split.length == 3) { - TextColor c1 = parseColor(split[1]).orElseThrow(() -> new ParseException("Can't parse gradient phase (not a color 1) " + token)); - TextColor c2 = parseColor(split[2]).orElseThrow(() -> new ParseException("Can't parse gradient phase (not a color 2) " + token)); - return new Gradient(c1, c2); - } else { - throw new ParseException("Can't parse gradient (wrong args) " + token); - } - } - return new Gradient(); - } - - @Nonnull - private static String handleInsertion(@Nonnull String token) { - String[] args = token.split(SEPARATOR); - if (args.length < 2) { - throw new ParseException("Can't parse insertion (too few args) " + token); - } - return token.replace(args[0] + SEPARATOR, ""); - } - - @Nonnull - private static Component handleTranslatable(@Nonnull String token, String inner) { - String[] args = token.split(SEPARATOR); - if (args.length < 2) { - throw new ParseException("Can't parse translatable (too few args) " + token); - } - if (inner == null) { - return TranslatableComponent.of(args[1]); - } else { - List inners = new ArrayList<>(); - String toSplit = token.replace(args[0] + ":" + args[1] + ":", ""); - String[] split = dumSplitPattern.split(cleanInner(toSplit)); - for (String someInner : split) { - inners.add(parseFormat(someInner)); - } - return TranslatableComponent.of(args[1], inners); - } - } - - @Nonnull - private static KeybindComponent handleKeybind(@Nonnull String token) { - String[] args = token.split(SEPARATOR); - if (args.length < 2) { - throw new ParseException("Can't parse keybind (too few args) " + token); - } - return KeybindComponent.of(args[1]); - } - - @Nonnull - private static ClickEvent handleClick(@Nonnull String token, @Nonnull String inner) { - String[] args = token.split(SEPARATOR); - if (args.length < 2) { - throw new ParseException("Can't parse click action (too few args) " + token); - } - ClickEvent.Action action = Optional.ofNullable(ClickEvent.Action.NAMES.value(args[1].toLowerCase(Locale.ROOT))) - .orElseThrow(() -> new ParseException("Can't parse click action (invalid action) " + token)); - return ClickEvent.of(action, token.replace(CLICK + SEPARATOR + args[1] + SEPARATOR, "")); - } - - @Nonnull - private static HoverEvent handleHover(@Nonnull String token, @Nonnull String inner) { - String[] args = token.split(SEPARATOR); - if (args.length < 2) { - throw new ParseException("Can't parse hover action (too few args) " + token); - } - inner = cleanInner(inner); - HoverEvent.Action action = Optional.ofNullable(HoverEvent.Action.NAMES.value(args[1].toLowerCase(Locale.ROOT))) - .orElseThrow(() -> new ParseException("Can't parse hover action (invalid action) " + token)); - return HoverEvent.of(action, parseFormat(inner)); - } - - @Nonnull - private static Optional resolveColor(@Nonnull String token) { - return Optional.ofNullable(NamedTextColor.NAMES.value(token.toLowerCase(Locale.ROOT))); - } - - @Nonnull - private static Optional resolveColorNew(@Nonnull String token) { - String[] args = token.split(SEPARATOR); - if (args.length < 2) { - throw new ParseException("Can't parse color (too few args) " + token); - } - - return parseColor(args[1]); - } - - @Nonnull - private static Optional parseColor(String color) { - if (color.charAt(0) == '#') { - return Optional.ofNullable(TextColor.fromHexString(color)); - } else { - return Optional.ofNullable(NamedTextColor.NAMES.value(color.toLowerCase(Locale.ROOT))); - } - } - - @Nonnull - private static Optional resolveDecoration(@Nonnull String token) { - try { - return Optional.of(HelperTextDecoration.valueOf(token.toUpperCase(Locale.ROOT))); - } catch (IllegalArgumentException ex) { - return Optional.empty(); - } - } - - private static String cleanInner(String inner) { - return inner.substring(1).substring(0, inner.length() - 2); // cut off first and last "/' - } - -} diff --git a/minimessage-text/src/main/java/me/minidigger/minimessage/text/MiniMessageSerializer.java b/minimessage-text/src/main/java/me/minidigger/minimessage/text/MiniMessageSerializer.java deleted file mode 100644 index b845dbde7..000000000 --- a/minimessage-text/src/main/java/me/minidigger/minimessage/text/MiniMessageSerializer.java +++ /dev/null @@ -1,221 +0,0 @@ -package me.minidigger.minimessage.text; - -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.format.NamedTextColor; -import net.kyori.adventure.text.KeybindComponent; -import net.kyori.adventure.text.TextComponent; -import net.kyori.adventure.text.TranslatableComponent; -import net.kyori.adventure.text.event.ClickEvent; -import net.kyori.adventure.text.event.HoverEvent; -import net.kyori.adventure.text.format.TextColor; -import net.kyori.adventure.text.format.TextDecoration; - -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -import static me.minidigger.minimessage.text.Constants.BOLD; -import static me.minidigger.minimessage.text.Constants.CLICK; -import static me.minidigger.minimessage.text.Constants.CLOSE_TAG; -import static me.minidigger.minimessage.text.Constants.COLOR; -import static me.minidigger.minimessage.text.Constants.HOVER; -import static me.minidigger.minimessage.text.Constants.INSERTION; -import static me.minidigger.minimessage.text.Constants.ITALIC; -import static me.minidigger.minimessage.text.Constants.OBFUSCATED; -import static me.minidigger.minimessage.text.Constants.SEPARATOR; -import static me.minidigger.minimessage.text.Constants.STRIKETHROUGH; -import static me.minidigger.minimessage.text.Constants.TAG_END; -import static me.minidigger.minimessage.text.Constants.TAG_START; -import static me.minidigger.minimessage.text.Constants.UNDERLINED; - -public final class MiniMessageSerializer { - - private MiniMessageSerializer() { - } - - @Nonnull - public static String serialize(@Nonnull Component component) { - StringBuilder sb = new StringBuilder(); - - List components = new ArrayList<>(); - components.add(component); - - for (int i = 0; i < components.size(); i++) { - Component comp = components.get(i); - - // add childs - components.addAll(comp.children()); - - // # start tags - - // ## get prev comp - Component prevComp = null; - if (i > 0) { - prevComp = components.get(i - 1); - } - - // ## color - // ### white is not important - if (!NamedTextColor.WHITE.equals(comp.color()) && comp.color() != null && (prevComp == null || prevComp.color() != comp.color())) { - sb.append(startColor(Objects.requireNonNull(comp.color()))); - } - - // ## decoration - // ### only start if prevComp didn't start - if (comp.hasDecoration(TextDecoration.BOLD) && (prevComp == null || !prevComp.hasDecoration(TextDecoration.BOLD))) { - sb.append(startTag(BOLD)); - } - if (comp.hasDecoration(TextDecoration.ITALIC) && (prevComp == null || !prevComp.hasDecoration(TextDecoration.ITALIC))) { - sb.append(startTag(ITALIC)); - } - if (comp.hasDecoration(TextDecoration.OBFUSCATED) && (prevComp == null || !prevComp.hasDecoration(TextDecoration.OBFUSCATED))) { - sb.append(startTag(OBFUSCATED)); - } - if (comp.hasDecoration(TextDecoration.STRIKETHROUGH) && (prevComp == null || !prevComp.hasDecoration(TextDecoration.STRIKETHROUGH))) { - sb.append(startTag(STRIKETHROUGH)); - } - if (comp.hasDecoration(TextDecoration.UNDERLINED) && (prevComp == null || !prevComp.hasDecoration(TextDecoration.UNDERLINED))) { - sb.append(startTag(UNDERLINED)); - } - - // ## hover - // ### only start if prevComp didn't start the same one - HoverEvent hov = comp.hoverEvent(); - if (hov != null && (prevComp == null || areDifferent(hov, prevComp.hoverEvent()))) { - // TODO make sure the value cast is right - sb.append(startTag(String.format("%s" + SEPARATOR + "%s" + SEPARATOR + "\"%s\"", HOVER, HoverEvent.Action.NAMES.key(hov.action()), serialize((Component) hov.value())))); - } - - // ## click - // ### only start if prevComp didn't start the same one - ClickEvent click = comp.clickEvent(); - if (click != null && (prevComp == null || areDifferent(click, prevComp.clickEvent()))) { - sb.append(startTag(String.format("%s" + SEPARATOR + "%s" + SEPARATOR + "\"%s\"", CLICK, ClickEvent.Action.NAMES.key(click.action()), click.value()))); - } - - // ## insertion - // ### only start if prevComp didn't start the same one - String insert = comp.insertion(); - if (insert != null && (prevComp == null || !insert.equals(prevComp.insertion()))) { - sb.append(startTag(INSERTION + SEPARATOR + insert)); - } - - // # append text - if (comp instanceof TextComponent) { - sb.append(((TextComponent) comp).content()); - } else { - handleDifferentComponent(comp, sb); - } - - // # end tags - - // ## get next comp - Component nextComp = null; - if (i + 1 < components.size()) { - nextComp = components.get(i + 1); - } - - // ## color - // ### only end color if next comp is white and current isn't - if (nextComp != null && comp.color() != NamedTextColor.WHITE && comp.color() != null) { - if (nextComp.color() == NamedTextColor.WHITE || nextComp.color() == null) { - sb.append(endColor(Objects.requireNonNull(comp.color()))); - } - } - - // ## decoration - // ### only end decoration if next tag is different - if (nextComp != null) { - if (comp.hasDecoration(TextDecoration.BOLD) && !nextComp.hasDecoration(TextDecoration.BOLD)) { - sb.append(endTag(BOLD)); - } - if (comp.hasDecoration(TextDecoration.ITALIC) && !nextComp.hasDecoration(TextDecoration.ITALIC)) { - sb.append(endTag(ITALIC)); - } - if (comp.hasDecoration(TextDecoration.OBFUSCATED) && !nextComp.hasDecoration(TextDecoration.OBFUSCATED)) { - sb.append(endTag(OBFUSCATED)); - } - if (comp.hasDecoration(TextDecoration.STRIKETHROUGH) && !nextComp.hasDecoration(TextDecoration.STRIKETHROUGH)) { - sb.append(endTag(STRIKETHROUGH)); - } - if (comp.hasDecoration(TextDecoration.UNDERLINED) && !nextComp.hasDecoration(TextDecoration.UNDERLINED)) { - sb.append(endTag(UNDERLINED)); - } - } - - // ## hover - // ### only end hover if next tag is different - if (nextComp != null && comp.hoverEvent() != null) { - if (areDifferent(Objects.requireNonNull(comp.hoverEvent()), nextComp.hoverEvent())) { - sb.append(endTag(HOVER)); - } - } - - // ## click - // ### only end click if next tag is different - if (nextComp != null && comp.clickEvent() != null) { - if (areDifferent(Objects.requireNonNull(comp.clickEvent()), nextComp.clickEvent())) { - sb.append(endTag(CLICK)); - } - } - - // ## insertion - // ### only end insertion if next tag is different - if (nextComp != null && comp.insertion() != null) { - if (!Objects.equals(comp.insertion(), nextComp.insertion())) { - sb.append(endTag(INSERTION)); - } - } - } - - return sb.toString(); - } - - private static boolean areDifferent(@Nonnull ClickEvent c1, @Nullable ClickEvent c2) { - if (c2 == null) return true; - return !c1.equals(c2) && (!c1.action().equals(c2.action()) || !c1.value().equals(c2.value())); - } - - private static boolean areDifferent(@Nonnull HoverEvent h1, @Nullable HoverEvent h2) { - if (h2 == null) return true; - return !h1.equals(h2) && (!h1.action().equals(h2.action()));// TODO also compare value - } - - @Nonnull - private static String startColor(@Nonnull TextColor color) { - if (color instanceof NamedTextColor) { - return startTag(Objects.requireNonNull(NamedTextColor.NAMES.key((NamedTextColor) color))); - } else { - return startTag(COLOR + SEPARATOR + color.asHexString()); - } - } - - @Nonnull - private static String endColor(@Nonnull TextColor color) { - if (color instanceof NamedTextColor) { - return endTag(Objects.requireNonNull(NamedTextColor.NAMES.key((NamedTextColor) color))); - } else { - return endTag(COLOR + SEPARATOR + color.asHexString()); - } - } - - @Nonnull - private static String startTag(@Nonnull String content) { - return TAG_START + content + TAG_END; - } - - @Nonnull - private static String endTag(@Nonnull String content) { - return TAG_START + CLOSE_TAG + content + TAG_END; - } - - private static void handleDifferentComponent(@Nonnull Component component, @Nonnull StringBuilder sb) { - if (component instanceof KeybindComponent) { - sb.append(startTag("key" + SEPARATOR + ((KeybindComponent) component).keybind())); - } else if (component instanceof TranslatableComponent) { - sb.append(startTag("lang" + SEPARATOR + ((TranslatableComponent) component).key())); - } - } -} diff --git a/minimessage-text/src/main/java/me/minidigger/minimessage/text/ParseException.java b/minimessage-text/src/main/java/me/minidigger/minimessage/text/ParseException.java deleted file mode 100644 index 594423a6b..000000000 --- a/minimessage-text/src/main/java/me/minidigger/minimessage/text/ParseException.java +++ /dev/null @@ -1,9 +0,0 @@ -package me.minidigger.minimessage.text; - -import javax.annotation.Nonnull; - -public class ParseException extends RuntimeException { - public ParseException(@Nonnull String message) { - super(message); - } -} diff --git a/minimessage-text/src/main/java/me/minidigger/minimessage/text/fancy/Fancy.java b/minimessage-text/src/main/java/me/minidigger/minimessage/text/fancy/Fancy.java deleted file mode 100644 index 3332e7b87..000000000 --- a/minimessage-text/src/main/java/me/minidigger/minimessage/text/fancy/Fancy.java +++ /dev/null @@ -1,10 +0,0 @@ -package me.minidigger.minimessage.text.fancy; - -import net.kyori.adventure.text.Component; - -import java.util.function.Function; - -public interface Fancy extends Function { - - void init(int size); -} diff --git a/minimessage-text/src/main/java/me/minidigger/minimessage/text/fancy/Gradient.java b/minimessage-text/src/main/java/me/minidigger/minimessage/text/fancy/Gradient.java deleted file mode 100644 index 251a48ace..000000000 --- a/minimessage-text/src/main/java/me/minidigger/minimessage/text/fancy/Gradient.java +++ /dev/null @@ -1,46 +0,0 @@ -package me.minidigger.minimessage.text.fancy; - -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.format.TextColor; - -public class Gradient implements Fancy { - - private int colorIndex = 0; - - private float factorStep = 0; - private final TextColor color1; - private final TextColor color2; - - public Gradient() { - this(TextColor.fromHexString("#ffffff"), TextColor.fromHexString("#000000")); - } - - public Gradient(TextColor c1, TextColor c2) { - this.color1 = c1; - this.color2 = c2; - } - - @Override - public void init(int size) { - this.factorStep = (float) (1. / (size + this.colorIndex - 1)); - this.colorIndex = 0; - } - - @Override - public Component apply(Component current) { - return current.color(getColor()); - } - - private TextColor getColor() { - float factor = factorStep * colorIndex++; - return interpolate(color1, color2, factor); - } - - private TextColor interpolate(TextColor color1, TextColor color2, float factor) { - return TextColor.of( - Math.round(color1.red() + factor * (color2.red() - color1.red())), - Math.round(color1.green() + factor * (color2.green() - color1.green())), - Math.round(color1.blue() + factor * (color2.blue() - color1.blue())) - ); - } -} diff --git a/minimessage-text/src/main/java/me/minidigger/minimessage/text/fancy/Rainbow.java b/minimessage-text/src/main/java/me/minidigger/minimessage/text/fancy/Rainbow.java deleted file mode 100644 index db10458f2..000000000 --- a/minimessage-text/src/main/java/me/minidigger/minimessage/text/fancy/Rainbow.java +++ /dev/null @@ -1,43 +0,0 @@ -package me.minidigger.minimessage.text.fancy; - -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.format.TextColor; - -public class Rainbow implements Fancy { - - private int colorIndex = 0; - - private float center = 128; - private float width = 127; - private double frequency = 1; - - private final int phase; - - public Rainbow() { - this(0); - } - - public Rainbow(int phase) { - this.phase = phase; - } - - @Override - public void init(int size) { - center = 128; - width = 127; - frequency = Math.PI * 2 / size; - } - - @Override - public Component apply(Component current) { - return current.color(getColor(phase)); - } - - private TextColor getColor(float phase) { - int index = colorIndex++; - int red = (int) (Math.sin(frequency * index + 2 + phase) * width + center); - int green = (int) (Math.sin(frequency * index + 0 + phase) * width + center); - int blue = (int) (Math.sin(frequency * index + 4 + phase) * width + center); - return TextColor.of(red, green, blue); - } -} diff --git a/minimessage-text/src/test/java/me/minidigger/minimessage/text/MiniMessageParserTest.java b/minimessage-text/src/test/java/me/minidigger/minimessage/text/MiniMessageParserTest.java deleted file mode 100644 index d57fcd60f..000000000 --- a/minimessage-text/src/test/java/me/minidigger/minimessage/text/MiniMessageParserTest.java +++ /dev/null @@ -1,352 +0,0 @@ -package me.minidigger.minimessage.text; - -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; - -import org.junit.Ignore; -import org.junit.Test; - -import javax.annotation.Nonnull; - -import static org.junit.Assert.assertEquals; - -public class MiniMessageParserTest { - - @Test - public void test() { - String input1 = "TEST nestedTest"; - String input2 = "TEST nestedTest"; - String out1 = GsonComponentSerializer.gson().serialize(MiniMessageParser.parseFormat(input1)); - String out2 = GsonComponentSerializer.gson().serialize(MiniMessageParser.parseFormat(input2)); - System.out.println(out1); - System.out.println(out2); - assertEquals(out1, out2); - } - - @Test - public void testNewColor() { - String input1 = "TEST nestedTest"; - String input2 = "TEST nestedTest"; - String out1 = GsonComponentSerializer.gson().serialize(MiniMessageParser.parseFormat(input1)); - String out2 = GsonComponentSerializer.gson().serialize(MiniMessageParser.parseFormat(input2)); - - assertEquals(out1, out2); - } - - @Test - public void testHexColor() { - String input1 = "TEST nestedTest"; - String input2 = "TEST nestedTest"; - String out1 = GsonComponentSerializer.gson().serialize(MiniMessageParser.parseFormat(input1)); - String out2 = GsonComponentSerializer.gson().serialize(MiniMessageParser.parseFormat(input2)); - - assertEquals(out1, out2); - } - - @Test - public void testStripSimple() { - String input = "TEST nestedTest"; - String expected = "TEST nestedTest"; - assertEquals(expected, MiniMessageParser.stripTokens(input)); - } - - @Test - public void testStripComplex() { - String input = " random strangerclick here to FEEL it"; - String expected = " random strangerclick here to FEEL it"; - assertEquals(expected, MiniMessageParser.stripTokens(input)); - } - - @Test - public void testStripInner() { - String input = "test:TEST\">TEST"; - String expected = "TEST"; - assertEquals(expected, MiniMessageParser.stripTokens(input)); - } - - @Test - public void testEscapeSimple() { - String input = "TEST nestedTest"; - String expected = "\\TEST\\ nested\\Test"; - assertEquals(expected, MiniMessageParser.escapeTokens(input)); - } - - @Test - public void testEscapeComplex() { - String input = " random strangerclick here to FEEL it"; - String expected = "\\\\ random \\stranger\\\\\\\\click here\\\\ to \\FEEL\\ it"; - assertEquals(expected, MiniMessageParser.escapeTokens(input)); - } - - @Test - public void testEscapeInner() { - String input = "test:TEST\">TEST"; - String expected = "\\test:TEST\"\\>TEST"; - assertEquals(expected, MiniMessageParser.escapeTokens(input)); - } - - - @Test - public void checkPlaceholder() { - String input = ""; - String expected = "{\"text\":\"Hello!\"}"; - Component comp = MiniMessageParser.parseFormat(input, "test", "Hello!"); - - test(comp, expected); - } - - @Test - public void testNiceMix() { - String input = " random strangerclick here to FEEL it"; - String expected = "{\"text\":\"\",\"extra\":[{\"text\":\"Hello! random \",\"color\":\"yellow\"},{\"text\":\"stranger\",\"color\":\"yellow\",\"bold\":true},{\"text\":\"click here\",\"color\":\"red\",\"underlined\":true,\"clickEvent\":{\"action\":\"run_command\",\"value\":\"test command\"}},{\"text\":\" to \",\"color\":\"blue\",\"underlined\":true},{\"text\":\"FEEL\",\"color\":\"blue\",\"bold\":true,\"underlined\":true},{\"text\":\" it\",\"color\":\"blue\",\"bold\":true}]}"; - Component comp = MiniMessageParser.parseFormat(input, "test", "Hello!"); - - test(comp, expected); - } - - @Test - public void testColorSimple() { - String input = "TEST"; - String expected = "{\"text\":\"TEST\",\"color\":\"yellow\"}"; - - test(input, expected); - } - - @Test - public void testColorNested() { - String input = "TESTnestedTest"; - String expected = "{\"text\":\"\",\"extra\":[{\"text\":\"TEST\",\"color\":\"yellow\"},{\"text\":\"nested\",\"color\":\"green\"},{\"text\":\"Test\",\"color\":\"yellow\"}]}"; - - test(input, expected); - } - - @Test - public void testColorNotNested() { - String input = "TESTnestedTest"; - String expected = "{\"text\":\"\",\"extra\":[{\"text\":\"TEST\",\"color\":\"yellow\"},{\"text\":\"nested\",\"color\":\"green\"},{\"text\":\"Test\"}]}"; - - test(input, expected); - } - - @Test - public void testHover() { - String input = "test\">TEST"; - String expected = "{\"text\":\"TEST\",\"hoverEvent\":{\"action\":\"show_text\",\"contents\":{\"text\":\"test\",\"color\":\"red\"}}}"; - - test(input, expected); - } - - @Test - public void testHover2() { - String input = "test'>TEST"; - String expected = "{\"text\":\"TEST\",\"hoverEvent\":{\"action\":\"show_text\",\"contents\":{\"text\":\"test\",\"color\":\"red\"}}}"; - - test(input, expected); - } - - @Test - public void testHoverWithColon() { - String input = "test:TEST\">TEST"; - String expected = "{\"text\":\"TEST\",\"hoverEvent\":{\"action\":\"show_text\",\"contents\":{\"text\":\"test:TEST\",\"color\":\"red\"}}}"; - - test(input, expected); - } - - @Test - public void testClick() { - String input = "TEST"; - String expected = "{\"text\":\"TEST\",\"clickEvent\":{\"action\":\"run_command\",\"value\":\"test\"}}"; - - test(input, expected); - } - - @Test - public void testClickExtendedCommand() { - String input = "TEST"; - String expected = "{\"text\":\"TEST\",\"clickEvent\":{\"action\":\"run_command\",\"value\":\"/test command\"}}"; - - test(input, expected); - } - - @Test - public void testInvalidTag() { - String input = ""; - String expected = "{\"text\":\"\\u003ctest\\u003e\"}"; // gson makes it html save - Component comp = MiniMessageParser.parseFormat(input); - - test(comp, expected); - - // TODO am not totally happy about this yet, invalid tags arent getting colored for example, but good enough for now - } - - @Test - public void testInvalidTagComplex() { - String input = " random strangerclick here to FEEL it"; - String expected = "{\"text\":\"\",\"extra\":[{\"text\":\"\\u003ctest\\u003e\",\"color\":\"yellow\"},{\"text\":\" random \",\"color\":\"yellow\"},{\"text\":\"stranger\",\"color\":\"yellow\",\"bold\":true},{\"text\":\"\\u003coof\\u003e\",\"color\":\"yellow\",\"clickEvent\":{\"action\":\"run_command\",\"value\":\"test command\"}},{\"text\":\"\\u003c/oof\\u003e\",\"color\":\"yellow\",\"clickEvent\":{\"action\":\"run_command\",\"value\":\"test command\"}},{\"text\":\"click here\",\"color\":\"red\",\"underlined\":true,\"clickEvent\":{\"action\":\"run_command\",\"value\":\"test command\"}},{\"text\":\" to \",\"color\":\"blue\",\"underlined\":true},{\"text\":\"FEEL\",\"color\":\"blue\",\"bold\":true,\"underlined\":true},{\"text\":\" it\",\"color\":\"blue\",\"bold\":true}]}"; - Component comp = MiniMessageParser.parseFormat(input); - - test(comp, expected); - } - - @Test - public void testKeyBind() { - String input = "Press to jump!"; - String expected = "{\"text\":\"\",\"extra\":[{\"text\":\"Press \"},{\"keybind\":\"key.jump\"},{\"text\":\" to jump!\"}]}"; - Component comp = MiniMessageParser.parseFormat(input); - - test(comp, expected); - } - - @Test - public void testKeyBindWithColor() { - String input = "Press to jump!"; - String expected = "{\"text\":\"\",\"extra\":[{\"text\":\"Press \"},{\"keybind\":\"key.jump\",\"color\":\"red\"},{\"text\":\" to jump!\",\"color\":\"red\"}]}"; - Component comp = MiniMessageParser.parseFormat(input); - - test(comp, expected); - } - - @Test - public void testTranslatable() { - String input = "You should get a !"; - String expected = "{\"text\":\"\",\"extra\":[{\"text\":\"You should get a \"},{\"translate\":\"block.minecraft.diamond_block\"},{\"text\":\"!\"}]}"; - Component comp = MiniMessageParser.parseFormat(input); - - test(comp, expected); - } - - @Test - public void testTranslatableWith() { - String input = "Test: 1':'Stone'>!"; - String expected = "{\"text\":\"\",\"extra\":[{\"text\":\"Test: \"},{\"translate\":\"commands.drop.success.single\",\"with\":[{\"text\":\"1\",\"color\":\"red\"},{\"text\":\"Stone\",\"color\":\"blue\"}]},{\"text\":\"!\"}]}"; - Component comp = MiniMessageParser.parseFormat(input); - - test(comp, expected); - } - - @Test - @Ignore // TODO implement inner with ' or " - public void testTranslatableWithHover() { - String input = "Test: 1dum'>':'Stone'>!"; - String expected = "{\"text\":\"\",\"extra\":[{\"text\":\"Test: \"},{\"translate\":\"commands.drop.success.single\",\"with\":[{\"text\":\"1\",\"color\":\"red\"},{\"text\":\"Stone\",\"color\":\"blue\"}]},{\"text\":\"!\"}]}"; - Component comp = MiniMessageParser.parseFormat(input); - - test(comp, expected); - } - - @Test - public void testKingAlter() { - String input = "Ahoy mates!'>"; - String expected = "{\"text\":\"\",\"extra\":[{\"text\":\"Ahoy \"},{\"translate\":\"offset.-40\",\"with\":[{\"text\":\"mates!\",\"color\":\"red\"}]}]}"; - Component comp = MiniMessageParser.parseFormat(input); - - test(comp, expected); - } - - @Test - public void testInsertion() { - String input = "Click this to insert!"; - String expected = "{\"text\":\"\",\"extra\":[{\"text\":\"Click \"},{\"text\":\"this\",\"insertion\":\"test\"},{\"text\":\" to insert!\"}]}"; - Component comp = MiniMessageParser.parseFormat(input); - - test(comp, expected); - } - - @Test - public void testGH5() { - String input = "» To download it from the internet, >/!\\ install it from Options/ResourcePacks in your game\">CLICK HERE"; - String expected = "{\"text\":\"\",\"extra\":[{\"text\":\"»\",\"color\":\"dark_gray\"},{\"text\":\" To download it from the internet, \",\"color\":\"gray\"},{\"text\":\"CLICK HERE\",\"color\":\"green\",\"bold\":true,\"clickEvent\":{\"action\":\"open_url\",\"value\":\"https://www.google.com\"},\"hoverEvent\":{\"action\":\"show_text\",\"contents\":{\"text\":\"/!\\\\ install it from Options/ResourcePacks in your game\",\"color\":\"green\"}}}]}"; - - // should work - Component comp1 = MiniMessageParser.parseFormat(input, "pack_url", "https://www.google.com"); - test(comp1, expected); - - // shouldnt throw an error - MiniMessageParser.parseFormat(input, "url", "https://www.google.com"); - } - - @Test - @Ignore // TODO implement inner with ' or " - public void testGH5Modified() { - String input = "» To download it from the internet, >/!\\ install it from 'Options/ResourcePacks' in your game\">CLICK HERE"; - String expected = "{\"text\":\"\",\"extra\":[{\"text\":\"»\",\"color\":\"dark_gray\"},{\"text\":\" To download it from the internet, \",\"color\":\"gray\"},{\"text\":\"CLICK HERE\",\"color\":\"green\",\"bold\":true,\"clickEvent\":{\"action\":\"open_url\",\"value\":\"https://www.google.com\"},\"hoverEvent\":{\"action\":\"show_text\",\"value\":{\"text\":\"/!\\\\ install it from 'Options/ResourcePacks' in your game\",\"color\":\"green\"}}}]}"; - - // should work - Component comp1 = MiniMessageParser.parseFormat(input, "pack_url", "https://www.google.com"); - test(comp1, expected); - - // shouldnt throw an error - MiniMessageParser.parseFormat(input, "url", "https://www.google.com"); - } - - @Test - public void testReset() { - String input = "Click this to insert!"; - String expected = "{\"text\":\"\",\"extra\":[{\"text\":\"Click \"},{\"text\":\"this\",\"color\":\"yellow\",\"insertion\":\"test\"},{\"text\":\" to insert!\"}]}"; - Component comp = MiniMessageParser.parseFormat(input); - - test(comp, expected); - } - - @Test - public void testPre() { - String input = "Click
this
to insert!"; - String expected = "{\"text\":\"\",\"extra\":[{\"text\":\"Click \"},{\"text\":\"\\u003cinsert:test\\u003e\",\"color\":\"yellow\"},{\"text\":\"this\",\"color\":\"yellow\"},{\"text\":\" to \",\"color\":\"yellow\"},{\"text\":\"insert!\",\"color\":\"red\"}]}"; - Component comp = MiniMessageParser.parseFormat(input); - - test(comp, expected); - } - - @Test - public void testRainbow() { - String input = "Woo: ||||||||||||||||||||||||!"; - String expected = "{\"text\":\"\",\"extra\":[{\"text\":\"Woo: \",\"color\":\"yellow\"},{\"text\":\"|\",\"extra\":[{\"text\":\"|\",\"color\":\"#e1a00d\"},{\"text\":\"|\",\"color\":\"#c9bf03\"},{\"text\":\"|\",\"color\":\"#acd901\"},{\"text\":\"|\",\"color\":\"#8bed08\"},{\"text\":\"|\",\"color\":\"#6afa16\"},{\"text\":\"|\",\"color\":\"#4bff2c\"},{\"text\":\"|\",\"color\":\"#2ffa48\"},{\"text\":\"|\",\"color\":\"#18ed68\"},{\"text\":\"|\",\"color\":\"#08d989\"},{\"text\":\"|\",\"color\":\"#01bfa9\"},{\"text\":\"|\",\"color\":\"#02a0c7\"},{\"text\":\"|\",\"color\":\"#0c80e0\"},{\"text\":\"|\",\"color\":\"#1e5ff2\"},{\"text\":\"|\",\"color\":\"#3640fc\"},{\"text\":\"|\",\"color\":\"#5326fe\"},{\"text\":\"|\",\"color\":\"#7412f7\"},{\"text\":\"|\",\"color\":\"#9505e9\"},{\"text\":\"|\",\"color\":\"#b401d3\"},{\"text\":\"|\",\"color\":\"#d005b7\"},{\"text\":\"|\",\"color\":\"#e71297\"},{\"text\":\"|\",\"color\":\"#f72676\"},{\"text\":\"|\",\"color\":\"#fe4056\"},{\"text\":\"|\",\"color\":\"#fd5f38\"}],\"color\":\"#f3801f\"},{\"text\":\"!\",\"color\":\"yellow\"}]}"; - Component comp = MiniMessageParser.parseFormat(input); - - test(comp, expected); - } - - @Test - public void testRainbowPhase() { - String input = "Woo: ||||||||||||||||||||||||!"; - String expected = "{\"text\":\"\",\"extra\":[{\"text\":\"Woo: \",\"color\":\"yellow\"},{\"text\":\"|\",\"extra\":[{\"text\":\"|\",\"color\":\"#0de17d\"},{\"text\":\"|\",\"color\":\"#03c99e\"},{\"text\":\"|\",\"color\":\"#01acbd\"},{\"text\":\"|\",\"color\":\"#088bd7\"},{\"text\":\"|\",\"color\":\"#166aec\"},{\"text\":\"|\",\"color\":\"#2c4bf9\"},{\"text\":\"|\",\"color\":\"#482ffe\"},{\"text\":\"|\",\"color\":\"#6818fb\"},{\"text\":\"|\",\"color\":\"#8908ef\"},{\"text\":\"|\",\"color\":\"#a901db\"},{\"text\":\"|\",\"color\":\"#c702c1\"},{\"text\":\"|\",\"color\":\"#e00ca3\"},{\"text\":\"|\",\"color\":\"#f21e82\"},{\"text\":\"|\",\"color\":\"#fc3661\"},{\"text\":\"|\",\"color\":\"#fe5342\"},{\"text\":\"|\",\"color\":\"#f77428\"},{\"text\":\"|\",\"color\":\"#e99513\"},{\"text\":\"|\",\"color\":\"#d3b406\"},{\"text\":\"|\",\"color\":\"#b7d001\"},{\"text\":\"|\",\"color\":\"#97e704\"},{\"text\":\"|\",\"color\":\"#76f710\"},{\"text\":\"|\",\"color\":\"#56fe24\"},{\"text\":\"|\",\"color\":\"#38fd3e\"}],\"color\":\"#1ff35c\"},{\"text\":\"!\",\"color\":\"yellow\"}]}"; - Component comp = MiniMessageParser.parseFormat(input); - - test(comp, expected); - } - - @Test - public void testGradient() { - String input = "Woo: ||||||||||||||||||||||||!"; - String expected = "{\"text\":\"\",\"extra\":[{\"text\":\"Woo: \",\"color\":\"yellow\"},{\"text\":\"|\",\"extra\":[{\"text\":\"|\",\"color\":\"#f4f4f4\"},{\"text\":\"|\",\"color\":\"#e9e9e9\"},{\"text\":\"|\",\"color\":\"#dedede\"},{\"text\":\"|\",\"color\":\"#d3d3d3\"},{\"text\":\"|\",\"color\":\"#c8c8c8\"},{\"text\":\"|\",\"color\":\"#bcbcbc\"},{\"text\":\"|\",\"color\":\"#b1b1b1\"},{\"text\":\"|\",\"color\":\"#a6a6a6\"},{\"text\":\"|\",\"color\":\"#9b9b9b\"},{\"text\":\"|\",\"color\":\"#909090\"},{\"text\":\"|\",\"color\":\"#858585\"},{\"text\":\"|\",\"color\":\"#7a7a7a\"},{\"text\":\"|\",\"color\":\"#6f6f6f\"},{\"text\":\"|\",\"color\":\"#646464\"},{\"text\":\"|\",\"color\":\"#595959\"},{\"text\":\"|\",\"color\":\"#4e4e4e\"},{\"text\":\"|\",\"color\":\"#434343\"},{\"text\":\"|\",\"color\":\"#373737\"},{\"text\":\"|\",\"color\":\"#2c2c2c\"},{\"text\":\"|\",\"color\":\"#212121\"},{\"text\":\"|\",\"color\":\"#161616\"},{\"text\":\"|\",\"color\":\"#0b0b0b\"},{\"text\":\"|\",\"color\":\"black\"}],\"color\":\"white\"},{\"text\":\"!\",\"color\":\"yellow\"}]}"; - Component comp = MiniMessageParser.parseFormat(input); - - test(comp, expected); - } - - @Test - public void testGradient2() { - String input = "Woo: ||||||||||||||||||||||||!"; - String expected = "{\"text\":\"\",\"extra\":[{\"text\":\"Woo: \",\"color\":\"yellow\"},{\"text\":\"|\",\"extra\":[{\"text\":\"|\",\"color\":\"#65529f\"},{\"text\":\"|\",\"color\":\"#6b559c\"},{\"text\":\"|\",\"color\":\"#725898\"},{\"text\":\"|\",\"color\":\"#795b95\"},{\"text\":\"|\",\"color\":\"#7f5e92\"},{\"text\":\"|\",\"color\":\"#86618f\"},{\"text\":\"|\",\"color\":\"#8d648c\"},{\"text\":\"|\",\"color\":\"#936789\"},{\"text\":\"|\",\"color\":\"#9a6a85\"},{\"text\":\"|\",\"color\":\"#a16d82\"},{\"text\":\"|\",\"color\":\"#a7707f\"},{\"text\":\"|\",\"color\":\"#ae737c\"},{\"text\":\"|\",\"color\":\"#b47679\"},{\"text\":\"|\",\"color\":\"#bb7976\"},{\"text\":\"|\",\"color\":\"#c27c72\"},{\"text\":\"|\",\"color\":\"#c87f6f\"},{\"text\":\"|\",\"color\":\"#cf826c\"},{\"text\":\"|\",\"color\":\"#d68569\"},{\"text\":\"|\",\"color\":\"#dc8866\"},{\"text\":\"|\",\"color\":\"#e38b63\"},{\"text\":\"|\",\"color\":\"#ea8e5f\"},{\"text\":\"|\",\"color\":\"#f0915c\"},{\"text\":\"|\",\"color\":\"#f79459\"}],\"color\":\"#5e4fa2\"},{\"text\":\"!\",\"color\":\"yellow\"}]}"; - Component comp = MiniMessageParser.parseFormat(input); - - test(comp, expected); - } - - @Test - public void testGradient3() { - String input = "Woo: ||||||||||||||||||||||||!"; - String expected = "{\"text\":\"\",\"extra\":[{\"text\":\"Woo: \",\"color\":\"yellow\"},{\"text\":\"|\",\"extra\":[{\"text\":\"|\",\"color\":\"#55f85c\"},{\"text\":\"|\",\"color\":\"#55f064\"},{\"text\":\"|\",\"color\":\"#55e96b\"},{\"text\":\"|\",\"color\":\"#55e173\"},{\"text\":\"|\",\"color\":\"#55da7a\"},{\"text\":\"|\",\"color\":\"#55d381\"},{\"text\":\"|\",\"color\":\"#55cb89\"},{\"text\":\"|\",\"color\":\"#55c490\"},{\"text\":\"|\",\"color\":\"#55bc98\"},{\"text\":\"|\",\"color\":\"#55b59f\"},{\"text\":\"|\",\"color\":\"#55aea6\"},{\"text\":\"|\",\"color\":\"#55a6ae\"},{\"text\":\"|\",\"color\":\"#559fb5\"},{\"text\":\"|\",\"color\":\"#5598bc\"},{\"text\":\"|\",\"color\":\"#5590c4\"},{\"text\":\"|\",\"color\":\"#5589cb\"},{\"text\":\"|\",\"color\":\"#5581d3\"},{\"text\":\"|\",\"color\":\"#557ada\"},{\"text\":\"|\",\"color\":\"#5573e1\"},{\"text\":\"|\",\"color\":\"#556be9\"},{\"text\":\"|\",\"color\":\"#5564f0\"},{\"text\":\"|\",\"color\":\"#555cf8\"},{\"text\":\"|\",\"color\":\"blue\"}],\"color\":\"green\"},{\"text\":\"!\",\"color\":\"yellow\"}]}"; - Component comp = MiniMessageParser.parseFormat(input); - - test(comp, expected); - } - - private void test(@Nonnull String input, @Nonnull String expected) { - test(MiniMessageParser.parseFormat(input), expected); - } - - private void test(@Nonnull Component comp, @Nonnull String expected) { - assertEquals(expected, GsonComponentSerializer.gson().serialize(comp)); - } -} diff --git a/minimessage-text/src/test/java/me/minidigger/minimessage/text/MiniMessageSerializerTest.java b/minimessage-text/src/test/java/me/minidigger/minimessage/text/MiniMessageSerializerTest.java deleted file mode 100644 index 1035b262e..000000000 --- a/minimessage-text/src/test/java/me/minidigger/minimessage/text/MiniMessageSerializerTest.java +++ /dev/null @@ -1,188 +0,0 @@ -package me.minidigger.minimessage.text; - -import net.kyori.adventure.text.KeybindComponent; -import net.kyori.adventure.text.TextComponent; -import net.kyori.adventure.text.TextComponent.Builder; -import net.kyori.adventure.text.TranslatableComponent; -import net.kyori.adventure.text.event.ClickEvent; -import net.kyori.adventure.text.event.HoverEvent; -import net.kyori.adventure.text.format.NamedTextColor; -import net.kyori.adventure.text.format.TextColor; -import net.kyori.adventure.text.format.TextDecoration; - -import org.junit.Test; - -import javax.annotation.Nonnull; - -import static org.junit.Assert.assertEquals; - -public class MiniMessageSerializerTest { - - @Test - public void testColor() { - String expected = "This is a test"; - - Builder builder = TextComponent.builder().content("This is a test").color(NamedTextColor.RED); - - test(builder, expected); - } - - @Test - public void testColorClosing() { - String expected = "This is a test"; - - Builder builder = TextComponent.builder() - .content("This is a ").color(NamedTextColor.RED) - .append("test"); - - test(builder, expected); - } - - @Test - public void testNestedColor() { - String expected = "This is a blue test"; - - Builder builder = TextComponent.builder() - .content("This is a ").color(NamedTextColor.RED) - .append("blue ", NamedTextColor.BLUE) - .append("test", NamedTextColor.RED); - - test(builder, expected); - } - - @Test - public void testDecoration() { - String expected = "This is underlined, this
isn't"; - - Builder builder = TextComponent.builder() - .content("This is ").decoration(TextDecoration.UNDERLINED, true) - .append("underlined", b -> b.decoration(TextDecoration.UNDERLINED, true).decoration(TextDecoration.BOLD, true)) - .append(", this", b -> b.decoration(TextDecoration.BOLD, true)) - .append(" isn't"); - - test(builder, expected); - } - - @Test - public void testHover() { - String expected = "Some hover that ends here"; - - Builder builder = TextComponent.builder() - .content("Some hover").hoverEvent(HoverEvent.showText(TextComponent.of("---"))) - .append(" that ends here"); - - test(builder, expected); - } - - @Test - public void testHoverWithNested() { - String expected = "----\">Some hover that ends here"; - - Builder builder = TextComponent.builder() - .content("Some hover").hoverEvent(HoverEvent.showText(TextComponent.builder() - .content("---").color(NamedTextColor.RED) - .append("-", NamedTextColor.BLUE, TextDecoration.BOLD) - .build())) - .append(" that ends here"); - - test(builder, expected); - } - - @Test - public void testClick() { - String expected = "Some click that ends here"; - - Builder builder = TextComponent.builder() - .content("Some click").clickEvent(ClickEvent.runCommand("test")) - .append(" that ends here"); - - test(builder, expected); - } - - @Test - public void testContinuedClick() { - String expected = "Some click that doesn't end here"; - - Builder builder = TextComponent.builder() - .content("Some click").clickEvent(ClickEvent.runCommand("test")) - // TODO figure out how to avoid repeating the click event here - .append(" that doesn't end here", b -> b.color(NamedTextColor.RED).clickEvent(ClickEvent.runCommand("test"))); - - test(builder, expected); - } - - @Test - public void testContinuedClick2() { - String expected = "Some click that doesn't end here"; - - Builder builder = TextComponent.builder() - .content("Some click").clickEvent(ClickEvent.runCommand("test")) - .append(" that doesn't end here", b -> b.color(NamedTextColor.RED).clickEvent(ClickEvent.runCommand("test"))); - - test(builder, expected); - } - - @Test - public void testKeyBind() { - String expected = "Press to jump!"; - - Builder builder = TextComponent.builder() - .content("Press ") - .append(KeybindComponent.of("key.jump")) - .append(" to jump!"); - - test(builder, expected); - } - - @Test - public void testKeyBindWithColor() { - String expected = "Press to jump!"; - - Builder builder = TextComponent.builder() - .content("Press ") - .append(KeybindComponent.of("key.jump").color(NamedTextColor.RED)) - .append(" to jump!", NamedTextColor.RED); - - test(builder, expected); - } - - @Test - public void testTranslatable() { - String expected = "You should get a !"; - - Builder builder = TextComponent.builder() - .content("You should get a ") - .append(TranslatableComponent.of("block.minecraft.diamond_block")) - .append("!"); - - test(builder, expected); - } - - @Test - public void testInsertion() { - String expected = "Click this to insert!"; - - Builder builder = TextComponent.builder() - .content("Click ") - .append("this", b->b.insertion("test")) - .append(" to insert!"); - - test(builder, expected); - } - - @Test - public void testHexColor() { - String expected = "This is a test"; - - Builder builder = TextComponent.builder() - .content("This is a ").color(TextColor.fromHexString("#ff0000")) - .append("test"); - - test(builder, expected); - } - - private void test(@Nonnull Builder builder, @Nonnull String expected) { - String string = MiniMessageSerializer.serialize(builder.build()); - assertEquals(expected, string); - } -} diff --git a/pom.xml b/pom.xml deleted file mode 100644 index dd50a7569..000000000 --- a/pom.xml +++ /dev/null @@ -1,169 +0,0 @@ - - - 4.0.0 - - me.minidigger - MiniMessage - pom - 2.1.1-SNAPSHOT - MiniMessage - Simple library that implements an easy to use textual format to send rich json messages. - https://github.com/MiniDigger/MiniMessage - - - 1.8 - 1.8 - UTF-8 - - - - minimessage-text - minimessage-bungee - minimessage-ext-markdown - minimessage-plugin - - - - - papermc - https://papermc.io/repo/repository/maven-public/ - - - - - - com.google.code.findbugs - jsr305 - 3.0.2 - compile - - - junit - junit - 4.13 - test - - - - - - - org.apache.maven.plugins - maven-release-plugin - 3.0.0-M1 - - deploy - - true - - - - - - - - deploy - - - deploy - - - - clean deploy - - - org.apache.maven.plugins - maven-source-plugin - 3.2.1 - - - attach-sources - - jar - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - 3.2.0 - - - attach-javadocs - - jar - - - none - - - - - - org.apache.maven.plugins - maven-gpg-plugin - 1.6 - - - sign-artifacts - verify - - sign - - - - - - --batch - --pinentry-mode - loopback - - - - - - - - default - - true - - - - - - scm:git:https://github.com/MiniDigger/MiniMessage.git - scm:git:https://github.com/MiniDigger/MiniMessage.git - https://github.com/MiniDigger/MiniMessage - HEAD - - - - - MIT - https://github.com/MiniDigger/MiniMessage/blob/master/LICENSE - - - - - - MiniDigger - admin@minidigger.me - - Maintainer - - - - - - - sonatype - https://oss.sonatype.org/service/local/staging/deploy/maven2 - - - sonatype - https://oss.sonatype.org/content/repositories/snapshots - - - diff --git a/renovate.json b/renovate.json deleted file mode 100644 index f45d8f110..000000000 --- a/renovate.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "extends": [ - "config:base" - ] -} diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 000000000..c502bffb7 --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'adventure-text-minimessage' diff --git a/src/main/java/net/kyori/adventure/text/minimessage/MiniMarkdownParser.java b/src/main/java/net/kyori/adventure/text/minimessage/MiniMarkdownParser.java new file mode 100644 index 000000000..2ae0e183a --- /dev/null +++ b/src/main/java/net/kyori/adventure/text/minimessage/MiniMarkdownParser.java @@ -0,0 +1,199 @@ +/* + * This file is part of adventure-text-minimessage, licensed under the MIT License. + * + * Copyright (c) 2018-2020 KyoriPowered + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package net.kyori.adventure.text.minimessage; + +import net.kyori.examination.Examinable; +import net.kyori.examination.ExaminableProperty; +import net.kyori.examination.string.StringExaminer; + +import org.checkerframework.checker.nullness.qual.NonNull; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.stream.Stream; + +/* package */ class MiniMarkdownParser { + + private MiniMarkdownParser() { + } + + @NonNull + /* package */ static String stripMarkdown(final @NonNull String input) { + return handle(input, true); + } + + @NonNull + /* package */ static String parse(final @NonNull String input) { + return handle(input, false); + } + + @NonNull + private static String handle(final @NonNull String input, final boolean strip) { + StringBuilder sb = new StringBuilder(); + + int bold = -1; + Insert boldSkip = null; + int italic = -1; + Insert italicSkip = null; + int underline = -1; + Insert underlineSkip = null; + + List inserts = new ArrayList<>(); + int skip = 0; + for (int i = 0; i + skip < input.length(); i++) { + int currIndex = i + skip; + char c = input.charAt(currIndex); + + boolean shouldSkip = false; + if (c == Tokens.MD_EMPHASIS_1) { + char n = next(currIndex, input); + if (n == Tokens.MD_EMPHASIS_1) { + if (bold == -1) { + bold = sb.length(); + boldSkip = new Insert(sb.length(), c + ""); + } else { + inserts.add(new Insert(bold, "<" + Tokens.BOLD + ">")); + inserts.add(new Insert(sb.length(), "")); + bold = -1; + } + skip++; + } else { + if (italic == -1) { + italic = sb.length(); + italicSkip = new Insert(sb.length(), c + ""); + } else { + inserts.add(new Insert(italic, "<" + Tokens.ITALIC + ">")); + inserts.add(new Insert(sb.length(), "")); + italic = -1; + } + } + shouldSkip = true; + } else if (c == Tokens.MD_EMPHASIS_2) { + char n = next(currIndex, input); + if (n == Tokens.MD_EMPHASIS_2) { + if (bold == -1) { + bold = sb.length(); + boldSkip = new Insert(sb.length(), c + ""); + } else { + inserts.add(new Insert(bold, "<" + Tokens.BOLD + ">")); + inserts.add(new Insert(sb.length(), "")); + bold = -1; + } + skip++; + } else { + if (italic == -1) { + italic = currIndex; + italicSkip = new Insert(sb.length(), c + ""); + } else { + inserts.add(new Insert(italic, "<" + Tokens.ITALIC + ">")); + inserts.add(new Insert(currIndex - 1, "")); + italic = -1; + } + } + shouldSkip = true; + } else if (c == Tokens.MD_UNDERLINE) { + char n = next(currIndex, input); + if (n == Tokens.MD_UNDERLINE) { + if (underline == -1) { + underline = sb.length(); + underlineSkip = new Insert(sb.length(), c + ""); + } else { + inserts.add(new Insert(underline, "<" + Tokens.UNDERLINED + ">")); + inserts.add(new Insert(sb.length(), "")); + underline = -1; + } + skip++; + shouldSkip = true; + } + } + + if (!shouldSkip) { + sb.append(c); + } + } + + if (strip) { + inserts.clear(); + } else { + inserts.sort(Comparator.comparing(Insert::getPos).thenComparing(Insert::getValue).reversed()); + } + + if (underline != -1) { + inserts.add(underlineSkip); + } + if (bold != -1) { + inserts.add(boldSkip); + } + if (italic != -1) { + inserts.add(italicSkip); + } + + for (Insert el : inserts) { + sb.insert(el.getPos(), el.getValue()); + } + + return sb.toString(); + } + + private static char next(final int index, final @NonNull String input) { + if (index < input.length() - 1) { + return input.charAt(index + 1); + } else { + return ' '; + } + } + + /* package */ static class Insert implements Examinable { + private final int pos; + private final String value; + + private Insert(final int pos, final @NonNull String value) { + this.pos = pos; + this.value = value; + } + + private int getPos() { + return pos; + } + + @NonNull + private String getValue() { + return value; + } + + @Override + public @NonNull Stream examinableProperties() { + return Stream.of( + ExaminableProperty.of("pos", this.pos), + ExaminableProperty.of("value", this.value) + ); + } + + @Override + public String toString() { + return this.examine(StringExaminer.simpleEscaping()); + } + } +} diff --git a/src/main/java/net/kyori/adventure/text/minimessage/MiniMessage.java b/src/main/java/net/kyori/adventure/text/minimessage/MiniMessage.java new file mode 100644 index 000000000..8d0c76c8a --- /dev/null +++ b/src/main/java/net/kyori/adventure/text/minimessage/MiniMessage.java @@ -0,0 +1,150 @@ +/* + * This file is part of adventure-text-minimessage, licensed under the MIT License. + * + * Copyright (c) 2018-2020 KyoriPowered + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package net.kyori.adventure.text.minimessage; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.ComponentSerializer; +import net.kyori.adventure.util.Buildable; + +import org.checkerframework.checker.nullness.qual.NonNull; + +import java.util.List; +import java.util.Map; + +/** + * MiniMessage is a textual representation of components. This class allows you to serialize and deserialize them, strip + * or escape them, and even supports a markdown like format. + */ +public interface MiniMessage extends ComponentSerializer, Buildable { + + /** + * Gets a simple instance without markdown support + * + * @return a simple instance + */ + static @NonNull MiniMessage instance() { + return MiniMessageImpl.INSTANCE; + } + + /** + * Gets an instance with markdown support + * + * @return a instance of markdown support + */ + static @NonNull MiniMessage withMarkDown() { + return MiniMessageImpl.MARKDOWN; + } + + /** + * Escapes all tokens in the input message, so that they are ignored in deserialization. Useful for untrusted input. + * + * @param input the input message, with tokens + * @return the output, with escaped tokens + */ + @NonNull String escapeTokens(final @NonNull String input); + + /** + * Removes all tokens in the input message. Useful for untrusted input. + * + * @param input the input message, with tokens + * @return the output, without tokens + */ + @NonNull String stripTokens(final @NonNull String input); + + /** + * Parses a string into an component. + * + * @param input the input string + * @return the output component + */ + default Component parse(final @NonNull String input) { + return deserialize(input); + } + + /** + * Parses a string into an component, allows passing placeholders in key value pairs + * + * @param input the input string + * @param placeholders the placeholders + * @return the output component + */ + @NonNull Component parse(final @NonNull String input, final @NonNull String... placeholders); + + /** + * Parses a string into an component, allows passing placeholders in key value pairs + * + * @param input the input string + * @param placeholders the placeholders + * @return the output component + */ + @NonNull Component parse(final @NonNull String input, final @NonNull Map placeholders); + + /** + * Parses a string into an component, allows passing placeholders using templates (which support components) + * + * @param input the input string + * @param placeholders the placeholders + * @return the output component + */ + @NonNull Component parse(final @NonNull String input, final @NonNull Template... placeholders); + + /** + * Parses a string into an component, allows passing placeholders using templates (which support components) + * + * @param input the input string + * @param placeholders the placeholders + * @return the output component + */ + @NonNull Component parse(final @NonNull String input, final @NonNull List