From 307d0605ea9ea69765fced2f4a440726b1dbd4a5 Mon Sep 17 00:00:00 2001 From: Rico040 <93081680+Rico040@users.noreply.github.com> Date: Mon, 27 May 2024 13:05:08 +1000 Subject: [PATCH] feat(Dislate Lite): temp fix --- plugins/dislate/manifest.json | 22 ++++ plugins/dislate/src/api/DeepL.ts | 29 ++++++ plugins/dislate/src/api/index.ts | 5 + plugins/dislate/src/index.ts | 22 ++++ plugins/dislate/src/lang.ts | 32 ++++++ plugins/dislate/src/patches/ActionSheet.tsx | 106 ++++++++++++++++++++ plugins/dislate/src/patches/Commands.tsx | 68 +++++++++++++ plugins/dislate/src/settings/TargetLang.tsx | 35 +++++++ plugins/dislate/src/settings/index.tsx | 48 +++++++++ plugins/dislate/src/type.d.ts | 7 ++ 10 files changed, 374 insertions(+) create mode 100644 plugins/dislate/manifest.json create mode 100644 plugins/dislate/src/api/DeepL.ts create mode 100644 plugins/dislate/src/api/index.ts create mode 100644 plugins/dislate/src/index.ts create mode 100644 plugins/dislate/src/lang.ts create mode 100644 plugins/dislate/src/patches/ActionSheet.tsx create mode 100644 plugins/dislate/src/patches/Commands.tsx create mode 100644 plugins/dislate/src/settings/TargetLang.tsx create mode 100644 plugins/dislate/src/settings/index.tsx create mode 100644 plugins/dislate/src/type.d.ts diff --git a/plugins/dislate/manifest.json b/plugins/dislate/manifest.json new file mode 100644 index 0000000..01d370c --- /dev/null +++ b/plugins/dislate/manifest.json @@ -0,0 +1,22 @@ +{ + "name": "Dislate Lite", + "description": "Translates text into a desired language.", + "authors": [ + { + "name": "Acquite <3", + "id": "581573474296791211" + }, + { + "name": "sapphire :>", + "id": "757982547861962752" + }, + { + "name": "Rico040 ;)", + "id": "619474349845643275" + } + ], + "main": "src/index.ts", + "vendetta": { + "icon": "ic_locale_24px" + } +} \ No newline at end of file diff --git a/plugins/dislate/src/api/DeepL.ts b/plugins/dislate/src/api/DeepL.ts new file mode 100644 index 0000000..f455739 --- /dev/null +++ b/plugins/dislate/src/api/DeepL.ts @@ -0,0 +1,29 @@ +import { DeepLResponse } from "../type" + +// TODO: Change API link when it'll be down +const API_URL = "https://deeplx.vercel.app/translate" + +const translate = async (text: string, source_lang: string = "auto", target_lang: string, original: boolean = false) => { + try { + if (original) return { source_lang, text } + const data: DeepLResponse = await (await fetch(API_URL, { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({ + text, + source_lang, + target_lang + }) + })).json() + if (data.code !== 200) throw Error(`Failed to translate text from DeepL: ${data.message}`) + return { source_lang, text: data.data } + } catch (e) { + throw Error(`Failed to fetch from DeepL: ${e}`) + } +} + +export default { translate } + + diff --git a/plugins/dislate/src/api/index.ts b/plugins/dislate/src/api/index.ts new file mode 100644 index 0000000..e67f382 --- /dev/null +++ b/plugins/dislate/src/api/index.ts @@ -0,0 +1,5 @@ +import DeepL from "./DeepL" + +export { + DeepL +} \ No newline at end of file diff --git a/plugins/dislate/src/index.ts b/plugins/dislate/src/index.ts new file mode 100644 index 0000000..1acc998 --- /dev/null +++ b/plugins/dislate/src/index.ts @@ -0,0 +1,22 @@ +import { storage } from "@vendetta/plugin" +import patchActionSheet from "./patches/ActionSheet" +import patchCommands from "./patches/Commands" +import Settings from "./settings" + +export const settings: { + source_lang?: string + target_lang?: string +} = storage + +settings.target_lang ??= "EN" + +let patches = [] + +export default { + onLoad: () => patches = [ + patchActionSheet(), + patchCommands() + ], + onUnload: () => { for (const unpatch of patches) unpatch() }, + settings: Settings +} diff --git a/plugins/dislate/src/lang.ts b/plugins/dislate/src/lang.ts new file mode 100644 index 0000000..badcadb --- /dev/null +++ b/plugins/dislate/src/lang.ts @@ -0,0 +1,32 @@ +export default { + "arabic": "AR", + "bulgarian": "BG", + "czech": "CS", + "danish": "DA", + "german": "DE", + "greek": "EL", + "english": "EN", // EN-GB, EN-US + "spanish": "ES", + "estonian": "ET", + "finnish": "FI", + "french": "FR", + "hungarian": "HU", + "indonesian": "ID", + "italian": "IT", + "japanese": "JA", + "korean": "KO", + "lithuanian": "LT", + "latvian": "LV", + "norwegian": "NO", + "dutch": "NL", + "polish": "PL", + "portuguese": "PT", // PT-BR, PT-PT + "romanian": "RO", + "russian": "RU", + "slovak": "SK", + "slovenian": "SL", + "swedish": "SV", + "turkish": "TR", + "ukrainian": "UK", + "chinese-simplified": "ZH" +} diff --git a/plugins/dislate/src/patches/ActionSheet.tsx b/plugins/dislate/src/patches/ActionSheet.tsx new file mode 100644 index 0000000..8df5667 --- /dev/null +++ b/plugins/dislate/src/patches/ActionSheet.tsx @@ -0,0 +1,106 @@ +import { findByProps, findByStoreName } from "@vendetta/metro" +import { FluxDispatcher, React, ReactNative, i18n, stylesheet } from "@vendetta/metro/common" +import { before, after } from "@vendetta/patcher" +import { semanticColors } from "@vendetta/ui" +import { getAssetIDByName } from "@vendetta/ui/assets" +import { Forms } from "@vendetta/ui/components" +import { findInReactTree } from "@vendetta/utils" +import { settings } from ".." + +import { DeepL } from "../api" +import { showToast } from "@vendetta/ui/toasts" +import { logger } from "@vendetta" + +const LazyActionSheet = findByProps("openLazy", "hideActionSheet") +const ActionSheetRow = findByProps("ActionSheetRow")?.ActionSheetRow ?? Forms.FormRow // no icon if legacy +const MessageStore = findByStoreName("MessageStore") +const ChannelStore = findByStoreName("ChannelStore") + +const styles = stylesheet.createThemedStyleSheet({ + iconComponent: { + width: 24, + height: 24, + tintColor: semanticColors.INTERACTIVE_NORMAL + } +}) + +let cachedData: object[] = [] + +export default () => before("openLazy", LazyActionSheet, ([component, key, msg]) => { + const message = msg?.message + if (key !== "MessageLongPressActionSheet" || !message) return + component.then(instance => { + const unpatch = after("default", instance, (_, component) => { + React.useEffect(() => () => { unpatch() }, []) + + const buttons = findInReactTree(component, x => x?.[0]?.type?.name === "ButtonRow") + if (!buttons) return + const position = Math.max(buttons.findIndex((x: any) => x.props.message === i18n.Messages.MARK_UNREAD), 0) + + const originalMessage = MessageStore.getMessage( + message.channel_id, + message.id + ) + if (!originalMessage?.content && !message.content) return + + const messageId = originalMessage?.id ?? message.id + const messageContent = originalMessage?.content ?? message.content + const existingCachedObject = cachedData.find((o: any) => Object.keys(o)[0] === messageId, "cache object") + + const translateType = existingCachedObject ? "Revert" : "Translate" + const icon = translateType === "Translate" ? getAssetIDByName("ic_locale_24px") : getAssetIDByName("ic_highlight") + + const translate = async () => { + try { + const target_lang = settings.target_lang + const isTranslated = translateType === "Translate" + + const translate = await DeepL.translate(originalMessage.content, undefined, target_lang, !isTranslated) + + FluxDispatcher.dispatch({ + type: "MESSAGE_UPDATE", + message: { + ...originalMessage, + content: `${isTranslated ? translate.text : (existingCachedObject as object)[messageId]}` + + ` ${isTranslated ? `\`[${target_lang?.toLowerCase()}]\`` + : ""}`, + guild_id: ChannelStore.getChannel( + originalMessage.channel_id + ).guild_id, + }, + log_edit: false + }) + + isTranslated + ? cachedData.unshift({ [messageId]: messageContent }) + : cachedData = cachedData.filter((e: any) => e !== existingCachedObject, "cached data override") + } catch (e) { + showToast("Failed to translate message. Please check Debug Logs for more info.", getAssetIDByName("Small")) + logger.error(e) + } finally { + return LazyActionSheet.hideActionSheet() + } + } + + + buttons.splice(position, 0, ( + ( + + )} + /> + } + onPress={translate} + /> + )) + }) + }) +}) diff --git a/plugins/dislate/src/patches/Commands.tsx b/plugins/dislate/src/patches/Commands.tsx new file mode 100644 index 0000000..9d3cfe2 --- /dev/null +++ b/plugins/dislate/src/patches/Commands.tsx @@ -0,0 +1,68 @@ +import { logger } from "@vendetta" +import { registerCommand } from "@vendetta/commands" +import { ApplicationCommandInputType, ApplicationCommandType, ApplicationCommandOptionType } from "../../../../ApplicationCommandTypes" +import { showToast } from "@vendetta/ui/toasts" +import { getAssetIDByName } from "@vendetta/ui/assets" +import { Codeblock } from "@vendetta/ui/components" +import { showConfirmationAlert } from "@vendetta/ui/alerts" +import { findByProps } from "@vendetta/metro" + +import lang from "../lang" +import { DeepL } from "../api" + +const ClydeUtils = findByProps("sendBotMessage") +const langOptions = Object.entries(lang).map(([key, value]) => ({ + name: key, + displayName: key, + value: value +})) + +export default () => registerCommand({ + name: "translate", + displayName: "translate", + description: "Send a message using Dislate in any language chosen, using the DeepL Translate API.", + displayDescription: "Send a message using Dislate in any language chosen, using the DeepL Translate API.", + applicationId: "-1", + type: ApplicationCommandType.CHAT as number, + inputType: ApplicationCommandInputType.BUILT_IN_TEXT as number, + options: [ + { + name: "text", + displayName: "text", + description: "The text/message for Dislate to translate. Please note some formatting of mentions and emojis may break due to the API.", + displayDescription: "The text/message for Dislate to translate. Please note some formatting of mentions and emojis may break due to the API.", + type: ApplicationCommandOptionType.STRING as number, + required: true + }, + { + name: "language", + displayName: "language", + description: "The language that Dislate will translate the text into. This can be any language from the list.", + displayDescription: "The language that Dislate will translate the text into. This can be any language from the list.", + type: ApplicationCommandOptionType.STRING as number, + // @ts-ignore + choices: [...langOptions], + required: true + } + ], + async execute(args, ctx) { + const [text, lang] = args + try { + const content = await DeepL.translate(text.value, null, lang.value) + return await new Promise((resolve): void => showConfirmationAlert({ + title: "Are you sure you want to send it?", + content: ( + + {content.text} + + ), + confirmText: "Yep, send it!", + onConfirm: () => resolve({ content: content.text }), + cancelText: "Nope, don't send it" + })) + } catch (e) { + logger.error(e) + return ClydeUtils.sendBotMessage(ctx.channel.id, "Failed to translate message. Please check Debug Logs for more info.", getAssetIDByName("Small")) + } + } +}) \ No newline at end of file diff --git a/plugins/dislate/src/settings/TargetLang.tsx b/plugins/dislate/src/settings/TargetLang.tsx new file mode 100644 index 0000000..59e86e3 --- /dev/null +++ b/plugins/dislate/src/settings/TargetLang.tsx @@ -0,0 +1,35 @@ +import { getAssetIDByName } from "@vendetta/ui/assets" +import { React, ReactNative } from "@vendetta/metro/common" +import { Forms, Search } from "@vendetta/ui/components" +import { showToast } from "@vendetta/ui/toasts" +import { useProxy } from "@vendetta/storage" +import { settings } from ".." +import Lang from "../lang" + +const { FormRow } = Forms +const { ScrollView } = ReactNative + +export default () => { + useProxy(settings) + const [query, setQuery] = React.useState("") + return ( + { + setQuery(text) + }} + /> + { + Object.entries(Lang).filter(([key, value]) => key.includes(query)).map(([key, value]) => } + onPress={() => { + if (settings.target_lang == value) return + settings.target_lang = value + showToast(`Saved ToLang to ${key}`, getAssetIDByName("check")) + }} + />) + } + ) +} \ No newline at end of file diff --git a/plugins/dislate/src/settings/index.tsx b/plugins/dislate/src/settings/index.tsx new file mode 100644 index 0000000..5e50b52 --- /dev/null +++ b/plugins/dislate/src/settings/index.tsx @@ -0,0 +1,48 @@ +import { getAssetIDByName } from "@vendetta/ui/assets" +import { React, ReactNative, stylesheet, constants, NavigationNative, url } from "@vendetta/metro/common" +import { semanticColors } from "@vendetta/ui" +import { Forms } from "@vendetta/ui/components" +import { manifest } from "@vendetta/plugin" +import { useProxy } from "@vendetta/storage" + +import { settings } from ".." +import TargetLang from "./TargetLang" + +const { ScrollView, Text } = ReactNative +const { FormRow } = Forms + +const styles = stylesheet.createThemedStyleSheet({ + subheaderText: { + color: semanticColors.HEADER_SECONDARY, + textAlign: 'center', + margin: 10, + marginBottom: 50, + letterSpacing: 0.25, + fontFamily: constants.Fonts.PRIMARY_BOLD, + fontSize: 14 + } +}) + +export default () => { + const navigation = NavigationNative.useNavigation() + useProxy(settings) + + return ( + + } + trailing={() => } + onPress={() => navigation.push("VendettaCustomPage", { + title: "Translate to", + render: TargetLang, + })} + /> + + url.openURL("https://github.com/aeongdesu/vdplugins")}> + {`Build: (${manifest.hash.substring(0, 7)})`} + + + ) +} \ No newline at end of file diff --git a/plugins/dislate/src/type.d.ts b/plugins/dislate/src/type.d.ts new file mode 100644 index 0000000..0a7e9d1 --- /dev/null +++ b/plugins/dislate/src/type.d.ts @@ -0,0 +1,7 @@ +export interface DeepLResponse { + alternatives?: string[] + code?: number + message?: string + data?: string + id?: number +} \ No newline at end of file