diff --git a/README.md b/README.md index 31490f7..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 @@ -101,16 +99,36 @@ 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: + +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 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 ``` @@ -121,7 +139,6 @@ Permission of every automessage channel is : `aurionchat.automessage. getChannels(){ channel, new Channel( this.configuration.getString("channels." + channel + ".format"), - this.configuration.getString("channels." + channel + ".alias") + this.configuration.getString("channels." + channel + ".alias"), + 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 129d30a..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; @@ -37,7 +36,8 @@ public void onAsyncPlayerChatEvent(AsyncPlayerChatEvent event){ Component messageFormat = Utils.processMessage( plugin.getConfigurationAdapter().getChannels().get(currentChannel).format, LegacyComponentSerializer.legacy('&').deserialize(event.getMessage()).asComponent(), - aurionChatPlayer + aurionChatPlayer, + plugin.getConfigurationAdapter().getChannels().get(currentChannel).urlMode ); try{ diff --git a/bukkit/src/main/resources/config.yml b/bukkit/src/main/resources/config.yml index bae8cbd..7d2e22c 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: ["ALLOW"] # check https://github.com/Mineaurion/Aurionchat/#url-mode for reference infinity: format: "[&6I&f] {prefix}{display_name} : &f{message}" alias: "inf" + url_mode: ["ALLOW"] # 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: ["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 326f5c2..2a89903 100644 --- a/common/build.gradle +++ b/common/build.gradle @@ -4,7 +4,8 @@ plugins { } test { - useJUnitPlatform {} + useJUnit() + include '**/Test*.class' } jacocoTestReport { @@ -15,6 +16,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') @@ -52,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.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 0b3e380..3cae329 100644 --- a/common/src/main/java/com/mineaurion/aurionchat/common/Utils.java +++ b/common/src/main/java/com/mineaurion/aurionchat/common/Utils.java @@ -2,59 +2,145 @@ 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 java.util.Arrays; -import java.util.HashMap; import java.util.HashSet; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import static net.kyori.adventure.text.format.NamedTextColor.WHITE; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; 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-_.]+)*/?)\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\\}"); - Component beforeMessage = replaceToken(formatSplit[0], aurionChatPlayer); - Component afterMessage = replaceToken(formatSplit.length == 2 ? formatSplit[1] : "", aurionChatPlayer); + 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))); + } + + if(message instanceof TextComponent){ + TextComponent.Builder builder = Component.text(); + + final String display = ((TextComponent) message).content(); + final Matcher matcher = URL_PATTERN.matcher(display); + + 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))); + + if(urlModes.contains(URL_MODE.DISALLOW)){ + builder.append(urlRemoved); + } else { + String urlDisplay = matcher.group(2); + String urlAction = ""; - return beforeMessage.append(message).append(afterMessage); + 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); + } + } + // 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)); + } + component.append(builder.build()); + } + 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){ + 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 +148,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..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); + 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 789d6c5..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,12 +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){ + 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 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 f2bbb33..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,7 +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 -> { + 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 new file mode 100644 index 0000000..f649f79 --- /dev/null +++ b/common/src/test/java/TestMessageProcessing.java @@ -0,0 +1,235 @@ +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.Arrays; +import java.util.Collections; +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 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 + ""; + } + + static String testUrlHttp(String it) { + return "http://" + it; + } + + static String format(String message) { + return format + .replace("{display_name}", displayName) + .replace("{prefix}", prefix) + .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; + } + + 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 childrenIndex, int urlIndex, UnaryOperator urlFunc) { + String url = new String[]{testUrl1,testUrl2}[urlIndex]; + List children = output.children(); + 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 childrenIndex) { + List children = output.children(); + 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(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); + checkClickable(output, 0, 0, TestMessageProcessing::testUrlClickable); + + } + + @Test + 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); + checkClickable(output, 0, 0, TestMessageProcessing::testUrlClickable); + } + + @Test + public void testUrl() { + 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); + + checkClickable(output, 0, 0, TestMessageProcessing::testUrlClickable); + } + + @Test + public void testEmbeddedUrl() { + 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); + + checkClickable(output, 1, 0, TestMessageProcessing::testUrlClickable); + checkClickable(output, 3, 1, TestMessageProcessing::testUrlClickable); + } + + @Test + public void testDeniedUrl() { + 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); + 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(format, text(testText(testUrl1, testUrlClickable(testUrl2), false)), player, Arrays.asList(URL_MODE.DISALLOW)); + + // 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(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); + + checkClickable(output, 1, 0, TestMessageProcessing::testUrlClickable); + checkClickable(output, 3, 1, TestMessageProcessing::testUrlClickable); + } + + @Test + public void testHttp() { + 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); + + + 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 25edf7f..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,8 +1,8 @@ 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; import net.fabricmc.fabric.api.message.v1.ServerMessageEvents; import net.kyori.adventure.text.Component; @@ -26,10 +26,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 + aurionChatPlayer, + 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..212d1b1 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 = ["ALLOW"] # check https://github.com/Mineaurion/Aurionchat/#url-mode for reference } infinity { format = "[&6I&f] {prefix}{display_name} : &f{message}" alias = "inf" + 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 = ["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 d9f193c..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,8 +1,8 @@ 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; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; @@ -25,10 +25,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 + aurionChatPlayer, + 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..212d1b1 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 = ["ALLOW"] # check https://github.com/Mineaurion/Aurionchat/#url-mode for reference } infinity { format = "[&6I&f] {prefix}{display_name} : &f{message}" alias = "inf" + 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 = ["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 cd4f8ff..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,8 +1,8 @@ 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; import net.kyori.adventure.text.Component; import org.spongepowered.api.entity.living.player.server.ServerPlayer; @@ -32,10 +32,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 + aurionChatPlayer, + channel.urlMode ); try{ diff --git a/sponge/src/main/resources/aurionchat.conf b/sponge/src/main/resources/aurionchat.conf index fd6bcec..212d1b1 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 = ["ALLOW"] # check https://github.com/Mineaurion/Aurionchat/#url-mode for reference } infinity { format = "[&6I&f] {prefix}{display_name} : &f{message}" alias = "inf" + 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 = ["ALLOW"] # check https://github.com/Mineaurion/Aurionchat/#url-mode for reference } } \ No newline at end of file