diff --git a/commands/interaction/context/translate.js b/commands/interaction/context/translate.js index 3fdde69..80592c5 100644 --- a/commands/interaction/context/translate.js +++ b/commands/interaction/context/translate.js @@ -1,15 +1,105 @@ -const { googleTranslate } = require('#api'); +const { googleTranslateMulti } = require('#api'); const { TRANSLATE_DISPLAY_MAPPINGS, TRANSLATE_LANGUAGES, TRANSLATE_LANGUAGE_MAPPINGS, TRANSLATE_DEFAULT_LANGUAGE_LIST, PERMISSION_GROUPS } = require('#constants'); const { createEmbed } = require('#utils/embed'); const { acknowledge } = require('#utils/interactions'); -const { codeblock, icon, pill } = require('#utils/markdown'); +const { icon } = require('#utils/markdown'); const { editOrReply } = require('#utils/message'); -const { STATICS } = require('#utils/statics'); const { ApplicationCommandTypes, InteractionCallbackTypes, InteractionContextTypes, ApplicationIntegrationTypes } = require("detritus-client/lib/constants"); const { Components } = require('detritus-client/lib/utils'); +async function translateMessage(context, message, to, from){ + let mappings = {}; + + if(message.content){ + // Special Case - this is (very very very likely) a 1p translated message + if(message.content.startsWith(`-# ${icon("subtext_translate")}`)) { + let cnt = message.content.split("\n"); + cnt.shift(); + if(cnt.length >= 1){ mappings.content = cnt.join("\n") } + } else mappings.content = message.content + }; + if(message.embeds) { + let i = 0; + // Message Translation supports Descriptions and Fields + for(const e of message.embeds){ + let emb = e[1] + if(emb.description) mappings["embeds/" + i + "/description"] = emb.description; + if(emb.author?.name) mappings["embeds/" + i + "/author/name"] = emb.author.name; + if(emb.footer?.text) mappings["embeds/" + i + "/footer/text"] = emb.footer.text; + + if(emb.fields){ + let fi = 0; + for(const f of emb.fields){ + mappings["embeds/" + i + "/fields/" + fi + "/name"] = f.name; + mappings["embeds/" + i + "/fields/" + fi + "/value"] = f.value; + fi++; + } + } + } + } + + // Cancel if we don't have anything that can be translated + if(Object.keys(mappings).length == 0) return {}; + + // Translate message via multitranslate endpoint on 1p + try{ + let translation = await googleTranslateMulti(context, mappings, to, from) + + let tr = translation.response.body.translations + + // Deserialize message + let result = {}; + + // This relies on mappings.content to handle the special case + if(mappings.content) result.content = tr["content"]; + + if(message.embeds) { + let i = 0; + result.embeds = []; + // Message Translation supports Descriptions and Fields + for(const e of message.embeds){ + let emb = e[1] + let newEmbed = {}; + + // Elements we don't translate + if(emb.color) newEmbed.color = emb.color; + if(emb.thumbnail) newEmbed.thumbnail = emb.thumbnail; + if(emb.image) newEmbed.image = emb.image; + if(emb.url) newEmbed.url = emb.url; + + if(emb.description) newEmbed.description = tr["embeds/" + i + "/description"]; + + if(emb.author) newEmbed.author = Object.assign({}, emb.author); + if(emb.author?.name) newEmbed.author.name = tr["embeds/" + i + "/author/name"]; + + if(emb.footer) newEmbed.footer = Object.assign({}, emb.footer); + if(emb.footer?.text) newEmbed.footer.text = tr["embeds/" + i + "/footer/text"]; + + if(emb.fields){ + let fi = 0; + for(const f of emb.fields){ + newEmbed.fields[fi].name = tr["embeds/" + i + "/fields/" + fi + "/name"] + newEmbed.fields[fi].value = tr["embeds/" + i + "/fields/" + fi + "/value"] + fi++; + } + } + result.embeds[i] = Object.assign({}, newEmbed); + } + } + + return { + message: result, + metadata: translation.response.body + }; + }catch(e){ + console.log(e) + console.log(mappings) + throw "Translation Failed." + } +} + module.exports = { name: 'Translate Message', type: ApplicationCommandTypes.MESSAGE, @@ -26,15 +116,13 @@ module.exports = { const { message } = args; - if(!message.content) return editOrReply(context, createEmbed("warning", context, "No content found.")) + if(!message.content && !message.embeds) return editOrReply(context, createEmbed("warning", context, "No content found.")) try{ - let translate = await googleTranslate(context, message.content, "en", "auto") + let translate = await translateMessage(context, message, "en", "auto") - let fromFlag = TRANSLATE_DISPLAY_MAPPINGS[translate.response.body.language.from || sourceLanguage] || '' - let toFlag = TRANSLATE_DISPLAY_MAPPINGS[translate.response.body.language.to] || '' - - + if(!translate.message) return editOrReply(context, createEmbed("warning", context, "No translatable content found.")) + const components = new Components({ timeout: 100000, run: async (ctx) => { @@ -42,23 +130,22 @@ module.exports = { try{ if (ctx.userId !== context.userId) return await ctx.respond(InteractionCallbackTypes.DEFERRED_UPDATE_MESSAGE); - let translate = await googleTranslate(context, message.content, ctx.data.values[0], "auto") - - let fromFlag = TRANSLATE_DISPLAY_MAPPINGS[translate.response.body.language.from || sourceLanguage] || '' - let toFlag = TRANSLATE_DISPLAY_MAPPINGS[translate.response.body.language.to] || '' + let translate = await translateMessage(context, message, ctx.data.values[0], "auto") for (let i = 0; i < components.components[0].components[0].options.length; i++) { components.components[0].components[0].options[i].default = (components.components[0].components[0].options[i].value === ctx.data.values[0]) } - await ctx.editOrRespond({ - embeds: [{ - description: `${icon("locale")} ​ ${fromFlag} ${pill(TRANSLATE_LANGUAGES[translate.response.body.language.from || sourceLanguage] || translate.response.body.language.from || args.from)} ​ ​ ​​${icon("arrow_right")} ​ ​ ​ ​${toFlag} ${pill(TRANSLATE_LANGUAGES[translate.response.body.language.to] || translate.response.body.language.to)}\n${codeblock("ansi", [translate.response.body.translation])}`, - footer: { - iconUrl: STATICS.googletranslate, - text: `Google Translate • ${context.application.name}` - } - }], + let fromFlag = TRANSLATE_DISPLAY_MAPPINGS[translate.metadata.language.from || sourceLanguage] || '' + let toFlag = TRANSLATE_DISPLAY_MAPPINGS[translate.metadata.language.to] || '' + + let newMessage = translate.message; + let newMessageContent = "";; + if(newMessage.content) newMessageContent += "\n" + newMessage.content + + return await ctx.editOrRespond({ + content: `-# ${icon("subtext_translate")} Translated from ${fromFlag} **${TRANSLATE_LANGUAGES[translate.metadata.language.from || sourceLanguage] || translate.metadata.language.from || args.from}** to ${toFlag} **${TRANSLATE_LANGUAGES[translate.metadata.language.to] || translate.metadata.language.to}** • Google Translate${newMessageContent}`, + embeds: newMessage.embeds, components }) }catch(e){ @@ -82,15 +169,17 @@ module.exports = { customId: "target-language", options: selectLanguageOptions }) + + let fromFlag = TRANSLATE_DISPLAY_MAPPINGS[translate.metadata.language.from || sourceLanguage] || '' + let toFlag = TRANSLATE_DISPLAY_MAPPINGS[translate.metadata.language.to] || '' + + let newMessage = translate.message; + let newMessageContent = "";; + if(newMessage.content) newMessageContent += "\n" + newMessage.content return editOrReply(context, createEmbed("default", context, { - embeds: [{ - description: `${icon("locale")} ​ ${fromFlag} ${pill(TRANSLATE_LANGUAGES[translate.response.body.language.from || sourceLanguage] || translate.response.body.language.from || args.from)} ​ ​ ​​${icon("arrow_right")} ​ ​ ​ ​${toFlag} ${pill(TRANSLATE_LANGUAGES[translate.response.body.language.to] || translate.response.body.language.to)}\n${codeblock("ansi", [translate.response.body.translation])}`, - footer: { - iconUrl: STATICS.googletranslate, - text: `Google Translate • ${context.application.name}` - } - }], + content: `-# ${icon("subtext_translate")} Translated from ${fromFlag} **${TRANSLATE_LANGUAGES[translate.metadata.language.from || sourceLanguage] || translate.metadata.language.from || args.from}** to ${toFlag} **${TRANSLATE_LANGUAGES[translate.metadata.language.to] || translate.metadata.language.to}** • Google Translate${newMessageContent}`, + embeds: newMessage.embeds, components })) }catch(e){ diff --git a/labscore/api/endpoints.js b/labscore/api/endpoints.js index 8dba315..51794aa 100644 --- a/labscore/api/endpoints.js +++ b/labscore/api/endpoints.js @@ -11,6 +11,7 @@ const Api = Object.freeze({ GOOGLE_SPEECH_RECOGNIZE: '/google/speech/recognize', GOOGLE_SPEECH_RECOGNIZE_LABELS: '/google/speech/multirecognize', GOOGLE_TRANSLATE: '/google/translate/text', + GOOGLE_TRANSLATE_MULTI: '/google/translate/multi', GOOGLE_VISION_COLORS: '/google/vision/colors', GOOGLE_VISION_FACES: '/google/vision/faces', GOOGLE_VISION_LABELS: '/google/vision/labels', diff --git a/labscore/api/index.js b/labscore/api/index.js index eebeb87..6df9c93 100644 --- a/labscore/api/index.js +++ b/labscore/api/index.js @@ -68,6 +68,14 @@ module.exports.googleTranslate = async function(context, text, to, from){ }) } +module.exports.googleTranslateMulti = async function(context, messages, to, from){ + return await request(Api.GOOGLE_TRANSLATE_MULTI, "POST", {}, { + messages: messages, + to: to, + from: from + }) +} + module.exports.googleVisionColors = async function(context, url){ return await request(Api.GOOGLE_VISION_COLORS, "GET", {}, { url: url diff --git a/labscore/constants.js b/labscore/constants.js index 121f92c..f61e9cb 100644 --- a/labscore/constants.js +++ b/labscore/constants.js @@ -79,6 +79,7 @@ module.exports.ICONS = Object.freeze({ "flask_mini": "<:ico_subt_flask:1263593669215256597>", "subtext_lightbulb": "<:ico_subt_lightbulb:1263593690358616084>", + "subtext_translate": "<:ico_subtext_translate:1323844562875187291>", "button_mag": "<:ico_button_mag:1271212564122173552>", "button_thermometer": "<:ico_button_thermometer:1263593823016062987>",