diff --git a/src/main/java/ch/njol/skript/SkriptConfig.java b/src/main/java/ch/njol/skript/SkriptConfig.java index 646836404b6..86ea299fa7c 100644 --- a/src/main/java/ch/njol/skript/SkriptConfig.java +++ b/src/main/java/ch/njol/skript/SkriptConfig.java @@ -240,40 +240,44 @@ public static String formatDate(final long timestamp) { SkriptTimings.setEnabled(t); // Config option will be used }); - public static final Option parseLinks = new Option<>("parse links in chat messages", "disabled") - .setter(t -> { - try { - switch (t) { - case "false": - case "disabled": - TextComponentParser.instance().linkParseMode(LinkParseMode.DISABLED); - break; - case "true": - case "lenient": - TextComponentParser.instance().linkParseMode(LinkParseMode.LENIENT); - break; - case "strict": - TextComponentParser.instance().linkParseMode(LinkParseMode.STRICT); - break; - default: - TextComponentParser.instance().linkParseMode(LinkParseMode.DISABLED); - Skript.warning("Unknown link parse mode: " + t + ", please use disabled, strict or lenient"); - } - } catch (Error e) { - // Ignore it, we're on unsupported server platform and class loading failed - } - }); - public static final Option caseInsensitiveVariables = new Option<>("case-insensitive variables", true) .setter(t -> Variables.caseInsensitiveVariables = t); public static final Option caseInsensitiveCommands = new Option<>("case-insensitive commands", false) .optional(true); + public static final Option safeTags = new Option<>("safe tags", + new String[]{"color", "decorations", "gradient", "rainbow", "reset", "transition", "pride", "shadowColor"}, + raw -> raw.split(", ")) + .setter(tags -> { + try { + TextComponentParser.instance().setSafeTags(tags); + } catch (Error e) { + // Ignore it, we're on unsupported server platform and class loading failed + } + }); + + public static final Option parseLinks = new Option<>("parse links in chat messages", "disabled") + .setter(mode -> { + try { + TextComponentParser.instance().linkParseMode(switch (mode) { + case "false", "disabled" -> LinkParseMode.DISABLED; + case "true", "lenient" -> LinkParseMode.LENIENT; + case "strict" -> LinkParseMode.STRICT; + default -> { + Skript.warning("Unknown link parse mode: " + mode + ", please use disabled, strict or lenient"); + yield LinkParseMode.DISABLED; + } + }); + } catch (Error e) { + // Ignore it, we're on unsupported server platform and class loading failed + } + }); + public static final Option colorResetCodes = new Option<>("color codes reset formatting", true) - .setter(t -> { + .setter(causeReset -> { try { - TextComponentParser.instance().colorsCauseReset(t); + TextComponentParser.instance().colorsCauseReset(causeReset); } catch (Error e) { // Ignore it, we're on unsupported server platform and class loading failed } diff --git a/src/main/java/org/skriptlang/skript/bukkit/text/TextComponentParser.java b/src/main/java/org/skriptlang/skript/bukkit/text/TextComponentParser.java index 632dcca3149..ccc3a5ed468 100644 --- a/src/main/java/org/skriptlang/skript/bukkit/text/TextComponentParser.java +++ b/src/main/java/org/skriptlang/skript/bukkit/text/TextComponentParser.java @@ -1,8 +1,8 @@ package org.skriptlang.skript.bukkit.text; -import ch.njol.skript.Skript; import ch.njol.skript.registrations.Classes; import ch.njol.util.StringUtils; +import ch.njol.util.coll.CollectionUtils; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.TextReplacementConfig; import net.kyori.adventure.text.event.ClickEvent; @@ -25,8 +25,13 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; @@ -46,9 +51,9 @@ */ public final class TextComponentParser { - private record SkriptTag(Tag tag, boolean safe, boolean reset) { } + private record SkriptTag(Tag tag, @Nullable String parent, boolean reset) { } - private record SkriptTagResolver(TagResolver resolver, boolean safe) { + private record SkriptTagResolver(TagResolver resolver, @Nullable String parent) { @Override @SuppressWarnings("EqualsDoesntCheckParameterClass") // resolver impl will do this @@ -102,6 +107,7 @@ public enum LinkParseMode { public TextReplacementConfig textReplacementConfig() { return textReplacementConfig; } + } private static final TextComponentParser INSTANCE; @@ -151,48 +157,130 @@ private static Tag disableUnsetStyling(Tag tag) { } } - private TextComponentParser() { + TextComponentParser() { registerCompatibilityTags(); } + // The normal parser will process any proper tags + private final MiniMessage parser = MiniMessage.builder() + .strict(false) + .tags(TagResolver.builder() + .resolvers(StandardTags.defaults()) + .resolver(createSkriptTagResolver(false)) + .build()) + .build(); + + // The safe parser only parses those defined as safe in the configuration file + private final MiniMessage safeParser = MiniMessage.builder() + .strict(false) + .tags(TagResolver.builder() + .resolver(new TagResolver() { + @Override + public @Nullable Tag resolve(@NotNull String name, @NotNull ArgumentQueue arguments, @NotNull Context ctx) throws ParsingException { + return safeTagResolver.resolve(name, arguments, ctx); + } + + @Override + public boolean has(@NotNull String name) { + return safeTagResolver.has(name); + } + }) + .resolver(createSkriptTagResolver(true)) + .build()) + .build(); + private TagResolver safeTagResolver = TagResolver.empty(); + private final Map simplePlaceholders = new HashMap<>(); private final Set resolvers = new HashSet<>(); private LinkParseMode linkParseMode = LinkParseMode.DISABLED; private boolean colorsCauseReset = false; - /** - * @return The link parse mode for this parser, which describes how potential links should be treated. - */ - public LinkParseMode linkParseMode() { - return linkParseMode; - } + private TagResolver createSkriptTagResolver(boolean isSafeMode) { + return new TagResolver() { + @Override + public @Nullable Tag resolve(@NotNull String name, @NotNull ArgumentQueue arguments, @NotNull Context ctx) throws ParsingException { + SkriptTag simple = simplePlaceholders.get(name); + if (simple != null && isSafe(name, simple.parent)) { + if (colorsCauseReset && simple.reset) { + return disableUnsetStyling(simple.tag); + } + return simple.tag; + } - /** - * Sets the link parse mode for this parser, which describes how potential links should be treated. - * @param linkParseMode The link parse mode to use. - */ - public void linkParseMode(LinkParseMode linkParseMode) { - this.linkParseMode = linkParseMode; - } + // attempt our custom resolvers + for (SkriptTagResolver skriptResolver : resolvers) { + if (!isSafe(name, skriptResolver.parent) || !skriptResolver.resolver.has(name)) { + continue; + } + return skriptResolver.resolver.resolve(name, arguments, ctx); + } - /** - * @return Whether color codes cause a reset of existing formatting. - * Essentially, this setting controls whether all color tags should be prepended with a {@code } tag. - * @see ParserDirective#RESET - */ - public boolean colorsCauseReset() { - return colorsCauseReset; + // only process resets for standard color tags + if (colorsCauseReset && StandardTags.color().has(name)) { + Tag colorTag = StandardTags.color().resolve(name, arguments, ctx); + if (colorTag != null) { + return disableUnsetStyling(colorTag); + } + } + + return null; + } + + @Override + public boolean has(@NotNull String name) { + // check our simple placeholders + SkriptTag simple = simplePlaceholders.get(name); + if (simple != null) { + return isSafe(name, simple.parent); + } + + // check our custom resolvers + for (SkriptTagResolver skriptResolver : resolvers) { + if (isSafe(name, skriptResolver.parent) && skriptResolver.resolver.has(name)) { + return true; + } + } + + // otherwise, only process standard colors here if config applies + return colorsCauseReset && StandardTags.color().has(name); + } + + private boolean isSafe(String tag, @Nullable String parent) { + return !isSafeMode || safeTagResolver.has(tag) || (parent != null && safeTagResolver.has(parent)); + } + }; } /** - * Sets whether color codes cause a reset of existing formatting. - * Essentially, this setting controls whether all color tags should be prepended with a {@code } tag. - * @param colorsCauseReset Whether color codes should cause a reset. - * @see ParserDirective#RESET + * Sets the tags that are permitted to be parsed by the safe parser. + * @param safeTags An array containing the names of tags or their internal groups */ - public void colorsCauseReset(boolean colorsCauseReset) { - this.colorsCauseReset = colorsCauseReset; + public void setSafeTags(String... safeTags) { + // resolve safe tags + List resolvers = new ArrayList<>(); + MethodHandles.Lookup lookup = MethodHandles.lookup(); + for (String safeTag : safeTags) { + try { + MethodHandle handle = lookup.findStatic(StandardTags.class, safeTag, MethodType.methodType(TagResolver.class)); + resolvers.add((TagResolver) handle.invoke()); + } catch (Throwable ignored) { } + } + // create new resolver + safeTagResolver = new TagResolver() { + @Override + public @Nullable Tag resolve(@NotNull String name, @NotNull ArgumentQueue arguments, @NotNull Context ctx) throws ParsingException { + if (has(name)) { + return StandardTags.defaults().resolve(name, arguments, ctx); + } + return null; + } + + @Override + public boolean has(@NotNull String name) { + return resolvers.stream().anyMatch(resolver -> resolver.has(name)) || CollectionUtils.contains(safeTags, name); + } + }; } /** @@ -201,42 +289,46 @@ public void colorsCauseReset(boolean colorsCauseReset) { * @param result The result/value of the placeholder. */ public void registerPlaceholder(String name, Tag result) { - registerPlaceholder(name, result, false, false); + registerPlaceholder(name, result, null, false); } /** * Registers a simple key-value placeholder with Skript's unsafe message parser. - * The registered placeholder will instruct the parser to reset existing formatting before applying the tag - * if {@link #colorsCauseReset()} is true. * @param name The name/key of the placeholder. * @param result The result/value of the placeholder. + * @param parent If this tag acts as an alias or subset of another tag, the name of that tag. + * This is used for associating tags permitted for the safe parser. */ - public void registerResettingPlaceholder(String name, Tag result) { - registerPlaceholder(name, result, false, true); + public void registerPlaceholder(String name, Tag result, String parent) { + registerPlaceholder(name, result, parent, false); } /** - * Registers a simple key-value placeholder with Skript's safe and unsafe message parsers. + * Registers a simple key-value placeholder with Skript's unsafe message parser. + * The registered placeholder will instruct the parser to reset existing formatting before applying the tag + * if {@link #colorsCauseReset()} is true. * @param name The name/key of the placeholder. * @param result The result/value of the placeholder. */ - public void registerSafePlaceholder(String name, Tag result) { - registerPlaceholder(name, result, true, false); + public void registerResettingPlaceholder(String name, Tag result) { + registerPlaceholder(name, result, null, true); } /** - * Registers a simple key-value placeholder with Skript's safe and unsafe message parsers. + * Registers a simple key-value placeholder with Skript's unsafe message parser. * The registered placeholder will instruct the parser to reset existing formatting before applying the tag * if {@link #colorsCauseReset()} is true. * @param name The name/key of the placeholder. * @param result The result/value of the placeholder. + * @param parent If this tag acts as an alias or subset of another tag, the name of that tag. + * This is used for associating tags permitted for the safe parser. */ - public void registerSafeResettingPlaceholder(String name, Tag result) { - registerPlaceholder(name, result, true, true); + public void registerResettingPlaceholder(String name, Tag result, String parent) { + registerPlaceholder(name, result, parent, true); } - private void registerPlaceholder(String name, Tag result, boolean safe, boolean reset) { - simplePlaceholders.put(name, new SkriptTag(result, safe, reset)); + private void registerPlaceholder(String name, Tag result, @Nullable String parent, boolean reset) { + simplePlaceholders.put(name, new SkriptTag(result, parent, reset)); } /** @@ -252,19 +344,17 @@ public void unregisterPlaceholder(String tag) { * @param resolver The TagResolver to register. */ public void registerResolver(TagResolver resolver) { - registerResolver(resolver, false); + registerResolver(resolver, null); } /** * Registers a TagResolver with Skript's safe and unsafe message parsers. * @param resolver The TagResolver to register. + * @param parent If this tag acts as an alias or subset of another tag, the name of that tag. + * This is used for associating tags permitted for the safe parser. */ - public void registerSafeResolver(TagResolver resolver) { - registerResolver(resolver, true); - } - - private void registerResolver(TagResolver resolver, boolean safe) { - resolvers.add(new SkriptTagResolver(resolver, safe)); + public void registerResolver(TagResolver resolver, String parent) { + resolvers.add(new SkriptTagResolver(resolver, parent)); } /** @@ -273,83 +363,38 @@ private void registerResolver(TagResolver resolver, boolean safe) { */ public void unregisterResolver(TagResolver resolver) { // safe parameter is irrelevant as only resolver is considered in equality - resolvers.remove(new SkriptTagResolver(resolver, true)); + resolvers.remove(new SkriptTagResolver(resolver, null)); } - private TagResolver createSkriptTagResolver(boolean isSafeMode) { - return new TagResolver() { - - @Override - public @Nullable Tag resolve(@NotNull String name, @NotNull ArgumentQueue arguments, @NotNull Context ctx) throws ParsingException { - SkriptTag simple = simplePlaceholders.get(name); - if (simple != null && (!isSafeMode || simple.safe)) { - if (colorsCauseReset && simple.reset()) { - return disableUnsetStyling(simple.tag); - } - return simple.tag; - } - - // attempt our custom resolvers - for (SkriptTagResolver skriptResolver : resolvers) { - if ((isSafeMode && !skriptResolver.safe) || !skriptResolver.resolver.has(name)) { - continue; - } - return skriptResolver.resolver.resolve(name, arguments, ctx); - } - - // only process resets for standard color tags - if (colorsCauseReset && StandardTags.color().has(name)) { - Tag colorTag = StandardTags.color().resolve(name, arguments, ctx); - if (colorTag != null) { - return disableUnsetStyling(colorTag); - } - } - - return null; - } - - @Override - public boolean has(@NotNull String name) { - // check our simple placeholders - SkriptTag simple = simplePlaceholders.get(name); - if (simple != null) { - return !isSafeMode || simple.safe; - } - - // check our custom resolvers - for (SkriptTagResolver skriptResolver : resolvers) { - if ((!isSafeMode || skriptResolver.safe) && skriptResolver.resolver.has(name)) { - return true; - } - } + /** + * @return The link parse mode for this parser, which describes how potential links should be treated. + */ + public LinkParseMode linkParseMode() { + return linkParseMode; + } - // otherwise, only process standard colors here if config applies - return colorsCauseReset && StandardTags.color().has(name); - } - }; + /** + * Sets the link parse mode for this parser, which describes how potential links should be treated. + * @param linkParseMode The link parse mode to use. + */ + public void linkParseMode(LinkParseMode linkParseMode) { + this.linkParseMode = linkParseMode; } - // The normal parser will process any proper tags - private final MiniMessage parser = MiniMessage.builder() - .strict(false) - .tags(TagResolver.builder() - .resolvers(StandardTags.defaults()) - .resolver(createSkriptTagResolver(false)) - .build()) - .build(); + /** + * @return Whether color codes cause a reset of styling (i.e. close all open style tags). + */ + public boolean colorsCauseReset() { + return colorsCauseReset; + } - // The safe parser only parses color/decoration/formatting related tags - private final MiniMessage safeParser = MiniMessage.builder() - .strict(false) - .tags(TagResolver.builder() - .resolvers(StandardTags.color(), StandardTags.decorations(), StandardTags.font(), - StandardTags.gradient(), StandardTags.rainbow(), StandardTags.newline(), - StandardTags.reset(), StandardTags.transition()) - .resolvers(Skript.methodExists(StandardTags.class, "pride") ? - new TagResolver[]{StandardTags.pride(), StandardTags.shadowColor()} : new TagResolver[0]) - .resolver(createSkriptTagResolver(true)) - .build()) - .build(); + /** + * Sets whether color codes cause a reset of styling (i.e. close all open style tags). + * @param colorsCauseReset Whether color codes should cause a reset of styling. + */ + public void colorsCauseReset(boolean colorsCauseReset) { + this.colorsCauseReset = colorsCauseReset; + } /** * Parses a string using the safe and unsafe parsers. @@ -508,67 +553,67 @@ public String toLegacyString(Component component) { } private void registerCompatibilityTags() { - registerSafeResettingPlaceholder("dark_cyan", Tag.styling(NamedTextColor.DARK_AQUA)); - registerSafeResettingPlaceholder("dark_turquoise", Tag.styling(NamedTextColor.DARK_AQUA)); - registerSafeResettingPlaceholder("cyan", Tag.styling(NamedTextColor.DARK_AQUA)); + registerResettingPlaceholder("dark_cyan", Tag.styling(NamedTextColor.DARK_AQUA), "dark_aqua"); + registerResettingPlaceholder("dark_turquoise", Tag.styling(NamedTextColor.DARK_AQUA), "dark_aqua"); + registerResettingPlaceholder("cyan", Tag.styling(NamedTextColor.DARK_AQUA), "dark_aqua"); - registerSafeResettingPlaceholder("purple", Tag.styling(NamedTextColor.DARK_PURPLE)); + registerResettingPlaceholder("purple", Tag.styling(NamedTextColor.DARK_PURPLE), "dark_purple"); - registerSafeResettingPlaceholder("dark_yellow", Tag.styling(NamedTextColor.GOLD)); - registerSafeResettingPlaceholder("orange", Tag.styling(NamedTextColor.GOLD)); + registerResettingPlaceholder("dark_yellow", Tag.styling(NamedTextColor.GOLD), "gold"); + registerResettingPlaceholder("orange", Tag.styling(NamedTextColor.GOLD), "gold"); - registerSafeResettingPlaceholder("light_grey", Tag.styling(NamedTextColor.GRAY)); - registerSafeResettingPlaceholder("light_gray", Tag.styling(NamedTextColor.GRAY)); - registerSafeResettingPlaceholder("silver", Tag.styling(NamedTextColor.GRAY)); + registerResettingPlaceholder("light_grey", Tag.styling(NamedTextColor.GRAY), "gray"); + registerResettingPlaceholder("light_gray", Tag.styling(NamedTextColor.GRAY), "gray"); + registerResettingPlaceholder("silver", Tag.styling(NamedTextColor.GRAY), "gray"); - registerSafeResettingPlaceholder("dark_silver", Tag.styling(NamedTextColor.DARK_GRAY)); + registerResettingPlaceholder("dark_silver", Tag.styling(NamedTextColor.DARK_GRAY), "dark_gray"); - registerSafeResettingPlaceholder("light_blue", Tag.styling(NamedTextColor.BLUE)); - registerSafeResettingPlaceholder("indigo", Tag.styling(NamedTextColor.BLUE)); + registerResettingPlaceholder("light_blue", Tag.styling(NamedTextColor.BLUE), "blue"); + registerResettingPlaceholder("indigo", Tag.styling(NamedTextColor.BLUE), "blue"); - registerSafeResettingPlaceholder("light_green", Tag.styling(NamedTextColor.GREEN)); - registerSafeResettingPlaceholder("lime_green", Tag.styling(NamedTextColor.GREEN)); - registerSafeResettingPlaceholder("lime", Tag.styling(NamedTextColor.GREEN)); + registerResettingPlaceholder("light_green", Tag.styling(NamedTextColor.GREEN), "green"); + registerResettingPlaceholder("lime_green", Tag.styling(NamedTextColor.GREEN), "green"); + registerResettingPlaceholder("lime", Tag.styling(NamedTextColor.GREEN), "green"); - registerSafeResettingPlaceholder("light_cyan", Tag.styling(NamedTextColor.AQUA)); - registerSafeResettingPlaceholder("light_aqua", Tag.styling(NamedTextColor.AQUA)); - registerSafeResettingPlaceholder("turquoise", Tag.styling(NamedTextColor.AQUA)); + registerResettingPlaceholder("light_cyan", Tag.styling(NamedTextColor.AQUA), "aqua"); + registerResettingPlaceholder("light_aqua", Tag.styling(NamedTextColor.AQUA), "aqua"); + registerResettingPlaceholder("turquoise", Tag.styling(NamedTextColor.AQUA), "aqua"); - registerSafeResettingPlaceholder("light_red", Tag.styling(NamedTextColor.RED)); + registerResettingPlaceholder("light_red", Tag.styling(NamedTextColor.RED), "red"); - registerSafeResettingPlaceholder("pink", Tag.styling(NamedTextColor.LIGHT_PURPLE)); - registerSafeResettingPlaceholder("magenta", Tag.styling(NamedTextColor.LIGHT_PURPLE)); + registerResettingPlaceholder("pink", Tag.styling(NamedTextColor.LIGHT_PURPLE), "light_purple"); + registerResettingPlaceholder("magenta", Tag.styling(NamedTextColor.LIGHT_PURPLE), "light_purple"); - registerSafeResettingPlaceholder("light_yellow", Tag.styling(NamedTextColor.YELLOW)); + registerResettingPlaceholder("light_yellow", Tag.styling(NamedTextColor.YELLOW), "yellow"); // taken from DyeColor.BROWN - registerSafeResettingPlaceholder("brown", Tag.styling(TextColor.color(0x835432))); + registerResettingPlaceholder("brown", Tag.styling(TextColor.color(0x835432)), "color"); - registerSafePlaceholder("magic", Tag.styling(TextDecoration.OBFUSCATED)); + registerPlaceholder("magic", Tag.styling(TextDecoration.OBFUSCATED), "obfuscated"); - registerSafePlaceholder("strike", Tag.styling(TextDecoration.STRIKETHROUGH)); - registerSafePlaceholder("s", Tag.styling(TextDecoration.STRIKETHROUGH)); + registerPlaceholder("strike", Tag.styling(TextDecoration.STRIKETHROUGH), "strikethrough"); + registerPlaceholder("s", Tag.styling(TextDecoration.STRIKETHROUGH), "strikethrough"); - registerSafePlaceholder("underline", Tag.styling(TextDecoration.UNDERLINED)); + registerPlaceholder("underline", Tag.styling(TextDecoration.UNDERLINED), "underlined"); - registerSafePlaceholder("italics", Tag.styling(TextDecoration.ITALIC)); + registerPlaceholder("italics", Tag.styling(TextDecoration.ITALIC), "italic"); - registerSafePlaceholder("r", ParserDirective.RESET); + registerPlaceholder("r", ParserDirective.RESET, "reset"); registerResolver(TagResolver.resolver(Set.of("open_url", "link", "url"), (argumentQueue, context) -> { String url = argumentQueue.popOr("A link tag must have an argument of the url").value(); return Tag.styling(ClickEvent.openUrl(url)); - })); + }), "click"); registerResolver(TagResolver.resolver(Set.of("run_command", "command", "cmd"), (argumentQueue, context) -> { String command = argumentQueue.popOr("A run command tag must have an argument of the command to execute").value(); return Tag.styling(ClickEvent.runCommand(command)); - })); + }), "click"); registerResolver(TagResolver.resolver(Set.of("suggest_command", "sgt"), (argumentQueue, context) -> { String command = argumentQueue.popOr("A suggest command tag must have an argument of the command to suggest").value(); return Tag.styling(ClickEvent.suggestCommand(command)); - })); + }), "click"); registerResolver(TagResolver.resolver(Set.of("change_page"), (argumentQueue, context) -> { String rawPage = argumentQueue.popOr("A change page tag must have an argument of the page number").value(); @@ -579,30 +624,33 @@ private void registerCompatibilityTags() { throw context.newException(e.getMessage(), argumentQueue); } return Tag.styling(ClickEvent.changePage(page)); - })); + }), "click"); registerResolver(TagResolver.resolver(Set.of("copy_to_clipboard", "copy", "clipboard"), (argumentQueue, context) -> { String string = argumentQueue.popOr("A copy to clipboard tag must have an argument of the string to copy").value(); return Tag.styling(ClickEvent.copyToClipboard(string)); - })); + }), "click"); registerResolver(TagResolver.resolver(Set.of("show_text", "tooltip", "ttp"), (argumentQueue, context) -> { String tooltip = argumentQueue.popOr("A tooltip tag must have an argument of the message to show").value(); return Tag.styling(HoverEvent.showText(context.deserialize(tooltip))); - })); + }), "hover"); registerResolver(TagResolver.resolver(Set.of("f"), - (argumentQueue, context) -> StandardTags.font().resolve("font", argumentQueue, context))); + (argumentQueue, context) -> StandardTags.font().resolve("font", argumentQueue, context)), + "font"); registerResolver(TagResolver.resolver(Set.of("insertion", "ins"), - (argumentQueue, context) -> StandardTags.insertion().resolve("insert", argumentQueue, context))); + (argumentQueue, context) -> StandardTags.insertion().resolve("insert", argumentQueue, context)), + "insert"); registerResolver(TagResolver.resolver(Set.of("keybind"), - (argumentQueue, context) -> StandardTags.keybind().resolve("key", argumentQueue, context))); + (argumentQueue, context) -> StandardTags.keybind().resolve("key", argumentQueue, context)), + "key"); final Pattern unicodePattern = Pattern.compile("[0-9a-f]{4,}"); // note: "u" is already reserved by MiniMessage for underline, we override it - registerSafeResolver(TagResolver.resolver(Set.of("unicode", "u"), (argumentQueue, context) -> { + registerResolver(TagResolver.resolver(Set.of("unicode", "u"), (argumentQueue, context) -> { String argument = argumentQueue.popOr("A unicode tag must have an argument of the unicode").value(); Matcher matcher = unicodePattern.matcher(argument.toLowerCase(Locale.ENGLISH)); if (!matcher.matches()) diff --git a/src/main/resources/config.sk b/src/main/resources/config.sk index 8f809979952..6e99f846dab 100644 --- a/src/main/resources/config.sk +++ b/src/main/resources/config.sk @@ -192,6 +192,11 @@ enable timings: false # Warning: Paper no longer supports Timings as of 1.19.4. This option has no effect on versions 1.19.4 and above. # When false, timings are not enabled for scripts even if you're running Paper. +safe tags: color, decorations, gradient, rainbow, reset, transition, pride, shadowColor +# This list defines the tags that Skript will process by default, without the usage of the "formatted" expression. +# By default, this simply includes color and decoration applying tags. +# Tags are listed by their names or internal groups. They should be separated by only a comma and a space (', '). + parse links in chat messages: disabled # Controls how Skript will try to parse links in chat messages. # If 'disabled' or 'false', no links will be automatically parsed. diff --git a/src/test/java/org/skriptlang/skript/bukkit/text/TextComponentParserTest.java b/src/test/java/org/skriptlang/skript/bukkit/text/TextComponentParserTest.java new file mode 100644 index 00000000000..f5e433e7196 --- /dev/null +++ b/src/test/java/org/skriptlang/skript/bukkit/text/TextComponentParserTest.java @@ -0,0 +1,54 @@ +package org.skriptlang.skript.bukkit.text; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.Style; +import net.kyori.adventure.text.format.TextDecoration; +import org.junit.Test; + +import java.util.Set; + +import static org.junit.Assert.*; + +public class TextComponentParserTest { + + @Test + public void testSafeTags() { + TextComponentParser parser = new TextComponentParser(); + + parser.setSafeTags(""); + Component expected = Component.text("Hello world!"); + Component parsed = parser.parseSafe("Hello world!"); + assertEquals(expected, parsed); + + parser.setSafeTags("color"); + expected = Component.text("Hello world!", NamedTextColor.RED); + parsed = parser.parseSafe("Hello world!"); + assertEquals(expected, parsed); + + parser.setSafeTags("color", "bold"); + expected = Component.text("Hello ", NamedTextColor.RED) + .append(Component.text("world!", Style.style(TextDecoration.BOLD))); + parsed = parser.parseSafe("Hello world!"); + assertEquals(expected, parsed); + } + + @Test + public void testColorsCauseReset() { + TextComponentParser parser = new TextComponentParser(); + + parser.colorsCauseReset(false); + Component expected = Component.text("Hello ", NamedTextColor.RED, TextDecoration.BOLD) + .append(Component.text("world!", NamedTextColor.BLUE)); + Component parsed = parser.parse("Hello world!"); + assertEquals(expected, parsed); + + parser.colorsCauseReset(true); + expected = Component.text("Hello ", Style.style(NamedTextColor.RED) + .decorations(Set.of(TextDecoration.values()), false).decoration(TextDecoration.BOLD, true)) + .append(Component.text("world!", Style.style(NamedTextColor.BLUE, TextDecoration.BOLD.withState(false)))); + parsed = parser.parse("Hello world!"); + assertEquals(expected, parsed); + } + +}