pissbot-9000/commands/message/utils/translate.js
bignutty d9c2db1510 - do not translate non-rich embeds (fixes gifs, other issues)
- add placeholder for unknown languages in translate ops
2025-03-20 08:51:38 +01:00

202 lines
No EOL
8.5 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const { googleTranslate, googleTranslateMulti} = require('#api');
const { TRANSLATE_LANGUAGES, TRANSLATE_DISPLAY_MAPPINGS, PERMISSION_GROUPS } = require('#constants');
const { createEmbed } = require('#utils/embed');
const { acknowledge } = require('#utils/interactions');
const { codeblock, icon, pill, stringwrap, smallIconPill } = require('#utils/markdown');
const { editOrReply } = require('#utils/message')
const { STATICS } = require('#utils/statics');
const { isSupported, getCodeFromAny } = require('#utils/translate');
const {MessageEmbedTypes} = require("detritus-client/lib/constants");
// TODO(unity): interaction/context/translate.js
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(e[1].type !== MessageEmbedTypes.RICH) continue;
if(emb.description) mappings["embeds/" + i + "/description"] = emb.description;
if(emb.title) mappings["embeds/" + i + "/title"] = emb.title;
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[1].name;
mappings["embeds/" + i + "/fields/" + fi + "/value"] = f[1].value;
fi++;
}
}
i++;
}
}
// 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]
if(e[1].type !== MessageEmbedTypes.RICH) continue;
let newEmbed = {
fields: []
};
// 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.title) newEmbed.title = stringwrap(tr["embeds/" + i + "/title"], 256);
if(emb.description) newEmbed.description = stringwrap(tr["embeds/" + i + "/description"], 4096);
if(emb.author) newEmbed.author = Object.assign({}, emb.author);
if(emb.author?.name) newEmbed.author.name = stringwrap(tr["embeds/" + i + "/author/name"], 256);
if(emb.footer) newEmbed.footer = Object.assign({}, emb.footer);
if(emb.footer?.text) newEmbed.footer.text = stringwrap(tr["embeds/" + i + "/footer/text"], 2048);
if(emb.fields){
let fi = 0;
for(const f of emb.fields){
newEmbed.fields[fi] = {
inline: f[1].inline
}
newEmbed.fields[fi].name = stringwrap(tr["embeds/" + i + "/fields/" + fi + "/name"], 256);
newEmbed.fields[fi].value = stringwrap(tr["embeds/" + i + "/fields/" + fi + "/value"], 1024)
fi++;
}
}
result.embeds[i] = Object.assign({}, newEmbed);
i++;
}
}
return {
message: result,
metadata: translation.response.body
};
}catch(e){
console.log(e)
console.log(mappings)
throw "Translation Failed."
}
}
module.exports = {
name: 'translate',
label: 'text',
aliases: ['tr'],
metadata: {
description: `${smallIconPill("reply", "Supports Replies")}\n\nTranslates text. Supports automatic source language detection.`,
description_short: 'Translate text.',
examples: ['tr groß nussig -from de -to en'],
category: 'utils',
usage: `tr <text> [-to <target language>] [-from <origin language>]`,
slashCommand: "translate"
},
args: [
{name: 'to', default: 'en', type: 'language', help: "Target Language"},
{name: 'from', default: 'auto', type: 'language', help: "Source Language"}
],
permissionsClient: [...PERMISSION_GROUPS.baseline],
run: async (context, args) => {
await acknowledge(context);
let content = args.text;
if(!isSupported(args.to)) return editOrReply(context, createEmbed("warning", context, `Invalid target language (${stringwrap(args.to, 10, false)}).`))
if(!isSupported(args.from)) return editOrReply(context, createEmbed("warning", context, `Invalid source language (${stringwrap(args.from, 10, false)}).`))
let targetLanguage = getCodeFromAny(args.to)
let sourceLanguage = getCodeFromAny(args.from)
// Invoke Message Translation
if(context.message.messageReference) {
try{
// Quick language shortcut - lc.tr de
if(args.text.length >= 1 && getCodeFromAny(args.text)) args.to = args.text;
let targetLanguage = getCodeFromAny(args.to)
let message = await context.message.channel.fetchMessage(context.message.messageReference.messageId);
let translate = await translateMessage(context, message, targetLanguage, sourceLanguage);
if(!translate.message) return editOrReply(context, createEmbed("warning", context, "No translatable content found."))
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, {
content: stringwrap(`-# ${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}`, 2000),
embeds: newMessage.embeds
}));
}catch(e){
console.log(e)
return editOrReply(context, createEmbed("error", context, "Unable to translate message."))
}
}
if(!content.length) return editOrReply(context, createEmbed("warning", context, "No text supplied."))
if(!targetLanguage) return editOrReply(context, createEmbed("warning", context, `Invalid target language (${stringwrap(args.to, 10, false)}).`))
if(!sourceLanguage) return editOrReply(context, createEmbed("warning", context, `Invalid source language (${stringwrap(args.from, 10, false)}).`))
try{
let translate = await googleTranslate(context, content, targetLanguage, sourceLanguage)
let fromFlag = TRANSLATE_DISPLAY_MAPPINGS[translate.response.body.language.from || sourceLanguage] || ''
let toFlag = TRANSLATE_DISPLAY_MAPPINGS[translate.response.body.language.to] || ''
return editOrReply(context, createEmbed("default", context, {
description: `-# ${icon("locale")} ${fromFlag} ${pill(TRANSLATE_LANGUAGES[translate.response.body.language.from || sourceLanguage] || translate.response.body.language.from || "Detected Language")} ${icon("arrow_right")} ${toFlag} ${pill(TRANSLATE_LANGUAGES[translate.response.body.language.to] || translate.response.body.language.to)}\n${codeblock("ansi", [stringwrap(translate.response.body.translation, 1900)])}`,
footer: {
iconUrl: STATICS.googletranslate,
text: `Google Translate • ${context.application.name}`
}
}))
}catch(e){
if(e.response?.body?.status && e.response.body.status === 2) return editOrReply(context, createEmbed("error", context, `Unable to translate text.`))
console.log(e)
return editOrReply(context, createEmbed("error", context, `Something went wrong.`))
}
}
};