UNPKG

@deeplx/core

Version:

An unofficial but powerful and easy-to-use yet free DeepL API client for Node.js using [DeepL](https://www.deepl.com) by porting [OwO-Network/DeepLX](https://github.com/OwO-Network/DeepLX).

183 lines 6.85 kB
import { randomUUID } from 'node:crypto'; import { createProxy } from 'node-fetch-native/proxy'; import { ResponseError, xfetch } from 'x-fetch'; import { ONESHOT_FREE_ENDPOINT, ONESHOT_PRO_ENDPOINT, CHROME_EXTENSION_ID, CHROME_EXTENSION_VERSION, IMPERSONATED_CHROME_MAJOR, MAX_FREE_TEXT_LENGTH, HTTP_STATUS_NOT_FOUND, HTTP_STATUS_OK, HTTP_STATUS_SERVICE_UNAVAILABLE, HTTP_STATUS_BAD_REQUEST, HTTP_STATUS_PAYLOAD_TOO_LARGE, HTTP_STATUS_TOO_MANY_REQUESTS, TARGET_LANG_MAP, SOURCE_LANG_MAP, } from "./constants.js"; import { abbreviateLanguage } from "./utils.js"; let instanceID_; function getInstanceID() { return (instanceID_ ??= randomUUID()); } let sharedCookies = ''; let warmupPromise = null; export function getSharedCookies() { return sharedCookies; } async function warmCookies(proxyUrl) { if (warmupPromise !== null) { return warmupPromise; } warmupPromise = (async () => { try { const res = await xfetch('https://www.deepl.com/translator', { type: null, ...createProxy({ url: proxyUrl }), }); const setCookie = res.headers.get('set-cookie'); if (setCookie) { const cookies = []; const userCountryMatch = /userCountry=[^;]+/.exec(setCookie); if (userCountryMatch) { cookies.push(userCountryMatch[0]); } const verifiedBotMatch = /verifiedBot=[^;]+/.exec(setCookie); if (verifiedBotMatch) { cookies.push(verifiedBotMatch[0]); } if (cookies.length > 0) { sharedCookies = cookies.join('; '); } } } catch { warmupPromise = null; } })(); return warmupPromise; } function resolveLang(code, kind) { if (!code || code.toLowerCase() === 'auto') { if (kind === 'target') { return { success: false, error: 'target_lang cannot be "auto" or empty' }; } return { success: true, value: undefined }; } const langMap = kind === 'target' ? TARGET_LANG_MAP : SOURCE_LANG_MAP; const upperCode = code.toUpperCase(); let mapped = langMap[upperCode]; if (!mapped) { const abbreviated = abbreviateLanguage(code); if (abbreviated) { mapped = langMap[abbreviated.toUpperCase()]; } } if (!mapped) { return { success: false, error: `unsupported ${kind}_lang "${code}"` }; } return { success: true, value: mapped }; } function parseTranslationError(error, reqId) { if (error instanceof ResponseError) { const status = error.response.status; if (status === HTTP_STATUS_TOO_MANY_REQUESTS) { return { code: status, id: reqId, message: 'too many requests, ...' }; } return { code: status, id: reqId, message: error.message }; } return { code: HTTP_STATUS_SERVICE_UNAVAILABLE, id: reqId, message: String(error), }; } function buildHeaders(dlSession, requestCookies) { const authValue = dlSession ? `Bearer ${dlSession}` : 'None'; const headers = { 'Content-Type': 'application/json', Accept: '*/*', Authorization: authValue, Origin: `chrome-extension://${CHROME_EXTENSION_ID}`, 'Sec-Fetch-Site': 'cross-site', 'Sec-Fetch-Mode': 'cors', 'Sec-Fetch-Dest': 'empty', 'Accept-Encoding': 'gzip, deflate, br', 'User-Agent': `Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${IMPERSONATED_CHROME_MAJOR}.0.0.0 Safari/537.36`, }; if (requestCookies) { headers['Cookie'] = requestCookies; } else if (sharedCookies) { headers['Cookie'] = sharedCookies; } return headers; } function processTranslationResponse(response, reqId, sourceLang, targetLang, dlSession) { if (!response?.translations || response.translations.length === 0) { return { code: HTTP_STATUS_SERVICE_UNAVAILABLE, id: reqId, message: 'Translation failed', }; } const mainTranslation = response.translations[0]; if (!mainTranslation.text) { return { code: HTTP_STATUS_SERVICE_UNAVAILABLE, id: reqId, message: 'Translation failed', }; } const detectedLang = mainTranslation.detected_source_language ? mainTranslation.detected_source_language.toUpperCase() : sourceLang || 'auto'; return { code: HTTP_STATUS_OK, id: reqId, data: mainTranslation.text, alternatives: [], sourceLang: detectedLang, targetLang, method: dlSession ? 'Pro' : 'Free', }; } export const translateByDeepLX = async (sourceLang, targetLang, text, proxyUrl, dlSession, signal, skipWarm, cookies) => { if (!text) { return { code: HTTP_STATUS_NOT_FOUND, message: 'No text to translate' }; } if ([...text].length > MAX_FREE_TEXT_LENGTH) { return { code: HTTP_STATUS_PAYLOAD_TOO_LARGE, message: `text exceeds maximum length: ${[...text].length} characters (anonymous oneshot limit is ${MAX_FREE_TEXT_LENGTH})`, }; } let requestCookies = cookies; if (!requestCookies && !dlSession && !skipWarm) { await warmCookies(proxyUrl); requestCookies = sharedCookies; } const targetResult = resolveLang(targetLang, 'target'); if (!targetResult.success) { return { code: HTTP_STATUS_BAD_REQUEST, message: targetResult.error }; } const sourceResult = resolveLang(sourceLang, 'source'); if (!sourceResult.success) { return { code: HTTP_STATUS_BAD_REQUEST, message: sourceResult.error }; } const reqData = { text: [text], target_lang: targetResult.value, source_lang: sourceResult.value, usage_type: 'Translate', app_information: { os: 'brex_macOS', os_version: `brex_chrome_${IMPERSONATED_CHROME_MAJOR}.0.0.0`, app_version: CHROME_EXTENSION_VERSION, app_build: 'chrome_web_store', instance_id: getInstanceID(), }, }; const reqId = Date.now(); try { const response = await xfetch(dlSession ? ONESHOT_PRO_ENDPOINT : ONESHOT_FREE_ENDPOINT, { method: 'POST', body: reqData, headers: buildHeaders(dlSession, requestCookies), signal, ...createProxy({ url: proxyUrl }), }); return processTranslationResponse(response, reqId, sourceLang, targetLang, dlSession); } catch (error) { return parseTranslationError(error, reqId); } }; //# sourceMappingURL=translate.js.map