Skip to content
Closed
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 @@ -29,6 +29,9 @@ public static String format(Player player, String text, HashMap<String, String>

String formatted = text;

// UniDialog deserializes legacy color codes again. Preserve all inert
// boundaries until that last component conversion, then strip them from
// the already-parsed component instead of exposing active color syntax.
return MessageAPI.colorize(MessageAPI.replacePlaceHolder(formatted, placeholders, true));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,32 @@
import io.github.projectunified.unidialog.paper.dialog.PaperConfirmationDialog;
import io.github.projectunified.unidialog.paper.dialog.PaperMultiActionDialog;
import io.github.projectunified.unidialog.paper.dialog.PaperNoticeDialog;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TextReplacementConfig;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;

/**
* Paper implementation using UniDialog's Paper manager.
*/
public class PaperUniDialogPlatform extends AbstractUniDialogPlatform {

private static final TextReplacementConfig HIDE_INERT_GUARDS = TextReplacementConfig.builder()
.matchLiteral("\u2060").replacement("").build();
private final PaperDialogManager manager;

public PaperUniDialogPlatform(Plugin plugin, String namespace) {
super(plugin, namespace);
this.manager = new PaperDialogManager(plugin, namespace);
this.manager = new PaperDialogManager(plugin, namespace, PaperUniDialogPlatform::renderedText);
}

/**
* Called by UniDialog at its final component deserialization boundary.
* Deserialize while WORD JOINER guards are present, then remove those
* markers from the component tree, never from another parsable string.
*/
static Component renderedText(String prepared) {
return LegacyComponentSerializer.legacySection().deserialize(prepared)
.replaceText(HIDE_INERT_GUARDS);
}

@Override
Expand Down Expand Up @@ -275,6 +290,6 @@ private String getInputLabel(UniDialogInput input) {
}

private String getInputInitial(UniDialogInput input) {
return input.getInitialValue();
return input.getInitialValue() == null ? null : input.getInitialValue().replace("\u2060", "");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
import io.github.projectunified.unidialog.core.payload.DialogPayload;
import io.github.projectunified.unidialog.spigot.SpigotDialogManager;
import io.github.projectunified.unidialog.spigot.opener.SpigotDialogOpener;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.TextComponent;

/**
* Spigot implementation using UniDialog's Spigot manager.
Expand Down Expand Up @@ -69,13 +71,13 @@ public void showNotice(Player player, UniDialogNoticeRequest request) {
registerCustomAction(namespace, actionId, request.getCallback());
}

BungeeNoticeDialog<SpigotDialogOpener> dialog = manager.createNoticeDialog().title(request.getTitle())
.body(builder -> builder.text().text(request.getBody()));
BungeeNoticeDialog<SpigotDialogOpener> dialog = manager.createNoticeDialog().title(renderedText(request.getTitle()))
.body(builder -> builder.text().text(renderedText(request.getBody())));

applyInputs(dialog, request.getInputs());

dialog.action(action -> {
action.label(request.getButtonText());
action.label(renderedText(request.getButtonText()));
action.dynamicCustom(namespace, actionId);
});

Expand All @@ -97,17 +99,17 @@ public void showConfirmation(Player player, UniDialogConfirmationRequest request
}

BungeeConfirmationDialog<SpigotDialogOpener> dialog = manager.createConfirmationDialog()
.title(request.getTitle()).body(builder -> builder.text().text(request.getBody()));
.title(renderedText(request.getTitle())).body(builder -> builder.text().text(renderedText(request.getBody())));

applyInputs(dialog, request.getInputs());

dialog.yesAction(action -> {
action.label(request.getYesText());
action.label(renderedText(request.getYesText()));
action.dynamicCustom(namespace, yesActionId);
});

dialog.noAction(action -> {
action.label(request.getNoText());
action.label(renderedText(request.getNoText()));
action.dynamicCustom(namespace, noActionId);
});

Expand All @@ -118,8 +120,8 @@ public void showConfirmation(Player player, UniDialogConfirmationRequest request
public void showMultiAction(Player player, UniDialogMultiActionRequest request) {
String namespace = resolveNamespace(request.getNamespace());

BungeeMultiActionDialog<SpigotDialogOpener> dialog = manager.createMultiActionDialog().title(request.getTitle())
.body(builder -> builder.text().text(request.getBody())).columns(request.getColumns());
BungeeMultiActionDialog<SpigotDialogOpener> dialog = manager.createMultiActionDialog().title(renderedText(request.getTitle()))
.body(builder -> builder.text().text(renderedText(request.getBody()))).columns(request.getColumns());

applyInputs(dialog, request.getInputs());

Expand All @@ -131,10 +133,10 @@ public void showMultiAction(Player player, UniDialogMultiActionRequest request)
}

dialog.action(action -> {
action.label(button.getText());
action.label(renderedText(button.getText()));

if (button.getTooltip() != null && !button.getTooltip().isEmpty()) {
action.tooltip(button.getTooltip());
action.tooltip(renderedText(button.getTooltip()));
}

Integer width = button.getWidth();
Expand Down Expand Up @@ -190,13 +192,13 @@ private void applyInput(BungeeNoticeDialog<SpigotDialogOpener> dialog, UniDialog

dialog.input(input.getId(), builder -> {
if (input.getType() == UniDialogInput.InputType.BOOLEAN) {
builder.booleanInput().label(getInputLabel(input)).initial(input.isInitialBoolean());
builder.booleanInput().label(renderedText(getInputLabel(input))).initial(input.isInitialBoolean());
} else {
String initial = getInputInitial(input);
if (initial != null && !initial.isEmpty()) {
builder.textInput().label(getInputLabel(input)).initial(initial);
builder.textInput().label(renderedText(getInputLabel(input))).initial(initial);
} else {
builder.textInput().label(getInputLabel(input));
builder.textInput().label(renderedText(getInputLabel(input)));
}
}
});
Expand All @@ -209,13 +211,13 @@ private void applyInput(BungeeConfirmationDialog<SpigotDialogOpener> dialog, Uni

dialog.input(input.getId(), builder -> {
if (input.getType() == UniDialogInput.InputType.BOOLEAN) {
builder.booleanInput().label(getInputLabel(input)).initial(input.isInitialBoolean());
builder.booleanInput().label(renderedText(getInputLabel(input))).initial(input.isInitialBoolean());
} else {
String initial = getInputInitial(input);
if (initial != null && !initial.isEmpty()) {
builder.textInput().label(getInputLabel(input)).initial(initial);
builder.textInput().label(renderedText(getInputLabel(input))).initial(initial);
} else {
builder.textInput().label(getInputLabel(input));
builder.textInput().label(renderedText(getInputLabel(input)));
}
}
});
Expand All @@ -228,13 +230,13 @@ private void applyInput(BungeeMultiActionDialog<SpigotDialogOpener> dialog, UniD

dialog.input(input.getId(), builder -> {
if (input.getType() == UniDialogInput.InputType.BOOLEAN) {
builder.booleanInput().label(getInputLabel(input)).initial(input.isInitialBoolean());
builder.booleanInput().label(renderedText(getInputLabel(input))).initial(input.isInitialBoolean());
} else {
String initial = getInputInitial(input);
if (initial != null && !initial.isEmpty()) {
builder.textInput().label(getInputLabel(input)).initial(initial);
builder.textInput().label(renderedText(getInputLabel(input))).initial(initial);
} else {
builder.textInput().label(getInputLabel(input));
builder.textInput().label(renderedText(getInputLabel(input)));
}
}
});
Expand All @@ -250,7 +252,27 @@ private String getInputLabel(UniDialogInput input) {
return label;
}

/**
* UniDialog's Spigot methods deserialize Bungee legacy formatting again.
* Let that final conversion see the inert guards, then drop them from text
* components without reparsing color or click-action syntax.
*/
static BaseComponent renderedText(String prepared) {
BaseComponent component = TextComponent.fromLegacy(prepared);
stripGuards(component);
return component;
}

private static void stripGuards(BaseComponent component) {
if (component instanceof TextComponent text) {
text.setText(text.getText().replace("\u2060", ""));
}
if (component.getExtra() != null) {
for (BaseComponent extra : component.getExtra()) stripGuards(extra);
}
}

private String getInputInitial(UniDialogInput input) {
return input.getInitialValue();
return input.getInitialValue() == null ? null : input.getInitialValue().replace("\u2060", "");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,15 @@ protected boolean canReplace(ActionBarEntry current, ActionBarMessage nextMessag
*/
@SuppressWarnings("deprecation")
protected void sendNow(Player player, String message) {
player.spigot().sendMessage(ChatMessageType.ACTION_BAR, TextComponent.fromLegacyText(message));
// Parse legacy styling while the inert boundaries still separate
// untrusted text from formatting tokens. Remove them only from the
// finished components, after parsing can no longer be influenced.
net.md_5.bungee.api.chat.BaseComponent[] rendered = TextComponent.fromLegacyText(message);
for (net.md_5.bungee.api.chat.BaseComponent component : rendered) {
if (component instanceof TextComponent text) {
text.setText(text.getText().replace("\u2060", ""));
}
}
player.spigot().sendMessage(ChatMessageType.ACTION_BAR, rendered);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package com.bencodez.simpleapi.dialog;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.util.HashMap;

import org.junit.jupiter.api.Test;

import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;

class DialogTextFormatterTest {

@Test
void removesMarkersOnlyAfterSpigotAndPaperComponentParsing() {
HashMap<String, String> values = new HashMap<>();
values.put("site", "\u2060Minecraft Menu\u2060");

String formatted = DialogTextFormatter.format(null, "&7Site: &e%site%", values);
assertTrue(formatted.contains("\u2060"), "Keep guards through UniDialog's final legacy parser");
assertEquals("Site: Minecraft Menu", SpigotUniDialogPlatform.renderedText(formatted).toPlainText());
assertEquals("Site: Minecraft Menu", PlainTextComponentSerializer.plainText()
.serialize(PaperUniDialogPlatform.renderedText(formatted)));
}

@Test
void boundariesProtectBothAmpersandAndSectionSignCodesAcrossSubstitution() {
HashMap<String, String> values = new HashMap<>();
values.put("site", "\u2060a\u2060");

// Color code can be formed only if the boundaries are dropped BEFORE
// either parser runs. Verify the finished components still show literal
// text in both legacy serializers, including when a template has §.
String ampersand = DialogTextFormatter.format(null, "&%site%", values);
assertEquals("&a", SpigotUniDialogPlatform.renderedText(ampersand).toPlainText());
assertEquals("&a", PlainTextComponentSerializer.plainText()
.serialize(PaperUniDialogPlatform.renderedText(ampersand)));

String section = DialogTextFormatter.format(null, "\u00A7%site%", values);
// A legacy parser may drop the isolated invalid § character, but
// the site name must remain text, not turn into a green color token.
var spigot = SpigotUniDialogPlatform.renderedText(section);
var paper = PaperUniDialogPlatform.renderedText(section);
assertTrue(spigot.toPlainText().endsWith("a"));
assertTrue(PlainTextComponentSerializer.plainText().serialize(paper).endsWith("a"));
assertFalse(spigot.toLegacyText().contains("\u00A7a"));
// Re-serializing a literal § followed by 'a' to a legacy String can
// produce an ambiguous §a sequence; check the actual component style.
org.junit.jupiter.api.Assertions.assertNotEquals(
net.kyori.adventure.text.format.NamedTextColor.GREEN, paper.color());
}

@Test
void nestedPlaceholderNeverResolvesFromGuardedSiteName() {
HashMap<String, String> values = new HashMap<>();
values.put("site", "\u2060%\u2060player_name%\u2060");
values.put("player_name", "NotTheSite");

String formatted = DialogTextFormatter.format(null, "%site%", values);
assertFalse(formatted.contains("NotTheSite"));
assertEquals("%player_name%", SpigotUniDialogPlatform.renderedText(formatted).toPlainText());
assertEquals("%player_name%", PlainTextComponentSerializer.plainText()
.serialize(PaperUniDialogPlatform.renderedText(formatted)));
}
}
Loading