From b4c1b7e07c9a0d979a27769032b16405357bc790 Mon Sep 17 00:00:00 2001 From: "Tobias Burdow [Kaleidox]" Date: Sun, 7 Jan 2024 12:45:46 +0100 Subject: [PATCH 1/6] feat: add jetbrains annotations for compile --- common/build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/common/build.gradle b/common/build.gradle index 326f5c2..93ea18d 100644 --- a/common/build.gradle +++ b/common/build.gradle @@ -15,6 +15,7 @@ dependencies { compileOnly 'com.google.code.gson:gson:2.7' compileOnly 'com.google.guava:guava:19.0' + compileOnly 'org.jetbrains:annotations:21.+' api('net.kyori:adventure-api:4.11.0') { exclude(module: 'adventure-bom') From 088e0716a06461cd6b6aa4da4001368ecd4c4c4f Mon Sep 17 00:00:00 2001 From: "Tobias Burdow [Kaleidox]" Date: Sun, 7 Jan 2024 13:18:12 +0100 Subject: [PATCH 2/6] feat: Add URL parsing Closes https://github.com/Mineaurion/Aurionchat/issues/30 fix display text not changing for enforced https fix arguments introduce parameter for domain scanning --- .../bukkit/listeners/ChatListener.java | 3 +- .../mineaurion/aurionchat/common/Utils.java | 75 +++++++++++++++---- .../common/command/ChatCommandCommon.java | 2 +- .../fabric/listeners/ChatListener.java | 3 +- .../forge/listeners/ChatListener.java | 3 +- .../sponge/listeners/ChatListener.java | 3 +- 6 files changed, 71 insertions(+), 18 deletions(-) diff --git a/bukkit/src/main/java/com/mineaurion/aurionchat/bukkit/listeners/ChatListener.java b/bukkit/src/main/java/com/mineaurion/aurionchat/bukkit/listeners/ChatListener.java index 129d30a..6257b00 100644 --- a/bukkit/src/main/java/com/mineaurion/aurionchat/bukkit/listeners/ChatListener.java +++ b/bukkit/src/main/java/com/mineaurion/aurionchat/bukkit/listeners/ChatListener.java @@ -37,7 +37,8 @@ public void onAsyncPlayerChatEvent(AsyncPlayerChatEvent event){ Component messageFormat = Utils.processMessage( plugin.getConfigurationAdapter().getChannels().get(currentChannel).format, LegacyComponentSerializer.legacy('&').deserialize(event.getMessage()).asComponent(), - aurionChatPlayer + aurionChatPlayer, + Utils.URL_MODE_ALLOW ); try{ diff --git a/common/src/main/java/com/mineaurion/aurionchat/common/Utils.java b/common/src/main/java/com/mineaurion/aurionchat/common/Utils.java index 0b3e380..9f9360c 100644 --- a/common/src/main/java/com/mineaurion/aurionchat/common/Utils.java +++ b/common/src/main/java/com/mineaurion/aurionchat/common/Utils.java @@ -2,24 +2,37 @@ import net.kyori.adventure.text.Component; import net.kyori.adventure.text.TextComponent; +import net.kyori.adventure.text.event.ClickEvent; import net.kyori.adventure.text.format.Style; import net.kyori.adventure.text.format.TextDecoration; import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; +import org.intellij.lang.annotations.MagicConstant; import java.util.Arrays; -import java.util.HashMap; import java.util.HashSet; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.Stream; -import static net.kyori.adventure.text.format.NamedTextColor.WHITE; - public class Utils { - public static Component processMessage(String format, Component message, AurionChatPlayer aurionChatPlayer){ - if(!aurionChatPlayer.isAllowedColors()){ + private static final Pattern URL_PATTERN = Pattern.compile("(?xi) # comments and case insensitive \n" + + "(.*?)( # prepend least possible of anything and group actual result \n" + + "(?https?://)? # http/s \n" + + "(?([\\w-]+\\.)*([\\w-]+(\\.[\\w-]+)+?)) # domains with * subdomains and +? endings \n" + + "(?(/[\\w-_.]+)*/?) # url path \n" + + ").*? # append anything"); + public static final int URL_MODE_ALLOW = 0x1; + public static final int URL_MODE_ALLOW_HTTP = 0x2; + public static final int URL_MODE_SCAN_DOMAINS = 0x4; + public static final int URL_MODE_DISPLAY_ONLY_DOMAINS = 0x8; + + public static Component processMessage(String format, Component message, AurionChatPlayer aurionChatPlayer, + @MagicConstant(flagsFromClass = Utils.class) int urlMode) { + if (!aurionChatPlayer.isAllowedColors()) { Component messageWithoutStyle = Component.text(""); - if(!message.children().isEmpty()){ - for (Component component: message.children()) { + if (!message.children().isEmpty()) { + for (Component component : message.children()) { messageWithoutStyle = messageWithoutStyle.append(removeAllStyleAndColor(component)); } } else { @@ -28,12 +41,48 @@ public static Component processMessage(String format, Component message, AurionC message = messageWithoutStyle; } - String[] formatSplit = format.split("\\{message\\}"); + String[] formatSplit = format.split("\\{message}"); + + Component blob = replaceToken(formatSplit[0], aurionChatPlayer); + final String display = getDisplayString(message); + final Matcher matcher = URL_PATTERN.matcher(display); + int eIndex = display.length(); + while (matcher.find()) { + eIndex = matcher.end(); - Component beforeMessage = replaceToken(formatSplit[0], aurionChatPlayer); - Component afterMessage = replaceToken(formatSplit.length == 2 ? formatSplit[1] : "", aurionChatPlayer); + // append text before url + blob = blob.append(Component.text(matcher.group(1))); - return beforeMessage.append(message).append(afterMessage); + String urlDisplay = matcher.group(2), urlAction; + + // check protocol present + if (matcher.group("protocol") == null) { + // validate that simple domains should be scanned + if ((urlMode & URL_MODE_SCAN_DOMAINS) == 0) { + // otherwise append url as plaintext and continue + blob = blob.append(Component.text(urlDisplay)); + continue; + } + urlAction = "https://" + urlDisplay; + } + // check enforce https + else if ((urlMode & URL_MODE_ALLOW_HTTP) == 0 && urlDisplay.startsWith("http:")) + urlAction = urlDisplay = urlDisplay.replace("http:", "https:"); + // or just use the url + else urlAction = urlDisplay; + + // check display mode + if ((urlMode & URL_MODE_DISPLAY_ONLY_DOMAINS) != 0) + urlDisplay = matcher.group("domain"); + + // check urls allowed + if ((urlMode & URL_MODE_ALLOW) != 0) + blob = blob.append(Component.text(urlDisplay) + .clickEvent(ClickEvent.openUrl(urlAction))); + else blob = blob.append(Component.text("[url removed]")); + } + return blob.append(Component.text(display.substring(eIndex))) + .append(replaceToken(formatSplit.length == 2 ? formatSplit[1] : "", aurionChatPlayer)); } public static String getDisplayString(Component component) { @@ -54,7 +103,7 @@ public static String getDisplayString(Component component) { .collect(Collectors.joining()); } - private static Component replaceToken(String text, AurionChatPlayer aurionChatPlayer){ + private static Component replaceToken(String text, AurionChatPlayer aurionChatPlayer) { return LegacyComponentSerializer.legacy('&').deserialize( text.replace("{prefix}", aurionChatPlayer.getPlayer().getPreffix()) .replace("{suffix}", aurionChatPlayer.getPlayer().getSuffix()) @@ -62,7 +111,7 @@ private static Component replaceToken(String text, AurionChatPlayer aurionChatPl ); } - private static Component removeAllStyleAndColor(Component component){ + private static Component removeAllStyleAndColor(Component component) { return component .style(Style.empty()) .decorations( diff --git a/common/src/main/java/com/mineaurion/aurionchat/common/command/ChatCommandCommon.java b/common/src/main/java/com/mineaurion/aurionchat/common/command/ChatCommandCommon.java index 65a51a8..1e7fa6b 100644 --- a/common/src/main/java/com/mineaurion/aurionchat/common/command/ChatCommandCommon.java +++ b/common/src/main/java/com/mineaurion/aurionchat/common/command/ChatCommandCommon.java @@ -128,7 +128,7 @@ public boolean execute(AurionChatPlayer aurionChatPlayer, String channel, Action public boolean onCommand(AurionChatPlayer aurionChatPlayers, Component message, String channel, String format){ aurionChatPlayers.addChannel(channel); - Component messageFormat = Utils.processMessage(format, message, aurionChatPlayers); + Component messageFormat = Utils.processMessage(format, message, aurionChatPlayers, Utils.URL_MODE_ALLOW); try { plugin.getChatService().send(channel, messageFormat); return true; diff --git a/fabric/src/main/java/com/mineaurion/aurionchat/fabric/listeners/ChatListener.java b/fabric/src/main/java/com/mineaurion/aurionchat/fabric/listeners/ChatListener.java index 25edf7f..d11fa7c 100644 --- a/fabric/src/main/java/com/mineaurion/aurionchat/fabric/listeners/ChatListener.java +++ b/fabric/src/main/java/com/mineaurion/aurionchat/fabric/listeners/ChatListener.java @@ -29,7 +29,8 @@ public boolean allowChatMessage(SignedMessage message, ServerPlayerEntity sender Component messageFormat = Utils.processMessage( plugin.getConfigurationAdapter().getChannels().get(currentChannel).format, GsonComponentSerializer.gson().deserialize(Text.Serializer.toJson(message.getContent())), - aurionChatPlayer + aurionChatPlayer, + Utils.URL_MODE_ALLOW ); try { plugin.getChatService().send(currentChannel, messageFormat); diff --git a/forge/src/main/java/com/mineaurion/aurionchat/forge/listeners/ChatListener.java b/forge/src/main/java/com/mineaurion/aurionchat/forge/listeners/ChatListener.java index d9f193c..7a2bcad 100644 --- a/forge/src/main/java/com/mineaurion/aurionchat/forge/listeners/ChatListener.java +++ b/forge/src/main/java/com/mineaurion/aurionchat/forge/listeners/ChatListener.java @@ -28,7 +28,8 @@ public void onServerChatEvent(ServerChatEvent event) { Component messageFormat = Utils.processMessage( plugin.getConfigurationAdapter().getChannels().get(currentChannel).format, GsonComponentSerializer.gson().deserialize(net.minecraft.network.chat.Component.Serializer.toJson(event.getMessage())), - aurionChatPlayer + aurionChatPlayer, + Utils.URL_MODE_ALLOW ); try { plugin.getChatService().send(currentChannel, messageFormat); diff --git a/sponge/src/main/java/com/mineaurion/aurionchat/sponge/listeners/ChatListener.java b/sponge/src/main/java/com/mineaurion/aurionchat/sponge/listeners/ChatListener.java index cd4f8ff..0373f00 100644 --- a/sponge/src/main/java/com/mineaurion/aurionchat/sponge/listeners/ChatListener.java +++ b/sponge/src/main/java/com/mineaurion/aurionchat/sponge/listeners/ChatListener.java @@ -35,7 +35,8 @@ public void onPlayerChat(PlayerChatEvent event, @First ServerPlayer player){ Component messageFormat = Utils.processMessage( plugin.getConfigurationAdapter().getChannels().get(currentChannel).format, event.message(), - aurionChatPlayer + aurionChatPlayer, + Utils.URL_MODE_ALLOW ); try{ From 6706cd9d76d88ca8949d0f907baf1cc92272a3fd Mon Sep 17 00:00:00 2001 From: "Tobias Burdow [Kaleidox]" Date: Sun, 7 Jan 2024 13:20:17 +0100 Subject: [PATCH 3/6] feat : Add Unit Tests --- common/build.gradle | 6 +- .../src/test/java/TestMessageProcessing.java | 200 ++++++++++++++++++ 2 files changed, 205 insertions(+), 1 deletion(-) create mode 100644 common/src/test/java/TestMessageProcessing.java diff --git a/common/build.gradle b/common/build.gradle index 93ea18d..fd72d94 100644 --- a/common/build.gradle +++ b/common/build.gradle @@ -4,7 +4,8 @@ plugins { } test { - useJUnitPlatform {} + useJUnit() + include '**/Test*.class' } jacocoTestReport { @@ -53,4 +54,7 @@ dependencies { api('org.spongepowered:configurate-yaml:4.1.2') api('org.spongepowered:configurate-gson:4.1.2') api('org.spongepowered:configurate-hocon:4.1.2') + + testImplementation('junit:junit:4.12') + testImplementation('org.easymock:easymock:3.6') } \ No newline at end of file diff --git a/common/src/test/java/TestMessageProcessing.java b/common/src/test/java/TestMessageProcessing.java new file mode 100644 index 0000000..6d32e59 --- /dev/null +++ b/common/src/test/java/TestMessageProcessing.java @@ -0,0 +1,200 @@ +import com.mineaurion.aurionchat.common.AbstractAurionChat; +import com.mineaurion.aurionchat.common.AurionChatPlayer; +import com.mineaurion.aurionchat.common.config.ConfigurationAdapter; +import com.mineaurion.aurionchat.common.player.Player; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.event.ClickEvent; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.util.HashMap; +import java.util.List; +import java.util.function.UnaryOperator; + +import static com.mineaurion.aurionchat.common.Utils.*; +import static net.kyori.adventure.text.Component.text; +import static org.easymock.EasyMock.*; +import static org.junit.Assert.*; + +@SuppressWarnings("HttpUrlsUsage") +public class TestMessageProcessing { + static String prefix = "[U]"; + static String displayName = "Kaleidox"; + static String suffix = "!"; + static String formatPrefix = "Minecraft {prefix} {display_name}{suffix}: "; + static String testUrl1 = "java.com"; + static String testUrl2 = "minecraft.net"; + static String testUrlRemoved = "[url removed]"; + static String testBase = "here is a url; %s and here is another: %s thats all there is!"; + + static String testUrlClickable(String it) { + return "https://" + it; + } + + static String testUrlHttp(String it) { + return "http://" + it; + } + + static String format() { + return formatPrefix + .replace("{display_name}", displayName) + .replace("{prefix}", prefix) + .replace("{suffix}", suffix); + } + + static String testText(String url1, String url2, boolean output) { + String format = String.format(testBase, url1, url2); + return output + ? format()+format + : format; + } + + Player playerAdp; + ConfigurationAdapter configAdp; + AbstractAurionChat plugin; + AurionChatPlayer player; + + @Before + public void setupAurionChatPlayer() { + playerAdp = mock(Player.class); + configAdp = mock(ConfigurationAdapter.class); + plugin = mock(AbstractAurionChat.class); + + // we could specify call count here for micro-optimization + expect(playerAdp.getDisplayName()).andReturn(displayName).atLeastOnce(); + expect(playerAdp.getPreffix()).andReturn(prefix).atLeastOnce(); + expect(playerAdp.getSuffix()).andReturn(suffix).atLeastOnce(); + expect(playerAdp.hasPermission("aurionchat.chat.colors")).andReturn(true).atLeastOnce(); + expect(configAdp.getChannels()).andReturn(new HashMap<>()).anyTimes(); + expect(plugin.getConfigurationAdapter()).andReturn(configAdp).anyTimes(); + + replay(playerAdp, configAdp, plugin); + player = new AurionChatPlayer(playerAdp, plugin); + } + + @After + public void teardown() { + verify(playerAdp, configAdp, plugin); + reset(playerAdp, configAdp, plugin); + } + + void checkClickable(Component output, int ci, int ui, UnaryOperator urlFunc) { + String url = new String[]{testUrl1,testUrl2}[ui]; + List children = output.children(); + assertTrue("invalid children count", children.size() > ci); + ClickEvent event = children.get(ci).clickEvent(); + assertNotNull("url is not clickable: " + url, event); + assertEquals("event has wrong action", ClickEvent.Action.OPEN_URL, event.action()); + assertEquals("event has wrong url", urlFunc.apply(url), event.value()); + } + + void checkNotClickable(Component output, int ci) { + List children = output.children(); + assertTrue("invalid children count", children.size() > ci); + Component child = children.get(ci); + ClickEvent event = child.clickEvent(); + assertNull("url should not be clickable: "+getDisplayString(child),event); + } + + @Test + public void testDomain() { + Component output = processMessage(formatPrefix, text(testUrl1), player, URL_MODE_ALLOW); + + // check display + String displayString = getDisplayString(output); + System.out.println(displayString); + assertEquals("display string mismatch", format()+testUrl1, displayString); + + checkNotClickable(output, 0); + } + + @Test + public void testDomainScan() { + Component output = processMessage(formatPrefix, text(testUrl1), player, URL_MODE_ALLOW | URL_MODE_SCAN_DOMAINS); + + // check display + String displayString = getDisplayString(output); + System.out.println(displayString); + assertEquals("display string mismatch", format()+testUrl1, displayString); + + checkClickable(output, 0, 0, TestMessageProcessing::testUrlClickable); + } + + @Test + public void testUrl() { + Component output = processMessage(formatPrefix, text(testUrlClickable(testUrl1)), player, URL_MODE_ALLOW); + + // check display + String displayString = getDisplayString(output); + System.out.println(displayString); + assertEquals("display string mismatch", format()+testUrlClickable(testUrl1), displayString); + + checkClickable(output, 0, 0, TestMessageProcessing::testUrlClickable); + } + + @Test + public void testEmbeddedUrl() { + Component output = processMessage(formatPrefix, text(testText(testUrl1, testUrlHttp(testUrl2), false)), player, URL_MODE_ALLOW); + + // check display + String displayString = getDisplayString(output); + System.out.println(displayString); + assertEquals("display string mismatch", testText(testUrl1, testUrlClickable(testUrl2), true), displayString); + + checkNotClickable(output, 1); + checkClickable(output, 3, 1, TestMessageProcessing::testUrlClickable); + } + + @Test + public void testDeniedUrl() { + Component output = processMessage(formatPrefix, text(testText(testUrl1, testUrlClickable(testUrl2), false)), player, 0); + + // check display + String displayString = getDisplayString(output); + System.out.println(displayString); + assertEquals("display string mismatch", testText(testUrl1, testUrlRemoved, true), displayString); + + checkNotClickable(output, 1); + checkNotClickable(output, 3); + } + + @Test + public void testDeniedUrlDomainScan() { + Component output = processMessage(formatPrefix, text(testText(testUrl1, testUrlClickable(testUrl2), false)), player, URL_MODE_SCAN_DOMAINS); + + // check display + String displayString = getDisplayString(output); + System.out.println(displayString); + assertEquals("display string mismatch", testText(testUrlRemoved, testUrlRemoved, true), displayString); + + checkNotClickable(output, 1); + checkNotClickable(output, 3); + } + + @Test + public void testSimplifiedDisplay() { + Component output = processMessage(formatPrefix, text(testText(testUrl1, testUrlClickable(testUrl2), false)), player, URL_MODE_ALLOW | URL_MODE_DISPLAY_ONLY_DOMAINS); + + // check display + String displayString = getDisplayString(output); + System.out.println(displayString); + assertEquals("display string mismatch", testText(testUrl1, testUrl2, true), displayString); + + checkNotClickable(output, 1); + checkClickable(output, 3, 1, TestMessageProcessing::testUrlClickable); + } + + @Test + public void testHttp() { + Component output = processMessage(formatPrefix, text(testText(testUrl1, testUrlHttp(testUrl2), false)), player, URL_MODE_ALLOW | URL_MODE_ALLOW_HTTP); + + // check display + String displayString = getDisplayString(output); + System.out.println(displayString); + assertEquals("display string mismatch", testText(testUrl1, testUrlHttp(testUrl2), true), displayString); + + checkNotClickable(output, 1); + checkClickable(output, 3, 1, TestMessageProcessing::testUrlHttp); + } +} From d1acdc458c2280013da739daa936e4cb453f55fc Mon Sep 17 00:00:00 2001 From: "Tobias Burdow [Kaleidox]" Date: Sun, 7 Jan 2024 15:09:59 +0100 Subject: [PATCH 4/6] feat: add url fragment and parameters to incomplete regexp --- .../src/main/java/com/mineaurion/aurionchat/common/Utils.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/common/src/main/java/com/mineaurion/aurionchat/common/Utils.java b/common/src/main/java/com/mineaurion/aurionchat/common/Utils.java index 9f9360c..e01d02a 100644 --- a/common/src/main/java/com/mineaurion/aurionchat/common/Utils.java +++ b/common/src/main/java/com/mineaurion/aurionchat/common/Utils.java @@ -21,6 +21,8 @@ public class Utils { "(?https?://)? # http/s \n" + "(?([\\w-]+\\.)*([\\w-]+(\\.[\\w-]+)+?)) # domains with * subdomains and +? endings \n" + "(?(/[\\w-_.]+)*/?) # url path \n" + + "(\\#(?[\\w_-]+))? # url fragment \n" + + "(?\\?(([\\w_%-]+)=([\\w_%-]+)&?))? # urlencoded parameters \n" + ").*? # append anything"); public static final int URL_MODE_ALLOW = 0x1; public static final int URL_MODE_ALLOW_HTTP = 0x2; From 87b51fe81fccadfeae3cbb979f58df8a5a913dd4 Mon Sep 17 00:00:00 2001 From: "Tobias Burdow [Kaleidox]" Date: Sun, 7 Jan 2024 15:57:04 +0100 Subject: [PATCH 5/6] feat: add urlmode configuration option per channel --- README.md | 17 +++++++++++++++++ .../bukkit/BukkitConfigurationAdapter.java | 3 ++- bukkit/src/main/resources/config.yml | 5 ++++- .../aurionchat/common/config/Channel.java | 4 +++- .../common/config/ConfigurateConfigAdapter.java | 5 ++++- .../fabric/listeners/ChatListener.java | 6 ++++-- fabric/src/main/resources/aurionchat.conf | 3 +++ .../forge/listeners/ChatListener.java | 6 ++++-- forge/src/main/resources/aurionchat.conf | 3 +++ .../sponge/listeners/ChatListener.java | 6 ++++-- sponge/src/main/resources/aurionchat.conf | 3 +++ 11 files changed, 51 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 31490f7..6b24b90 100644 --- a/README.md +++ b/README.md @@ -101,6 +101,23 @@ Follow the [1.16](https://github.com/Mineaurion/aurionchat#forge-116) section fo The mod need [Luckperms](https://modrinth.com/mod/luckperms/versions) to be working with a minimal version of 5.1.20. The config file is the same syntax as the sponge one. +## URL Mode +Theres a variety of options available for handling URLs. Options are as follows: + +| Name | Value | Description | +|----------------------|-------|-------------------------------------------------------------------------| +| Allow URLs | `1` | When disabled, replaces scanned URLs with `[url removed]` | +| Allow HTTP | `2` | When enabled, does not enforce the use of `https://` | +| Scan Domains | `4` | When enabled, scans for domains that have no leading `https://` as well | +| Display Domains Only | `8` | When enabled, displays only the domain portion of scanned URLs in chat | + +The `options.url_mode` configuration value must be set to the sum of the values of all enabled options. + +For example; +- to allow URLs and only display the domain portion; you'd have to set it to `9`. +- to replace all URLs and domains with `[url removed]`, set it to `4` +- to allow URLs and domains, and only display the domain portion; set it to `13` + ## Automessage For this part to work you need to install this [plugin](https://github.com/Mineaurion/AurionChat-AutoMessage) on one of our server. diff --git a/bukkit/src/main/java/com/mineaurion/aurionchat/bukkit/BukkitConfigurationAdapter.java b/bukkit/src/main/java/com/mineaurion/aurionchat/bukkit/BukkitConfigurationAdapter.java index bfd166d..18dbe26 100644 --- a/bukkit/src/main/java/com/mineaurion/aurionchat/bukkit/BukkitConfigurationAdapter.java +++ b/bukkit/src/main/java/com/mineaurion/aurionchat/bukkit/BukkitConfigurationAdapter.java @@ -48,7 +48,8 @@ public Map getChannels(){ channel, new Channel( this.configuration.getString("channels." + channel + ".format"), - this.configuration.getString("channels." + channel + ".alias") + this.configuration.getString("channels." + channel + ".alias"), + this.configuration.getInt("channels." + channel + ".url_mode", 1) ) ); } diff --git a/bukkit/src/main/resources/config.yml b/bukkit/src/main/resources/config.yml index bae8cbd..f65d5d5 100644 --- a/bukkit/src/main/resources/config.yml +++ b/bukkit/src/main/resources/config.yml @@ -12,9 +12,12 @@ channels: global: format: "[GLOBAL] {prefix}{display_name} : &f{message}" alias: "g" + url_mode: 1 # check https://github.com/Mineaurion/Aurionchat/#url-mode for reference infinity: format: "[&6I&f] {prefix}{display_name} : &f{message}" alias: "inf" + url_mode: 1 # check https://github.com/Mineaurion/Aurionchat/#url-mode for reference revelation: format: "[&5R&f] {prefix}{display_name} : &f{message}" - alias: "reve" \ No newline at end of file + alias: "reve" + url_mode: 1 # check https://github.com/Mineaurion/Aurionchat/#url-mode for reference \ No newline at end of file diff --git a/common/src/main/java/com/mineaurion/aurionchat/common/config/Channel.java b/common/src/main/java/com/mineaurion/aurionchat/common/config/Channel.java index 789d6c5..f5a30fb 100644 --- a/common/src/main/java/com/mineaurion/aurionchat/common/config/Channel.java +++ b/common/src/main/java/com/mineaurion/aurionchat/common/config/Channel.java @@ -2,11 +2,13 @@ public class Channel { - public Channel(String format, String alias){ + public Channel(String format, String alias, int urlMode){ this.format = format; this.alias = alias; + this.urlMode = urlMode; } public String format; public String alias; + public int urlMode; } diff --git a/common/src/main/java/com/mineaurion/aurionchat/common/config/ConfigurateConfigAdapter.java b/common/src/main/java/com/mineaurion/aurionchat/common/config/ConfigurateConfigAdapter.java index f2bbb33..7877ab4 100644 --- a/common/src/main/java/com/mineaurion/aurionchat/common/config/ConfigurateConfigAdapter.java +++ b/common/src/main/java/com/mineaurion/aurionchat/common/config/ConfigurateConfigAdapter.java @@ -60,7 +60,10 @@ public Map getChannels() { return node.childrenMap().entrySet().stream().collect( Collectors.toMap( k -> k.getKey().toString(), - v -> new Channel(v.getValue().node("format").getString(), v.getValue().node("alias").getString()) + v -> new Channel( + v.getValue().node("format").getString(), + v.getValue().node("alias").getString(), + v.getValue().node("url_mode").getInt(1)) ) ); } diff --git a/fabric/src/main/java/com/mineaurion/aurionchat/fabric/listeners/ChatListener.java b/fabric/src/main/java/com/mineaurion/aurionchat/fabric/listeners/ChatListener.java index d11fa7c..ede6734 100644 --- a/fabric/src/main/java/com/mineaurion/aurionchat/fabric/listeners/ChatListener.java +++ b/fabric/src/main/java/com/mineaurion/aurionchat/fabric/listeners/ChatListener.java @@ -3,6 +3,7 @@ import com.mineaurion.aurionchat.common.AurionChatPlayer; import com.mineaurion.aurionchat.common.ChatService; import com.mineaurion.aurionchat.common.Utils; +import com.mineaurion.aurionchat.common.config.Channel; import com.mineaurion.aurionchat.fabric.AurionChat; import net.fabricmc.fabric.api.message.v1.ServerMessageEvents; import net.kyori.adventure.text.Component; @@ -26,11 +27,12 @@ public ChatListener(AurionChat plugin) { public boolean allowChatMessage(SignedMessage message, ServerPlayerEntity sender, Parameters params) { AurionChatPlayer aurionChatPlayer = this.plugin.getAurionChatPlayers().get(sender.getUuid()); String currentChannel = aurionChatPlayer.getCurrentChannel(); + Channel channel = plugin.getConfigurationAdapter().getChannels().get(currentChannel); Component messageFormat = Utils.processMessage( - plugin.getConfigurationAdapter().getChannels().get(currentChannel).format, + channel.format, GsonComponentSerializer.gson().deserialize(Text.Serializer.toJson(message.getContent())), aurionChatPlayer, - Utils.URL_MODE_ALLOW + channel.urlMode ); try { plugin.getChatService().send(currentChannel, messageFormat); diff --git a/fabric/src/main/resources/aurionchat.conf b/fabric/src/main/resources/aurionchat.conf index fd6bcec..29ed3eb 100644 --- a/fabric/src/main/resources/aurionchat.conf +++ b/fabric/src/main/resources/aurionchat.conf @@ -11,13 +11,16 @@ channels { global { format = "[GLOBAL] {prefix}{display_name} : &f{message}" alias = "g" + url_mode = 1 # check https://github.com/Mineaurion/Aurionchat/#url-mode for reference } infinity { format = "[&6I&f] {prefix}{display_name} : &f{message}" alias = "inf" + url_mode = 1 # check https://github.com/Mineaurion/Aurionchat/#url-mode for reference } revelation { format = "[&5R&f] {prefix}{display_name} : &f{message}" alias = "reve" + url_mode = 1 # check https://github.com/Mineaurion/Aurionchat/#url-mode for reference } } \ No newline at end of file diff --git a/forge/src/main/java/com/mineaurion/aurionchat/forge/listeners/ChatListener.java b/forge/src/main/java/com/mineaurion/aurionchat/forge/listeners/ChatListener.java index 7a2bcad..71a2a38 100644 --- a/forge/src/main/java/com/mineaurion/aurionchat/forge/listeners/ChatListener.java +++ b/forge/src/main/java/com/mineaurion/aurionchat/forge/listeners/ChatListener.java @@ -3,6 +3,7 @@ import com.mineaurion.aurionchat.common.AurionChatPlayer; import com.mineaurion.aurionchat.common.ChatService; import com.mineaurion.aurionchat.common.Utils; +import com.mineaurion.aurionchat.common.config.Channel; import com.mineaurion.aurionchat.forge.AurionChat; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; @@ -25,11 +26,12 @@ public void onServerChatEvent(ServerChatEvent event) { AurionChatPlayer aurionChatPlayer = this.plugin.getAurionChatPlayers().get(event.getPlayer().getUUID()); String currentChannel = aurionChatPlayer.getCurrentChannel(); + Channel channel = plugin.getConfigurationAdapter().getChannels().get(currentChannel); Component messageFormat = Utils.processMessage( - plugin.getConfigurationAdapter().getChannels().get(currentChannel).format, + channel.format, GsonComponentSerializer.gson().deserialize(net.minecraft.network.chat.Component.Serializer.toJson(event.getMessage())), aurionChatPlayer, - Utils.URL_MODE_ALLOW + channel.urlMode ); try { plugin.getChatService().send(currentChannel, messageFormat); diff --git a/forge/src/main/resources/aurionchat.conf b/forge/src/main/resources/aurionchat.conf index fd6bcec..29ed3eb 100644 --- a/forge/src/main/resources/aurionchat.conf +++ b/forge/src/main/resources/aurionchat.conf @@ -11,13 +11,16 @@ channels { global { format = "[GLOBAL] {prefix}{display_name} : &f{message}" alias = "g" + url_mode = 1 # check https://github.com/Mineaurion/Aurionchat/#url-mode for reference } infinity { format = "[&6I&f] {prefix}{display_name} : &f{message}" alias = "inf" + url_mode = 1 # check https://github.com/Mineaurion/Aurionchat/#url-mode for reference } revelation { format = "[&5R&f] {prefix}{display_name} : &f{message}" alias = "reve" + url_mode = 1 # check https://github.com/Mineaurion/Aurionchat/#url-mode for reference } } \ No newline at end of file diff --git a/sponge/src/main/java/com/mineaurion/aurionchat/sponge/listeners/ChatListener.java b/sponge/src/main/java/com/mineaurion/aurionchat/sponge/listeners/ChatListener.java index 0373f00..0733b2e 100644 --- a/sponge/src/main/java/com/mineaurion/aurionchat/sponge/listeners/ChatListener.java +++ b/sponge/src/main/java/com/mineaurion/aurionchat/sponge/listeners/ChatListener.java @@ -3,6 +3,7 @@ import com.mineaurion.aurionchat.common.AurionChatPlayer; import com.mineaurion.aurionchat.common.ChatService; import com.mineaurion.aurionchat.common.Utils; +import com.mineaurion.aurionchat.common.config.Channel; import com.mineaurion.aurionchat.sponge.AurionChat; import net.kyori.adventure.text.Component; import org.spongepowered.api.entity.living.player.server.ServerPlayer; @@ -32,11 +33,12 @@ public void onPlayerChat(PlayerChatEvent event, @First ServerPlayer player){ AurionChatPlayer aurionChatPlayer = this.plugin.getAurionChatPlayers().get(player.uniqueId()); String currentChannel = aurionChatPlayer.getCurrentChannel(); + Channel channel = plugin.getConfigurationAdapter().getChannels().get(currentChannel); Component messageFormat = Utils.processMessage( - plugin.getConfigurationAdapter().getChannels().get(currentChannel).format, + channel.format, event.message(), aurionChatPlayer, - Utils.URL_MODE_ALLOW + channel.urlMode ); try{ diff --git a/sponge/src/main/resources/aurionchat.conf b/sponge/src/main/resources/aurionchat.conf index fd6bcec..29ed3eb 100644 --- a/sponge/src/main/resources/aurionchat.conf +++ b/sponge/src/main/resources/aurionchat.conf @@ -11,13 +11,16 @@ channels { global { format = "[GLOBAL] {prefix}{display_name} : &f{message}" alias = "g" + url_mode = 1 # check https://github.com/Mineaurion/Aurionchat/#url-mode for reference } infinity { format = "[&6I&f] {prefix}{display_name} : &f{message}" alias = "inf" + url_mode = 1 # check https://github.com/Mineaurion/Aurionchat/#url-mode for reference } revelation { format = "[&5R&f] {prefix}{display_name} : &f{message}" alias = "reve" + url_mode = 1 # check https://github.com/Mineaurion/Aurionchat/#url-mode for reference } } \ No newline at end of file From 2bec2cf6ae71d90ee05ed65bbf9fb7121aa8181b Mon Sep 17 00:00:00 2001 From: Yann Date: Sat, 27 Jan 2024 14:27:25 +0100 Subject: [PATCH 6/6] feat: refacto the code to use enum and support for recursive children --- README.md | 46 ++--- .../bukkit/BukkitConfigurationAdapter.java | 2 +- .../bukkit/listeners/ChatListener.java | 3 +- bukkit/src/main/resources/config.yml | 6 +- common/build.gradle | 2 +- .../aurionchat/common/AbstractAurionChat.java | 1 - .../mineaurion/aurionchat/common/Utils.java | 171 +++++++++++------- .../common/command/ChatCommandCommon.java | 4 +- .../aurionchat/common/config/Channel.java | 17 +- .../config/ConfigurateConfigAdapter.java | 22 ++- .../common/config/ConfigurationAdapter.java | 17 +- .../src/test/java/TestMessageProcessing.java | 93 +++++++--- .../fabric/listeners/ChatListener.java | 1 - fabric/src/main/resources/aurionchat.conf | 6 +- forge/build.gradle | 4 +- .../aurionchat/forge/PlayerFactory.java | 1 - .../forge/listeners/ChatListener.java | 1 - forge/src/main/resources/aurionchat.conf | 6 +- .../sponge/listeners/ChatListener.java | 1 - sponge/src/main/resources/aurionchat.conf | 6 +- 20 files changed, 255 insertions(+), 155 deletions(-) diff --git a/README.md b/README.md index 6b24b90..6a2c4c7 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,7 @@ ### What is it ? - -AurionChat is a cross chat plugin beetween server for bukkit and sponge. We can describe as a chat channel plugin too. This plugin work with rabbitmq to transfer the message beetween the server if you don't know what i'm talking please refer to [this](https://www.rabbitmq.com/) +AurionChat is a cross chat plugin beetween server for bukkit and sponge. We can describe as a chat channel plugin too. This plugin work with rabbitmq to transfer the message beetween the server if you don't know what i'm talking please refer to [this](https://www.rabbitmq.com/) ### How it works ? @@ -11,15 +10,13 @@ When you send the message if other people listen to the same channel as you type ### Features - - Chat channel - Formating chat -- Bukkit 1.7 to 1.20.1 - Sponge 8 - Fabric 5.1.20 - Forge 1.16 and 1.18 support +- Bukkit 1.7 to 1.20.1 - Sponge 8 - Fabric 5.1.20 - Forge 1.16 and 1.18 support - Automessage ### Commands - - `channel` : List the channel listened - `channel join ` : Listen and Join the desired channel - `channel leave ` : Leave the desired channel @@ -33,7 +30,6 @@ When you send the message if other people listen to the same channel as you type - `aurionchat.joinchannel.` : Auto join the channel - `aurionchat.listenchannel.` : Listen to the channel - --- ## Chat @@ -46,6 +42,7 @@ You can have more info about the uri here : https://www.rabbitmq.com/uri-spec.ht The plugin/mod assume that a global channel is always defined and player who join the server will listen to the channel and the channel of the server by default This an example to declare a channel : + ``` channels: #You can put any alphanumeric name you want. @@ -64,9 +61,10 @@ At this time you can use only this token : You can put any color code beetwen the token or any characters if you want. ``` + ## Forge 1.16 -The mod need FTBRanks to be working with a minimal version of 1605.1.5. +The mod need FTBRanks to be working with a minimal version of 1605.1.5. The config file is the same syntax as the sponge one. @@ -88,7 +86,7 @@ java.lang.NoClassDefFoundError: org/slf4j/spi/SLF4JServiceProvider ``` You need to delete this folder from your server : `libraries/org/apache/logging/log4j/log4j-slf4j18-impl` and you can start again the server. - + ## Forge 1.18 @@ -102,32 +100,35 @@ Follow the [1.16](https://github.com/Mineaurion/aurionchat#forge-116) section fo The mod need [Luckperms](https://modrinth.com/mod/luckperms/versions) to be working with a minimal version of 5.1.20. The config file is the same syntax as the sponge one. ## URL Mode + Theres a variety of options available for handling URLs. Options are as follows: -| Name | Value | Description | -|----------------------|-------|-------------------------------------------------------------------------| -| Allow URLs | `1` | When disabled, replaces scanned URLs with `[url removed]` | -| Allow HTTP | `2` | When enabled, does not enforce the use of `https://` | -| Scan Domains | `4` | When enabled, scans for domains that have no leading `https://` as well | -| Display Domains Only | `8` | When enabled, displays only the domain portion of scanned URLs in chat | +The option are based on enum in the code : https://github.com/Mineaurion/Aurionchat/blob/master/common/src/main/java/com/mineaurion/aurionchat/common/Utils.java#L24 + +| Name | Value | Description | +|-------------------------|------------------------|--------------------------------------------------------------------| +| Disallow URL and Domain | `DISALLOW` | Disallow domain and url and replace it with "[url removed]" | +| Disallow URL | `DISALLOW_URL` | Disallow only url and domain remain intact | +| Display only Domain | `DISPLAY_ONLY_DOMAINS` | Replace any url with only the domain, the url is instact for click | +| Force Https | `FORCE_HTTPS` | Replace any http url with https instead | +| Allow only domain click | `CLICK_DOMAIN` | Domain will be only clickable | +| Allow URLs | `ALLOW` | Allow url to be clickable and domain | -The `options.url_mode` configuration value must be set to the sum of the values of all enabled options. -For example; -- to allow URLs and only display the domain portion; you'd have to set it to `9`. -- to replace all URLs and domains with `[url removed]`, set it to `4` -- to allow URLs and domains, and only display the domain portion; set it to `13` +The `options.url_mode` configuration value is an array so you can add multiple to value to it. Be careful to respect the syntax of the value or the config will not work. ## Automessage -For this part to work you need to install this [plugin](https://github.com/Mineaurion/AurionChat-AutoMessage) on one of our server. +For this part to work you need to install this [plugin](https://github.com/Mineaurion/AurionChat-AutoMessage) on one of our server. You can disable the automessage for one server in the configurtion just switch this : + ``` enable=true ``` -To this : +To this : + ``` enable=false ``` @@ -138,7 +139,6 @@ Permission of every automessage channel is : `aurionchat.automessage. getChannels(){ new Channel( this.configuration.getString("channels." + channel + ".format"), this.configuration.getString("channels." + channel + ".alias"), - this.configuration.getInt("channels." + channel + ".url_mode", 1) + stringListToUrlMode(this.configuration.getStringList("channels." + channel + ".url_mode")) ) ); } diff --git a/bukkit/src/main/java/com/mineaurion/aurionchat/bukkit/listeners/ChatListener.java b/bukkit/src/main/java/com/mineaurion/aurionchat/bukkit/listeners/ChatListener.java index 6257b00..38daad1 100644 --- a/bukkit/src/main/java/com/mineaurion/aurionchat/bukkit/listeners/ChatListener.java +++ b/bukkit/src/main/java/com/mineaurion/aurionchat/bukkit/listeners/ChatListener.java @@ -2,7 +2,6 @@ import com.mineaurion.aurionchat.bukkit.AurionChat; import com.mineaurion.aurionchat.common.AurionChatPlayer; -import com.mineaurion.aurionchat.common.ChatService; import com.mineaurion.aurionchat.common.Utils; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; @@ -38,7 +37,7 @@ public void onAsyncPlayerChatEvent(AsyncPlayerChatEvent event){ plugin.getConfigurationAdapter().getChannels().get(currentChannel).format, LegacyComponentSerializer.legacy('&').deserialize(event.getMessage()).asComponent(), aurionChatPlayer, - Utils.URL_MODE_ALLOW + plugin.getConfigurationAdapter().getChannels().get(currentChannel).urlMode ); try{ diff --git a/bukkit/src/main/resources/config.yml b/bukkit/src/main/resources/config.yml index f65d5d5..7d2e22c 100644 --- a/bukkit/src/main/resources/config.yml +++ b/bukkit/src/main/resources/config.yml @@ -12,12 +12,12 @@ channels: global: format: "[GLOBAL] {prefix}{display_name} : &f{message}" alias: "g" - url_mode: 1 # check https://github.com/Mineaurion/Aurionchat/#url-mode for reference + url_mode: ["ALLOW"] # check https://github.com/Mineaurion/Aurionchat/#url-mode for reference infinity: format: "[&6I&f] {prefix}{display_name} : &f{message}" alias: "inf" - url_mode: 1 # check https://github.com/Mineaurion/Aurionchat/#url-mode for reference + url_mode: ["ALLOW"] # check https://github.com/Mineaurion/Aurionchat/#url-mode for reference revelation: format: "[&5R&f] {prefix}{display_name} : &f{message}" alias: "reve" - url_mode: 1 # check https://github.com/Mineaurion/Aurionchat/#url-mode for reference \ No newline at end of file + url_mode: ["ALLOW"] # check https://github.com/Mineaurion/Aurionchat/#url-mode for reference \ No newline at end of file diff --git a/common/build.gradle b/common/build.gradle index fd72d94..2a89903 100644 --- a/common/build.gradle +++ b/common/build.gradle @@ -55,6 +55,6 @@ dependencies { api('org.spongepowered:configurate-gson:4.1.2') api('org.spongepowered:configurate-hocon:4.1.2') - testImplementation('junit:junit:4.12') + testImplementation('junit:junit:4.13.2') testImplementation('org.easymock:easymock:3.6') } \ No newline at end of file diff --git a/common/src/main/java/com/mineaurion/aurionchat/common/AbstractAurionChat.java b/common/src/main/java/com/mineaurion/aurionchat/common/AbstractAurionChat.java index 5a08fb7..b4ccd2d 100644 --- a/common/src/main/java/com/mineaurion/aurionchat/common/AbstractAurionChat.java +++ b/common/src/main/java/com/mineaurion/aurionchat/common/AbstractAurionChat.java @@ -3,7 +3,6 @@ import com.mineaurion.aurionchat.common.config.ConfigurationAdapter; import com.mineaurion.aurionchat.common.logger.PluginLogger; import com.mineaurion.aurionchat.common.player.PlayerFactory; -import net.luckperms.api.LuckPermsProvider; import java.io.IOException; import java.io.InputStream; diff --git a/common/src/main/java/com/mineaurion/aurionchat/common/Utils.java b/common/src/main/java/com/mineaurion/aurionchat/common/Utils.java index e01d02a..3cae329 100644 --- a/common/src/main/java/com/mineaurion/aurionchat/common/Utils.java +++ b/common/src/main/java/com/mineaurion/aurionchat/common/Utils.java @@ -6,103 +6,138 @@ import net.kyori.adventure.text.format.Style; import net.kyori.adventure.text.format.TextDecoration; import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; -import org.intellij.lang.annotations.MagicConstant; import java.util.Arrays; import java.util.HashSet; +import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; -import java.util.stream.Collectors; -import java.util.stream.Stream; public class Utils { private static final Pattern URL_PATTERN = Pattern.compile("(?xi) # comments and case insensitive \n" + "(.*?)( # prepend least possible of anything and group actual result \n" + "(?https?://)? # http/s \n" + "(?([\\w-]+\\.)*([\\w-]+(\\.[\\w-]+)+?)) # domains with * subdomains and +? endings \n" + - "(?(/[\\w-_.]+)*/?) # url path \n" + - "(\\#(?[\\w_-]+))? # url fragment \n" + - "(?\\?(([\\w_%-]+)=([\\w_%-]+)&?))? # urlencoded parameters \n" + - ").*? # append anything"); - public static final int URL_MODE_ALLOW = 0x1; - public static final int URL_MODE_ALLOW_HTTP = 0x2; - public static final int URL_MODE_SCAN_DOMAINS = 0x4; - public static final int URL_MODE_DISPLAY_ONLY_DOMAINS = 0x8; - - public static Component processMessage(String format, Component message, AurionChatPlayer aurionChatPlayer, - @MagicConstant(flagsFromClass = Utils.class) int urlMode) { + "(?(?(/[\\w-_.]+)*/?)\n(\\#(?[\\w_-]+))?\n(?\\?(([\\w_%-]+)=([\\w_%-]+)&?))? # all of the url \n" + + ")).*? # append anything"); + + public enum URL_MODE { + DISALLOW, + DISSALLOW_URL, + DISPLAY_ONLY_DOMAINS, + ALLOW, + FORCE_HTTPS, + CLICK_DOMAIN + } + + public static Component processMessage(String format, Component message, AurionChatPlayer aurionChatPlayer, List urlModes) { + Component processedMessage = urlScanning(message, urlModes); if (!aurionChatPlayer.isAllowedColors()) { Component messageWithoutStyle = Component.text(""); - if (!message.children().isEmpty()) { - for (Component component : message.children()) { + if (!processedMessage.children().isEmpty()) { + for (Component component : processedMessage.children()) { messageWithoutStyle = messageWithoutStyle.append(removeAllStyleAndColor(component)); } } else { - messageWithoutStyle = messageWithoutStyle.append(removeAllStyleAndColor(message)); + messageWithoutStyle = messageWithoutStyle.append(removeAllStyleAndColor(processedMessage)); } - message = messageWithoutStyle; + processedMessage = messageWithoutStyle; + } + + String[] formatSplit = format.split("\\{message\\}"); + + return Component.text() + .append(replaceToken(formatSplit[0], aurionChatPlayer)) + .append(processedMessage.children()) + .append(replaceToken(formatSplit.length == 2 ? formatSplit[1] : "", aurionChatPlayer)) + .build() + ; + } + + /** + * Sanitize url or add click event on it + */ + private static Component urlScanning(Component message, List urlModes) { + final Component urlRemoved = Component.text("[url removed]"); + TextComponent.Builder component = Component.text(); + + if(!message.children().isEmpty()){ + message.children().forEach(children -> component.append(urlScanning(children, urlModes))); } - String[] formatSplit = format.split("\\{message}"); + if(message instanceof TextComponent){ + TextComponent.Builder builder = Component.text(); - Component blob = replaceToken(formatSplit[0], aurionChatPlayer); - final String display = getDisplayString(message); - final Matcher matcher = URL_PATTERN.matcher(display); - int eIndex = display.length(); - while (matcher.find()) { - eIndex = matcher.end(); + final String display = ((TextComponent) message).content(); + final Matcher matcher = URL_PATTERN.matcher(display); - // append text before url - blob = blob.append(Component.text(matcher.group(1))); + if(matcher.find()){ + matcher.reset(); // Discard internal state + int eIndex = display.length(); + while(matcher.find()){ + eIndex = matcher.end(); + // append text before url + builder.append(Component.text(matcher.group(1))); - String urlDisplay = matcher.group(2), urlAction; + if(urlModes.contains(URL_MODE.DISALLOW)){ + builder.append(urlRemoved); + } else { + String urlDisplay = matcher.group(2); + String urlAction = ""; - // check protocol present - if (matcher.group("protocol") == null) { - // validate that simple domains should be scanned - if ((urlMode & URL_MODE_SCAN_DOMAINS) == 0) { - // otherwise append url as plaintext and continue - blob = blob.append(Component.text(urlDisplay)); - continue; + if(urlModes.contains(URL_MODE.FORCE_HTTPS) && urlDisplay.startsWith("http:")){ + urlDisplay = urlDisplay.replace("http:", "https:"); + } + if(matcher.group("protocol") != null || !matcher.group("url").isEmpty() ){ + if(urlModes.contains(URL_MODE.DISSALLOW_URL)){ + builder.append(urlRemoved); + continue; + } + } + if(urlModes.contains(URL_MODE.DISPLAY_ONLY_DOMAINS)){ + urlDisplay = matcher.group("domain"); + } + + if(urlModes.contains(URL_MODE.CLICK_DOMAIN) || urlModes.contains(URL_MODE.ALLOW)){ + String protocol = urlDisplay.startsWith("http") ? "" : "https://"; + urlAction = protocol + urlDisplay + matcher.group("url"); + } + + Component clickComponent = urlAction.isEmpty() ? Component.text(urlDisplay) : Component.text(urlDisplay).clickEvent(ClickEvent.openUrl(urlAction)); + + builder.append(clickComponent); + } } - urlAction = "https://" + urlDisplay; + // Append last non match string + builder.append(Component.text(display.substring(eIndex))); + } else { + // Don't match anything then just append the string to the component + builder.append(Component.text(display)); } - // check enforce https - else if ((urlMode & URL_MODE_ALLOW_HTTP) == 0 && urlDisplay.startsWith("http:")) - urlAction = urlDisplay = urlDisplay.replace("http:", "https:"); - // or just use the url - else urlAction = urlDisplay; - - // check display mode - if ((urlMode & URL_MODE_DISPLAY_ONLY_DOMAINS) != 0) - urlDisplay = matcher.group("domain"); - - // check urls allowed - if ((urlMode & URL_MODE_ALLOW) != 0) - blob = blob.append(Component.text(urlDisplay) - .clickEvent(ClickEvent.openUrl(urlAction))); - else blob = blob.append(Component.text("[url removed]")); + component.append(builder.build()); } - return blob.append(Component.text(display.substring(eIndex))) - .append(replaceToken(formatSplit.length == 2 ? formatSplit[1] : "", aurionChatPlayer)); + return component.build(); } public static String getDisplayString(Component component) { - return Stream.concat(Stream.of(component), component.children().stream()) - .map(it -> { - if (it instanceof TextComponent) - return ((TextComponent) it).content(); - // todo: support other types - //else if (it instanceof BlockNBTComponent) ; - //else if (it instanceof EntityNBTComponent) ; - //else if (it instanceof KeybindComponent) ; - //else if (it instanceof ScoreComponent) ; - //else if (it instanceof SelectorComponent) ; - //else if (it instanceof StorageNBTComponent) ; - //else if (it instanceof TranslatableComponent) ; - return ""; - }) - .collect(Collectors.joining()); + StringBuilder content = new StringBuilder(); + + if(!component.children().isEmpty()){ + component.children().forEach( child -> content.append(getDisplayString(child))); + } + + if(component instanceof TextComponent){ + content.append(((TextComponent) component).content()); + } + // todo: support other types + //else if (it instanceof BlockNBTComponent) ; + //else if (it instanceof EntityNBTComponent) ; + //else if (it instanceof KeybindComponent) ; + //else if (it instanceof ScoreComponent) ; + //else if (it instanceof SelectorComponent) ; + //else if (it instanceof StorageNBTComponent) ; + //else if (it instanceof TranslatableComponent) ; + return content.toString(); } private static Component replaceToken(String text, AurionChatPlayer aurionChatPlayer) { diff --git a/common/src/main/java/com/mineaurion/aurionchat/common/command/ChatCommandCommon.java b/common/src/main/java/com/mineaurion/aurionchat/common/command/ChatCommandCommon.java index 1e7fa6b..0718fff 100644 --- a/common/src/main/java/com/mineaurion/aurionchat/common/command/ChatCommandCommon.java +++ b/common/src/main/java/com/mineaurion/aurionchat/common/command/ChatCommandCommon.java @@ -2,12 +2,12 @@ import com.mineaurion.aurionchat.common.AbstractAurionChat; import com.mineaurion.aurionchat.common.AurionChatPlayer; -import com.mineaurion.aurionchat.common.ChatService; import com.mineaurion.aurionchat.common.Utils; import com.mineaurion.aurionchat.common.exception.ChannelNotFoundException; import net.kyori.adventure.text.Component; import java.io.IOException; +import java.util.Collections; import java.util.Set; import static net.kyori.adventure.text.Component.*; @@ -128,7 +128,7 @@ public boolean execute(AurionChatPlayer aurionChatPlayer, String channel, Action public boolean onCommand(AurionChatPlayer aurionChatPlayers, Component message, String channel, String format){ aurionChatPlayers.addChannel(channel); - Component messageFormat = Utils.processMessage(format, message, aurionChatPlayers, Utils.URL_MODE_ALLOW); + Component messageFormat = Utils.processMessage(format, message, aurionChatPlayers, Collections.singletonList(Utils.URL_MODE.ALLOW)); try { plugin.getChatService().send(channel, messageFormat); return true; diff --git a/common/src/main/java/com/mineaurion/aurionchat/common/config/Channel.java b/common/src/main/java/com/mineaurion/aurionchat/common/config/Channel.java index f5a30fb..1400054 100644 --- a/common/src/main/java/com/mineaurion/aurionchat/common/config/Channel.java +++ b/common/src/main/java/com/mineaurion/aurionchat/common/config/Channel.java @@ -1,14 +1,27 @@ package com.mineaurion.aurionchat.common.config; +import com.mineaurion.aurionchat.common.Utils.URL_MODE; + +import java.util.Collections; +import java.util.List; + public class Channel { - public Channel(String format, String alias, int urlMode){ + public Channel(String format, String alias, List urlMode){ this.format = format; this.alias = alias; this.urlMode = urlMode; } + public Channel(String format, String alias, URL_MODE urlMode){ + this( + format, + alias, + Collections.singletonList(urlMode) + ); + } + public String format; public String alias; - public int urlMode; + public List urlMode; } diff --git a/common/src/main/java/com/mineaurion/aurionchat/common/config/ConfigurateConfigAdapter.java b/common/src/main/java/com/mineaurion/aurionchat/common/config/ConfigurateConfigAdapter.java index 7877ab4..f393ef0 100644 --- a/common/src/main/java/com/mineaurion/aurionchat/common/config/ConfigurateConfigAdapter.java +++ b/common/src/main/java/com/mineaurion/aurionchat/common/config/ConfigurateConfigAdapter.java @@ -1,13 +1,15 @@ package com.mineaurion.aurionchat.common.config; import com.google.common.base.Splitter; -import com.mineaurion.aurionchat.common.AbstractAurionChat; +import com.mineaurion.aurionchat.common.Utils; import org.spongepowered.configurate.ConfigurationNode; import org.spongepowered.configurate.loader.ConfigurationLoader; +import org.spongepowered.configurate.serialize.SerializationException; import java.io.IOException; import java.nio.file.Path; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Optional; import java.util.stream.Collectors; @@ -60,10 +62,20 @@ public Map getChannels() { return node.childrenMap().entrySet().stream().collect( Collectors.toMap( k -> k.getKey().toString(), - v -> new Channel( - v.getValue().node("format").getString(), - v.getValue().node("alias").getString(), - v.getValue().node("url_mode").getInt(1)) + v -> { + List urlModeList; + try { + urlModeList = stringListToUrlMode(v.getValue().node("url_mode").getList(String.class)); + } catch (SerializationException e) { + throw new RuntimeException(e); + } + return new Channel( + v.getValue().node("format").getString(), + v.getValue().node("alias").getString(), + urlModeList + + ); + } ) ); } diff --git a/common/src/main/java/com/mineaurion/aurionchat/common/config/ConfigurationAdapter.java b/common/src/main/java/com/mineaurion/aurionchat/common/config/ConfigurationAdapter.java index 8f6696a..6d89937 100644 --- a/common/src/main/java/com/mineaurion/aurionchat/common/config/ConfigurationAdapter.java +++ b/common/src/main/java/com/mineaurion/aurionchat/common/config/ConfigurationAdapter.java @@ -1,9 +1,8 @@ package com.mineaurion.aurionchat.common.config; -import com.mineaurion.aurionchat.common.AbstractAurionChat; +import com.mineaurion.aurionchat.common.Utils; -import java.util.Map; -import java.util.Optional; +import java.util.*; public interface ConfigurationAdapter { @@ -29,4 +28,16 @@ default Optional getChannelByNameOrAlias(String search){ } return Optional.empty(); }; + + default List stringListToUrlMode(List slist) { + List list = new ArrayList<>(); + for (String val : slist) { + try { + list.add(Utils.URL_MODE.valueOf(val)); + } catch (IllegalArgumentException e){ + throw new RuntimeException("This url mode '" + val + "' is not supported. We supported this mode : " + Arrays.toString(Utils.URL_MODE.values())); + } + } + return list; + } } diff --git a/common/src/test/java/TestMessageProcessing.java b/common/src/test/java/TestMessageProcessing.java index 6d32e59..f649f79 100644 --- a/common/src/test/java/TestMessageProcessing.java +++ b/common/src/test/java/TestMessageProcessing.java @@ -8,6 +8,8 @@ import org.junit.Before; import org.junit.Test; +import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.function.UnaryOperator; @@ -22,31 +24,33 @@ public class TestMessageProcessing { static String prefix = "[U]"; static String displayName = "Kaleidox"; static String suffix = "!"; - static String formatPrefix = "Minecraft {prefix} {display_name}{suffix}: "; + static String format = "Minecraft {prefix} {display_name}{suffix}: {message}"; static String testUrl1 = "java.com"; static String testUrl2 = "minecraft.net"; static String testUrlRemoved = "[url removed]"; static String testBase = "here is a url; %s and here is another: %s thats all there is!"; static String testUrlClickable(String it) { - return "https://" + it; + return "https://" + it + ""; } static String testUrlHttp(String it) { return "http://" + it; } - static String format() { - return formatPrefix + static String format(String message) { + return format .replace("{display_name}", displayName) .replace("{prefix}", prefix) - .replace("{suffix}", suffix); + .replace("{suffix}", suffix) + .replace("{message}", message) + ; } static String testText(String url1, String url2, boolean output) { String format = String.format(testBase, url1, url2); return output - ? format()+format + ? format(format) : format; } @@ -79,76 +83,106 @@ public void teardown() { reset(playerAdp, configAdp, plugin); } - void checkClickable(Component output, int ci, int ui, UnaryOperator urlFunc) { - String url = new String[]{testUrl1,testUrl2}[ui]; + void checkClickable(Component output, int childrenIndex, int urlIndex, UnaryOperator urlFunc) { + String url = new String[]{testUrl1,testUrl2}[urlIndex]; List children = output.children(); - assertTrue("invalid children count", children.size() > ci); - ClickEvent event = children.get(ci).clickEvent(); + assertTrue("invalid children count", children.get(1).children().size() > childrenIndex); + ClickEvent event = children.get(1).children().get(childrenIndex).clickEvent(); assertNotNull("url is not clickable: " + url, event); assertEquals("event has wrong action", ClickEvent.Action.OPEN_URL, event.action()); assertEquals("event has wrong url", urlFunc.apply(url), event.value()); } - void checkNotClickable(Component output, int ci) { + void checkNotClickable(Component output, int childrenIndex) { List children = output.children(); - assertTrue("invalid children count", children.size() > ci); - Component child = children.get(ci); + assertTrue("invalid children count", children.get(1).children().size() > childrenIndex); + Component child = children.get(1).children().get(childrenIndex); ClickEvent event = child.clickEvent(); assertNull("url should not be clickable: "+getDisplayString(child),event); } + /** + * Test to only check if we recursively handle children + */ + @Test + public void testChildrenOneLevel(){ + Component withChild = Component.text().append(text(testUrl1)).append(text(testUrl2)).build(); + + Component output = processMessage(format, withChild, player, Arrays.asList(URL_MODE.DISPLAY_ONLY_DOMAINS)); + + String displayString = getDisplayString(output); + + // +1 is the prefix component we create in the method + assertEquals(2 + 1, output.children().size()); + } + + @Test + public void testChildrenMoreThanOneLevel(){ + Component child = Component.text().append(text("Child")).build(); + Component parentWithChild = Component.text().append(text("Parent")).append(child).build(); + Component grandParent = Component.text() + .append(parentWithChild) + .append(text("No Child")) + .build() + ; + Component output = processMessage(format, grandParent, player, Collections.emptyList()); + String displayString = getDisplayString(output); + System.out.println(displayString); + assertEquals(2 + 1, output.children().size()); + } + @Test public void testDomain() { - Component output = processMessage(formatPrefix, text(testUrl1), player, URL_MODE_ALLOW); + Component output = processMessage(format, text(testUrl1), player, Arrays.asList(URL_MODE.ALLOW, URL_MODE.DISPLAY_ONLY_DOMAINS)); // check display String displayString = getDisplayString(output); System.out.println(displayString); - assertEquals("display string mismatch", format()+testUrl1, displayString); + assertEquals("display string mismatch", format(testUrl1), displayString); + checkClickable(output, 0, 0, TestMessageProcessing::testUrlClickable); - checkNotClickable(output, 0); } @Test - public void testDomainScan() { - Component output = processMessage(formatPrefix, text(testUrl1), player, URL_MODE_ALLOW | URL_MODE_SCAN_DOMAINS); + public void testClickDomain() { + Component output = processMessage(format, text(testUrl1), player, Arrays.asList(URL_MODE.CLICK_DOMAIN)); // check display String displayString = getDisplayString(output); System.out.println(displayString); - assertEquals("display string mismatch", format()+testUrl1, displayString); + assertEquals("display string mismatch", format(testUrl1), displayString); checkClickable(output, 0, 0, TestMessageProcessing::testUrlClickable); } @Test public void testUrl() { - Component output = processMessage(formatPrefix, text(testUrlClickable(testUrl1)), player, URL_MODE_ALLOW); + Component output = processMessage(format, text(testUrlClickable(testUrl1)), player, Arrays.asList(URL_MODE.ALLOW)); // check display String displayString = getDisplayString(output); System.out.println(displayString); - assertEquals("display string mismatch", format()+testUrlClickable(testUrl1), displayString); + assertEquals("display string mismatch", format(testUrlClickable(testUrl1)), displayString); checkClickable(output, 0, 0, TestMessageProcessing::testUrlClickable); } @Test public void testEmbeddedUrl() { - Component output = processMessage(formatPrefix, text(testText(testUrl1, testUrlHttp(testUrl2), false)), player, URL_MODE_ALLOW); + Component output = processMessage(format, text(testText(testUrl1, testUrlHttp(testUrl2), false)), player, Arrays.asList(URL_MODE.FORCE_HTTPS, URL_MODE.ALLOW)); // check display String displayString = getDisplayString(output); System.out.println(displayString); assertEquals("display string mismatch", testText(testUrl1, testUrlClickable(testUrl2), true), displayString); - checkNotClickable(output, 1); + checkClickable(output, 1, 0, TestMessageProcessing::testUrlClickable); checkClickable(output, 3, 1, TestMessageProcessing::testUrlClickable); } @Test public void testDeniedUrl() { - Component output = processMessage(formatPrefix, text(testText(testUrl1, testUrlClickable(testUrl2), false)), player, 0); + Component output = processMessage(format, text(testText(testUrl1, testUrlClickable(testUrl2), false)), player, Arrays.asList(URL_MODE.DISPLAY_ONLY_DOMAINS, URL_MODE.DISSALLOW_URL)); // check display String displayString = getDisplayString(output); @@ -161,7 +195,7 @@ public void testDeniedUrl() { @Test public void testDeniedUrlDomainScan() { - Component output = processMessage(formatPrefix, text(testText(testUrl1, testUrlClickable(testUrl2), false)), player, URL_MODE_SCAN_DOMAINS); + Component output = processMessage(format, text(testText(testUrl1, testUrlClickable(testUrl2), false)), player, Arrays.asList(URL_MODE.DISALLOW)); // check display String displayString = getDisplayString(output); @@ -174,27 +208,28 @@ public void testDeniedUrlDomainScan() { @Test public void testSimplifiedDisplay() { - Component output = processMessage(formatPrefix, text(testText(testUrl1, testUrlClickable(testUrl2), false)), player, URL_MODE_ALLOW | URL_MODE_DISPLAY_ONLY_DOMAINS); + Component output = processMessage(format, text(testText(testUrlClickable(testUrl1), testUrlClickable(testUrl2), false)), player, Arrays.asList(URL_MODE.CLICK_DOMAIN, URL_MODE.DISPLAY_ONLY_DOMAINS)); // check display String displayString = getDisplayString(output); System.out.println(displayString); assertEquals("display string mismatch", testText(testUrl1, testUrl2, true), displayString); - checkNotClickable(output, 1); + checkClickable(output, 1, 0, TestMessageProcessing::testUrlClickable); checkClickable(output, 3, 1, TestMessageProcessing::testUrlClickable); } @Test public void testHttp() { - Component output = processMessage(formatPrefix, text(testText(testUrl1, testUrlHttp(testUrl2), false)), player, URL_MODE_ALLOW | URL_MODE_ALLOW_HTTP); + Component output = processMessage(format, text(testText(testUrl1, testUrlHttp(testUrl2), false)), player, Arrays.asList(URL_MODE.ALLOW)); // check display String displayString = getDisplayString(output); System.out.println(displayString); assertEquals("display string mismatch", testText(testUrl1, testUrlHttp(testUrl2), true), displayString); - checkNotClickable(output, 1); + + checkClickable(output, 1, 0, TestMessageProcessing::testUrlClickable); checkClickable(output, 3, 1, TestMessageProcessing::testUrlHttp); } } diff --git a/fabric/src/main/java/com/mineaurion/aurionchat/fabric/listeners/ChatListener.java b/fabric/src/main/java/com/mineaurion/aurionchat/fabric/listeners/ChatListener.java index ede6734..6c01f0e 100644 --- a/fabric/src/main/java/com/mineaurion/aurionchat/fabric/listeners/ChatListener.java +++ b/fabric/src/main/java/com/mineaurion/aurionchat/fabric/listeners/ChatListener.java @@ -1,7 +1,6 @@ package com.mineaurion.aurionchat.fabric.listeners; import com.mineaurion.aurionchat.common.AurionChatPlayer; -import com.mineaurion.aurionchat.common.ChatService; import com.mineaurion.aurionchat.common.Utils; import com.mineaurion.aurionchat.common.config.Channel; import com.mineaurion.aurionchat.fabric.AurionChat; diff --git a/fabric/src/main/resources/aurionchat.conf b/fabric/src/main/resources/aurionchat.conf index 29ed3eb..212d1b1 100644 --- a/fabric/src/main/resources/aurionchat.conf +++ b/fabric/src/main/resources/aurionchat.conf @@ -11,16 +11,16 @@ channels { global { format = "[GLOBAL] {prefix}{display_name} : &f{message}" alias = "g" - url_mode = 1 # check https://github.com/Mineaurion/Aurionchat/#url-mode for reference + url_mode = ["ALLOW"] # check https://github.com/Mineaurion/Aurionchat/#url-mode for reference } infinity { format = "[&6I&f] {prefix}{display_name} : &f{message}" alias = "inf" - url_mode = 1 # check https://github.com/Mineaurion/Aurionchat/#url-mode for reference + url_mode = ["ALLOW"] # check https://github.com/Mineaurion/Aurionchat/#url-mode for reference } revelation { format = "[&5R&f] {prefix}{display_name} : &f{message}" alias = "reve" - url_mode = 1 # check https://github.com/Mineaurion/Aurionchat/#url-mode for reference + url_mode = ["ALLOW"] # check https://github.com/Mineaurion/Aurionchat/#url-mode for reference } } \ No newline at end of file diff --git a/forge/build.gradle b/forge/build.gradle index d4ad8e7..67eb3a6 100644 --- a/forge/build.gradle +++ b/forge/build.gradle @@ -86,8 +86,8 @@ dependencies { minecraft 'net.minecraftforge:forge:1.20.1-47.1.44' shadow project(':common') - shadow 'com.google.code.gson:gson:2.7' - shadow 'com.google.guava:guava:19.0' + shadow 'com.google.code.gson:gson:2.10.1' + shadow 'com.google.guava:guava:31.1-jre' // Real mod deobf dependency examples - these get remapped to your current mappings // compileOnly fg.deobf("mezz.jei:jei-${mc_version}:${jei_version}:api") // Adds JEI API as a compile dependency // runtimeOnly fg.deobf("mezz.jei:jei-${mc_version}:${jei_version}") // Adds the full JEI mod as a runtime dependency diff --git a/forge/src/main/java/com/mineaurion/aurionchat/forge/PlayerFactory.java b/forge/src/main/java/com/mineaurion/aurionchat/forge/PlayerFactory.java index 4dff743..92339bd 100644 --- a/forge/src/main/java/com/mineaurion/aurionchat/forge/PlayerFactory.java +++ b/forge/src/main/java/com/mineaurion/aurionchat/forge/PlayerFactory.java @@ -1,7 +1,6 @@ package com.mineaurion.aurionchat.forge; import net.kyori.adventure.text.Component; -import net.minecraft.Util; import net.minecraft.server.level.ServerPlayer; import java.util.UUID; diff --git a/forge/src/main/java/com/mineaurion/aurionchat/forge/listeners/ChatListener.java b/forge/src/main/java/com/mineaurion/aurionchat/forge/listeners/ChatListener.java index 71a2a38..14ff8eb 100644 --- a/forge/src/main/java/com/mineaurion/aurionchat/forge/listeners/ChatListener.java +++ b/forge/src/main/java/com/mineaurion/aurionchat/forge/listeners/ChatListener.java @@ -1,7 +1,6 @@ package com.mineaurion.aurionchat.forge.listeners; import com.mineaurion.aurionchat.common.AurionChatPlayer; -import com.mineaurion.aurionchat.common.ChatService; import com.mineaurion.aurionchat.common.Utils; import com.mineaurion.aurionchat.common.config.Channel; import com.mineaurion.aurionchat.forge.AurionChat; diff --git a/forge/src/main/resources/aurionchat.conf b/forge/src/main/resources/aurionchat.conf index 29ed3eb..212d1b1 100644 --- a/forge/src/main/resources/aurionchat.conf +++ b/forge/src/main/resources/aurionchat.conf @@ -11,16 +11,16 @@ channels { global { format = "[GLOBAL] {prefix}{display_name} : &f{message}" alias = "g" - url_mode = 1 # check https://github.com/Mineaurion/Aurionchat/#url-mode for reference + url_mode = ["ALLOW"] # check https://github.com/Mineaurion/Aurionchat/#url-mode for reference } infinity { format = "[&6I&f] {prefix}{display_name} : &f{message}" alias = "inf" - url_mode = 1 # check https://github.com/Mineaurion/Aurionchat/#url-mode for reference + url_mode = ["ALLOW"] # check https://github.com/Mineaurion/Aurionchat/#url-mode for reference } revelation { format = "[&5R&f] {prefix}{display_name} : &f{message}" alias = "reve" - url_mode = 1 # check https://github.com/Mineaurion/Aurionchat/#url-mode for reference + url_mode = ["ALLOW"] # check https://github.com/Mineaurion/Aurionchat/#url-mode for reference } } \ No newline at end of file diff --git a/sponge/src/main/java/com/mineaurion/aurionchat/sponge/listeners/ChatListener.java b/sponge/src/main/java/com/mineaurion/aurionchat/sponge/listeners/ChatListener.java index 0733b2e..aaf593e 100644 --- a/sponge/src/main/java/com/mineaurion/aurionchat/sponge/listeners/ChatListener.java +++ b/sponge/src/main/java/com/mineaurion/aurionchat/sponge/listeners/ChatListener.java @@ -1,7 +1,6 @@ package com.mineaurion.aurionchat.sponge.listeners; import com.mineaurion.aurionchat.common.AurionChatPlayer; -import com.mineaurion.aurionchat.common.ChatService; import com.mineaurion.aurionchat.common.Utils; import com.mineaurion.aurionchat.common.config.Channel; import com.mineaurion.aurionchat.sponge.AurionChat; diff --git a/sponge/src/main/resources/aurionchat.conf b/sponge/src/main/resources/aurionchat.conf index 29ed3eb..212d1b1 100644 --- a/sponge/src/main/resources/aurionchat.conf +++ b/sponge/src/main/resources/aurionchat.conf @@ -11,16 +11,16 @@ channels { global { format = "[GLOBAL] {prefix}{display_name} : &f{message}" alias = "g" - url_mode = 1 # check https://github.com/Mineaurion/Aurionchat/#url-mode for reference + url_mode = ["ALLOW"] # check https://github.com/Mineaurion/Aurionchat/#url-mode for reference } infinity { format = "[&6I&f] {prefix}{display_name} : &f{message}" alias = "inf" - url_mode = 1 # check https://github.com/Mineaurion/Aurionchat/#url-mode for reference + url_mode = ["ALLOW"] # check https://github.com/Mineaurion/Aurionchat/#url-mode for reference } revelation { format = "[&5R&f] {prefix}{display_name} : &f{message}" alias = "reve" - url_mode = 1 # check https://github.com/Mineaurion/Aurionchat/#url-mode for reference + url_mode = ["ALLOW"] # check https://github.com/Mineaurion/Aurionchat/#url-mode for reference } } \ No newline at end of file