Skip to content

Commit 9792207

Browse files
RollczivLuckyyy
andauthored
GH-51 Use modern api. Fix chat formatter <message> placeholder duplicate. Add permissions per tag resolver. (#51)
* Use modern adventure api methods. * Fix chat formatter <message> placeholder duplicate. Add permissions per tag resolver. * Update README.md * Update README.md * Update permission chat -> chatformatter * Update readme Co-authored-by: Martin Sulikowski <[email protected]>
1 parent b2b830a commit 9792207

File tree

9 files changed

+212
-29
lines changed

9 files changed

+212
-29
lines changed

README.md

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,33 @@
1919
- [MiniMessages Support](https://docs.adventure.kyori.net/minimessage/format.html) with Legacy Colors Support!
2020
- Template System
2121
- Custom Placeholders System
22+
- Per permission miniMessages Tags (check permissions 👇)
2223

2324
### Useful links:
2425
- [Web UI](https://webui.adventure.kyori.net)
2526
- [MiniMessages Format](https://docs.adventure.kyori.net/minimessage/format.html)
2627

2728
### Permissions:
28-
- chatformatter.chat.color - Chat colors for players
29-
- chatformatter.chat.reload - Reload
30-
31-
29+
- chatformatter.color - `<red>`, `<blue>`, etc. tags.
30+
- chatformatter.legacycolor - Allows the use of legacy color codes, such as `&c`, `&4`, `&l`, etc.
31+
- chatformatter.decorations.* - `<bold>`, `<italic>`, `<underlined>`, `<strikethrough>`, and `<obfuscated>` tags.
32+
- chatformatter.decorations.bold - `<bold>`
33+
- chatformatter.decorations.italic - `<italic>`
34+
- chatformatter.decorations.underlined - `<underlined>`
35+
- chatformatter.decorations.strikethrough - `<strikethrough>`
36+
- chatformatter.decorations.obfuscated - `<obfuscated>`
37+
- chatformatter.reset - `<reset>`
38+
- chatformatter.gradient - `<gradient>`
39+
- chatformatter.hover - `<hover>`
40+
- chatformatter.click - `<click>`
41+
- chatformatter.insertion - `<insertion>`
42+
- chatformatter.font - `<font>`
43+
- chatformatter.transition - `<transition>`
44+
- chatformatter.translatable - `<lang>`
45+
- chatformatter.selector - `<selector>`
46+
- chatformatter.keybind - `<key>`
47+
- chatformatter.newline - `<newline>`
48+
- chatformatter.chat.reload - reload the plugin `/chatformatter reload`
3249

3350
### config.yml
3451
```yaml
@@ -38,7 +55,7 @@
3855
# | |___| | | | (_| | |_| _| (_) | | | | | | | | (_| | |_| || __/ |
3956
# \____|_| |_|\__,_|\__|_| \___/|_| |_| |_| |_|\__,_|\__|\__\___|_|
4057

41-
# Do you want to use pre chat format? (Other plugins could join custom prefixes etc.)
58+
# Do you want to use pre-chat format? (Other plugins could join custom prefixes etc.)
4259
# INFO: This option requires to use custom badges like {displayname} and {message} in each message.
4360
preFormatting: false
4461
defaultFormat: "{displayname} {arrow_right} {message}"

chat-formatter-test/src/com/eternalcode/formatter/legacy/LegacyTest.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ void decolor() {
1414
@Test
1515
void shadow() {
1616
assertShadow("&7", "<ampersand>7");
17+
assertShadow("&2 siema &l siema", "<ampersand>2 siema <ampersand>l siema");
18+
assertShadow("&2 siema &l &5yo&&8", "<ampersand>2 siema <ampersand>l <ampersand>5yo&<ampersand>8");
1719
assertShadow("&&7", "&<ampersand>7");
1820
assertShadow("&#c", "<ampersand>#c");
1921
assertShadow("<ampersand>7", "&7", "<ampersand>7");
@@ -22,16 +24,16 @@ void shadow() {
2224
}
2325

2426
private void assertShadow(String input, String output, String expectedShadowed) {
25-
String shadowed = Legacy.shadow(input);
26-
String deshadowed = Legacy.deshadow(shadowed);
27+
String shadowed = Legacy.ampersandToPlaceholder(input);
28+
String deshadowed = Legacy.placeholderToAmpersand(shadowed);
2729

2830
assertEquals(expectedShadowed, shadowed);
2931
assertEquals(output, deshadowed);
3032
}
3133

3234
private void assertShadow(String input, String expectedShadowed) {
33-
String shadowed = Legacy.shadow(input);
34-
String deshadowed = Legacy.deshadow(shadowed);
35+
String shadowed = Legacy.ampersandToPlaceholder(input);
36+
String deshadowed = Legacy.placeholderToAmpersand(shadowed);
3537

3638
assertEquals(expectedShadowed, shadowed);
3739
assertEquals(input, deshadowed);

chat-formatter/src/com/eternalcode/formatter/ChatController.java

Lines changed: 68 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,67 @@
11
package com.eternalcode.formatter;
22

33
import com.eternalcode.formatter.legacy.Legacy;
4+
import com.eternalcode.formatter.legacy.LegacyPostMessageProcessor;
5+
import com.eternalcode.formatter.legacy.LegacyPreProcessor;
46
import com.eternalcode.formatter.placeholder.PlaceholderRegistry;
57
import com.eternalcode.formatter.preparatory.ChatPreparatoryService;
68
import com.eternalcode.formatter.preparatory.ChatPrepareResult;
9+
import com.eternalcode.formatter.adventure.PlayerSignedMessage;
710
import com.eternalcode.formatter.template.TemplateService;
11+
import com.google.common.collect.ImmutableMap;
812
import net.kyori.adventure.audience.Audience;
13+
import net.kyori.adventure.chat.ChatType;
914
import net.kyori.adventure.identity.Identity;
1015
import net.kyori.adventure.platform.AudienceProvider;
1116
import net.kyori.adventure.text.Component;
17+
import net.kyori.adventure.text.format.TextDecoration;
1218
import net.kyori.adventure.text.minimessage.MiniMessage;
1319
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
1420
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
21+
import net.kyori.adventure.text.minimessage.tag.standard.StandardTags;
1522
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
1623
import org.bukkit.entity.Player;
1724
import org.bukkit.event.EventHandler;
1825
import org.bukkit.event.EventPriority;
1926
import org.bukkit.event.Listener;
2027
import org.bukkit.event.player.AsyncPlayerChatEvent;
2128

29+
import java.util.ArrayList;
30+
import java.util.List;
31+
import java.util.Map;
2232
import java.util.Set;
2333

2434
class ChatController implements Listener {
2535

36+
private static final String PERMISSION_ALL = "chatformatter.*";
37+
private static final String PERMISSION_LEGACY = "chatformatter.legacycolor";
38+
private static final Map<String, TagResolver> TAG_RESOLVERS_BY_PERMISSION = new ImmutableMap.Builder<String, TagResolver>()
39+
.put("chatformatter.color", StandardTags.color())
40+
.put("chatformatter.decorations.*", StandardTags.decorations())
41+
.put("chatformatter.decorations.bold", StandardTags.decorations(TextDecoration.BOLD))
42+
.put("chatformatter.decorations.italic", StandardTags.decorations(TextDecoration.ITALIC))
43+
.put("chatformatter.decorations.underlined", StandardTags.decorations(TextDecoration.UNDERLINED))
44+
.put("chatformatter.decorations.strikethrough", StandardTags.decorations(TextDecoration.STRIKETHROUGH))
45+
.put("chatformatter.decorations.obfuscated", StandardTags.decorations(TextDecoration.OBFUSCATED))
46+
.put("chatformatter.reset", StandardTags.reset())
47+
.put("chatformatter.gradient", StandardTags.gradient())
48+
.put("chatformatter.hover", StandardTags.hoverEvent())
49+
.put("chatformatter.click", StandardTags.clickEvent())
50+
.put("chatformatter.insertion", StandardTags.insertion())
51+
.put("chatformatter.font", StandardTags.font())
52+
.put("chatformatter.transition", StandardTags.transition())
53+
.put("chatformatter.translatable", StandardTags.translatable())
54+
.put("chatformatter.selector", StandardTags.selector())
55+
.put("chatformatter.keybind", StandardTags.keybind())
56+
.put("chatformatter.newline", StandardTags.newline())
57+
.build();
58+
2659
private static final GsonComponentSerializer GSON = GsonComponentSerializer.gson();
60+
private static final MiniMessage MESSAGE_DESERIALIZER = MiniMessage.builder()
61+
.tags(TagResolver.empty())
62+
.preProcessor(new LegacyPreProcessor())
63+
.postProcessor(new LegacyPostMessageProcessor())
64+
.build();
2765

2866
private final AudienceProvider audienceProvider;
2967
private final MiniMessage miniMessage;
@@ -71,7 +109,7 @@ void onChat(AsyncPlayerChatEvent event) {
71109
message = this.templateService.applyTemplates(message);
72110
message = this.placeholderRegistry.format(message, player);
73111

74-
Component messageComponent = miniMessage.deserialize(message, this.messageTag(event), this.displaynameTag(event));
112+
Component messageComponent = miniMessage.deserialize(message, this.createTagResolvers(event));
75113

76114
Set<Player> recipients = event.getRecipients();
77115

@@ -86,27 +124,48 @@ void onChat(AsyncPlayerChatEvent event) {
86124
recipients = result.getReceivers();
87125
}
88126

127+
PlayerSignedMessage signedMessage = new PlayerSignedMessage(messageComponent, identity);
128+
ChatType.Bound chatType = ChatType.CHAT.bind(Component.text("chat"));
129+
89130
for (Player recipient : recipients) {
90131
Audience recipientAudience = this.audienceProvider.player(recipient.getUniqueId());
91132

92-
recipientAudience.sendMessage(identity, messageComponent);
133+
recipientAudience.sendMessage(signedMessage, chatType);
93134
}
94135

95-
this.audienceProvider.console().sendMessage(identity, messageComponent);
136+
this.audienceProvider.console().sendMessage(signedMessage, chatType);
96137
}
97138

98-
private TagResolver.Single messageTag(AsyncPlayerChatEvent event) {
139+
private TagResolver createTagResolvers(AsyncPlayerChatEvent event) {
99140
String message = event.getMessage();
100141
Player player = event.getPlayer();
101142

102-
return player.hasPermission("chatformatter.chat.color")
103-
? Placeholder.parsed("message", message)
104-
: Placeholder.unparsed("message", Legacy.shadow(message));
143+
TagResolver.Single displayNamePlaceholder = Placeholder.parsed("displayname", Legacy.clearSection(event.getPlayer().getDisplayName()));
144+
145+
String safeMessage = player.hasPermission(PERMISSION_LEGACY)
146+
? message
147+
: Legacy.ampersandToPlaceholder(message);
148+
Component componentMessage = MESSAGE_DESERIALIZER.deserialize(safeMessage, this.messageResolver(player));
149+
TagResolver.Single messagePlaceholder = Placeholder.component("message", componentMessage);
150+
151+
return TagResolver.resolver(displayNamePlaceholder, messagePlaceholder);
105152
}
106153

154+
private TagResolver messageResolver(Player player) {
155+
List<TagResolver> tagResolvers = new ArrayList<>();
156+
157+
if (player.hasPermission(PERMISSION_ALL)) {
158+
tagResolvers.addAll(TAG_RESOLVERS_BY_PERMISSION.values());
159+
return TagResolver.resolver(tagResolvers);
160+
}
161+
162+
for (Map.Entry<String, TagResolver> entry : TAG_RESOLVERS_BY_PERMISSION.entrySet()) {
163+
if (player.hasPermission(entry.getKey())) {
164+
tagResolvers.add(entry.getValue());
165+
}
166+
}
107167

108-
private TagResolver.Single displaynameTag(AsyncPlayerChatEvent event) {
109-
return Placeholder.parsed("displayname", Legacy.clearSection(event.getPlayer().getDisplayName()));
168+
return TagResolver.resolver(tagResolvers);
110169
}
111170

112171
}

chat-formatter/src/com/eternalcode/formatter/ChatFormatterCommand.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import com.google.common.base.Stopwatch;
55
import dev.rollczi.litecommands.command.execute.Execute;
66
import dev.rollczi.litecommands.command.permission.Permission;
7-
import dev.rollczi.litecommands.command.section.Section;
7+
import dev.rollczi.litecommands.command.route.Route;
88
import net.kyori.adventure.audience.Audience;
99
import net.kyori.adventure.platform.AudienceProvider;
1010
import net.kyori.adventure.text.Component;
@@ -14,7 +14,7 @@
1414

1515
import java.util.concurrent.TimeUnit;
1616

17-
@Section(route = "chatformatter")
17+
@Route(name = "chatformatter")
1818
@Permission("chatformatter.chat.reload")
1919
class ChatFormatterCommand {
2020

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package com.eternalcode.formatter.adventure;
2+
3+
import net.kyori.adventure.chat.SignedMessage;
4+
import net.kyori.adventure.identity.Identity;
5+
import net.kyori.adventure.text.Component;
6+
import org.jetbrains.annotations.NotNull;
7+
import org.jetbrains.annotations.Nullable;
8+
9+
import java.security.SecureRandom;
10+
import java.time.Instant;
11+
12+
public class PlayerSignedMessage implements SignedMessage {
13+
14+
private static final SecureRandom RANDOM = new SecureRandom();
15+
16+
private final Instant instant;
17+
private final long salt;
18+
private final Component unsignedContent;
19+
private final String message;
20+
private final Identity identity;
21+
22+
public PlayerSignedMessage(Component unsignedContent, Identity identity) {
23+
this.identity = identity;
24+
this.instant = Instant.now();
25+
this.salt = RANDOM.nextLong();
26+
this.unsignedContent = unsignedContent;
27+
this.message = "-";
28+
}
29+
30+
@Override
31+
public @NotNull Instant timestamp() {
32+
return this.instant;
33+
}
34+
35+
@Override
36+
public long salt() {
37+
return this.salt;
38+
}
39+
40+
@Override
41+
public Signature signature() {
42+
return null;
43+
}
44+
45+
@Override
46+
public @Nullable Component unsignedContent() {
47+
return this.unsignedContent;
48+
}
49+
50+
@Override
51+
public @NotNull String message() {
52+
return this.message;
53+
}
54+
55+
@Override
56+
public @NotNull Identity identity() {
57+
return identity;
58+
}
59+
60+
}

chat-formatter/src/com/eternalcode/formatter/config/PluginConfig.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public class PluginConfig implements ChatSettings, PlaceholderStack, TemplateRep
2222
@Description("# \\____|_| |_|\\__,_|\\__|_| \\___/|_| |_| |_| |_|\\__,_|\\__|\\__\\___|_| ")
2323
@Description({ " " })
2424

25-
@Description("# Do you want to use pre chat format? (Other plugins could join custom prefixes etc.)")
25+
@Description("# Do you want to use pre-chat format? (Other plugins could join custom prefixes etc.)")
2626
@Description("# INFO: This option requires to use custom badges like {displayname} and {message} in each message.")
2727
public boolean preFormatting = false;
2828

chat-formatter/src/com/eternalcode/formatter/legacy/Legacy.java

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ public final class Legacy {
1919
private Legacy() {
2020
}
2121

22-
public static final LegacyComponentSerializer LEGACY_SERIALIZER = LegacyComponentSerializer.builder()
22+
public static final LegacyComponentSerializer LEGACY_AMPERSAND_SERIALIZER = LegacyComponentSerializer.builder()
2323
.hexColors()
2424
.character('&')
2525
.hexCharacter('#')
@@ -42,7 +42,7 @@ public static String toAdventureFormat(String text) {
4242
.replace("%2$s", "<message>");
4343
}
4444

45-
public static String shadow(String text) {
45+
public static String ampersandToPlaceholder(String text) {
4646
StringBuilder builder = new StringBuilder(text);
4747
Matcher colorMatcher = AMPERSAND_PATTERN.matcher(builder.toString());
4848

@@ -51,27 +51,28 @@ public static String shadow(String text) {
5151
String color = colorMatcher.group(0);
5252

5353
builder.replace(colorMatcher.start() + matched, colorMatcher.end() + matched, SHADOW + color.charAt(1));
54-
matched++;
54+
matched += SHADOW.length() - 1;
5555
}
5656

5757
return builder.toString();
5858
}
5959

60-
static Component deshadow(Component component) {
60+
static Component placeholderToAmpersand(Component component) {
6161
return component.replaceText(shadowBuilder -> shadowBuilder
6262
.match(ALL_PATTERN)
63-
.replacement((matchResult, builder) -> Component.text(Legacy.deshadow(matchResult.group()))));
63+
.replacement((matchResult, builder) -> Component.text(Legacy.placeholderToAmpersand(matchResult.group()))));
6464
}
6565

66-
static String deshadow(String text) {
66+
static String placeholderToAmpersand(String text) {
6767
Matcher matcher = SHADOW_PATTERN.matcher(text);
6868
StringBuilder builder = new StringBuilder(text);
6969

7070
int matched = 0;
7171
while (matcher.find()) {
7272
int length = (matcher.end() - matcher.start()) - 1;
7373
builder.replace(matcher.start() + matched, matcher.end() + matched - 1, String.valueOf( AMPERSAND ));
74-
matched += length;
74+
matched -= length;
75+
matched += 1;
7576
}
7677

7778
return builder.toString();
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package com.eternalcode.formatter.legacy;
2+
3+
import net.kyori.adventure.text.Component;
4+
import net.kyori.adventure.text.ComponentLike;
5+
import net.kyori.adventure.text.TextComponent;
6+
import net.kyori.adventure.text.TextReplacementConfig;
7+
8+
import java.util.function.BiFunction;
9+
import java.util.function.Consumer;
10+
import java.util.function.UnaryOperator;
11+
import java.util.regex.MatchResult;
12+
13+
public final class LegacyPostMessageProcessor implements UnaryOperator<Component> {
14+
15+
private static final Replacer REPLACER = new Replacer();
16+
17+
@Override
18+
public Component apply(Component component) {
19+
return component.replaceText(REPLACER);
20+
}
21+
22+
private static final class Replacer implements Consumer<TextReplacementConfig.Builder> {
23+
24+
private static final Replacement REPLACEMENT = new Replacement();
25+
26+
@Override
27+
public void accept(TextReplacementConfig.Builder builder) {
28+
builder
29+
.match(Legacy.ALL_PATTERN)
30+
.replacement(REPLACEMENT);
31+
}
32+
33+
}
34+
35+
private static final class Replacement implements BiFunction<MatchResult, TextComponent.Builder, ComponentLike> {
36+
37+
@Override
38+
public ComponentLike apply(MatchResult matchResult, TextComponent.Builder builder) {
39+
return Legacy.LEGACY_AMPERSAND_SERIALIZER.deserialize(matchResult.group());
40+
}
41+
42+
}
43+
44+
}

0 commit comments

Comments
 (0)