diff --git a/apps/readest-app/src/pages/api/deepl/translate.ts b/apps/readest-app/src/pages/api/deepl/translate.ts index d149c00b..8f6679b5 100644 --- a/apps/readest-app/src/pages/api/deepl/translate.ts +++ b/apps/readest-app/src/pages/api/deepl/translate.ts @@ -2,6 +2,7 @@ import { NextApiRequest, NextApiResponse } from 'next'; import { corsAllMethods, runMiddleware } from '@/utils/cors'; import { supabase } from '@/utils/supabase'; import { getUserPlan } from '@/utils/access'; +import { query as deeplQuery } from '@/utils/deepl'; const DEEPL_FREE_API = 'https://api-free.deepl.com/v2/translate'; const DEEPL_PRO_API = 'https://api.deepl.com/v2/translate'; @@ -27,8 +28,9 @@ const getDeepLAPIKey = (keys: string | undefined) => { const handler = async (req: NextApiRequest, res: NextApiResponse) => { const { user, token } = await getUserAndToken(req.headers['authorization']); let deeplApiUrl = DEEPL_FREE_API; + let userPlan = 'free'; if (user && token) { - const userPlan = getUserPlan(token); + userPlan = getUserPlan(token); if (userPlan !== 'free') deeplApiUrl = DEEPL_PRO_API; } const deeplAuthKey = @@ -38,7 +40,16 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => { await runMiddleware(req, res, corsAllMethods); + const { text, source_lang = 'auto', target_lang = 'en' } = req.body; try { + if (userPlan !== 'pro') { + const result = await deeplQuery({ + text: text[0], + sourceLang: source_lang, + targetLang: target_lang, + }); + return res.status(200).json(result); + } const response = await fetch(deeplApiUrl, { method: 'POST', headers: { diff --git a/apps/readest-app/src/utils/deepl.ts b/apps/readest-app/src/utils/deepl.ts new file mode 100644 index 00000000..7b8c9784 --- /dev/null +++ b/apps/readest-app/src/utils/deepl.ts @@ -0,0 +1,83 @@ +const API_URL = 'https://www2.deepl.com/jsonrpc'; +const HEADERS = { + 'Content-Type': 'application/json', +}; + +export type RequestParams = { + text: string; + sourceLang: string; + targetLang: string; +}; + +export type RawResponseParams = { + jsonrpc: string; + id: number; + result: { + texts: { + alternatives: { + text: string; + }[]; + text: string; + }[]; + lang: string; + lang_is_confident: boolean; + detectedLanguages: { unsupported: number } & Record; + }; +}; + +const buildRequestData = (params: RequestParams) => { + const getTimeStamp = () => { + const ts = Date.now(); + const iCount = params.text.split('i').length; + return iCount > 1 ? ts - (ts % iCount) + iCount : ts; + }; + + const postData = { + jsonrpc: '2.0', + method: 'LMT_handle_texts', + id: Math.floor(Math.random() * 90000000) + 10000000, + params: { + texts: [{ text: params.text, requestAlternatives: 3 }], + timestamp: getTimeStamp(), + splitting: 'newlines', + lang: { + source_lang_user_selected: params.sourceLang.toUpperCase(), + target_lang: params.targetLang.toUpperCase(), + }, + }, + }; + + let postStr = JSON.stringify(postData); + + if ((postData.id + 5) % 29 === 0 || (postData.id + 3) % 13 === 0) { + postStr = postStr.replace('"method":"', '"method" : "'); + } else { + postStr = postStr.replace('"method":"', '"method": "'); + } + + return postStr; +}; + +export const query = async (params: RequestParams) => { + const response = await fetch(API_URL, { + headers: HEADERS, + method: 'POST', + body: buildRequestData(params), + }); + + if (!response.ok) { + throw new Error( + response.status === 429 ? 'Too many requests, please try again later.' : 'Unknown error.', + ); + } + + const { result } = (await response.json()) as RawResponseParams; + return { + translations: [ + { + detected_source_language: result?.lang || params?.sourceLang || 'auto', + text: result?.texts?.[0]?.text || '', + }, + ], + }; +};