diff --git a/src/main/java/tect/host/tpl/config/migration/ConfigMigrations.java b/src/main/java/tect/host/tpl/config/migration/ConfigMigrations.java index 376df41..423e688 100644 --- a/src/main/java/tect/host/tpl/config/migration/ConfigMigrations.java +++ b/src/main/java/tect/host/tpl/config/migration/ConfigMigrations.java @@ -58,6 +58,7 @@ private ConfigMigrations() {} config.set("modules.grammar", false); config.set("modules.anti-unicode", false); config.set("storage.remote.useSSL", false); + config.set("modules.clickable-links", false); }); } } \ No newline at end of file diff --git a/src/main/java/tect/host/tpl/module/impl/chat/antiadvertising/AntiAdvertisingMatcher.java b/src/main/java/tect/host/tpl/module/impl/chat/antiadvertising/AntiAdvertisingMatcher.java index 9dd2716..77c1122 100644 --- a/src/main/java/tect/host/tpl/module/impl/chat/antiadvertising/AntiAdvertisingMatcher.java +++ b/src/main/java/tect/host/tpl/module/impl/chat/antiadvertising/AntiAdvertisingMatcher.java @@ -5,6 +5,7 @@ import tect.host.tpl.util.text.CensorUtil; import tect.host.tpl.util.text.NormalizedText; import tect.host.tpl.util.text.ObfuscationNormalizer; +import tect.host.tpl.util.text.UrlPatterns; import java.util.ArrayList; import java.util.List; @@ -29,16 +30,6 @@ private AntiAdvertisingMatcher() {} ")(?::\\d{1,5})?", Pattern.CASE_INSENSITIVE); - private static final Pattern DOMAIN_URL = Pattern.compile( - "(?:https?://|ftp://|www\\.)?" + - "(?:[a-z0-9](?:[a-z0-9\\-]{0,61}[a-z0-9])?\\.)" + - "+" + - "(?:com|net|org|gg|io|me|co|uk|de|fr|es|ru|xyz|tk|top|club|site|online|store|" + - "info|biz|tv|us|ca|au|jp|cn|br|in|pl|nl|se|no|fi|dk|be|ch|at|nz|mx|ar|cl|" + - "cc|eu|mobi|pro|name|coop|aero|museum|gov|mil|edu|int)" + - "(?:/\\S*)?", - Pattern.CASE_INSENSITIVE); - private record Span(int start, int end, @NonNull String type) {} public static @NonNull AntiAdvertisingResult check(@NonNull String raw, @NonNull AntiAdvertisingConfig config) { @@ -73,7 +64,7 @@ private record Span(int start, int end, @NonNull String type) {} } if (config.isCheckDomains() || config.isCheckUrls()) { - Pattern p = config.getCustomDomainUrlPattern() != null ? config.getCustomDomainUrlPattern() : DOMAIN_URL; + Pattern p = config.getCustomDomainUrlPattern() != null ? config.getCustomDomainUrlPattern() : UrlPatterns.DOMAIN_URL; Matcher m = p.matcher(normalized); while (m.find()) { Span span = classify(m, config); @@ -100,7 +91,7 @@ private record Span(int start, int end, @NonNull String type) {} } if (config.isCheckDomains() || config.isCheckUrls()) { - Pattern p = config.getCustomDomainUrlPattern() != null ? config.getCustomDomainUrlPattern() : DOMAIN_URL; + Pattern p = config.getCustomDomainUrlPattern() != null ? config.getCustomDomainUrlPattern() : UrlPatterns.DOMAIN_URL; Matcher m = p.matcher(normalized); while (m.find()) { Span span = classify(m, config); diff --git a/src/main/java/tect/host/tpl/module/impl/chat/clickablelinks/ClickableLinksModule.java b/src/main/java/tect/host/tpl/module/impl/chat/clickablelinks/ClickableLinksModule.java new file mode 100644 index 0000000..031666a --- /dev/null +++ b/src/main/java/tect/host/tpl/module/impl/chat/clickablelinks/ClickableLinksModule.java @@ -0,0 +1,40 @@ +package tect.host.tpl.module.impl.chat.clickablelinks; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.TextReplacementConfig; +import net.kyori.adventure.text.event.ClickEvent; +import org.jspecify.annotations.NonNull; +import tect.host.tpl.context.MessageContext; +import tect.host.tpl.module.ModuleContext; +import tect.host.tpl.module.type.ChatModule; +import tect.host.tpl.util.text.UrlPatterns; + +public final class ClickableLinksModule implements ChatModule { + + private static final String ID = "clickable-links"; + + public ClickableLinksModule(@NonNull ModuleContext moduleContext) {} + + @Override + public void process(@NonNull MessageContext ctx) { + if (ctx.isCancelled()) return; + + Component message = ctx.getMessage(); + + Component replaced = message.replaceText(TextReplacementConfig.builder() + .match(UrlPatterns.DOMAIN_URL) + .replacement((matchResult, builder) -> { + String url = matchResult.group(); + String target = url.matches("(?i)^https?://.*") ? url : "https://" + url; + return builder.clickEvent(ClickEvent.openUrl(target)); + }) + .build()); + + if (replaced != message) { + ctx.setMessage(replaced); + } + } + + @Override + public @NonNull String getId() { return ID; } +} \ No newline at end of file diff --git a/src/main/java/tect/host/tpl/module/registry/ModuleRegistry.java b/src/main/java/tect/host/tpl/module/registry/ModuleRegistry.java index bf490ff..bbfaa48 100644 --- a/src/main/java/tect/host/tpl/module/registry/ModuleRegistry.java +++ b/src/main/java/tect/host/tpl/module/registry/ModuleRegistry.java @@ -19,6 +19,7 @@ import tect.host.tpl.module.impl.chat.channel.ChannelModule; import tect.host.tpl.module.impl.chat.chatcooldown.ChatCooldownModule; import tect.host.tpl.module.impl.chat.chatplaceholders.ChatPlaceholdersModule; +import tect.host.tpl.module.impl.chat.clickablelinks.ClickableLinksModule; import tect.host.tpl.module.impl.chat.colorchat.ColorChatModule; import tect.host.tpl.module.impl.chat.format.FormatModule; import tect.host.tpl.module.impl.chat.grammar.GrammarModule; @@ -83,6 +84,8 @@ private ModuleRegistry() {} .phase(ModulePhase.PRE_PROCESS).priority(70).build(), ModuleDescriptor.builder("colorchat", "colorchat", ColorChatModule::new) .phase(ModulePhase.PRE_PROCESS).priority(80).build(), + ModuleDescriptor.builder("clickable-links", "clickable-links", ClickableLinksModule::new) + .phase(ModulePhase.PRE_PROCESS).priority(85).build(), ModuleDescriptor.builder("group", "group", GroupModule::new) .phase(ModulePhase.FORMAT).priority(99).build(), ModuleDescriptor.builder("format", "format", FormatModule::new) diff --git a/src/main/java/tect/host/tpl/util/text/UrlPatterns.java b/src/main/java/tect/host/tpl/util/text/UrlPatterns.java new file mode 100644 index 0000000..07479f4 --- /dev/null +++ b/src/main/java/tect/host/tpl/util/text/UrlPatterns.java @@ -0,0 +1,18 @@ +package tect.host.tpl.util.text; + +import org.jspecify.annotations.NonNull; + +import java.util.regex.Pattern; + +public final class UrlPatterns { + + private UrlPatterns() {} + + public static final @NonNull Pattern DOMAIN_URL = Pattern.compile( + "(?:https?://|s?ftp://|www\\.)?" + + "(?:[a-z0-9](?:[a-z0-9\\-]{0,61}[a-z0-9])?\\.)+" + + "[a-z]{2,}" + + "(?:/\\S*)?", + Pattern.CASE_INSENSITIVE + ); +} \ No newline at end of file