Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.eternalcode.formatter.adventure.AdventureUrlPostProcessor;
import com.eternalcode.formatter.adventure.TextColorTagResolver;
import com.eternalcode.formatter.legacy.Legacy;
import com.eternalcode.formatter.minedown.MineDown;
import com.eternalcode.formatter.placeholder.PlaceholderRegistry;
import com.eternalcode.formatter.rank.ChatRankProvider;
import com.eternalcode.formatter.template.TemplateService;
Expand Down Expand Up @@ -135,6 +136,11 @@ private TagResolver.Single namePlaceholder(Player sender) {

private TagResolver.Single messagePlaceholder(Player sender, String rawMessage) {
TagResolver permittedTags = this.providePermittedTags(sender);

if (this.settings.isMineDownEnabled()) {
rawMessage = MineDown.mineDownToAdventure(rawMessage, permittedTags);
}

rawMessage = Legacy.legacyToAdventure(rawMessage, permission -> sender.hasPermission(permission));
Component componentMessage = EMPTY_MESSAGE_DESERIALIZER.deserialize(rawMessage, permittedTags);
return Placeholder.component("message", componentMessage);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ public interface ChatSettings {

boolean isReceiveUpdates();

boolean isMineDownEnabled();

String getRawFormat(String rank);

}
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
package com.eternalcode.formatter.adventure;

import java.util.function.UnaryOperator;
import java.util.regex.Pattern;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TextReplacementConfig;
import net.kyori.adventure.text.event.ClickEvent;
import org.jetbrains.annotations.NotNull;

public class AdventureUrlPostProcessor implements UnaryOperator<Component> {

private static final Pattern URL_PATTERN = Pattern.compile("https?:\\/\\/(www\\.)?[-a-zA-Z0-9@:%._\\+~#=]{1,256}\\.[a-zA-Z0-9()]{1,6}\\b([-a-zA-Z0-9()!@:%_\\+.~#?&\\/\\/=]*)");

public static final @NotNull TextReplacementConfig CLICKABLE_URL_CONFIG = TextReplacementConfig.builder()
.match(URL_PATTERN)
.match(Urls.URL_PATTERN)
.replacement(url -> url.clickEvent(ClickEvent.openUrl(url.content())))
.build();

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.eternalcode.formatter.adventure;

import java.util.ArrayList;
import java.util.List;
import java.util.function.UnaryOperator;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public final class Urls {

public static final Pattern URL_PATTERN = Pattern.compile("https?:\\/\\/(www\\.)?[-a-zA-Z0-9@:%._\\+~#=]{1,256}\\.[a-zA-Z0-9()]{1,6}\\b([-a-zA-Z0-9()!@:%_\\+.~#?&\\/\\/=]*)");

// Private use characters, so no formatting syntax can match them or the index between them.
private static final char MASK_START = '';
private static final char MASK_END = '';
private static final Pattern MASK_PATTERN = Pattern.compile(MASK_START + "(\\d+)" + MASK_END);

private Urls() {
}

/**
* Applies the transformation to the input while keeping every URL in it unchanged,
* so formatting syntax such as {@code &b} or {@code __} inside a URL is not translated.
*/
public static String preserving(String input, UnaryOperator<String> transformation) {
List<String> urls = new ArrayList<>();

String masked = URL_PATTERN.matcher(input).replaceAll(match -> {
urls.add(match.group());
return MASK_START + String.valueOf(urls.size() - 1) + MASK_END;
});

String transformed = transformation.apply(masked);

return MASK_PATTERN.matcher(transformed).replaceAll(match -> {
int index = Integer.parseInt(match.group(1));
return Matcher.quoteReplacement(index < urls.size() ? urls.get(index) : match.group());
});
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,16 @@ public class PluginConfig implements ChatSettings, TemplateRepository {
@Description({ " ", "# Do you want to receive updates about new versions of ChatFormatter?" })
public boolean receiveUpdates = true;

@Description({ " ", "# Do you want to enable MineDown syntax in player messages? (Markdown-like formatting for Minecraft)",
"# It works on top of MiniMessage and legacy colors, so all of them can be mixed in one message.",
"# Supported: **bold**, ##italic##, __underlined__, ~~strikethrough~~, ??obfuscated??, &red&, &#ff0000&,",
"# &#ff0000-#0000ff& (gradient), &rainbow&, [text](https://url), [text](/command),",
"# [text](red bold hover=Hover text), [text](suggest_command=/msg Steve), [text](copy_to_clipboard=...),",
"# [text](insert=...), [text](font=minecraft:uniform)",
"# Every MineDown element requires the same permission as its MiniMessage equivalent (e.g. **bold** -> chatformatter.decorations.bold).",
"# See https://github.com/Phoenix616/MineDown for more information." })
public boolean mineDownSupport = false;


@Description({ " ", "# Chat format for ranks (Vault) Support mini-messages and legacy colors" })
@Description({ " ", "# We're recommending to use webui for mini-messages: https://webui.adventure.kyori.net/" })
Expand Down Expand Up @@ -93,6 +103,11 @@ public boolean isReceiveUpdates() {
return this.receiveUpdates;
}

@Override
public boolean isMineDownEnabled() {
return this.mineDownSupport;
}

@Override
public String getRawFormat(String rank) {
return this.format.getOrDefault(rank, this.defaultFormat);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package com.eternalcode.formatter.legacy;

import com.eternalcode.formatter.adventure.Urls;
import com.google.common.collect.ImmutableMap;
import java.util.Set;
import java.util.function.Predicate;

import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.jetbrains.annotations.VisibleForTesting;

Expand All @@ -14,6 +16,9 @@ public final class Legacy {
private static final Pattern HEX_LEGACY_PATTERN = Pattern.compile("(?i)&#([0-9A-F]{6})");
private static final Pattern HEX_LEGACY_VANILLA_PATTERN = Pattern.compile("(?i)&x(&[0-9A-F]){6}");

// Private use character that temporarily replaces the '&' of a hex color that is not allowed
private static final char HIDDEN_AMPERSAND = '\uE002';

private static final Set<Character> COLORS = Set.of(
'0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
Expand Down Expand Up @@ -86,13 +91,27 @@ public static String legacyToAdventure(String input) {
}

public static String legacyToAdventure(String input, Predicate<String> hasPermission) {
String result = clearSection(input);
result = HEX_LEGACY_VANILLA_PATTERN.matcher(result).replaceAll(matchResult -> {
return Urls.preserving(clearSection(input), text -> translate(text, hasPermission));
}

private static String translate(String input, Predicate<String> hasPermission) {
boolean hasHexPermission = hasPermissionForHex(hasPermission);

String result = HEX_LEGACY_VANILLA_PATTERN.matcher(input).replaceAll(matchResult -> {
if (!hasHexPermission) {
// Hide the '&' so the single color codes of this hex color are not translated below
return Matcher.quoteReplacement(matchResult.group().replace('&', HIDDEN_AMPERSAND));
}

String hexColor = matchResult.group().replace("&x", "").replace("&", "");
return "<#" + hexColor + ">";
});

result = HEX_LEGACY_PATTERN.matcher(result).replaceAll(matchResult -> {
if (!hasHexPermission) {
return Matcher.quoteReplacement(matchResult.group());
}

String hex = matchResult.group(1);
return "<#" + hex + ">";
});
Expand All @@ -106,7 +125,12 @@ public static String legacyToAdventure(String input, Predicate<String> hasPermis
return "&" + color;
});

return result;
return result.replace(HIDDEN_AMPERSAND, '&');
}

// Hex colors are allowed the same way as <#rrggbb> in MiniMessage, which requires chatformatter.color.*
private static boolean hasPermissionForHex(Predicate<String> hasPermission) {
return hasPermission.test("chatformatter.*") || hasPermission.test("chatformatter.color.*");
}

private static boolean hasPermissionForLegacyCode(Predicate<String> hasPermission, char code) {
Expand Down
Loading
Loading