diff --git a/README.md b/README.md index 0439646..5f60833 100644 --- a/README.md +++ b/README.md @@ -9,4 +9,6 @@ If you want to use GoogleChat serverside (which is supported btw), a config like "serverLanguage": "en", "clientLanguage": "auto" } -``` \ No newline at end of file +``` + +You should also enable previews-chat in your server.properties to allow signed message translations \ No newline at end of file diff --git a/build.gradle b/build.gradle index 82b41e4..090b6f3 100644 --- a/build.gradle +++ b/build.gradle @@ -1,16 +1,15 @@ apply from: "https://jfmods.gitlab.io/scripts/jfmod.gradle" dependencies { - include modImplementation("io.gitlab.jfronny.libjf:libjf-config-v0:${project.jfapi_version}") - include modImplementation("io.gitlab.jfronny.libjf:libjf-translate-v1:${project.jfapi_version}") - include("io.gitlab.jfronny.libjf:libjf-unsafe-v0:${project.jfapi_version}") - include("io.gitlab.jfronny.libjf:libjf-base:${project.jfapi_version}") - modRuntimeOnly("io.gitlab.jfronny.libjf:libjf-devutil-v0:${project.jfapi_version}") + modImplementation("io.gitlab.jfronny.libjf:libjf-config-v0:${project.jfapi_version}") + modImplementation("io.gitlab.jfronny.libjf:libjf-translate-v1:${project.jfapi_version}") - modImplementation "com.terraformersmc:modmenu:3.1.0" + //modRuntimeOnly("io.gitlab.jfronny.libjf:libjf-devutil-v0:${project.jfapi_version}") + + modImplementation "com.terraformersmc:modmenu:4.0.0-beta.4" // Compat fix - include modImplementation(fabricApi.module("fabric-command-api-v1", "${project.fabric_version}")) - include modImplementation(fabricApi.module("fabric-lifecycle-events-v1", "${project.fabric_version}")) - include modRuntimeOnly('io.gitlab.jfronny:gson:2.9.0.2022.4.2.19.45.43') // Dependency of LibJF 2.7.0 + include modImplementation(fabricApi.module("fabric-command-api-v2", project.fabric_version)) + include modImplementation(fabricApi.module("fabric-lifecycle-events-v1", project.fabric_version)) + include modImplementation(fabricApi.module("fabric-message-api-v1", project.fabric_version)) } diff --git a/gradle.properties b/gradle.properties index 6fcf715..738038b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,14 +1,16 @@ # https://fabricmc.net/develop/ -minecraft_version=1.18.2 -yarn_mappings=build.2 -loader_version=0.13.3 +minecraft_version=1.19 +yarn_mappings=build.1 +loader_version=0.14.6 maven_group=io.gitlab.jfronny archives_base_name=GoogleChat -jfapi_version=2.7.0 -fabric_version=0.48.0+1.18.2 +jfapi_version=2.9.0 +fabric_version=0.55.1+1.19 modrinth_id=Hd7ZLKRk +modrinth_required_dependencies=WKwQAwke modrinth_optional_dependencies=mOgUt4GM curseforge_id=574331 +curseforge_required_dependencies=libjf curseforge_optional_dependencies=modmenu \ No newline at end of file diff --git a/src/main/java/io/gitlab/jfronny/googlechat/CacheKey.java b/src/main/java/io/gitlab/jfronny/googlechat/CacheKey.java new file mode 100644 index 0000000..8a159e6 --- /dev/null +++ b/src/main/java/io/gitlab/jfronny/googlechat/CacheKey.java @@ -0,0 +1,4 @@ +package io.gitlab.jfronny.googlechat; + +public record CacheKey(String sourceText, String sourceLangId, String targetLangId) { +} diff --git a/src/main/java/io/gitlab/jfronny/googlechat/FixedSizeCache.java b/src/main/java/io/gitlab/jfronny/googlechat/FixedSizeCache.java new file mode 100644 index 0000000..dcf1773 --- /dev/null +++ b/src/main/java/io/gitlab/jfronny/googlechat/FixedSizeCache.java @@ -0,0 +1,17 @@ +package io.gitlab.jfronny.googlechat; + +import java.util.*; + +public class FixedSizeCache extends LinkedHashMap { + private final int maxSize; + + public FixedSizeCache(int size) { + super(size + 2, 1F); + this.maxSize = size; + } + + @Override + protected boolean removeEldestEntry(Map.Entry eldest) { + return size() > maxSize; + } +} diff --git a/src/main/java/io/gitlab/jfronny/googlechat/GoogleChat.java b/src/main/java/io/gitlab/jfronny/googlechat/GoogleChat.java index e2c3442..1a28e15 100644 --- a/src/main/java/io/gitlab/jfronny/googlechat/GoogleChat.java +++ b/src/main/java/io/gitlab/jfronny/googlechat/GoogleChat.java @@ -1,22 +1,32 @@ package io.gitlab.jfronny.googlechat; -import io.gitlab.jfronny.libjf.translate.api.Language; -import io.gitlab.jfronny.libjf.translate.api.TranslateException; -import io.gitlab.jfronny.libjf.translate.api.TranslateService; -import net.fabricmc.api.EnvType; -import net.fabricmc.loader.api.FabricLoader; -import net.minecraft.client.MinecraftClient; +import io.gitlab.jfronny.commons.log.*; +import io.gitlab.jfronny.libjf.translate.api.*; +import net.fabricmc.api.*; +import net.fabricmc.fabric.api.message.v1.*; +import net.fabricmc.loader.api.*; +import net.minecraft.client.*; import net.minecraft.text.*; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import java.util.Arrays; -import java.util.UUID; +import java.util.*; +import java.util.concurrent.*; -public class GoogleChat { +public class GoogleChat implements ModInitializer { public static final String MOD_ID = "google-chat"; - public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID); + public static final Logger LOGGER = Logger.forName(MOD_ID); public static final TranslateService TRANSLATE_SERVICE = TranslateService.getConfigured(); + private static final FixedSizeCache TRANSLATION_CACHE = new FixedSizeCache<>(126); + + @Override + public void onInitialize() { + ServerMessageDecoratorEvent.EVENT.register(ServerMessageDecoratorEvent.CONTENT_PHASE, (sender, message) -> { + if (sender != null) // Client messages should first be translated to the server language + message = translateIfNeeded(message, Direction.C2S, true); + // All messages should be translated to the client language before sending + message = translateIfNeeded(message, Direction.S2C, true); + return CompletableFuture.completedFuture(message); + }); + } public static boolean isSelf(UUID sender) { MinecraftClient mc = MinecraftClient.getInstance(); @@ -29,14 +39,14 @@ public class GoogleChat { if (respectRegex && failsRegex(sourceString, direction)) return source; if (GoogleChatConfig.desugar) { - LiteralText translatedText = new LiteralText(translateIfNeeded(sourceString, direction, true)); + MutableText translatedText = Text.literal(translateIfNeeded(sourceString, direction, true)); if (GoogleChatConfig.translationTooltip) - return source.copy().setStyle(addHover(Style.EMPTY, new LiteralText("Translated: ").append(translatedText))); + return source.copy().setStyle(addHover(Style.EMPTY, Text.literal("Translated: ").append(translatedText))); else - return translatedText.setStyle(addHover(Style.EMPTY, new LiteralText("Original: ").append(source))); + return translatedText.setStyle(addHover(Style.EMPTY, Text.literal("Original: ").append(source))); } MutableText translated; - if (source instanceof TranslatableText tx) { + if (source.getContent() instanceof TranslatableTextContent tx) { Object[] args = tx.getArgs(); args = Arrays.copyOf(args, args.length); // We're not translating TranslatableText but want potential keys @@ -45,9 +55,9 @@ public class GoogleChat { : args[i] instanceof String tx1 ? translateIfNeeded(tx1, direction, false) : args[i]; } - translated = new TranslatableText(tx.getKey(), args); - } else if (source instanceof LiteralText tx) { - translated = new LiteralText(translateIfNeeded(tx.getRawString(), direction, false)).setStyle(tx.getStyle()); + translated = Text.translatable(tx.getKey(), args); + } else if (source.getContent() instanceof LiteralTextContent tx) { + translated = Text.literal(translateIfNeeded(tx.string(), direction, false)).setStyle(source.getStyle()); } else { //LOGGER.info("Unhandled text type: " + source.getClass() + " (" + source + ")"); translated = source.copy(); @@ -76,12 +86,14 @@ public class GoogleChat { if (respectRegex && failsRegex(source, direction)) return source; TranslateService svc = (TranslateService) GoogleChat.TRANSLATE_SERVICE; // Generics bypass - TLang clientLang = svc.parseLang(GoogleChatConfig.clientLanguage); - TLang serverLang = svc.parseLang(GoogleChatConfig.serverLanguage); + TLang sourceLang = svc.parseLang(direction == Direction.C2S ? GoogleChatConfig.clientLanguage : GoogleChatConfig.serverLanguage); + TLang targetLang = svc.parseLang(direction == Direction.C2S ? GoogleChatConfig.serverLanguage : GoogleChatConfig.clientLanguage); + CacheKey key = new CacheKey(source, sourceLang.getIdentifier(), targetLang.getIdentifier()); + if (TRANSLATION_CACHE.containsKey(key)) return TRANSLATION_CACHE.get(key); try { - return svc.translate(source, - direction == Direction.C2S ? clientLang : serverLang, - direction == Direction.C2S ? serverLang : clientLang); + String translated = svc.translate(source, sourceLang, targetLang); + TRANSLATION_CACHE.put(key, translated); + return translated; } catch (TranslateException e) { LOGGER.error("Could not translate text: " + source, e); return source; diff --git a/src/main/java/io/gitlab/jfronny/googlechat/GoogleChatConfig.java b/src/main/java/io/gitlab/jfronny/googlechat/GoogleChatConfig.java index d998fba..d957377 100644 --- a/src/main/java/io/gitlab/jfronny/googlechat/GoogleChatConfig.java +++ b/src/main/java/io/gitlab/jfronny/googlechat/GoogleChatConfig.java @@ -1,8 +1,8 @@ package io.gitlab.jfronny.googlechat; -import io.gitlab.jfronny.libjf.config.api.Entry; -import io.gitlab.jfronny.libjf.config.api.JfConfig; -import io.gitlab.jfronny.libjf.config.api.Preset; +import io.gitlab.jfronny.libjf.config.api.*; +import net.fabricmc.api.*; +import net.fabricmc.loader.api.*; public class GoogleChatConfig implements JfConfig { @Entry public static Boolean enabled = true; @@ -38,4 +38,12 @@ public class GoogleChatConfig implements JfConfig { sendingRegex = tmp; } } + + @Verifier + public static void verify() { + if (FabricLoader.getInstance().getEnvironmentType() == EnvType.SERVER && !clientLanguage.equals("auto")) { + System.err.println("Your client language is not set to \"auto\" and you are using a server.\n" + + "This setup is not recommended! Please set up GoogleChat according to its documentation!"); + } + } } diff --git a/src/main/java/io/gitlab/jfronny/googlechat/mixin/ChatMessageC2SPacketMixin.java b/src/main/java/io/gitlab/jfronny/googlechat/mixin/ChatMessageC2SPacketMixin.java deleted file mode 100644 index 65d3d63..0000000 --- a/src/main/java/io/gitlab/jfronny/googlechat/mixin/ChatMessageC2SPacketMixin.java +++ /dev/null @@ -1,38 +0,0 @@ -package io.gitlab.jfronny.googlechat.mixin; - -import io.gitlab.jfronny.googlechat.GoogleChat; -import io.gitlab.jfronny.libjf.translate.api.Language; -import io.gitlab.jfronny.libjf.translate.api.TranslateService; -import net.minecraft.network.PacketByteBuf; -import net.minecraft.network.packet.c2s.play.ChatMessageC2SPacket; -import org.spongepowered.asm.mixin.Final; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Mutable; -import org.spongepowered.asm.mixin.Shadow; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -@Mixin(ChatMessageC2SPacket.class) -public class ChatMessageC2SPacketMixin { - @Mutable - @Final - @Shadow - private String chatMessage; - - @Inject(at = @At("RETURN"), method = "(Ljava/lang/String;)V") - public void init(String chatMessage, CallbackInfo info) { - googlechat$translate(GoogleChat.TRANSLATE_SERVICE); - } - - @Inject(at = @At("RETURN"), method = "(Lnet/minecraft/network/PacketByteBuf;)V") - public void init(PacketByteBuf buf, CallbackInfo ci) { - googlechat$translate(GoogleChat.TRANSLATE_SERVICE); - } - - private void googlechat$translate(TranslateService ts) { - if (chatMessage.startsWith("/")) return; - chatMessage = GoogleChat.translateIfNeeded(chatMessage, GoogleChat.Direction.C2S, true); - if (chatMessage.length() > 256) chatMessage = chatMessage.substring(0, 256); - } -} diff --git a/src/main/java/io/gitlab/jfronny/googlechat/mixin/ChatScreenMixin.java b/src/main/java/io/gitlab/jfronny/googlechat/mixin/ChatScreenMixin.java new file mode 100644 index 0000000..f116a63 --- /dev/null +++ b/src/main/java/io/gitlab/jfronny/googlechat/mixin/ChatScreenMixin.java @@ -0,0 +1,14 @@ +package io.gitlab.jfronny.googlechat.mixin; + +import io.gitlab.jfronny.googlechat.*; +import net.minecraft.client.gui.screen.*; +import org.spongepowered.asm.mixin.*; +import org.spongepowered.asm.mixin.injection.*; + +@Mixin(ChatScreen.class) +public class ChatScreenMixin { + @ModifyVariable(method = "tryRequestChatPreview(Ljava/lang/String;)V", at = @At(value = "HEAD"), argsOnly = true, ordinal = 0) + String googlechat$translateChatText(String source) { + return GoogleChat.translateIfNeeded(source, GoogleChat.Direction.C2S, true); + } +} diff --git a/src/main/java/io/gitlab/jfronny/googlechat/mixin/ClientPlayNetworkHandlerMixin.java b/src/main/java/io/gitlab/jfronny/googlechat/mixin/ClientPlayNetworkHandlerMixin.java new file mode 100644 index 0000000..39d14dc --- /dev/null +++ b/src/main/java/io/gitlab/jfronny/googlechat/mixin/ClientPlayNetworkHandlerMixin.java @@ -0,0 +1,19 @@ +package io.gitlab.jfronny.googlechat.mixin; + +import io.gitlab.jfronny.googlechat.*; +import net.minecraft.client.gui.hud.*; +import net.minecraft.client.network.*; +import net.minecraft.network.message.*; +import net.minecraft.text.*; +import org.spongepowered.asm.mixin.*; +import org.spongepowered.asm.mixin.injection.*; + +@Mixin(ClientPlayNetworkHandler.class) +public class ClientPlayNetworkHandlerMixin { + @Redirect(method = "handleMessage(Lnet/minecraft/network/message/MessageType;Lnet/minecraft/network/message/SignedMessage;Lnet/minecraft/network/message/MessageSender;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/hud/InGameHud;onChatMessage(Lnet/minecraft/network/message/MessageType;Lnet/minecraft/text/Text;Lnet/minecraft/network/message/MessageSender;)V")) + private void googlechat$interceptChatMessage(InGameHud hud, MessageType type, Text message, MessageSender sender) { + if (!GoogleChat.isSelf(sender.uuid())) + message = GoogleChat.translateIfNeeded(message, GoogleChat.Direction.S2C, true); + hud.onChatMessage(type, message, sender); + } +} diff --git a/src/main/java/io/gitlab/jfronny/googlechat/mixin/ClientPlayerEntityMixin.java b/src/main/java/io/gitlab/jfronny/googlechat/mixin/ClientPlayerEntityMixin.java new file mode 100644 index 0000000..fb4682b --- /dev/null +++ b/src/main/java/io/gitlab/jfronny/googlechat/mixin/ClientPlayerEntityMixin.java @@ -0,0 +1,14 @@ +package io.gitlab.jfronny.googlechat.mixin; + +import io.gitlab.jfronny.googlechat.*; +import net.minecraft.client.network.*; +import org.spongepowered.asm.mixin.*; +import org.spongepowered.asm.mixin.injection.*; + +@Mixin(ClientPlayerEntity.class) +public class ClientPlayerEntityMixin { + @ModifyVariable(method = "sendChatMessage(Ljava/lang/String;Lnet/minecraft/text/Text;)V", at = @At("HEAD"), argsOnly = true, ordinal = 0) + String googlechat$translateMessage(String source) { + return GoogleChat.translateIfNeeded(source, GoogleChat.Direction.C2S, true); + } +} diff --git a/src/main/java/io/gitlab/jfronny/googlechat/mixin/GameMessageS2CPacketMixin.java b/src/main/java/io/gitlab/jfronny/googlechat/mixin/GameMessageS2CPacketMixin.java deleted file mode 100644 index e347ffa..0000000 --- a/src/main/java/io/gitlab/jfronny/googlechat/mixin/GameMessageS2CPacketMixin.java +++ /dev/null @@ -1,42 +0,0 @@ -package io.gitlab.jfronny.googlechat.mixin; - -import io.gitlab.jfronny.googlechat.GoogleChat; -import io.gitlab.jfronny.googlechat.GoogleChatConfig; -import io.gitlab.jfronny.libjf.translate.api.Language; -import net.fabricmc.api.EnvType; -import net.fabricmc.loader.api.FabricLoader; -import net.minecraft.network.MessageType; -import net.minecraft.network.PacketByteBuf; -import net.minecraft.network.packet.s2c.play.GameMessageS2CPacket; -import net.minecraft.text.Text; -import org.spongepowered.asm.mixin.Final; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Mutable; -import org.spongepowered.asm.mixin.Shadow; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -import java.util.UUID; - -@Mixin(GameMessageS2CPacket.class) -public class GameMessageS2CPacketMixin { - @Shadow @Final @Mutable private Text message; - @Shadow @Final private UUID sender; - - @Inject(at = @At("RETURN"), method = "(Lnet/minecraft/text/Text;Lnet/minecraft/network/MessageType;Ljava/util/UUID;)V") - private void init(Text message, MessageType type, UUID sender, CallbackInfo ci) { - googlechat$translate(); - } - - @Inject(at = @At("RETURN"), method = "(Lnet/minecraft/network/PacketByteBuf;)V") - private void init(PacketByteBuf buf, CallbackInfo ci) { - googlechat$translate(); - } - - private void googlechat$translate() { - if (!GoogleChatConfig.enabled) return; - if (FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT && GoogleChat.isSelf(sender)) return; - message = GoogleChat.translateIfNeeded(message, GoogleChat.Direction.S2C, true); - } -} diff --git a/src/main/resources/GoogleChat.mixins.json b/src/main/resources/GoogleChat.mixins.json index 76aa120..19e2b1e 100644 --- a/src/main/resources/GoogleChat.mixins.json +++ b/src/main/resources/GoogleChat.mixins.json @@ -3,9 +3,10 @@ "minVersion": "0.8", "package": "io.gitlab.jfronny.googlechat.mixin", "compatibilityLevel": "JAVA_17", - "mixins": [ - "ChatMessageC2SPacketMixin", - "GameMessageS2CPacketMixin" + "client": [ + "ChatScreenMixin", + "ClientPlayerEntityMixin", + "ClientPlayNetworkHandlerMixin" ], "injectors": { "defaultRequire": 1 diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 666bac4..13b6db1 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -10,9 +10,8 @@ "icon": "assets/google-chat/icon.png", "environment": "*", "entrypoints": { - "libjf:config": [ - "io.gitlab.jfronny.googlechat.GoogleChatConfig" - ] + "libjf:config": ["io.gitlab.jfronny.googlechat.GoogleChatConfig"], + "main": ["io.gitlab.jfronny.googlechat.GoogleChat"] }, "mixins": [ "GoogleChat.mixins.json"