@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
JavaScript
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