├── api ├── index.js └── translate.js ├── vercel.json ├── package.json ├── LICENSE ├── .github └── workflows │ └── sync.yml ├── .gitignore ├── server.js ├── README.md ├── README_RU.md └── main.js /api/index.js: -------------------------------------------------------------------------------- 1 | module.exports = (req, res) => { 2 | res.json({ 3 | code: 200, 4 | message: "Welcome to the DeepL Free API. Please POST to /api/translate. Visit http://github.com/OwO-Network/DeepLX for more information. \n If ip not working, write issue on github" 5 | }); 6 | }; 7 | -------------------------------------------------------------------------------- /vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "rewrites": [ 3 | { 4 | "source": "/", 5 | "destination": "/api/index.js" 6 | } 7 | ], 8 | "headers": [ 9 | { 10 | "source": "/(.*)", 11 | "headers": [ 12 | { 13 | "key": "Access-Control-Allow-Origin", 14 | "value": "*" 15 | } 16 | ] 17 | } 18 | ] 19 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "deeplx-serverless", 3 | "version": "1.0.0", 4 | "description": "DeepLX Free API for serverless", 5 | "main": "main.js", 6 | "scripts": { 7 | "start": "node server.js", 8 | "zip": "zip -r dist.zip ./" 9 | }, 10 | "keywords": [ 11 | "deeplx", 12 | "deepl", 13 | "translate", 14 | "serverless" 15 | ], 16 | "license": "MIT", 17 | "dependencies": { 18 | "axios": "^1.8.1", 19 | "body-parser": "^1.20.2", 20 | "brotli": "^1.3.3", 21 | "chalk": "^5.4.1", 22 | "express": "^4.18.2", 23 | "lodash": "^4.17.21", 24 | "random-int": "^3.0.0" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Sergey Pinus 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/sync.yml: -------------------------------------------------------------------------------- 1 | name: Upstream Sync 2 | 3 | permissions: 4 | contents: write 5 | 6 | on: 7 | schedule: 8 | - cron: "0 0 * * *" 9 | workflow_dispatch: 10 | 11 | jobs: 12 | sync_with_upstream: 13 | name: Sync with Upstream 14 | runs-on: ubuntu-latest 15 | if: ${{ github.event.repository.fork }} 16 | 17 | steps: 18 | - name: Checkout target repo 19 | uses: actions/checkout@v3 20 | 21 | - name: Sync Upstream 22 | uses: aormsby/Fork-Sync-With-Upstream-action@v3.4 23 | with: 24 | target_repo_token: ${{ secrets.GITHUB_TOKEN }} 25 | upstream_sync_repo: bropines/Deeplx-vercel 26 | upstream_sync_branch: main 27 | target_sync_branch: main 28 | test_mode: false 29 | 30 | - name: Check for Failure 31 | if: failure() 32 | run: | 33 | echo "[Error] Due to a change in the workflow file of the upstream repository, GitHub has automatically suspended the scheduled automatic update. You need to manually sync your fork." 34 | exit 1 35 | -------------------------------------------------------------------------------- /api/translate.js: -------------------------------------------------------------------------------- 1 | const { translate } = require('../main'); 2 | 3 | module.exports = async (req, res) => { 4 | const startTime = Date.now(); 5 | 6 | if (req.method === 'OPTIONS') { 7 | res.setHeader('Access-Control-Allow-Origin', '*'); 8 | res.setHeader('Access-Control-Allow-Methods', 'POST, GET, OPTIONS'); 9 | res.setHeader('Access-Control-Allow-Headers', 'Content-Type'); 10 | res.setHeader('Access-Control-Max-Age', '86400'); 11 | return res.status(204).end(); 12 | } 13 | 14 | if (req.method !== 'POST' || !req.body || !req.body.text) { 15 | const duration = Date.now() - startTime; 16 | console.log(`[LOG] ${new Date().toISOString()} | 404 | ${duration}ms | POST "/translate"`); 17 | return res.status(404).json({ 18 | code: 404, 19 | message: 'The path was not found', 20 | }); 21 | } 22 | 23 | const { text, source_lang = 'auto', target_lang = 'En', tag_handling = '', dl_session = '', proxy = '' } = req.body; 24 | 25 | try { 26 | const result = await translate(text, source_lang, target_lang, tag_handling, dl_session, proxy); 27 | const duration = Date.now() - startTime; 28 | console.log(`[LOG] ${new Date().toISOString()} | 200 | ${duration}ms | POST "/translate"`); 29 | 30 | res.json({ 31 | alternatives: result.alternatives, 32 | code: 200, 33 | data: result.data, 34 | id: result.id, 35 | method: result.method, 36 | source_lang: result.source_lang, 37 | target_lang: result.target_lang, 38 | }); 39 | } catch (error) { 40 | const duration = Date.now() - startTime; 41 | console.error(`[ERROR] ${new Date().toISOString()} | 500 | ${duration}ms | POST "/translate" | ${error.message}`); 42 | res.status(500).json({ code: 500, message: 'Translation error', error: error.message }); 43 | } 44 | }; 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Snowpack dependency directory (https://snowpack.dev/) 46 | web_modules/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Optional stylelint cache 58 | .stylelintcache 59 | 60 | # Microbundle cache 61 | .rpt2_cache/ 62 | .rts2_cache_cjs/ 63 | .rts2_cache_es/ 64 | .rts2_cache_umd/ 65 | 66 | # Optional REPL history 67 | .node_repl_history 68 | 69 | # Output of 'npm pack' 70 | *.tgz 71 | 72 | # Yarn Integrity file 73 | .yarn-integrity 74 | 75 | # dotenv environment variable files 76 | .env 77 | .env.development.local 78 | .env.test.local 79 | .env.production.local 80 | .env.local 81 | 82 | # parcel-bundler cache (https://parceljs.org/) 83 | .cache 84 | .parcel-cache 85 | 86 | # Next.js build output 87 | .next 88 | out 89 | 90 | # Nuxt.js build / generate output 91 | .nuxt 92 | dist 93 | 94 | # Gatsby files 95 | .cache/ 96 | # Comment in the public line in if your project uses Gatsby and not Next.js 97 | # https://nextjs.org/blog/next-9-1#public-directory-support 98 | # public 99 | 100 | # vuepress build output 101 | .vuepress/dist 102 | 103 | # vuepress v2.x temp and cache directory 104 | .temp 105 | .cache 106 | 107 | # Docusaurus cache and generated files 108 | .docusaurus 109 | 110 | # Serverless directories 111 | .serverless/ 112 | 113 | # FuseBox cache 114 | .fusebox/ 115 | 116 | # DynamoDB Local files 117 | .dynamodb/ 118 | 119 | # TernJS port file 120 | .tern-port 121 | 122 | # Stores VSCode versions used for testing VSCode extensions 123 | .vscode-test 124 | 125 | # yarn v2 126 | .yarn/cache 127 | .yarn/unplugged 128 | .yarn/build-state.yml 129 | .yarn/install-state.gz 130 | .pnp.* 131 | bun* 132 | test* 133 | temp/ -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const chalk = require('chalk').default; 3 | 4 | const args = process.argv.slice(2); 5 | const isDebugMode = args.includes('-d') || args.includes('--debug'); 6 | 7 | const app = express(); 8 | const PORT = process.env.PORT || 3000; 9 | 10 | function getStatusColor(statusCode) { 11 | if (statusCode >= 500) return chalk.redBright; 12 | if (statusCode >= 400) return chalk.yellowBright; 13 | if (statusCode >= 300) return chalk.cyanBright; 14 | if (statusCode >= 200) return chalk.greenBright; 15 | return chalk.whiteBright; // Default 16 | } 17 | 18 | function getMethodColor(method) { 19 | switch (method.toUpperCase()) { 20 | case 'GET': return chalk.blueBright; 21 | case 'POST': return chalk.magentaBright; 22 | case 'PUT': return chalk.yellow; 23 | case 'DELETE': return chalk.red; 24 | case 'OPTIONS': return chalk.grey; 25 | default: return chalk.white; 26 | } 27 | } 28 | 29 | app.use(express.json()); 30 | 31 | app.use((req, res, next) => { 32 | const start = Date.now(); 33 | const timestamp = new Date().toISOString(); 34 | 35 | res.on('finish', () => { 36 | const duration = Date.now() - start; 37 | const status = res.statusCode; 38 | const statusColor = getStatusColor(status); 39 | const methodColor = getMethodColor(req.method); 40 | 41 | console.log( 42 | `${chalk.dim(timestamp)} | ${statusColor(status)} | ${chalk.cyan(duration.toString().padStart(4, ' ') + 'ms')} | ${methodColor(req.method.padEnd(7))} ${chalk.white(req.originalUrl)}` 43 | ); 44 | 45 | if (isDebugMode) { 46 | console.log(chalk.dim(' ├─ Headers:'), chalk.grey(JSON.stringify(req.headers, null, 2).substring(0, 500) + (JSON.stringify(req.headers).length > 500 ? '...' : ''))); // Log first 500 chars of headers 47 | if (req.body && Object.keys(req.body).length > 0) { 48 | // IMPORTANT: Be careful logging bodies in production, might contain sensitive data! 49 | // Here we log a snippet for debugging. 50 | console.log(chalk.dim(' └─ Body Snippet:'), chalk.grey(JSON.stringify(req.body).substring(0, 200) + (JSON.stringify(req.body).length > 200 ? '...' : ''))); // Log first 200 chars of body 51 | } else { 52 | console.log(chalk.dim(' └─ Body:'), chalk.grey('(empty)')); 53 | } 54 | } 55 | }); 56 | next(); 57 | }); 58 | 59 | app.use((req, res, next) => { 60 | res.header('Access-Control-Allow-Origin', '*'); 61 | res.header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS'); 62 | res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization'); 63 | if (req.method === 'OPTIONS') { 64 | console.log(`${chalk.dim(new Date().toISOString())} | ${chalk.grey('204')} | ${chalk.grey('Preflight')} | ${getMethodColor('OPTIONS')('OPTIONS'.padEnd(7))} ${chalk.white(req.originalUrl)}`); 65 | return res.status(204).end(); 66 | } 67 | next(); 68 | }); 69 | 70 | const indexHandler = require('./api/index.js'); 71 | app.get('/', indexHandler); 72 | 73 | const translateHandler = require('./api/translate.js'); 74 | app.post('/api/translate', translateHandler); 75 | 76 | app.use((req, res) => { 77 | res.status(404).json({ 78 | code: 404, 79 | message: "Path not found", 80 | }); 81 | }); 82 | 83 | 84 | app.listen(PORT, () => { 85 | console.log(chalk.green(`[INFO] Server running on http://localhost:${PORT}`)); 86 | if (isDebugMode) { 87 | console.log(chalk.yellow('[DEBUG] Debug mode enabled. Verbose logging active.')); 88 | } else { 89 | console.log(chalk.blue('[INFO] Logging enabled (standard). Use -d or --debug for verbose logs.')); 90 | } 91 | console.log(chalk.blue('[INFO] Ready for requests...')); 92 | }); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | DeepLX Vercel 2 | ============= 3 | 4 | [English](README.md) | [Русский](README_RU.md) 5 | 6 | A free, serverless solution for DeepL translation API, allowing seamless integration with your applications. Deploy your own instance or run it locally as per your needs. 7 | 8 | Features 9 | -------- 10 | 11 | * **Serverless Architecture**: Built to leverage the scalability and simplicity of serverless platforms. 12 | * **Easy Deployment**: One-click deployment to Vercel with minimal configuration. 13 | * **Local Development**: Supports local development for testing and modifications. (Either via Vercel CLI, or via the method described below) 14 | * **Updated API Logic**: Compatible with the latest DeepLX API changes for reliable translations. 15 | 16 | Deploying on Vercel 17 | ------------------- 18 | 19 | Deploy your own DeepLX translation API by clicking the button below. 20 | 21 | [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fbropines%2FDeeplx-vercel) 22 | 23 | After clicking the button, simply follow the prompts to set up the API on Vercel. 24 | 25 | Running Locally 26 | --------------- 27 | 28 | All the same, I added a local server so that you can start and test it. To do this, you need to install Node.js and NPM (I use Bun, but this is not necessary). 29 | After installing node.js and NPM, follow the following commands in the terminal: 30 | 31 | ```bash 32 | git clone https://github.com/bropines/Deeplx-vercel 33 | cd Deeplx-vercel 34 | npm install (or bun install) 35 | npm server.js (or bun server.js) 36 | ``` 37 | 38 | This will start a local server with the API running on `http://localhost:9000`. 39 | 40 | Usage 41 | ----- 42 | 43 | Once deployed or running locally, you can start making requests to translate text. Make a `POST` request to `/api/translate` with the following JSON structure: 44 | 45 | ```json 46 | { 47 | "text": "Your text to translate", 48 | "source_lang": "Source language code (e.g., 'EN')", 49 | "target_lang": "Target language code (e.g., 'DE')", 50 | "tag_handling": "Optional tag handling ('html', 'xml', or leave blank)" 51 | } 52 | ``` 53 | 54 | **Parameters:** 55 | 56 | * `text` **(required)**: The text you want to translate. 57 | * `source_lang` _(optional)_: The language code of the source text (e.g., `'EN'`). Use `'auto'` for automatic detection. Default is `'auto'`. 58 | * `target_lang` **(required)**: The language code to translate the text into (e.g., `'DE'`). 59 | * `tag_handling` _(optional)_: Specify `'html'` or `'xml'` if your text contains such tags and you want them to be handled properly. 60 | 61 | **Example using `curl`:** 62 | 63 | ```bash 64 | curl --location --request POST 'https://your-deployment-url/api/translate' \ 65 | --header 'Content-Type: application/json' \ 66 | --data-raw '{ 67 | "text": "Hello, World!", 68 | "source_lang": "EN", 69 | "target_lang": "DE", 70 | "tag_handling": "" 71 | }' 72 | ``` 73 | 74 | Replace `https://your-deployment-url` with the actual URL of your Vercel deployment or `http://localhost:9000` if you're running locally. 75 | 76 | **Response:** 77 | 78 | The API will respond with a JSON object containing the translation and additional information: 79 | 80 | ```json 81 | { 82 | "code": 200, 83 | "id": 1234567890, 84 | "data": "Hallo, Welt!", 85 | "alternatives": ["Hallo, Welt!"], 86 | "source_lang": "EN", 87 | "target_lang": "DE", 88 | "method": "Free" 89 | } 90 | ``` 91 | 92 | * `code`: HTTP status code. 93 | * `id`: A unique identifier for the translation request. 94 | * `data`: The translated text. 95 | * `alternatives`: An array of alternative translations. 96 | * `source_lang`: The detected or specified source language. 97 | * `target_lang`: The target language for translation. 98 | * `method`: Indicates whether the translation used the `'Free'` or `'Pro'` method. 99 | 100 | Important Notes 101 | --------------- 102 | 103 | * **Rate Limiting**: Excessive requests may result in temporary blocking by DeepL. Please avoid sending too many requests in a short period. 104 | * **Usage Limitations**: This API is intended for personal or development use. For commercial use, consider subscribing to DeepL's official API. 105 | * **No Guarantee**: As this API relies on DeepL's public endpoints, functionality may break if DeepL changes their API. Please report issues or contribute fixes if this occurs. 106 | 107 | For [Ballon Translator](https://github.com/dmMaze/BallonsTranslator) users 108 | --------------------------- 109 | 110 | After receiving the link in the format `https://your-deployment-url/api/translate`, just paste it into `api_url`. 111 | 112 | ![image](https://github.com/bropines/Deeplx-vercel/assets/57861007/335afdf4-2c3c-4970-b266-2cabdb5c7931) 113 | 114 | ### For Auto-Update 115 | 116 | Just in case I think of something clever, I borrowed `sync.yml` from [drmartinmar/Deeplx-vercel](https://github.com/drmartinmar/Deeplx-vercel). Thank you. 117 | 118 | * To make it work, you have to manually go into **Actions** and run the workflow. 119 | 120 | Thanks 121 | ------ 122 | 123 | * [LegendLeo](https://github.com/LegendLeo) for the basic serverless implementation. 124 | * [OwO-Network](https://github.com/OwO-Network) for the project [DeepLX](https://github.com/OwO-Network/DeepLX). 125 | * ChatGPT (who helped write this README) 126 | 127 | Contributing 128 | ------------ 129 | 130 | Contributions are welcome! If you have a suggestion or fix, please fork the repository and submit a pull request. 131 | 132 | License 133 | ------- 134 | 135 | This project is open-sourced under the MIT license. See the [LICENSE](LICENSE) file for more details. 136 | -------------------------------------------------------------------------------- /README_RU.md: -------------------------------------------------------------------------------- 1 | 2 | DeepLX Vercel 3 | ============= 4 | 5 | [English](README.md) | [Русский](README_RU.md) 6 | 7 | Бесплатное бессерверное решение для API перевода DeepL, позволяющее легко интегрироваться с вашими приложениями. Разверните свой собственный экземпляр или запустите его локально в соответствии с вашими потребностями. 8 | 9 | Особенности 10 | ---------------------- 11 | 12 | * **Архитектура Serverless**: Построено с использованием масштабируемости и простоты бессерверных платформ. 13 | * **Лёгкое развертывание**: Однокликовое развертывание на Vercel с минимальной конфигурацией. 14 | * **Локальная разработка**: Поддерживает локальную разработку для тестирования и модификаций (либо через Vercel CLI, либо с помощью описанного ниже метода). 15 | * **Обновлённая логика API**: Совместимо с последними изменениями API DeepLX для надёжного перевода. 16 | 17 | Развертывание на Vercel 18 | -------------------------------------- 19 | 20 | Разверните свой собственный API перевода DeepLX, нажав на кнопку ниже. 21 | 22 | [![Развернуть с помощью Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fbropines%2FDeeplx-vercel) 23 | 24 | После нажатия на кнопку просто следуйте инструкциям для настройки API на Vercel. 25 | 26 | Локальный запуск 27 | ------------------------------- 28 | 29 | Вот метод локального развертывания, описанный в репозитории [deeplx-serverless](https://github.com/LegendLeo/deeplx-serverless/) от [LegendLeo](https://github.com/LegendLeo): 30 | 31 | ```bash 32 | git clone https://github.com/LegendLeo/deeplx-serverless 33 | cd deeplx-serverless 34 | npm install 35 | npm run start 36 | ``` 37 | 38 | Это запустит локальный сервер с API, работающим на `http://localhost:9000`. 39 | 40 | Использование 41 | -------------------------- 42 | 43 | После развертывания или локального запуска вы можете начать отправлять запросы на перевод текста. Отправьте `POST` запрос на `/api/translate` со следующей структурой JSON: 44 | 45 | ```json 46 | { 47 | "text": "Ваш текст для перевода", 48 | "source_lang": "Код исходного языка (например, 'EN')", 49 | "target_lang": "Код целевого языка (например, 'DE')", 50 | "tag_handling": "Опционально: обработка тегов ('html', 'xml' или оставьте пустым)" 51 | } 52 | ``` 53 | 54 | **Параметры:** 55 | 56 | * `text` **(обязательный)**: Текст, который вы хотите перевести. 57 | * `source_lang` _(опционально)_: Код языка исходного текста (например, `'EN'`). Используйте `'auto'` для автоматического определения. По умолчанию `'auto'`. 58 | * `target_lang` **(обязательный)**: Код языка, на который нужно перевести текст (например, `'DE'`). 59 | * `tag_handling` _(опционально)_: Укажите `'html'` или `'xml'`, если ваш текст содержит такие теги и вы хотите, чтобы они корректно обрабатывались. 60 | 61 | **Пример использования `curl`:** 62 | 63 | ```bash 64 | curl --location --request POST 'https://your-deployment-url/api/translate' \ 65 | --header 'Content-Type: application/json' \ 66 | --data-raw '{ 67 | "text": "Hello, World!", 68 | "source_lang": "EN", 69 | "target_lang": "DE", 70 | "tag_handling": "" 71 | }' 72 | ``` 73 | 74 | Замените `https://your-deployment-url` на фактический URL вашего развертывания на Vercel или `http://localhost:9000`, если вы работаете локально. 75 | 76 | **Ответ:** 77 | 78 | API вернёт JSON-объект с переводом и дополнительной информацией: 79 | 80 | ```json 81 | { 82 | "code": 200, 83 | "id": 1234567890, 84 | "data": "Hallo, Welt!", 85 | "alternatives": ["Hallo, Welt!"], 86 | "source_lang": "EN", 87 | "target_lang": "DE", 88 | "method": "Free" 89 | } 90 | ``` 91 | 92 | * `code`: HTTP статус-код. 93 | * `id`: Уникальный идентификатор запроса на перевод. 94 | * `data`: Переведённый текст. 95 | * `alternatives`: Массив альтернативных переводов. 96 | * `source_lang`: Определённый или указанный исходный язык. 97 | * `target_lang`: Целевой язык перевода. 98 | * `method`: Указывает, использовался ли метод `'Free'` или `'Pro'`. 99 | 100 | Важные замечания 101 | ------------------------------- 102 | 103 | * **Ограничение частоты запросов**: Избыточное количество запросов может привести к временному блокированию со стороны DeepL. Пожалуйста, избегайте отправки слишком большого количества запросов за короткий промежуток времени. 104 | * **Ограничения использования**: Этот API предназначен для личного или разработческого использования. Для коммерческого использования рассмотрите возможность подписки на официальный API DeepL. 105 | * **Нет гарантий**: Поскольку этот API зависит от публичных конечных точек DeepL, функциональность может быть нарушена, если DeepL изменит свой API. Пожалуйста, сообщайте о проблемах или вносите исправления, если это произойдёт. 106 | 107 | Для пользователей [Ballon Translator](https://github.com/dmMaze/BallonsTranslator) 108 | --------------------------------------------------- 109 | 110 | После получения ссылки в формате `https://your-deployment-url/api/translate` просто вставьте её в поле `api_url`. 111 | 112 | ![изображение](https://github.com/bropines/Deeplx-vercel/assets/57861007/335afdf4-2c3c-4970-b266-2cabdb5c7931) 113 | 114 | ### Для автоматического обновления 115 | 116 | На случай, если я придумаю что-то новое, я заимствовал `sync.yml` из репозитория [drmartinmar/Deeplx-vercel](https://github.com/drmartinmar/Deeplx-vercel). Спасибо. 117 | 118 | * Чтобы это работало, вам нужно вручную перейти в раздел **Actions** и запустить workflow. 119 | 120 | Благодарности 121 | -------------------------- 122 | 123 | * [LegendLeo](https://github.com/LegendLeo) за оригинальную бессерверную реализацию. 124 | * [OwO-Network](https://github.com/OwO-Network) за проект [DeepLX](https://github.com/OwO-Network/DeepLX). 125 | * ChatGPT (который помог написать этот README и помог разобраться с новым API) 126 | 127 | Участие 128 | -------------- 129 | 130 | Предложения и вклад приветствуются! Если у вас есть предложение или исправление, пожалуйста, создайте форк репозитория и отправьте pull request. 131 | 132 | Лицензия 133 | ---------------- 134 | 135 | Этот проект распространяется под лицензией MIT. См. файл [LICENSE](LICENSE) для получения дополнительной информации. 136 | -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios').default; 2 | const zlib = require('zlib'); 3 | const brotli = require('brotli'); 4 | 5 | const DEEPL_BASE_URL = 'https://www2.deepl.com/jsonrpc'; 6 | 7 | function getICount(translateText) { 8 | return (translateText || '').split('i').length - 1; 9 | } 10 | 11 | function getRandomNumber() { 12 | const src = crypto.getRandomValues(new Uint32Array(1))[0]; 13 | return (src % 99999 + 8300000) * 1000; 14 | } 15 | 16 | function getTimeStamp(iCount) { 17 | const ts = Date.now(); 18 | if (iCount !== 0) { 19 | iCount = iCount + 1; 20 | return ts - (ts % iCount) + iCount; 21 | } 22 | return ts; 23 | } 24 | 25 | function formatPostString(postData) { 26 | let postStr = JSON.stringify(postData); 27 | 28 | if ((postData.id + 5) % 29 === 0 || (postData.id + 3) % 13 === 0) { 29 | postStr = postStr.replace('"method":"', '"method" : "'); 30 | } else { 31 | postStr = postStr.replace('"method":"', '"method": "'); 32 | } 33 | 34 | return postStr; 35 | } 36 | 37 | async function makeRequest(postData, dlSession = '', proxy = '') { 38 | const urlFull = `${DEEPL_BASE_URL}`; 39 | const postStr = formatPostString(postData); 40 | 41 | // **Debug Logging - Added Here:** 42 | console.log("JSON Payload отправляется в DeepL:", JSON.stringify(postData, null, 2)); 43 | 44 | const headers = { 45 | 'Content-Type': 'application/json', 46 | 'User-Agent': 'DeepL/1627620 CFNetwork/3826.500.62.2.1 Darwin/24.4.0', 47 | 'Accept': '*/*', 48 | 'X-App-Os-Name': 'iOS', 49 | 'X-App-Os-Version': '18.4.0', 50 | 'Accept-Language': 'en-US,en;q=0.9', 51 | 'Accept-Encoding': 'gzip, deflate, br', 52 | 'X-App-Device': 'iPhone16,2', 53 | 'Referer': 'https://www.deepl.com/', 54 | 'X-Product': 'translator', 55 | 'X-App-Build': '1627620', 56 | 'X-App-Version': '25.1', 57 | }; 58 | 59 | if (dlSession) { 60 | headers['Cookie'] = `dl_session=${dlSession}`; 61 | } 62 | 63 | const axiosOptions = { 64 | method: 'POST', 65 | url: urlFull, 66 | headers: headers, 67 | data: postStr, 68 | responseType: 'arraybuffer', 69 | decompress: false, 70 | validateStatus: function (status) { 71 | return status >= 200 && status < 500; 72 | }, 73 | }; 74 | 75 | if (proxy) { 76 | const [host, port] = proxy.split(':'); 77 | axiosOptions.proxy = { 78 | host: host, 79 | port: parseInt(port, 10), 80 | }; 81 | } else { 82 | axiosOptions.proxy = false; 83 | } 84 | 85 | try { 86 | const response = await axios(axiosOptions); 87 | 88 | let data; 89 | const encoding = response.headers['content-encoding']; 90 | if (encoding === 'br') { 91 | data = brotli.decompress(response.data).toString('utf-8'); 92 | } else if (encoding === 'gzip') { 93 | data = zlib.gunzipSync(response.data).toString('utf-8'); 94 | } else if (encoding === 'deflate') { 95 | data = zlib.inflateRawSync(response.data).toString('utf-8'); 96 | } 97 | else { 98 | data = response.data.toString('utf-8'); 99 | } 100 | 101 | 102 | if (response.status >= 400) { 103 | throw new Error(`Ошибка от сервера DeepL: ${response.status} - ${data}`); 104 | } 105 | 106 | return JSON.parse(data); 107 | } catch (err) { 108 | throw err; 109 | } 110 | } 111 | 112 | 113 | async function translate( 114 | text, 115 | sourceLang = 'auto', 116 | targetLang = 'RU', 117 | tagHandling = 'plaintext', 118 | dlSession = '', 119 | proxy = '' 120 | ) { 121 | if (!text) { 122 | throw new Error('Нет текста для перевода.'); 123 | } 124 | 125 | if (!tagHandling) { 126 | tagHandling = 'plaintext'; 127 | } 128 | 129 | const textParts = text.split('\n'); 130 | const translatedParts = []; 131 | const allAlternatives = []; 132 | 133 | for (const part of textParts) { 134 | if (part.trim() === '') { 135 | translatedParts.push(''); 136 | allAlternatives.push(['']); 137 | continue; 138 | } 139 | 140 | let currentSourceLang = sourceLang; 141 | if (sourceLang === 'auto' || sourceLang === '') { 142 | currentSourceLang = 'auto'; 143 | } 144 | 145 | const jobs = []; 146 | jobs.push({ 147 | kind: 'default', 148 | preferred_num_beams: 4, 149 | raw_en_context_before: [], 150 | raw_en_context_after: [], 151 | sentences: [{ 152 | prefix: '', 153 | text: part, 154 | id: 0, 155 | }], 156 | }); 157 | 158 | 159 | let hasRegionalVariant = false; 160 | let targetLangCode = targetLang; 161 | const targetLangParts = targetLang.split('-'); 162 | if (targetLangParts.length > 1) { 163 | targetLangCode = targetLangParts[0]; 164 | hasRegionalVariant = true; 165 | } 166 | 167 | const id = getRandomNumber(); 168 | 169 | let postData = { 170 | jsonrpc: '2.0', 171 | method: 'LMT_handle_jobs', 172 | id: id, 173 | params: { 174 | commonJobParams: { 175 | mode: 'translate', 176 | formality: 'undefined', 177 | transcribeAs: 'romanize', 178 | advancedMode: false, 179 | textType: tagHandling, 180 | wasSpoken: false, 181 | }, 182 | lang: { 183 | source_lang_user_selected: 'auto', 184 | target_lang: targetLangCode.toUpperCase(), 185 | source_lang_computed: currentSourceLang.toUpperCase(), 186 | }, 187 | jobs: jobs, 188 | timestamp: getTimeStamp(getICount(part)), 189 | }, 190 | }; 191 | 192 | 193 | if (hasRegionalVariant) { 194 | postData.params.commonJobParams.regionalVariant = targetLang; 195 | } 196 | 197 | 198 | const response = await makeRequest(postData, dlSession, proxy); 199 | 200 | if (!response || !response.result) { 201 | throw new Error('Не удалось выполнить перевод.'); 202 | } 203 | 204 | let partTranslation = ''; 205 | let partAlternatives = []; 206 | 207 | const translations = response.result.translations; 208 | if (translations && translations.length > 0) { 209 | for (const translation of translations) { 210 | partTranslation += translation.beams[0].sentences[0].text + ' '; 211 | } 212 | partTranslation = partTranslation.trim(); 213 | 214 | const numBeams = translations[0].beams.length; 215 | for (let i = 1; i < numBeams; i++) { 216 | let altText = ''; 217 | for (const translation of translations) { 218 | if (i < translation.beams.length) { 219 | altText += translation.beams[i].sentences[0].text + ' '; 220 | } 221 | } 222 | if (altText.trim() !== '') { 223 | partAlternatives.push(altText.trim()); 224 | } 225 | } 226 | } 227 | 228 | 229 | if (!partTranslation) { 230 | throw new Error('Перевод не получен.'); 231 | } 232 | 233 | translatedParts.push(partTranslation); 234 | allAlternatives.push(partAlternatives); 235 | } 236 | 237 | const translatedText = translatedParts.join('\n'); 238 | 239 | let combinedAlternatives = []; 240 | const maxAlts = Math.max(...allAlternatives.map(alts => alts.length)); 241 | 242 | for (let i = 0; i < maxAlts; i++) { 243 | let altParts = []; 244 | for (let j = 0; j < allAlternatives.length; j++) { 245 | if (i < allAlternatives[j].length) { 246 | altParts.push(allAlternatives[j][i]); 247 | } else if (translatedParts[j] === '') { 248 | altParts.push(''); 249 | } 250 | else { 251 | altParts.push(translatedParts[j]); 252 | } 253 | } 254 | combinedAlternatives.push(altParts.join('\n')); 255 | } 256 | 257 | const result = { 258 | code: 200, 259 | id: getRandomNumber(), 260 | data: translatedText, 261 | alternatives: combinedAlternatives, 262 | source_lang: sourceLang, 263 | target_lang: targetLang, 264 | method: dlSession ? 'Pro' : 'Free', 265 | }; 266 | 267 | return result; 268 | } 269 | 270 | module.exports = { 271 | translate: translate, 272 | }; 273 | --------------------------------------------------------------------------------