├── .DS_Store ├── .github └── workflows │ └── main_deepl-api.yml ├── .gitignore ├── README.md ├── api ├── __init__.py └── pypt.py ├── config └── app.json ├── main.py └── requirements.txt /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reycn/deepl-custom-server-for-bob/308579588753973408872eaddb460373482720a3/.DS_Store -------------------------------------------------------------------------------- /.github/workflows/main_deepl-api.yml: -------------------------------------------------------------------------------- 1 | # Docs for the Azure Web Apps Deploy action: https://github.com/Azure/webapps-deploy 2 | # More GitHub Actions for Azure: https://github.com/Azure/actions 3 | # More info on Python, GitHub Actions, and Azure App Service: https://aka.ms/python-webapps-actions 4 | 5 | name: Build and deploy Python app to Azure Web App - deepl-api 6 | 7 | on: 8 | push: 9 | branches: 10 | - main 11 | workflow_dispatch: 12 | 13 | jobs: 14 | build: 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | 20 | - name: Set up Python version 21 | uses: actions/setup-python@v1 22 | with: 23 | python-version: '3.8' 24 | 25 | - name: Create and start virtual environment 26 | run: | 27 | python -m venv venv 28 | source venv/bin/activate 29 | 30 | - name: Install dependencies 31 | run: pip install -r requirements.txt 32 | 33 | # Optional: Add step to run tests here (PyTest, Django test suites, etc.) 34 | 35 | - name: Upload artifact for deployment jobs 36 | uses: actions/upload-artifact@v2 37 | with: 38 | name: python-app 39 | path: | 40 | . 41 | !venv/ 42 | 43 | deploy: 44 | runs-on: ubuntu-latest 45 | needs: build 46 | environment: 47 | name: 'Production' 48 | url: ${{ steps.deploy-to-webapp.outputs.webapp-url }} 49 | 50 | steps: 51 | - name: Download artifact from build job 52 | uses: actions/download-artifact@v2 53 | with: 54 | name: python-app 55 | path: . 56 | 57 | - name: 'Deploy to Azure Web App' 58 | uses: azure/webapps-deploy@v2 59 | id: deploy-to-webapp 60 | with: 61 | app-name: 'deepl-api' 62 | slot-name: 'Production' 63 | publish-profile: ${{ secrets.AZUREAPPSERVICE_PUBLISHPROFILE_15249520C3AF4A05B072EAB82350E227 }} 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vscode/ 3 | driver/chromedriver 4 | test 5 | old 6 | config/config.ini 7 | *.pid 8 | *.rpm 9 | .deployment 10 | profile.publishsettings 11 | log 12 | api/api.py 13 | 14 | # Byte-compiled / optimized / DLL files 15 | __pycache__/ 16 | *.py[cod] 17 | *$py.class 18 | 19 | # C extensions 20 | *.so 21 | 22 | # Distribution / packaging 23 | .Python 24 | build/ 25 | develop-eggs/ 26 | dist/ 27 | downloads/ 28 | eggs/ 29 | .eggs/ 30 | lib/ 31 | lib64/ 32 | parts/ 33 | sdist/ 34 | var/ 35 | wheels/ 36 | pip-wheel-metadata/ 37 | share/python-wheels/ 38 | *.egg-info/ 39 | .installed.cfg 40 | *.egg 41 | MANIFEST 42 | 43 | # PyInstaller 44 | # Usually these files are written by a python script from a template 45 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 46 | *.manifest 47 | *.spec 48 | 49 | # Installer logs 50 | pip-log.txt 51 | pip-delete-this-directory.txt 52 | 53 | # Unit test / coverage reports 54 | htmlcov/ 55 | .tox/ 56 | .nox/ 57 | .coverage 58 | .coverage.* 59 | .cache 60 | nosetests.xml 61 | coverage.xml 62 | *.cover 63 | *.py,cover 64 | .hypothesis/ 65 | .pytest_cache/ 66 | 67 | # Translations 68 | *.mo 69 | *.pot 70 | 71 | # Django stuff: 72 | *.log 73 | local_settings.py 74 | db.sqlite3 75 | db.sqlite3-journal 76 | 77 | # Flask stuff: 78 | instance/ 79 | .webassets-cache 80 | 81 | # Scrapy stuff: 82 | .scrapy 83 | 84 | # Sphinx documentation 85 | docs/_build/ 86 | 87 | # PyBuilder 88 | target/ 89 | 90 | # Jupyter Notebook 91 | .ipynb_checkpoints 92 | 93 | # IPython 94 | profile_default/ 95 | ipython_config.py 96 | 97 | # pyenv 98 | .python-version 99 | 100 | # pipenv 101 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 102 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 103 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 104 | # install all needed dependencies. 105 | #Pipfile.lock 106 | 107 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 108 | __pypackages__/ 109 | 110 | # Celery stuff 111 | celerybeat-schedule 112 | celerybeat.pid 113 | 114 | # SageMath parsed files 115 | *.sage.py 116 | 117 | # Environments 118 | .env 119 | .venv 120 | env/ 121 | venv/ 122 | ENV/ 123 | env.bak/ 124 | venv.bak/ 125 | 126 | # Spyder project settings 127 | .spyderproject 128 | .spyproject 129 | 130 | # Rope project settings 131 | .ropeproject 132 | 133 | # mkdocs documentation 134 | /site 135 | 136 | # mypy 137 | .mypy_cache/ 138 | .dmypy.json 139 | dmypy.json 140 | 141 | # Pyre type checker 142 | .pyre/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DeepL 翻译接口 2 | 3 | 将 DeepL 的网页功能转换为类似官方文档的 API 接口 4 | 5 | # 典型用例 6 | - 为 [Bob](https://github.com/ripperhe/Bob) 多接口翻译软件提供 [自定义的免费 DeepL 服务](https://github.com/reycn/bob-plugin-deepl-translate) 7 | - 为其他应用提供通用的 DeepL 翻译接口 8 | # Documentation 9 | `EXAMPLE REQUEST` 10 | ```POST /v2/translate?> HTTP/1.0 11 | Host: YOUR_HOST 12 | User-Agent: YourApp 13 | Accept: */* 14 | Content-Length: 54 15 | Content-Type: application/x-www-form-urlencoded 16 | 17 | text=Hello, world&target_lang=DE``` 18 | ``` 19 | 20 | `EXAMPLE RESPONSE` 21 | 22 | ``` 23 | { 24 | "translations": [{ 25 | "detected_source_language":"EN", 26 | "text":"Hallo, Welt!" 27 | }] 28 | } 29 | ``` 30 | 31 | \* *Language supports in progress* 32 | 33 | ## TODO 34 | 35 | 36 | 37 | - Translation for long strings (Len > 5000) 38 | 39 | -------------------------------------------------------------------------------- /api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reycn/deepl-custom-server-for-bob/308579588753973408872eaddb460373482720a3/api/__init__.py -------------------------------------------------------------------------------- /api/pypt.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import time 3 | from pyppeteer import launch 4 | 5 | PARMS = { 6 | "headless": 7 | True, 8 | "args": [ 9 | '--disable-infobars', # 关闭自动化提示框 10 | '--log-level=30', # 日志保存等级, 建议设置越好越好,要不然生成的日志占用的空间会很大 30为warning级别 11 | '--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36', # UA 12 | # '--single-process', 13 | '--disable-gpu', 14 | '--no-sandbox', # 关闭沙盒模式 15 | '--start-maximized', # 窗口最大化模式 16 | # '--proxy-server=127.0.0.1:1080' 17 | # '--window-size=1920,1080', # 窗口大小 18 | # '--proxy-server=http://localhost:1080' # 代理 19 | # '--enable-automation' 20 | ], 21 | } 22 | 23 | JS_TEXT = """ 24 | () =>{ 25 | Object.defineProperties(navigator, { webdriver:{ get: () => false } }); 26 | window.navigator.chrome = { runtime: {}, }; 27 | Object.defineProperty(navigator, 'languages', { get: () => ['en-US', 'en'] }); 28 | Object.defineProperty(navigator, 'plugins', { get: () => [1, 2, 3, 4, 5,6], }); 29 | } 30 | """ 31 | 32 | SELECTOR = "button.lmt__translations_as_text__text_btn" 33 | 34 | BROWSER = None 35 | 36 | text = '我喜欢在一起' 37 | 38 | 39 | async def translator(text: str, 40 | lang_tgt: str = 'ZH', 41 | lang_src: str = 'EN', 42 | detect: bool = False) -> tuple: 43 | time_start = time.time() 44 | global BROWSER 45 | BROWSER = await launch(args=PARMS['args'], 46 | handleSIGINT=False, 47 | handleSIGTERM=False, 48 | handleSIGHUP=False) 49 | page = await BROWSER.newPage() 50 | await page.evaluateOnNewDocument(JS_TEXT) # 本页刷新后值不变,自动执行js 51 | await page.goto( 52 | f'https://www.deepl.com/en/translator#{lang_src.upper()}/{lang_tgt.upper()}/{text}' 53 | ) 54 | # await page.waitForSelector(SELECTOR, {'visible':True}) 55 | # await page.waitForFunction( 56 | # 'document.querySelector("#dl_translator > div.lmt__text > div.lmt__sides_container > div.lmt__side_container.lmt__side_container--target > div.lmt__textarea_container.halfViewHeight > div.lmt__translations_as_text > p.lmt__translations_as_text__item.lmt__translations_as_text__main_translation > button.lmt__translations_as_text__text_btn").innerText.length > 0' 57 | # ); 58 | await page.waitForFunction( 59 | 'document.querySelector("button.lmt__translations_as_text__text_btn").textContent.length >0' 60 | ) 61 | element = await page.querySelector(SELECTOR) 62 | result = await page.evaluate('(element) => element.textContent', element) 63 | time_end = time.time() 64 | # await page.screenshot({'path': './deepl.png','x':0,'y':0,'width':300,'height':300}) 65 | await BROWSER.close() 66 | print(result, time_end - time_start) 67 | return result 68 | 69 | 70 | def trans_auto(text): 71 | return translator(text, 'zh') 72 | 73 | 74 | if __name__ == '__main__': 75 | text = '测试' 76 | try: 77 | print('Start running...') 78 | asyncio.get_event_loop().run_until_complete(translator(text)) 79 | except KeyboardInterrupt as k: 80 | print('\nKey pressed to interrupt...') 81 | -------------------------------------------------------------------------------- /config/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "deepl", 3 | "cwd": "/root/deepl/", 4 | "script": "main.py 1337", 5 | "interpreter": "python3.8", 6 | "instances": 1, 7 | "autorestart": true, 8 | "watch": false, 9 | "max_memory_restart": "256M", 10 | "pid": "log/deepl.pid", 11 | "out_file": "log/app-out.log", 12 | "error_file": "log/app-err.log" 13 | } -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | # from pypt import trans 2 | import sys 3 | from loguru import logger 4 | from sanic import Sanic, response 5 | from sanic.response import json 6 | from sentry_sdk import capture_message, init 7 | from sys import path as syspath 8 | from configparser import ConfigParser 9 | # from api.api import translator 10 | from api.pypt import translator 11 | 12 | app = Sanic("DeepL") 13 | path = syspath[0] + '/config/config.ini' 14 | 15 | try: 16 | cfg = ConfigParser() 17 | cfg.read(path) 18 | sentry = cfg.get('sentry', 'sdk') 19 | init(sentry, traces_sample_rate=1.0) 20 | except Exception as e: 21 | logger.exception("Init:" + str(e)) 22 | capture_message('Init: ' + str(e)) 23 | exit() 24 | 25 | 26 | @app.route('/', methods=["GET"]) 27 | async def index(request): 28 | print('DeepL API sevice is running.') 29 | return response.html( 30 | '
DeepL API service is running
More on GitHub
' 31 | ) 32 | 33 | 34 | @app.route('/v2/translate', methods=["GET"]) 35 | async def get_translate(request, lang_tgt='ZH', lang_src='EN'): 36 | if request.args.get('lang_tgt'): 37 | lang_tgt = request.args.get('lang_tgt') 38 | if request.args.get('lang_src'): 39 | lang_src = request.args.get('lang_src') 40 | if request.args.get('text'): 41 | text = request.args.get('text') 42 | logger.info(f'[>>] {text}') 43 | try: 44 | # result = await trans(text, lang_tgt=lang_tgt, lang_src=lang_src) # headless 45 | result = await translator(text, lang_tgt=lang_tgt) # API Cracked 46 | logger.info(f'[<<] [{lang_tgt}] {result}') 47 | except Exception as e: 48 | logger.exception("Trans:" + str(e)) 49 | capture_message('Trans: ' + str(e)) 50 | result = "API Error" 51 | else: 52 | result = "API Error" 53 | logger.error("API Error") 54 | return json(text_to_dict(result, lang_src)) 55 | 56 | 57 | @app.route('/v2/translate', methods=["POST"]) 58 | async def post_translate(request, lang_tgt='ZH', lang_src='EN'): 59 | if request.form.get('lang_tgt'): 60 | lang_tgt = request.form.get('lang_tgt') 61 | if request.form.get('lang_src'): 62 | lang_src = request.form.get('lang_src') 63 | if request.body: 64 | text = request.form.get('text') 65 | logger.info(f'[>>] {text}') 66 | try: 67 | # result = await trans(text, lang_tgt=lang_tgt, lang_src=lang_src) # headless 68 | result = await translator(text, lang_tgt=lang_tgt) # API Cracked 69 | logger.info(f'[<<] [{lang_tgt}] {result}') 70 | except Exception as e: 71 | logger.exception("Trans:" + str(e)) 72 | capture_message('Trans: ' + str(e)) 73 | result = "API Error" 74 | else: 75 | result = "API Error" 76 | logger.error("API Error") 77 | return json(text_to_dict(result, lang_src)) 78 | 79 | 80 | def text_to_dict(string, lang_src): 81 | dct = { 82 | "translations": [{ 83 | "detected_source_language": lang_src, 84 | "text": string 85 | }] 86 | } 87 | return dct 88 | 89 | 90 | def start(argv): 91 | host = '0.0.0.0' 92 | if len(argv) >= 2: 93 | port = int(sys.argv[1]) 94 | else: 95 | port = 80 96 | logger.info(f'[Start] {host} {port}') 97 | app.run(host=host, port=port) 98 | 99 | 100 | if __name__ == '__main__': 101 | start(sys.argv) -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # Automatically generated by https://github.com/damnever/pigar. 2 | 3 | # deepl/main.py: 2 4 | loguru == 0.6.0 5 | 6 | # deepl/api/pypt.py: 3 7 | # deepl/test/test.py: 2 8 | pyppeteer == 1.0.2 9 | 10 | # deepl/api/api.py: 3 11 | requests == 2.27.1 12 | 13 | # deepl/main.py: 3,4 14 | sanic == 22.3.0 15 | 16 | # deepl/main.py: 5 17 | sentry-sdk == 1.5.8 18 | --------------------------------------------------------------------------------