diff --git a/connector/pom.xml b/connector/pom.xml
index d837f057f..db267a717 100644
--- a/connector/pom.xml
+++ b/connector/pom.xml
@@ -143,17 +143,23 @@
compile
- net.kyori
+ com.github.kyoripowered.adventure
adventure-text-serializer-gson
- 4.1.1
+ 4d8a67d798
compile
- net.kyori
+ com.github.kyoripowered.adventure
adventure-text-serializer-legacy
- 4.1.1
+ 0599048
compile
+
+ junit
+ junit
+ 4.13.1
+ test
+
@@ -283,6 +289,15 @@
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ 2.22.0
+
+
+ -Dfile.encoding=${project.build.sourceEncoding}
+
+
diff --git a/connector/src/main/java/org/geysermc/connector/entity/CommandBlockMinecartEntity.java b/connector/src/main/java/org/geysermc/connector/entity/CommandBlockMinecartEntity.java
index 8cabba645..7d34cc795 100644
--- a/connector/src/main/java/org/geysermc/connector/entity/CommandBlockMinecartEntity.java
+++ b/connector/src/main/java/org/geysermc/connector/entity/CommandBlockMinecartEntity.java
@@ -26,13 +26,12 @@
package org.geysermc.connector.entity;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata;
-import com.github.steveice10.mc.protocol.data.message.Message;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
import org.geysermc.connector.entity.type.EntityType;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
-import org.geysermc.connector.utils.MessageUtils;
+import org.geysermc.connector.network.translators.chat.MessageTranslator;
public class CommandBlockMinecartEntity extends DefaultBlockMinecartEntity {
@@ -51,7 +50,7 @@ public class CommandBlockMinecartEntity extends DefaultBlockMinecartEntity {
metadata.put(EntityData.COMMAND_BLOCK_COMMAND, entityMetadata.getValue());
}
if (entityMetadata.getId() == 14) {
- metadata.put(EntityData.COMMAND_BLOCK_LAST_OUTPUT, MessageUtils.getBedrockMessage((Message) entityMetadata.getValue()));
+ metadata.put(EntityData.COMMAND_BLOCK_LAST_OUTPUT, MessageTranslator.convertMessage(entityMetadata.getValue().toString()));
}
super.updateBedrockMetadata(entityMetadata, session);
}
diff --git a/connector/src/main/java/org/geysermc/connector/entity/Entity.java b/connector/src/main/java/org/geysermc/connector/entity/Entity.java
index 20cd2f76b..7b1fa1cf0 100644
--- a/connector/src/main/java/org/geysermc/connector/entity/Entity.java
+++ b/connector/src/main/java/org/geysermc/connector/entity/Entity.java
@@ -54,7 +54,7 @@ import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.item.ItemRegistry;
import org.geysermc.connector.utils.AttributeUtils;
import org.geysermc.connector.utils.ChunkUtils;
-import org.geysermc.connector.utils.MessageUtils;
+import org.geysermc.connector.network.translators.chat.MessageTranslator;
import java.util.ArrayList;
import java.util.HashMap;
@@ -318,7 +318,7 @@ public class Entity {
Message message = (Message) entityMetadata.getValue();
if (message != null)
// Always translate even if it's a TextMessage since there could be translatable parameters
- metadata.put(EntityData.NAMETAG, MessageUtils.getTranslatedBedrockMessage(message, session.getLocale(), true));
+ metadata.put(EntityData.NAMETAG, MessageTranslator.convertMessage(message.toString(), session.getLocale()));
}
break;
case 3: // is custom name visible
diff --git a/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java b/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java
index 8eeae4736..be65525cb 100644
--- a/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java
+++ b/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java
@@ -51,7 +51,7 @@ import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.session.cache.EntityEffectCache;
import org.geysermc.connector.scoreboard.Team;
import org.geysermc.connector.utils.AttributeUtils;
-import org.geysermc.connector.utils.MessageUtils;
+import org.geysermc.connector.network.translators.chat.MessageTranslator;
import java.util.ArrayList;
import java.util.List;
@@ -243,13 +243,13 @@ public class PlayerEntity extends LivingEntity {
String username = this.username;
TextMessage name = (TextMessage) entityMetadata.getValue();
if (name != null) {
- username = MessageUtils.getBedrockMessage(name);
+ username = MessageTranslator.convertMessage(name.toString());
}
Team team = session.getWorldCache().getScoreboard().getTeamFor(username);
if (team != null) {
String displayName = "";
if (team.isVisibleFor(session.getPlayerEntity().getUsername())) {
- displayName = MessageUtils.toChatColor(team.getColor()) + username;
+ displayName = MessageTranslator.toChatColor(team.getColor()) + username;
displayName = team.getCurrentData().getDisplayName(displayName);
}
metadata.put(EntityData.NAMETAG, displayName);
diff --git a/connector/src/main/java/org/geysermc/connector/network/ConnectorServerEventHandler.java b/connector/src/main/java/org/geysermc/connector/network/ConnectorServerEventHandler.java
index 9fb4ad9e1..150d298c7 100644
--- a/connector/src/main/java/org/geysermc/connector/network/ConnectorServerEventHandler.java
+++ b/connector/src/main/java/org/geysermc/connector/network/ConnectorServerEventHandler.java
@@ -25,7 +25,6 @@
package org.geysermc.connector.network;
-import com.github.steveice10.mc.protocol.data.message.MessageSerializer;
import com.nukkitx.protocol.bedrock.BedrockPong;
import com.nukkitx.protocol.bedrock.BedrockServerEventHandler;
import com.nukkitx.protocol.bedrock.BedrockServerSession;
@@ -36,7 +35,7 @@ import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.configuration.GeyserConfiguration;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.ping.IGeyserPingPassthrough;
-import org.geysermc.connector.utils.MessageUtils;
+import org.geysermc.connector.network.translators.chat.MessageTranslator;
import org.geysermc.connector.utils.LanguageUtils;
import java.net.InetSocketAddress;
@@ -76,7 +75,7 @@ public class ConnectorServerEventHandler implements BedrockServerEventHandler {
pong.setIpv4Port(config.getBedrock().getPort());
if (config.isPassthroughMotd() && pingInfo != null && pingInfo.getDescription() != null) {
- String[] motd = MessageUtils.getBedrockMessage(MessageSerializer.fromString(pingInfo.getDescription())).split("\n");
+ String[] motd = MessageTranslator.convertMessageLenient(pingInfo.getDescription()).split("\n");
String mainMotd = motd[0]; // First line of the motd.
String subMotd = (motd.length != 1) ? motd[1] : ""; // Second line of the motd if present, otherwise blank.
diff --git a/connector/src/main/java/org/geysermc/connector/network/QueryPacketHandler.java b/connector/src/main/java/org/geysermc/connector/network/QueryPacketHandler.java
index 7faf36bdd..510bba2d2 100644
--- a/connector/src/main/java/org/geysermc/connector/network/QueryPacketHandler.java
+++ b/connector/src/main/java/org/geysermc/connector/network/QueryPacketHandler.java
@@ -25,12 +25,11 @@
package org.geysermc.connector.network;
-import com.github.steveice10.mc.protocol.data.message.MessageSerializer;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import org.geysermc.connector.common.ping.GeyserPingInfo;
import org.geysermc.connector.GeyserConnector;
-import org.geysermc.connector.utils.MessageUtils;
+import org.geysermc.connector.network.translators.chat.MessageTranslator;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@@ -148,7 +147,7 @@ public class QueryPacketHandler {
}
if (connector.getConfig().isPassthroughMotd() && pingInfo != null) {
- String[] javaMotd = MessageUtils.getBedrockMessage(MessageSerializer.fromString(pingInfo.getDescription())).split("\n");
+ String[] javaMotd = MessageTranslator.convertMessageLenient(pingInfo.getDescription()).split("\n");
motd = javaMotd[0].trim(); // First line of the motd.
} else {
motd = connector.getConfig().getBedrock().getMotd1();
diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java
index a6085e218..00b48a565 100644
--- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java
+++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java
@@ -34,7 +34,6 @@ import com.github.steveice10.mc.protocol.data.SubProtocol;
import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode;
import com.github.steveice10.mc.protocol.data.game.statistic.Statistic;
import com.github.steveice10.mc.protocol.data.game.window.VillagerTrade;
-import com.github.steveice10.mc.protocol.data.message.MessageSerializer;
import com.github.steveice10.mc.protocol.packet.handshake.client.HandshakePacket;
import com.github.steveice10.mc.protocol.packet.ingame.client.world.ClientTeleportConfirmPacket;
import com.github.steveice10.mc.protocol.packet.ingame.server.ServerRespawnPacket;
@@ -66,6 +65,7 @@ import org.geysermc.connector.common.AuthType;
import org.geysermc.connector.entity.Entity;
import org.geysermc.connector.entity.PlayerEntity;
import org.geysermc.connector.inventory.PlayerInventory;
+import org.geysermc.connector.network.translators.chat.MessageTranslator;
import org.geysermc.connector.network.remote.RemoteServer;
import org.geysermc.connector.network.session.auth.AuthData;
import org.geysermc.connector.network.session.auth.BedrockClientData;
@@ -496,7 +496,7 @@ public class GeyserSession implements CommandSender {
event.getCause().printStackTrace();
}
- upstream.disconnect(MessageUtils.getBedrockMessage(MessageSerializer.fromString(event.getReason())));
+ upstream.disconnect(MessageTranslator.convertMessageLenient(event.getReason()));
}
@Override
diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/BossBar.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/BossBar.java
index fdc609ab9..7eadb7942 100644
--- a/connector/src/main/java/org/geysermc/connector/network/session/cache/BossBar.java
+++ b/connector/src/main/java/org/geysermc/connector/network/session/cache/BossBar.java
@@ -33,7 +33,7 @@ import com.nukkitx.protocol.bedrock.packet.BossEventPacket;
import com.nukkitx.protocol.bedrock.packet.RemoveEntityPacket;
import lombok.AllArgsConstructor;
import org.geysermc.connector.network.session.GeyserSession;
-import org.geysermc.connector.utils.MessageUtils;
+import org.geysermc.connector.network.translators.chat.MessageTranslator;
@AllArgsConstructor
public class BossBar {
@@ -58,7 +58,7 @@ public class BossBar {
BossEventPacket bossEventPacket = new BossEventPacket();
bossEventPacket.setBossUniqueEntityId(entityId);
bossEventPacket.setAction(BossEventPacket.Action.CREATE);
- bossEventPacket.setTitle(MessageUtils.getTranslatedBedrockMessage(title, session.getLocale()));
+ bossEventPacket.setTitle(MessageTranslator.convertMessage(title.toString(), session.getLocale()));
bossEventPacket.setHealthPercentage(health);
bossEventPacket.setColor(color); //ignored by client
bossEventPacket.setOverlay(overlay);
@@ -72,7 +72,7 @@ public class BossBar {
BossEventPacket bossEventPacket = new BossEventPacket();
bossEventPacket.setBossUniqueEntityId(entityId);
bossEventPacket.setAction(BossEventPacket.Action.UPDATE_NAME);
- bossEventPacket.setTitle(MessageUtils.getTranslatedBedrockMessage(title, session.getLocale()));
+ bossEventPacket.setTitle(MessageTranslator.convertMessage(title.toString(), session.getLocale()));
session.sendUpstreamPacket(bossEventPacket);
}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockCommandRequestTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockCommandRequestTranslator.java
index 1f31367c3..f572538e7 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockCommandRequestTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockCommandRequestTranslator.java
@@ -34,7 +34,7 @@ import org.geysermc.connector.network.translators.Translator;
import com.github.steveice10.mc.protocol.packet.ingame.client.ClientChatPacket;
import com.nukkitx.protocol.bedrock.packet.CommandRequestPacket;
-import org.geysermc.connector.utils.MessageUtils;
+import org.geysermc.connector.network.translators.chat.MessageTranslator;
@Translator(packet = CommandRequestPacket.class)
public class BedrockCommandRequestTranslator extends PacketTranslator {
@@ -48,7 +48,7 @@ public class BedrockCommandRequestTranslator extends PacketTranslator {
@@ -40,7 +40,7 @@ public class BedrockTextTranslator extends PacketTranslator {
public void translate(TextPacket packet, GeyserSession session) {
String message = packet.getMessage().replaceAll("^\\.", "/").trim();
- if (MessageUtils.isTooLong(message, session)) {
+ if (MessageTranslator.isTooLong(message, session)) {
return;
}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/chat/MessageTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/chat/MessageTranslator.java
new file mode 100644
index 000000000..be01362fa
--- /dev/null
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/chat/MessageTranslator.java
@@ -0,0 +1,278 @@
+/*
+ * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @author GeyserMC
+ * @link https://github.com/GeyserMC/Geyser
+ */
+
+package org.geysermc.connector.network.translators.chat;
+
+import com.github.steveice10.mc.protocol.data.game.scoreboard.TeamColor;
+import com.github.steveice10.mc.protocol.data.message.style.ChatColor;
+import com.github.steveice10.mc.protocol.data.message.style.ChatFormat;
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.renderer.TranslatableComponentRenderer;
+import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
+import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
+import net.kyori.adventure.translation.TranslationRegistry;
+import org.geysermc.connector.network.session.GeyserSession;
+import org.geysermc.connector.utils.LanguageUtils;
+
+import java.util.*;
+
+public class MessageTranslator {
+
+ // These are used for handling the translations of the messages
+ private static final TranslationRegistry REGISTRY = new MinecraftTranslationRegistry();
+ private static final TranslatableComponentRenderer RENDERER = TranslatableComponentRenderer.usingTranslationSource(REGISTRY);
+
+ // Store team colors for player names
+ private static final Map TEAM_COLORS = new HashMap<>();
+
+ static {
+ TEAM_COLORS.put(TeamColor.BLACK, getColor(ChatColor.BLACK));
+ TEAM_COLORS.put(TeamColor.DARK_BLUE, getColor(ChatColor.DARK_BLUE));
+ TEAM_COLORS.put(TeamColor.DARK_GREEN, getColor(ChatColor.DARK_GREEN));
+ TEAM_COLORS.put(TeamColor.DARK_AQUA, getColor(ChatColor.DARK_AQUA));
+ TEAM_COLORS.put(TeamColor.DARK_RED, getColor(ChatColor.DARK_RED));
+ TEAM_COLORS.put(TeamColor.DARK_PURPLE, getColor(ChatColor.DARK_PURPLE));
+ TEAM_COLORS.put(TeamColor.GOLD, getColor(ChatColor.GOLD));
+ TEAM_COLORS.put(TeamColor.GRAY, getColor(ChatColor.GRAY));
+ TEAM_COLORS.put(TeamColor.DARK_GRAY, getColor(ChatColor.DARK_GRAY));
+ TEAM_COLORS.put(TeamColor.BLUE, getColor(ChatColor.BLUE));
+ TEAM_COLORS.put(TeamColor.GREEN, getColor(ChatColor.GREEN));
+ TEAM_COLORS.put(TeamColor.AQUA, getColor(ChatColor.AQUA));
+ TEAM_COLORS.put(TeamColor.RED, getColor(ChatColor.RED));
+ TEAM_COLORS.put(TeamColor.LIGHT_PURPLE, getColor(ChatColor.LIGHT_PURPLE));
+ TEAM_COLORS.put(TeamColor.YELLOW, getColor(ChatColor.YELLOW));
+ TEAM_COLORS.put(TeamColor.WHITE, getColor(ChatColor.WHITE));
+ TEAM_COLORS.put(TeamColor.OBFUSCATED, getFormat(ChatFormat.OBFUSCATED));
+ TEAM_COLORS.put(TeamColor.BOLD, getFormat(ChatFormat.BOLD));
+ TEAM_COLORS.put(TeamColor.STRIKETHROUGH, getFormat(ChatFormat.STRIKETHROUGH));
+ TEAM_COLORS.put(TeamColor.ITALIC, getFormat(ChatFormat.ITALIC));
+ }
+
+ /**
+ * Convert a Java message to the legacy format ready for bedrock
+ *
+ * @param message Java message
+ * @param locale Locale to use for translation strings
+ * @return Parsed and formatted message for bedrock
+ */
+ public static String convertMessage(String message, String locale) {
+ Component component = GsonComponentSerializer.gson().deserialize(message);
+
+ // Get a Locale from the given locale string
+ Locale localeCode = Locale.forLanguageTag(locale.replace('_', '-'));
+ component = RENDERER.render(component, localeCode);
+
+ return LegacyComponentSerializer.legacySection().serialize(component);
+ }
+
+ public static String convertMessage(String message) {
+ return convertMessage(message, LanguageUtils.getDefaultLocale());
+ }
+
+ /**
+ * Verifies the message is valid JSON in case it's plaintext. Works around GsonComponentSeraializer not using lenient mode.
+ * See https://wiki.vg/Chat for messages sent in lenient mode, and for a description on leniency.
+ *
+ * @param message Potentially lenient JSON message
+ * @param locale Locale to use for translation strings
+ * @return Bedrock formatted message
+ */
+ public static String convertMessageLenient(String message, String locale) {
+ if (isMessage(message)) {
+ return convertMessage(message, locale);
+ } else {
+ String convertedMessage = convertMessage(convertToJavaMessage(message), locale);
+
+ // We have to do this since Adventure strips the starting reset character
+ if (message.startsWith(getColor(ChatColor.RESET))) {
+ convertedMessage = getColor(ChatColor.RESET) + convertedMessage;
+ }
+
+ return convertedMessage;
+ }
+ }
+
+ public static String convertMessageLenient(String message) {
+ return convertMessageLenient(message, LanguageUtils.getDefaultLocale());
+ }
+
+ /**
+ * Convert a Bedrock message string back to a format Java can understand
+ *
+ * @param message Message to convert
+ * @return The formatted JSON string
+ */
+ public static String convertToJavaMessage(String message) {
+ Component component = LegacyComponentSerializer.legacySection().deserialize(message);
+ return GsonComponentSerializer.gson().serialize(component);
+ }
+
+ /**
+ * Checks if the given text string is a JSON message
+ *
+ * @param text String to test
+ * @return True if its a valid message JSON string, false if not
+ */
+ public static boolean isMessage(String text) {
+ if (text.trim().isEmpty()) {
+ return false;
+ }
+
+ try {
+ GsonComponentSerializer.gson().deserialize(text);
+ } catch (Exception ex) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Convert a {@link ChatColor} into a string for inserting into messages
+ *
+ * @param color {@link ChatColor} to convert
+ * @return The converted color string
+ */
+ private static String getColor(String color) {
+ String base = "\u00a7";
+ switch (color) {
+ case ChatColor.BLACK:
+ base += "0";
+ break;
+ case ChatColor.DARK_BLUE:
+ base += "1";
+ break;
+ case ChatColor.DARK_GREEN:
+ base += "2";
+ break;
+ case ChatColor.DARK_AQUA:
+ base += "3";
+ break;
+ case ChatColor.DARK_RED:
+ base += "4";
+ break;
+ case ChatColor.DARK_PURPLE:
+ base += "5";
+ break;
+ case ChatColor.GOLD:
+ base += "6";
+ break;
+ case ChatColor.GRAY:
+ base += "7";
+ break;
+ case ChatColor.DARK_GRAY:
+ base += "8";
+ break;
+ case ChatColor.BLUE:
+ base += "9";
+ break;
+ case ChatColor.GREEN:
+ base += "a";
+ break;
+ case ChatColor.AQUA:
+ base += "b";
+ break;
+ case ChatColor.RED:
+ base += "c";
+ break;
+ case ChatColor.LIGHT_PURPLE:
+ base += "d";
+ break;
+ case ChatColor.YELLOW:
+ base += "e";
+ break;
+ case ChatColor.WHITE:
+ base += "f";
+ break;
+ case ChatColor.RESET:
+ base += "r";
+ break;
+ default:
+ return "";
+ }
+
+ return base;
+ }
+
+ /**
+ * Convert a {@link ChatFormat} into a string for inserting into messages
+ *
+ * @param format {@link ChatFormat} to convert
+ * @return The converted chat formatting string
+ */
+ private static String getFormat(ChatFormat format) {
+ StringBuilder str = new StringBuilder();
+ String base = "\u00a7";
+ switch (format) {
+ case OBFUSCATED:
+ base += "k";
+ break;
+ case BOLD:
+ base += "l";
+ break;
+ case STRIKETHROUGH:
+ base += "m";
+ break;
+ case UNDERLINED:
+ base += "n";
+ break;
+ case ITALIC:
+ base += "o";
+ break;
+ default:
+ return "";
+ }
+
+ str.append(base);
+
+ return str.toString();
+ }
+
+ /**
+ * Convert a team color to a chat color
+ *
+ * @param teamColor
+ * @return The chat color character
+ */
+ public static String toChatColor(TeamColor teamColor) {
+ return TEAM_COLORS.getOrDefault(teamColor, "");
+ }
+
+ /**
+ * Checks if the given message is over 256 characters (Java edition server chat limit) and sends a message to the user if it is
+ *
+ * @param message Message to check
+ * @param session {@link GeyserSession} for the user
+ * @return True if the message is too long, false if not
+ */
+ public static boolean isTooLong(String message, GeyserSession session) {
+ if (message.length() > 256) {
+ session.sendMessage(LanguageUtils.getPlayerLocaleString("geyser.chat.too_long", session.getLocale(), message.length()));
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/chat/MinecraftTranslationRegistry.java b/connector/src/main/java/org/geysermc/connector/network/translators/chat/MinecraftTranslationRegistry.java
new file mode 100644
index 000000000..a23167ac2
--- /dev/null
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/chat/MinecraftTranslationRegistry.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @author GeyserMC
+ * @link https://github.com/GeyserMC/Geyser
+ */
+
+package org.geysermc.connector.network.translators.chat;
+
+import net.kyori.adventure.key.Key;
+import net.kyori.adventure.translation.TranslationRegistry;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.geysermc.connector.utils.LocaleUtils;
+
+import java.text.MessageFormat;
+import java.util.Locale;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * This class is used for mapping a translation key with the already loaded Java locale data
+ * Used in MessageTranslator.java as part of the KyoriPowered/Adventure library
+ */
+public class MinecraftTranslationRegistry implements TranslationRegistry {
+ @Override
+ public @NonNull Key name() {
+ return Key.key("", "");
+ }
+
+ @Override
+ public @Nullable MessageFormat translate(@NonNull String key, @NonNull Locale locale) {
+ // Get the locale string
+ String localeString = LocaleUtils.getLocaleString(key, locale.toString());
+
+ // Replace the `%s` with numbered inserts `{0}`
+ Pattern p = Pattern.compile("%s");
+ Matcher m = p.matcher(localeString);
+ StringBuffer sb = new StringBuffer();
+ int i = 0;
+ while (m.find()) {
+ m.appendReplacement(sb, "{" + (i++) + "}");
+ }
+ m.appendTail(sb);
+
+ return new MessageFormat(sb.toString(), locale);
+ }
+
+ @Override
+ public void defaultLocale(@NonNull Locale locale) {
+
+ }
+
+ @Override
+ public void register(@NonNull String key, @NonNull Locale locale, @NonNull MessageFormat format) {
+
+ }
+
+ @Override
+ public void unregister(@NonNull String key) {
+
+ }
+}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemTranslator.java
index 55db9a254..00c9138a9 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemTranslator.java
@@ -26,7 +26,6 @@
package org.geysermc.connector.network.translators.item;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack;
-import com.github.steveice10.mc.protocol.data.message.MessageSerializer;
import com.github.steveice10.opennbt.tag.builtin.*;
import com.nukkitx.nbt.NbtList;
import com.nukkitx.nbt.NbtMap;
@@ -44,7 +43,7 @@ import org.geysermc.connector.network.translators.ItemRemapper;
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
import org.geysermc.connector.utils.FileUtils;
import org.geysermc.connector.utils.LanguageUtils;
-import org.geysermc.connector.utils.MessageUtils;
+import org.geysermc.connector.network.translators.chat.MessageTranslator;
import org.reflections.Reflections;
import java.util.*;
@@ -385,26 +384,17 @@ public abstract class ItemTranslator {
public static void translateDisplayProperties(GeyserSession session, CompoundTag tag) {
if (tag != null) {
CompoundTag display = tag.get("display");
- if (display != null && !display.isEmpty() && display.contains("Name")) {
+ if (display != null && display.contains("Name")) {
String name = ((StringTag) display.get("Name")).getValue();
- // If its not a message convert it
- if (!MessageUtils.isMessage(name)) {
- Component component = LegacyComponentSerializer.legacySection().deserialize(name);
- name = GsonComponentSerializer.gson().serialize(component);
- }
+ // Get the translated name and prefix it with a reset char
+ name = MessageTranslator.convertMessageLenient(name, session.getLocale());
- // Check if its a message to translate
- if (MessageUtils.isMessage(name)) {
- // Get the translated name
- name = MessageUtils.getTranslatedBedrockMessage(MessageSerializer.fromString(name), session.getLocale());
+ // Add the new name tag
+ display.put(new StringTag("Name", name));
- // Add the new name tag
- display.put(new StringTag("Name", name));
-
- // Add to the new root tag
- tag.put(display);
- }
+ // Add to the new root tag
+ tag.put(display);
}
}
}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/BasicItemTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/BasicItemTranslator.java
index 1d21bbfb7..3fd9df8a0 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/BasicItemTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/BasicItemTranslator.java
@@ -37,7 +37,6 @@ import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.ItemRemapper;
import org.geysermc.connector.network.translators.item.ItemEntry;
import org.geysermc.connector.network.translators.item.NbtItemStackTranslator;
-import org.geysermc.connector.utils.MessageUtils;
import java.util.ArrayList;
import java.util.List;
@@ -108,7 +107,7 @@ public class BasicItemTranslator extends NbtItemStackTranslator {
private String toBedrockMessage(StringTag tag) {
String message = tag.getValue();
if (message == null) return null;
- TextComponent component = (TextComponent) MessageUtils.phraseJavaMessage(message);
+ TextComponent component = (TextComponent) GsonComponentSerializer.gson().deserialize(message);
String legacy = LegacyComponentSerializer.legacySection().serialize(component);
if (hasFormatting(LegacyComponentSerializer.legacySection().deserialize(legacy))) {
return "§r" + legacy;
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/BookPagesTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/BookPagesTranslator.java
index 41ee4fbca..294dd81ed 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/BookPagesTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/BookPagesTranslator.java
@@ -33,7 +33,7 @@ import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.ItemRemapper;
import org.geysermc.connector.network.translators.item.NbtItemStackTranslator;
import org.geysermc.connector.network.translators.item.ItemEntry;
-import org.geysermc.connector.utils.MessageUtils;
+import org.geysermc.connector.network.translators.chat.MessageTranslator;
import java.util.ArrayList;
import java.util.List;
@@ -56,7 +56,7 @@ public class BookPagesTranslator extends NbtItemStackTranslator {
CompoundTag pageTag = new CompoundTag("");
pageTag.put(new StringTag("photoname", ""));
- pageTag.put(new StringTag("text", MessageUtils.getBedrockMessageLenient(textTag.getValue())));
+ pageTag.put(new StringTag("text", MessageTranslator.convertMessageLenient(textTag.getValue())));
pages.add(pageTag);
}
@@ -78,7 +78,7 @@ public class BookPagesTranslator extends NbtItemStackTranslator {
CompoundTag pageTag = (CompoundTag) tag;
StringTag textTag = pageTag.get("text");
- pages.add(new StringTag(MessageUtils.getJavaMessage(textTag.getValue())));
+ pages.add(new StringTag(MessageTranslator.convertToJavaMessage(textTag.getValue())));
}
itemTag.remove("pages");
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaChatTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaChatTranslator.java
index 186aaf660..f5128ed6f 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaChatTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaChatTranslator.java
@@ -25,15 +25,12 @@
package org.geysermc.connector.network.translators.java;
-import com.github.steveice10.mc.protocol.data.message.TranslationMessage;
import com.github.steveice10.mc.protocol.packet.ingame.server.ServerChatPacket;
import com.nukkitx.protocol.bedrock.packet.TextPacket;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
-import org.geysermc.connector.utils.MessageUtils;
-
-import java.util.List;
+import org.geysermc.connector.network.translators.chat.MessageTranslator;
@Translator(packet = ServerChatPacket.class)
public class JavaChatTranslator extends PacketTranslator {
@@ -59,21 +56,8 @@ public class JavaChatTranslator extends PacketTranslator {
break;
}
- String locale = session.getLocale();
-
- if (packet.getMessage() instanceof TranslationMessage) {
- textPacket.setType(TextPacket.Type.TRANSLATION);
- textPacket.setNeedsTranslation(true);
-
- List paramsTranslated = MessageUtils.getTranslationParams(((TranslationMessage) packet.getMessage()).getWith(), locale, packet.getMessage());
- textPacket.setParameters(paramsTranslated);
-
- textPacket.setMessage(MessageUtils.insertParams(MessageUtils.getTranslatedBedrockMessage(packet.getMessage(), locale, true, packet.getMessage()), paramsTranslated));
- } else {
- textPacket.setNeedsTranslation(false);
-
- textPacket.setMessage(MessageUtils.getTranslatedBedrockMessage(packet.getMessage(), locale, false, packet.getMessage()));
- }
+ textPacket.setNeedsTranslation(false);
+ textPacket.setMessage(MessageTranslator.convertMessage(packet.getMessage().toString(), session.getLocale()));
session.sendUpstreamPacket(textPacket);
}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDisconnectPacket.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDisconnectPacket.java
index f36da367b..1945a8e10 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDisconnectPacket.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDisconnectPacket.java
@@ -29,13 +29,13 @@ import com.github.steveice10.mc.protocol.packet.ingame.server.ServerDisconnectPa
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
-import org.geysermc.connector.utils.MessageUtils;
+import org.geysermc.connector.network.translators.chat.MessageTranslator;
@Translator(packet = ServerDisconnectPacket.class)
public class JavaDisconnectPacket extends PacketTranslator {
@Override
public void translate(ServerDisconnectPacket packet, GeyserSession session) {
- session.disconnect(MessageUtils.getTranslatedBedrockMessage(packet.getReason(), session.getLocale(), true));
+ session.disconnect(MessageTranslator.convertMessage(packet.getReason().toString(), session.getLocale()));
}
}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaLoginDisconnectTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaLoginDisconnectTranslator.java
index e7486c992..0a1cc3ddb 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaLoginDisconnectTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaLoginDisconnectTranslator.java
@@ -29,7 +29,7 @@ import com.github.steveice10.mc.protocol.packet.login.server.LoginDisconnectPack
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
-import org.geysermc.connector.utils.MessageUtils;
+import org.geysermc.connector.network.translators.chat.MessageTranslator;
@Translator(packet = LoginDisconnectPacket.class)
public class JavaLoginDisconnectTranslator extends PacketTranslator {
@@ -37,6 +37,6 @@ public class JavaLoginDisconnectTranslator extends PacketTranslator {
SetTitlePacket titlePacket = new SetTitlePacket();
String locale = session.getLocale();
+ String text;
+ if (packet.getTitle() == null) {
+ text = " ";
+ } else {
+ text = MessageTranslator.convertMessage(packet.getTitle().toString(), locale);
+ }
+
switch (packet.getAction()) {
case TITLE:
titlePacket.setType(SetTitlePacket.Type.TITLE);
- titlePacket.setText(MessageUtils.getTranslatedBedrockMessage(packet.getTitle(), locale));
+ titlePacket.setText(text);
break;
case SUBTITLE:
titlePacket.setType(SetTitlePacket.Type.SUBTITLE);
- titlePacket.setText(MessageUtils.getTranslatedBedrockMessage(packet.getTitle(), locale));
+ titlePacket.setText(text);
break;
case CLEAR:
case RESET:
@@ -57,9 +64,10 @@ public class JavaTitleTranslator extends PacketTranslator {
break;
case ACTION_BAR:
titlePacket.setType(SetTitlePacket.Type.ACTIONBAR);
- titlePacket.setText(MessageUtils.getTranslatedBedrockMessage(packet.getTitle(), locale));
+ titlePacket.setText(text);
break;
case TIMES:
+ titlePacket.setType(SetTitlePacket.Type.TIMES);
titlePacket.setFadeInTime(packet.getFadeIn());
titlePacket.setFadeOutTime(packet.getFadeOut());
titlePacket.setStayTime(packet.getStay());
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaScoreboardObjectiveTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaScoreboardObjectiveTranslator.java
index 31b9d95b2..1996f696f 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaScoreboardObjectiveTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaScoreboardObjectiveTranslator.java
@@ -32,7 +32,7 @@ import org.geysermc.connector.network.translators.Translator;
import org.geysermc.connector.scoreboard.Objective;
import org.geysermc.connector.scoreboard.Scoreboard;
import org.geysermc.connector.scoreboard.ScoreboardUpdater;
-import org.geysermc.connector.utils.MessageUtils;
+import org.geysermc.connector.network.translators.chat.MessageTranslator;
import com.github.steveice10.mc.protocol.data.game.scoreboard.ObjectiveAction;
import com.github.steveice10.mc.protocol.packet.ingame.server.scoreboard.ServerScoreboardObjectivePacket;
@@ -54,7 +54,7 @@ public class JavaScoreboardObjectiveTranslator extends PacketTranslator {
switch (packet.getAction()) {
case CREATE:
scoreboard.registerNewTeam(packet.getTeamName(), toPlayerSet(packet.getPlayers()))
- .setName(MessageUtils.getBedrockMessage(packet.getDisplayName()))
+ .setName(MessageTranslator.convertMessage(packet.getDisplayName().toString()))
.setColor(packet.getColor())
.setNameTagVisibility(packet.getNameTagVisibility())
- .setPrefix(MessageUtils.getTranslatedBedrockMessage(packet.getPrefix(), session.getLocale()))
- .setSuffix(MessageUtils.getTranslatedBedrockMessage(packet.getSuffix(), session.getLocale()));
+ .setPrefix(MessageTranslator.convertMessage(packet.getPrefix().toString(), session.getLocale()))
+ .setSuffix(MessageTranslator.convertMessage(packet.getSuffix().toString(), session.getLocale()));
break;
case UPDATE:
if (team == null) {
@@ -74,11 +74,11 @@ public class JavaTeamTranslator extends PacketTranslator {
return;
}
- team.setName(MessageUtils.getBedrockMessage(packet.getDisplayName()))
+ team.setName(MessageTranslator.convertMessage(packet.getDisplayName().toString()))
.setColor(packet.getColor())
.setNameTagVisibility(packet.getNameTagVisibility())
- .setPrefix(MessageUtils.getTranslatedBedrockMessage(packet.getPrefix(), session.getLocale()))
- .setSuffix(MessageUtils.getTranslatedBedrockMessage(packet.getSuffix(), session.getLocale()))
+ .setPrefix(MessageTranslator.convertMessage(packet.getPrefix().toString(), session.getLocale()))
+ .setSuffix(MessageTranslator.convertMessage(packet.getSuffix().toString(), session.getLocale()))
.setUpdateType(UpdateType.UPDATE);
break;
case ADD_PLAYER:
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenWindowTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenWindowTranslator.java
index 2c10ded60..1fb088717 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenWindowTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenWindowTranslator.java
@@ -25,7 +25,6 @@
package org.geysermc.connector.network.translators.java.window;
-import com.github.steveice10.mc.protocol.data.message.MessageSerializer;
import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientCloseWindowPacket;
import com.github.steveice10.mc.protocol.packet.ingame.server.window.ServerOpenWindowPacket;
import org.geysermc.connector.inventory.Inventory;
@@ -35,7 +34,7 @@ import org.geysermc.connector.network.translators.Translator;
import org.geysermc.connector.network.translators.inventory.InventoryTranslator;
import org.geysermc.connector.utils.InventoryUtils;
import org.geysermc.connector.utils.LocaleUtils;
-import org.geysermc.connector.utils.MessageUtils;
+import org.geysermc.connector.network.translators.chat.MessageTranslator;
@Translator(packet = ServerOpenWindowPacket.class)
public class JavaOpenWindowTranslator extends PacketTranslator {
@@ -57,8 +56,7 @@ public class JavaOpenWindowTranslator extends PacketTranslator
- * The color names correspond to dye names, because of this we can't use {@link MessageUtils#getColor(String)}.
+ * The color names correspond to dye names, because of this we can't use {@link MessageTranslator#getColor(String)}.
*
* @param javaColor The dye color stored in the sign's Color tag.
* @return A Bedrock Edition formatting code for valid dye colors, otherwise an empty string.
*/
- private static String getBedrockSignColor(String javaColor) {
+ private String getBedrockSignColor(String javaColor) {
String base = "\u00a7";
switch (javaColor) {
case "white":
@@ -100,7 +99,12 @@ public class SignBlockEntityTranslator extends BlockEntityTranslator {
for (int i = 0; i < 4; i++) {
int currentLine = i + 1;
String signLine = getOrDefault(tag.getValue().get("Text" + currentLine), "");
- signLine = MessageUtils.getBedrockMessage(MessageSerializer.fromString(signLine));
+ signLine = MessageTranslator.convertMessageLenient(signLine);
+
+ // Trim any trailing formatting codes
+ if (signLine.length() > 2 && signLine.toCharArray()[signLine.length() - 2] == '\u00a7') {
+ signLine = signLine.substring(0, signLine.length() - 2);
+ }
// Check the character width on the sign to ensure there is no overflow that is usually hidden
// to Java Edition clients but will appear to Bedrock clients
@@ -124,6 +128,6 @@ public class SignBlockEntityTranslator extends BlockEntityTranslator {
signText.append("\n");
}
- builder.put("Text", MessageUtils.getBedrockMessage(MessageSerializer.fromString(signText.toString())));
+ builder.put("Text", signText.toString());
}
}
diff --git a/connector/src/main/java/org/geysermc/connector/utils/FileUtils.java b/connector/src/main/java/org/geysermc/connector/utils/FileUtils.java
index 63255cfa0..0b2b132ab 100644
--- a/connector/src/main/java/org/geysermc/connector/utils/FileUtils.java
+++ b/connector/src/main/java/org/geysermc/connector/utils/FileUtils.java
@@ -159,7 +159,8 @@ public class FileUtils {
}
/**
- * Calculate the SHA256 hash of the resource pack file
+ * Calculate the SHA256 hash of a file
+ *
* @param file File to calculate the hash for
* @return A byte[] representation of the hash
*/
@@ -175,6 +176,24 @@ public class FileUtils {
return sha256;
}
+ /**
+ * Calculate the SHA1 hash of a file
+ *
+ * @param file File to calculate the hash for
+ * @return A byte[] representation of the hash
+ */
+ public static byte[] calculateSHA1(File file) {
+ byte[] sha1;
+
+ try {
+ sha1 = MessageDigest.getInstance("SHA-1").digest(Files.readAllBytes(file.toPath()));
+ } catch (Exception e) {
+ throw new RuntimeException("Could not calculate pack hash", e);
+ }
+
+ return sha1;
+ }
+
/**
* Get the stored reflection data for a given path
*
diff --git a/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java b/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java
index dfde21b33..4e9e4b003 100644
--- a/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java
+++ b/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java
@@ -47,7 +47,7 @@ public class LocaleUtils {
private static final Map ASSET_MAP = new HashMap<>();
- private static String smallestURL = "";
+ private static VersionDownload clientJarInfo;
static {
// Create the locales folder
@@ -87,9 +87,8 @@ public class LocaleUtils {
// Get the client jar for use when downloading the en_us locale
GeyserConnector.getInstance().getLogger().debug(GeyserConnector.JSON_MAPPER.writeValueAsString(versionInfo.getDownloads()));
- VersionDownload download = versionInfo.getDownloads().get("client");
- GeyserConnector.getInstance().getLogger().debug(GeyserConnector.JSON_MAPPER.writeValueAsString(download));
- smallestURL = download.getUrl();
+ clientJarInfo = versionInfo.getDownloads().get("client");
+ GeyserConnector.getInstance().getLogger().debug(GeyserConnector.JSON_MAPPER.writeValueAsString(clientJarInfo));
// Get the assets list
JsonNode assets = GeyserConnector.JSON_MAPPER.readTree(WebUtils.getBody(versionInfo.getAssetIndex().getUrl())).get("objects");
@@ -136,8 +135,28 @@ public class LocaleUtils {
// Check if we have already downloaded the locale file
if (localeFile.exists()) {
- GeyserConnector.getInstance().getLogger().debug("Locale already downloaded: " + locale);
- return;
+ String curHash = "";
+ String targetHash = "";
+
+ if (locale.equals("en_us")) {
+ try {
+ Path hashFile = localeFile.getParentFile().toPath().resolve("en_us.hash");
+ if (hashFile.toFile().exists()) {
+ curHash = String.join("", Files.readAllLines(hashFile));
+ }
+ } catch (IOException ignored) { }
+ targetHash = clientJarInfo.getSha1();
+ } else {
+ curHash = byteArrayToHexString(FileUtils.calculateSHA1(localeFile));
+ targetHash = ASSET_MAP.get("minecraft/lang/" + locale + ".json").getHash();
+ }
+
+ if (!curHash.equals(targetHash)) {
+ GeyserConnector.getInstance().getLogger().debug("Locale out of date; re-downloading: " + locale);
+ } else {
+ GeyserConnector.getInstance().getLogger().debug("Locale already downloaded and up-to date: " + locale);
+ return;
+ }
}
// Create the en_us locale
@@ -202,11 +221,11 @@ public class LocaleUtils {
try {
// Let the user know we are downloading the JAR
GeyserConnector.getInstance().getLogger().info(LanguageUtils.getLocaleStringLog("geyser.locale.download.en_us"));
- GeyserConnector.getInstance().getLogger().debug("Download URL: " + smallestURL);
+ GeyserConnector.getInstance().getLogger().debug("Download URL: " + clientJarInfo.getUrl());
// Download the smallest JAR (client or server)
Path tmpFilePath = GeyserConnector.getInstance().getBootstrap().getConfigFolder().resolve("tmp_locale.jar");
- WebUtils.downloadFile(smallestURL, tmpFilePath.toString());
+ WebUtils.downloadFile(clientJarInfo.getUrl(), tmpFilePath.toString());
// Load in the JAR as a zip and extract the file
ZipFile localeJar = new ZipFile(tmpFilePath.toString());
@@ -227,6 +246,9 @@ public class LocaleUtils {
fileStream.close();
localeJar.close();
+ // Store the latest jar hash
+ FileUtils.writeFile(localeFile.getParentFile().toPath().resolve("en_us.hash").toString(), clientJarInfo.getSha1().toCharArray());
+
// Delete the nolonger needed client/server jar
Files.delete(tmpFilePath);
} catch (Exception e) {
@@ -255,6 +277,20 @@ public class LocaleUtils {
return localeStrings.getOrDefault(messageText, messageText);
}
+ /**
+ * Convert a byte array into a hex string
+ *
+ * @param b Byte array to convert
+ * @return The hex representation of the given byte array
+ */
+ private static String byteArrayToHexString(byte[] b) {
+ StringBuilder result = new StringBuilder();
+ for (byte value : b) {
+ result.append(Integer.toString((value & 0xff) + 0x100, 16).substring(1));
+ }
+ return result.toString();
+ }
+
public static void init() {
// no-op
}
diff --git a/connector/src/main/java/org/geysermc/connector/utils/MessageUtils.java b/connector/src/main/java/org/geysermc/connector/utils/MessageUtils.java
deleted file mode 100644
index b5a2bfdcc..000000000
--- a/connector/src/main/java/org/geysermc/connector/utils/MessageUtils.java
+++ /dev/null
@@ -1,494 +0,0 @@
-/*
- * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- *
- * @author GeyserMC
- * @link https://github.com/GeyserMC/Geyser
- */
-
-package org.geysermc.connector.utils;
-
-import com.github.steveice10.mc.protocol.data.game.scoreboard.TeamColor;
-import com.github.steveice10.mc.protocol.data.message.Message;
-import com.github.steveice10.mc.protocol.data.message.MessageSerializer;
-import com.github.steveice10.mc.protocol.data.message.TextMessage;
-import com.github.steveice10.mc.protocol.data.message.TranslationMessage;
-import com.github.steveice10.mc.protocol.data.message.style.ChatColor;
-import com.github.steveice10.mc.protocol.data.message.style.ChatFormat;
-import com.github.steveice10.mc.protocol.data.message.style.MessageStyle;
-import com.google.gson.JsonObject;
-import com.google.gson.JsonParser;
-import net.kyori.adventure.text.Component;
-import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
-import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
-import org.geysermc.connector.network.session.GeyserSession;
-
-import java.util.*;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-public class MessageUtils {
-
- private static final Map COLORS = new HashMap<>();
- private static final Map TEAM_COLORS = new HashMap<>();
-
- static {
- COLORS.put(ChatColor.BLACK, 0x000000);
- COLORS.put(ChatColor.DARK_BLUE, 0x0000aa);
- COLORS.put(ChatColor.DARK_GREEN, 0x00aa00);
- COLORS.put(ChatColor.DARK_AQUA, 0x00aaaa);
- COLORS.put(ChatColor.DARK_RED, 0xaa0000);
- COLORS.put(ChatColor.DARK_PURPLE, 0xaa00aa);
- COLORS.put(ChatColor.GOLD, 0xffaa00);
- COLORS.put(ChatColor.GRAY, 0xaaaaaa);
- COLORS.put(ChatColor.DARK_GRAY, 0x555555);
- COLORS.put(ChatColor.BLUE, 0x5555ff);
- COLORS.put(ChatColor.GREEN, 0x55ff55);
- COLORS.put(ChatColor.AQUA, 0x55ffff);
- COLORS.put(ChatColor.RED, 0xff5555);
- COLORS.put(ChatColor.LIGHT_PURPLE, 0xff55ff);
- COLORS.put(ChatColor.YELLOW, 0xffff55);
- COLORS.put(ChatColor.WHITE, 0xffffff);
-
- TEAM_COLORS.put(TeamColor.BLACK, getColor(ChatColor.BLACK));
- TEAM_COLORS.put(TeamColor.DARK_BLUE, getColor(ChatColor.DARK_BLUE));
- TEAM_COLORS.put(TeamColor.DARK_GREEN, getColor(ChatColor.DARK_GREEN));
- TEAM_COLORS.put(TeamColor.DARK_AQUA, getColor(ChatColor.DARK_AQUA));
- TEAM_COLORS.put(TeamColor.DARK_RED, getColor(ChatColor.DARK_RED));
- TEAM_COLORS.put(TeamColor.DARK_PURPLE, getColor(ChatColor.DARK_PURPLE));
- TEAM_COLORS.put(TeamColor.GOLD, getColor(ChatColor.GOLD));
- TEAM_COLORS.put(TeamColor.GRAY, getColor(ChatColor.GRAY));
- TEAM_COLORS.put(TeamColor.DARK_GRAY, getColor(ChatColor.DARK_GRAY));
- TEAM_COLORS.put(TeamColor.BLUE, getColor(ChatColor.BLUE));
- TEAM_COLORS.put(TeamColor.GREEN, getColor(ChatColor.GREEN));
- TEAM_COLORS.put(TeamColor.AQUA, getColor(ChatColor.AQUA));
- TEAM_COLORS.put(TeamColor.RED, getColor(ChatColor.RED));
- TEAM_COLORS.put(TeamColor.LIGHT_PURPLE, getColor(ChatColor.LIGHT_PURPLE));
- TEAM_COLORS.put(TeamColor.YELLOW, getColor(ChatColor.YELLOW));
- TEAM_COLORS.put(TeamColor.WHITE, getColor(ChatColor.WHITE));
- TEAM_COLORS.put(TeamColor.OBFUSCATED, getFormat(Collections.singletonList(ChatFormat.OBFUSCATED)));
- TEAM_COLORS.put(TeamColor.BOLD, getFormat(Collections.singletonList(ChatFormat.BOLD)));
- TEAM_COLORS.put(TeamColor.STRIKETHROUGH, getFormat(Collections.singletonList(ChatFormat.STRIKETHROUGH)));
- TEAM_COLORS.put(TeamColor.ITALIC, getFormat(Collections.singletonList(ChatFormat.ITALIC)));
- }
-
- /**
- * Recursively parse each message from a list for usage in a {@link TranslationMessage}
- *
- * @param messages A {@link List} of {@link Message} to parse
- * @param locale A locale loaded to get the message for
- * @param parent A {@link Message} to use as the parent (can be null)
- * @return the translation parameters
- */
- public static List getTranslationParams(List messages, String locale, Message parent) {
- List strings = new ArrayList<>();
- for (Message message : messages) {
- message = fixMessageStyle(message, parent);
-
- if (message instanceof TranslationMessage) {
- TranslationMessage translation = (TranslationMessage) message;
-
- if (locale == null) {
- String builder = "%" + translation.getKey();
- strings.add(builder);
- }
-
- // Collect all params and add format corrections to the end of them
- List furtherParams = new ArrayList<>();
- for (String param : getTranslationParams(translation.getWith(), locale, message)) {
- String newParam = param;
- if (parent.getStyle().getFormats().size() != 0) {
- newParam += getFormat(parent.getStyle().getFormats());
- }
- if (parent.getStyle().getColor() != ChatColor.NONE) {
- newParam += getColor(parent.getStyle().getColor());
- }
-
- furtherParams.add(newParam);
- }
-
- if (locale != null) {
- String builder = getFormat(message.getStyle().getFormats()) +
- getColor(message.getStyle().getColor());
- builder += insertParams(LocaleUtils.getLocaleString(translation.getKey(), locale), furtherParams);
- strings.add(builder);
- } else {
- String format = getFormat(message.getStyle().getFormats()) +
- getColor(message.getStyle().getColor());
- for (String param : furtherParams) {
- strings.add(format + param);
- }
- }
- } else {
- String builder = getFormat(message.getStyle().getFormats()) +
- getColor(message.getStyle().getColor());
- builder += getTranslatedBedrockMessage(message, locale, false, parent);
- strings.add(builder);
- }
- }
-
- return strings;
- }
-
- public static String getTranslatedBedrockMessage(Message message, String locale) {
- return getTranslatedBedrockMessage(message, locale, true);
- }
-
- public static String getTranslatedBedrockMessage(Message message, String locale, boolean shouldTranslate) {
- return getTranslatedBedrockMessage(message, locale, shouldTranslate, null);
- }
-
- /**
- * Translate a given {@link TranslationMessage} to the given locale
- *
- * @param message The {@link Message} to send
- * @param locale the locale
- * @param shouldTranslate if the message should be translated
- * @param parent the parent message
- * @return the given translation message translated from the given locale
- */
- public static String getTranslatedBedrockMessage(Message message, String locale, boolean shouldTranslate, Message parent) {
- JsonParser parser = new JsonParser();
- if (isMessage(message.toString())) {
- JsonObject object = parser.parse(message.toString()).getAsJsonObject();
- message = MessageSerializer.fromJson(object);
- }
-
- message = fixMessageStyle(message, parent);
-
- String messageText = (message instanceof TranslationMessage) ? ((TranslationMessage) message).getKey() : ((TextMessage) message).getText();
- if (locale != null && shouldTranslate) {
- messageText = LocaleUtils.getLocaleString(messageText, locale);
- }
-
- StringBuilder builder = new StringBuilder();
- builder.append(getFormat(message.getStyle().getFormats()));
- builder.append(getColor(message.getStyle().getColor()));
- builder.append(messageText);
-
- for (Message msg : message.getExtra()) {
- builder.append(getFormat(msg.getStyle().getFormats()));
- builder.append(getColor(msg.getStyle().getColor()));
- if (!(msg.toString() == null)) {
- boolean isTranslationMessage = (msg instanceof TranslationMessage);
- String extraText = "";
-
- if (isTranslationMessage) {
- List paramsTranslated = getTranslationParams(((TranslationMessage) msg).getWith(), locale, message);
- extraText = insertParams(getTranslatedBedrockMessage(msg, locale, isTranslationMessage, message), paramsTranslated);
- } else {
- extraText = getTranslatedBedrockMessage(msg, locale, isTranslationMessage, message);
- }
-
- builder.append(extraText);
- builder.append("\u00a7r");
- }
- }
-
- return builder.toString();
- }
-
- /**
- * If the passed {@link Message} color or format are empty then copy from parent
- *
- * @param message {@link Message} to update
- * @param parent Parent {@link Message} for style
- * @return The updated {@link Message}
- */
- private static Message fixMessageStyle(Message message, Message parent) {
- if (parent == null) {
- return message;
- }
- MessageStyle.Builder styleBuilder = message.getStyle().toBuilder();
-
- // Copy color from parent
- if (message.getStyle().getColor() == ChatColor.NONE) {
- styleBuilder.color(parent.getStyle().getColor());
- }
-
- // Copy formatting from parent
- if (message.getStyle().getFormats().size() == 0) {
- styleBuilder.formats(parent.getStyle().getFormats());
- }
-
- return message.toBuilder().style(styleBuilder.build()).build();
- }
-
- public static String getBedrockMessage(Message message) {
- if (isMessage(((TextMessage) message).getText())) {
- return getBedrockMessage(((TextMessage) message).getText());
- } else {
- return getBedrockMessage(MessageSerializer.toJsonString(message));
- }
- }
-
- /**
- * Verifies the message is valid JSON in case it's plaintext. Works around GsonComponentSeraializer not using lenient mode.
- * See https://wiki.vg/Chat for messages sent in lenient mode, and for a description on leniency.
- *
- * @param message Potentially lenient JSON message
- * @return Bedrock formatted message
- */
- public static String getBedrockMessageLenient(String message) {
- if (isMessage(message)) {
- return getBedrockMessage(message);
- } else {
- final JsonObject obj = new JsonObject();
- obj.addProperty("text", message);
- return getBedrockMessage(obj.toString());
- }
- }
-
- public static String getBedrockMessage(String message) {
- Component component = phraseJavaMessage(message);
- return LegacyComponentSerializer.legacySection().serialize(component);
- }
-
- public static Component phraseJavaMessage(String message) {
- return GsonComponentSerializer.gson().deserialize(message);
- }
-
- public static String getJavaMessage(String message) {
- Component component = LegacyComponentSerializer.legacySection().deserialize(message);
- return GsonComponentSerializer.gson().serialize(component);
- }
-
- /**
- * Inserts the given parameters into the given message both in sequence and as requested
- *
- * @param message Message containing possible parameter replacement strings
- * @param params A list of parameter strings
- * @return Parsed message with all params inserted as needed
- */
- public static String insertParams(String message, List params) {
- String newMessage = message;
-
- Pattern p = Pattern.compile("%([1-9])\\$s");
- Matcher m = p.matcher(message);
- while (m.find()) {
- try {
- newMessage = newMessage.replaceFirst("%" + m.group(1) + "\\$s", params.get(Integer.parseInt(m.group(1)) - 1));
- } catch (Exception e) {
- // Couldn't find the param to replace
- }
- }
-
- for (String text : params) {
- newMessage = newMessage.replaceFirst("%s", text.replaceAll("%s", "%r"));
- }
-
- newMessage = newMessage.replaceAll("%r", "MISSING!");
-
- return newMessage;
- }
-
- /**
- * Convert a ChatColor into a string for inserting into messages
- *
- * @param color ChatColor to convert
- * @return The converted color string
- */
- private static String getColor(String color) {
- String base = "\u00a7";
- switch (color) {
- case ChatColor.BLACK:
- base += "0";
- break;
- case ChatColor.DARK_BLUE:
- base += "1";
- break;
- case ChatColor.DARK_GREEN:
- base += "2";
- break;
- case ChatColor.DARK_AQUA:
- base += "3";
- break;
- case ChatColor.DARK_RED:
- base += "4";
- break;
- case ChatColor.DARK_PURPLE:
- base += "5";
- break;
- case ChatColor.GOLD:
- base += "6";
- break;
- case ChatColor.GRAY:
- base += "7";
- break;
- case ChatColor.DARK_GRAY:
- base += "8";
- break;
- case ChatColor.BLUE:
- base += "9";
- break;
- case ChatColor.GREEN:
- base += "a";
- break;
- case ChatColor.AQUA:
- base += "b";
- break;
- case ChatColor.RED:
- base += "c";
- break;
- case ChatColor.LIGHT_PURPLE:
- base += "d";
- break;
- case ChatColor.YELLOW:
- base += "e";
- break;
- case ChatColor.WHITE:
- base += "f";
- break;
- case ChatColor.RESET:
- //case NONE:
- base += "r";
- break;
- case "": // To stop recursion
- return "";
- default:
- return getClosestColor(color);
- }
-
- return base;
- }
-
- /**
- * Based on https://github.com/ViaVersion/ViaBackwards/blob/master/core/src/main/java/nl/matsv/viabackwards/protocol/protocol1_15_2to1_16/chat/TranslatableRewriter1_16.java
- *
- * @param color A color string
- * @return The closest color to that string
- */
- private static String getClosestColor(String color) {
- if (!color.startsWith("#")) {
- return "";
- }
-
- int rgb = Integer.parseInt(color.substring(1), 16);
- int r = (rgb >> 16) & 0xFF;
- int g = (rgb >> 8) & 0xFF;
- int b = rgb & 0xFF;
-
- String closest = null;
- int smallestDiff = 0;
-
- for (Map.Entry testColor : COLORS.entrySet()) {
- if (testColor.getValue() == rgb) {
- closest = testColor.getKey();
- break;
- }
-
- int testR = (testColor.getValue() >> 16) & 0xFF;
- int testG = (testColor.getValue() >> 8) & 0xFF;
- int testB = testColor.getValue() & 0xFF;
-
- // Check by the greatest diff of the 3 values
- int rAverage = (testR + r) / 2;
- int rDiff = testR - r;
- int gDiff = testG - g;
- int bDiff = testB - b;
- int diff = ((2 + (rAverage >> 8)) * rDiff * rDiff)
- + (4 * gDiff * gDiff)
- + ((2 + ((255 - rAverage) >> 8)) * bDiff * bDiff);
- if (closest == null || diff < smallestDiff) {
- closest = testColor.getKey();
- smallestDiff = diff;
- }
- }
-
- return getColor(closest);
- }
-
- /**
- * Convert a list of ChatFormats into a string for inserting into messages
- *
- * @param formats ChatFormats to convert
- * @return The converted chat formatting string
- */
- private static String getFormat(List formats) {
- StringBuilder str = new StringBuilder();
- for (ChatFormat cf : formats) {
- String base = "\u00a7";
- switch (cf) {
- case OBFUSCATED:
- base += "k";
- break;
- case BOLD:
- base += "l";
- break;
- case STRIKETHROUGH:
- base += "m";
- break;
- case UNDERLINED:
- base += "n";
- break;
- case ITALIC:
- base += "o";
- break;
- default:
- return "";
- }
-
- str.append(base);
- }
-
- return str.toString();
- }
-
- /**
- * Checks if the given text string is a json message
- *
- * @param text String to test
- * @return True if its a valid message json string, false if not
- */
- public static boolean isMessage(String text) {
- JsonParser parser = new JsonParser();
- try {
- JsonObject object = parser.parse(text).getAsJsonObject();
- try {
- MessageSerializer.fromJson(object);
- } catch (Exception ex) {
- return false;
- }
- } catch (Exception ex) {
- return false;
- }
- return true;
- }
-
- public static String toChatColor(TeamColor teamColor) {
- return TEAM_COLORS.getOrDefault(teamColor, "");
- }
-
- /**
- * Checks if the given message is over 256 characters (Java edition server chat limit) and sends a message to the user if it is
- *
- * @param message Message to check
- * @param session GeyserSession for the user
- * @return True if the message is too long, false if not
- */
- public static boolean isTooLong(String message, GeyserSession session) {
- if (message.length() > 256) {
- session.sendMessage(LanguageUtils.getPlayerLocaleString("geyser.chat.too_long", session.getLocale(), message.length()));
- return true;
- }
-
- return false;
- }
-}
diff --git a/connector/src/test/java/org/geysermc/connector/network/translators/chat/MessageTranslatorTest.java b/connector/src/test/java/org/geysermc/connector/network/translators/chat/MessageTranslatorTest.java
new file mode 100644
index 000000000..5d52c79b9
--- /dev/null
+++ b/connector/src/test/java/org/geysermc/connector/network/translators/chat/MessageTranslatorTest.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @author GeyserMC
+ * @link https://github.com/GeyserMC/Geyser
+ */
+
+package org.geysermc.connector.network.translators.chat;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class MessageTranslatorTest {
+
+ private Map messages = new HashMap<>();
+
+ @Before
+ public void setUp() throws Exception {
+ messages.put("{\"text\":\"\",\"extra\":[{\"text\":\"DoctorMad9952 joined the game\",\"color\":\"yellow\"}]}",
+ "§eDoctorMad9952 joined the game");
+
+ messages.put("{\"text\":\"\",\"extra\":[\"Plugins (3): \",{\"text\":\"WorldEdit\",\"color\":\"green\"},{\"text\":\", \",\"color\":\"white\"},{\"text\":\"ViaVersion\",\"color\":\"green\"},{\"text\":\", \",\"color\":\"white\"},{\"text\":\"Geyser-Spigot\",\"color\":\"green\"}]}",
+ "Plugins (3): §aWorldEdit§f, §aViaVersion§f, §aGeyser-Spigot");
+
+ // RGB downgrade test
+ messages.put("{\"extra\":[{\"text\":\" \"},{\"color\":\"gold\",\"text\":\"The \"},{\"color\":\"#E14248\",\"obfuscated\":true,\"text\":\"||\"},{\"color\":\"#3AA9FF\",\"bold\":true,\"text\":\"CubeCraft\"},{\"color\":\"#E14248\",\"obfuscated\":true,\"text\":\"||\"},{\"color\":\"gold\",\"text\":\" Network \"},{\"color\":\"green\",\"text\":\"[1.8/1.9+]\\n \"},{\"color\":\"#f5e342\",\"text\":\"✦ \"},{\"color\":\"#b042f5\",\"bold\":true,\"text\":\"N\"},{\"color\":\"#c142f5\",\"bold\":true,\"text\":\"E\"},{\"color\":\"#d342f5\",\"bold\":true,\"text\":\"W\"},{\"color\":\"#e442f5\",\"bold\":true,\"text\":\":\"},{\"color\":\"#f542f5\",\"bold\":true,\"text\":\" \"},{\"color\":\"#bcf542\",\"bold\":true,\"text\":\"A\"},{\"color\":\"#acee3f\",\"bold\":true,\"text\":\"M\"},{\"color\":\"#9ce73c\",\"bold\":true,\"text\":\"O\"},{\"color\":\"#8ce039\",\"bold\":true,\"text\":\"N\"},{\"color\":\"#7cd936\",\"bold\":true,\"text\":\"G\"},{\"color\":\"#6cd233\",\"bold\":true,\"text\":\" \"},{\"color\":\"#5ccb30\",\"bold\":true,\"text\":\"S\"},{\"color\":\"#4cc42d\",\"bold\":true,\"text\":\"L\"},{\"color\":\"#3cbd2a\",\"bold\":true,\"text\":\"I\"},{\"color\":\"#2cb627\",\"bold\":true,\"text\":\"M\"},{\"color\":\"#1caf24\",\"bold\":true,\"text\":\"E\"},{\"color\":\"#0ca821\",\"bold\":true,\"text\":\"S\"},{\"color\":\"#f5e342\",\"text\":\" \"},{\"color\":\"#6d7c87\",\"text\":\"(kinda sus) \"},{\"color\":\"#f5e342\",\"text\":\"✦\"}],\"text\":\"\"}",
+ " §6The §c§k||§r§3§lCubeCraft§r§c§k||§r§6 Network §a[1.8/1.9+]\n" +
+ " §e✦ §d§lN§r§d§lE§r§d§lW§r§d§l:§r§d§l §r§e§lA§r§e§lM§r§a§lO§r§a§lN§r§a§lG§r§a§l §r§a§lS§r§a§lL§r§2§lI§r§2§lM§r§2§lE§r§2§lS§r§e §8(kinda sus) §e✦");
+ }
+
+ @Test
+ public void convertMessage() {
+ for (Map.Entry entry : messages.entrySet()) {
+ String bedrockMessage = MessageTranslator.convertMessage(entry.getKey(), "en_US");
+ Assert.assertEquals("Translation of messages is incorrect", bedrockMessage, entry.getValue());
+ }
+ }
+
+ @Test
+ public void convertMessageLenient() {
+ Assert.assertEquals("All newline message is not handled properly", "\n\n\n\n", MessageTranslator.convertMessageLenient("\n\n\n\n"));
+ Assert.assertEquals("Empty message is not handled properly", "", MessageTranslator.convertMessageLenient(""));
+ Assert.assertEquals("Reset before message is not handled properly", "§r§eGame Selector", MessageTranslator.convertMessageLenient("§r§eGame Selector"));
+ }
+}