From 79aa4c9575d9b374af60781265b1dc75e19355e6 Mon Sep 17 00:00:00 2001 From: NetheriteMiner <88792142+NetheriteMiner@users.noreply.github.com> Date: Mon, 28 Aug 2023 17:43:25 -0400 Subject: Add Translator (#383) Co-authored-by: hannibal2 <24389977+hannibal00212@users.noreply.github.com> --- src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt | 1 + .../hannibal2/skyhanni/config/commands/Commands.kt | 12 ++ .../skyhanni/config/features/ChatConfig.java | 5 + .../hannibal2/skyhanni/features/chat/Translator.kt | 218 +++++++++++++++++++++ 4 files changed, 236 insertions(+) create mode 100644 src/main/java/at/hannibal2/skyhanni/features/chat/Translator.kt diff --git a/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt b/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt index e428782df..c16a19a5c 100644 --- a/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt +++ b/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt @@ -381,6 +381,7 @@ class SkyHanniMod { loadModule(WatchdogHider()) loadModule(AccountUpgradeReminder()) loadModule(PetExpTooltip()) + loadModule(Translator()) init() diff --git a/src/main/java/at/hannibal2/skyhanni/config/commands/Commands.kt b/src/main/java/at/hannibal2/skyhanni/config/commands/Commands.kt index 0768e9f65..ab94a1e6d 100644 --- a/src/main/java/at/hannibal2/skyhanni/config/commands/Commands.kt +++ b/src/main/java/at/hannibal2/skyhanni/config/commands/Commands.kt @@ -7,6 +7,7 @@ import at.hannibal2.skyhanni.data.ChatManager import at.hannibal2.skyhanni.data.GuiEditManager import at.hannibal2.skyhanni.features.bingo.BingoCardDisplay import at.hannibal2.skyhanni.features.bingo.BingoNextStepHelper +import at.hannibal2.skyhanni.features.chat.Translator import at.hannibal2.skyhanni.features.event.diana.BurrowWarpHelper import at.hannibal2.skyhanni.features.event.diana.InquisitorWaypointShare import at.hannibal2.skyhanni.features.fame.AccountUpgradeReminder @@ -142,6 +143,13 @@ object Commands { "shfarmingprofile", "Look up the farming profile from yourself or another player on elitebot.dev" ) { FarmingWeightDisplay.lookUpCommand(it) } + registerCommand( + "shcopytranslation", + " \n" + + "Requires the Chat > Translator feature to be enabled.\n" + + "Copies the translation for a given message to your clipboard. " + + "Language codes are at the end of the translation when you click on a message." + ) { Translator.fromEnglish(it) } } private fun usersBugFix() { @@ -237,6 +245,10 @@ object Commands { registerCommand("shstopcityprojectreminder", "") { CityProjectFeatures.disable() } registerCommand("shsendcontests", "") { GardenNextJacobContest.shareContestConfirmed(it) } registerCommand("shstopaccountupgradereminder", "") { AccountUpgradeReminder.disable() } + registerCommand( + "shsendtranslation", + "Respond with a translation of the message that the user clicks" + ) { Translator.toEnglish(it) } } private fun commandHelp(args: Array) { diff --git a/src/main/java/at/hannibal2/skyhanni/config/features/ChatConfig.java b/src/main/java/at/hannibal2/skyhanni/config/features/ChatConfig.java index 30bba65bd..ee46054f6 100644 --- a/src/main/java/at/hannibal2/skyhanni/config/features/ChatConfig.java +++ b/src/main/java/at/hannibal2/skyhanni/config/features/ChatConfig.java @@ -133,4 +133,9 @@ public class ChatConfig { @ConfigOption(name = "Arachne Hider", desc = "Hide chat messages about the Arachne Fight while outside of §eArachne's Sanctuary§7.") @ConfigEditorBoolean public boolean hideArachneMessages = false; + + @Expose + @ConfigOption(name = "Translator", desc = "Click on a message to translate it into English. Use /shcopytranslation to get the translation from English. Translation is not guaranteed to be 100% accurate.") + @ConfigEditorBoolean + public boolean translator = false; } diff --git a/src/main/java/at/hannibal2/skyhanni/features/chat/Translator.kt b/src/main/java/at/hannibal2/skyhanni/features/chat/Translator.kt new file mode 100644 index 000000000..f39905876 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/features/chat/Translator.kt @@ -0,0 +1,218 @@ +package at.hannibal2.skyhanni.features.chat + +import at.hannibal2.skyhanni.SkyHanniMod +import at.hannibal2.skyhanni.test.command.CopyErrorCommand +import at.hannibal2.skyhanni.utils.LorenzUtils +import at.hannibal2.skyhanni.utils.OSUtils +import at.hannibal2.skyhanni.utils.StringUtils.removeColor +import com.google.gson.* +import net.minecraft.event.ClickEvent +import net.minecraft.event.HoverEvent +import net.minecraft.util.ChatComponentText +import net.minecraft.util.ChatStyle +import net.minecraftforge.client.event.ClientChatReceivedEvent +import net.minecraftforge.fml.common.eventhandler.EventPriority +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent +import org.apache.http.client.config.RequestConfig +import org.apache.http.client.methods.HttpGet +import org.apache.http.impl.client.HttpClientBuilder +import org.apache.http.impl.client.HttpClients +import org.apache.http.message.BasicHeader +import org.apache.http.util.EntityUtils +import java.net.URLDecoder +import java.net.URLEncoder + +class Translator { + + private val messageContentRegex = Regex(".*: (.*)") + + // Logic for listening for a user click on a chat message is from NotEnoughUpdates + + @SubscribeEvent(priority = EventPriority.LOWEST, receiveCanceled = true) + fun onGuiChat(e: ClientChatReceivedEvent) { + if (!SkyHanniMod.feature.chat.translator) return + if (e.type != 0.toByte()) return // If this is not a player-sent message, return + + val chatComponent = e.message + // If you want to help me debug, experience the bug while this line is uncommented (spams logs) +// consoleLog(chatComponent.toString()) + val message = chatComponent.unformattedText + if (!messageContentRegex.matches(message.removeColor())) return + + val clickStyle = createClickStyle(message) + chatComponent.setChatStyle(clickStyle) + } + + private fun createClickStyle(message: String): ChatStyle { + val style = ChatStyle() + style.setChatClickEvent( + ClickEvent( + ClickEvent.Action.RUN_COMMAND, + "/shsendtranslation ${messageContentRegex.find(message.removeColor())!!.groupValues[1]}" + ) + ) + style.setChatHoverEvent( + HoverEvent( + HoverEvent.Action.SHOW_TEXT, + ChatComponentText("§bClick to translate!") + ) + ) + return style + } + + + companion object { + // Using my own getJSONResponse because of 1 line of difference. + private val parser = JsonParser() + private val builder: HttpClientBuilder = + HttpClients.custom().setUserAgent("SkyHanni/${SkyHanniMod.version}") + .setDefaultHeaders( + mutableListOf( + BasicHeader("Pragma", "no-cache"), + BasicHeader("Cache-Control", "no-cache") + ) + ) + .setDefaultRequestConfig( + RequestConfig.custom() + .build() + ) + .useSystemProperties() + + /* + * Simplified version of the JSON response: + * [ + * [ + * [ + * 'translated sentence one with a space after the punctuation. ' + * 'original sentence one without a space after the punctuation.' + * ], + * [ + * 'translated sentence two without punctuation bc it's last' + * 'original sentence two without punctuation' + * ] + * ], + * null, + * 'target language as a two letter code following ISO 639-1', + * ] + */ + + private fun getJSONResponse(urlString: String, silentError: Boolean = false): JsonElement { + val client = builder.build() + try { + client.execute(HttpGet(urlString)).use { response -> + val entity = response.entity + if (entity != null) { + val retSrc = EntityUtils.toString(entity) + try { + return parser.parse(retSrc) + } catch (e: JsonSyntaxException) { + if (e.message?.contains("Use JsonReader.setLenient(true)") == true) { + println("MalformedJsonException: Use JsonReader.setLenient(true)") + println(" - getJSONResponse: '$urlString'") + LorenzUtils.debug("MalformedJsonException: Use JsonReader.setLenient(true)") + } else if (retSrc.contains("

502 Bad Gateway

")) { + e.printStackTrace() + + } else { + CopyErrorCommand.logError( + Error("Hypixel API error for url: '$urlString'", e), + "Failed to load data from Hypixel API" + ) + } + } + } + } + } catch (throwable: Throwable) { + if (silentError) { + throw throwable + } else { + CopyErrorCommand.logError( + Error("Hypixel API error for url: '$urlString'", throwable), + "Failed to load data from Hypixel API" + ) + } + } finally { + client.close() + } + return JsonObject() + } + + private fun getTranslationToEnglish(message: String): String { + val url = + "https://translate.googleapis.com/translate_a/single?client=gtx&sl=auto&tl=en&dt=t&q=" + URLEncoder.encode( + message, + "UTF-8" + ) + + var messageToSend = "" + val layer1 = getJSONResponse(url).asJsonArray + val language = layer1[2].toString() + if (language == "en") return "Unable to translate!" + + val layer2 = layer1[0] as JsonArray + + for (layer3 in layer2) { + val arrayLayer3 = layer3 as JsonArray + val sentence = arrayLayer3[0].toString() + val sentenceWithoutQuotes = sentence.substring(1, sentence.length - 1) + messageToSend = "$messageToSend$sentenceWithoutQuotes" + } + messageToSend = "$messageToSend §7(Language: $language)" + + return URLDecoder.decode(messageToSend, "UTF-8").replace("\\", "") + + } + + private fun getTranslationFromEnglish(message: String, lang: String): String { + val url = + "https://translate.googleapis.com/translate_a/single?client=gtx&sl=en&tl=$lang&dt=t&q=" + URLEncoder.encode( + message, + "UTF-8" + ) + + val layer1 = getJSONResponse(url).asJsonArray + val layer2 = layer1[0] as JsonArray + + val firstSentence = (layer2[0] as JsonArray).get(0).toString() + var messageToSend = firstSentence.substring(0, firstSentence.length - 1) + for (sentenceIndex in 1..) { + if (!SkyHanniMod.feature.chat.translator) return + var message = "" + for (i in args) { + message = "$message$i " + } + + val translation = getTranslationToEnglish(message) + + if (translation == "Unable to translate!") LorenzUtils.chat("§c[SkyHanni] Unable to translate message :(") + else LorenzUtils.chat("§e[SkyHanni] Found translation: §f$translation") + } + + fun fromEnglish(args: Array) { + if (!SkyHanniMod.feature.chat.translator) return + if (args.size < 2 || args[0].length != 2) { // args[0] is the language code + LorenzUtils.chat("§cUsage: /shcopytranslation ") + return + } + val language = args[0] + var message = "" + for (i in 1..