├── bobplugin ├── independently │ └── src │ │ ├── icon.png │ │ ├── utils.js │ │ ├── config.js │ │ ├── info.json │ │ └── main.js └── dependOnService │ ├── src │ ├── icon.png │ ├── config.js │ ├── utils.js │ ├── info.json │ └── main.js │ ├── scripts │ └── update_appcast.py │ └── appcast.json ├── youdaoTranslateServer ├── pythonServer │ ├── requirements.txt │ └── youdao.py └── javaScript │ ├── youdaoDict.js │ └── youdao.js ├── .github └── workflows │ └── release.yml └── README.md /bobplugin/independently/src/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akl7777777/bob-plugin-akl-youdao-free-translate/HEAD/bobplugin/independently/src/icon.png -------------------------------------------------------------------------------- /bobplugin/dependOnService/src/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akl7777777/bob-plugin-akl-youdao-free-translate/HEAD/bobplugin/dependOnService/src/icon.png -------------------------------------------------------------------------------- /bobplugin/independently/src/utils.js: -------------------------------------------------------------------------------- 1 | var config = require('./config.js'); 2 | 3 | const langMap = new Map(config.supportedLanguages); 4 | const langMapReverse = new Map(config.supportedLanguages.map(([standardLang, lang]) => [lang, standardLang])); 5 | 6 | exports.langMap = langMap; 7 | exports.langMapReverse = langMapReverse; 8 | -------------------------------------------------------------------------------- /youdaoTranslateServer/pythonServer/requirements.txt: -------------------------------------------------------------------------------- 1 | certifi==2022.12.7 2 | charset-normalizer==3.0.1 3 | click==8.1.3 4 | Flask==2.2.3 5 | idna==3.4 6 | importlib-metadata==6.0.0 7 | itsdangerous==2.1.2 8 | Jinja2==3.1.2 9 | MarkupSafe==2.1.2 10 | PyExecJS==1.5.1 11 | requests==2.28.2 12 | six==1.16.0 13 | urllib3==1.26.14 14 | Werkzeug==2.2.3 15 | zipp==3.13.0 16 | -------------------------------------------------------------------------------- /bobplugin/independently/src/config.js: -------------------------------------------------------------------------------- 1 | const supportedLanguages = [ 2 | ['auto', 'auto'], 3 | ['zh-Hans', 'zh'], 4 | ['en', 'en'], 5 | ['de', 'de'], 6 | ['fr', 'fr'], 7 | ['it', 'it'], 8 | ['ja', 'ja'], 9 | ['es', 'es'], 10 | ['nl', 'nl'], 11 | ['pl', 'pl'], 12 | ['pt', 'pt'], 13 | ['ru', 'ru'], 14 | ]; 15 | 16 | 17 | 18 | exports.supportedLanguages = supportedLanguages; 19 | 20 | -------------------------------------------------------------------------------- /bobplugin/dependOnService/src/config.js: -------------------------------------------------------------------------------- 1 | const supportedLanguages = [ 2 | ["auto", "Auto"], 3 | ["de", "de"], 4 | ["en", "en"], 5 | ["es", "es"], 6 | ["fr", "fr"], 7 | ["it", "it"], 8 | ["ja", "ja"], 9 | ["ko", "ko"], 10 | ["nl", "nl"], 11 | ["pl", "pl"], 12 | ["pt", "pt"], 13 | ["ru", "ru"], 14 | ["zh-Hans", "zh-CHS"], 15 | ["zh-Hant", "zh-CHS"], 16 | ["bg", "bg"], 17 | ["cs", "cs"], 18 | ["da", "da"], 19 | ["el", "el"], 20 | ["et", "et"], 21 | ["fi", "fi"], 22 | ["hu", "hu"], 23 | ["lt", "lt"], 24 | ["lv", "lv"], 25 | ["ro", "ro"], 26 | ["sk", "sk"], 27 | ["sl", "sl"], 28 | ["sv", "sv"] 29 | ]; 30 | 31 | 32 | 33 | exports.supportedLanguages = supportedLanguages; 34 | 35 | -------------------------------------------------------------------------------- /bobplugin/independently/src/info.json: -------------------------------------------------------------------------------- 1 | { 2 | "identifier": "com.akl.bob-plugin-akl-youdao-free-translate", 3 | "version": "0.0.1", 4 | "category": "translate", 5 | "name": "有道翻译网页版api", 6 | "summary": "有道翻译网页版api", 7 | "icon": "icon.png", 8 | "author": "akl7777777 ", 9 | "homepage": "https://github.com/akl7777777/bob-plugin-akl-deepl-free-translate", 10 | "appcast": "", 11 | "minBobVersion": "0.5.0", 12 | "options": [ 13 | { 14 | "identifier": "desc", 15 | "type": "menu", 16 | "title": "描述", 17 | "defaultValue": "akl7777777", 18 | "menuValues": [ 19 | { 20 | "title": "有道翻译网页版api无需token无需登录", 21 | "value": "akl7777777" 22 | } 23 | ], 24 | "desc":"小芍同学有道翻译网页版api无需token无需登录" 25 | } 26 | ] 27 | } -------------------------------------------------------------------------------- /youdaoTranslateServer/javaScript/youdaoDict.js: -------------------------------------------------------------------------------- 1 | const crypto = require('crypto'); 2 | 3 | var y = ["option_avatar", "nickname"] 4 | , w = "Mk6hqtUp33DGGtoS63tTJbMUYjRrG1Lu" 5 | , v = "webdict" 6 | , _ = "web" 7 | , x = "https://dict.youdao.com" 8 | , k = "https://dict.youdao.com/jsonapi_s?doctype=json&jsonversion=4" 9 | , S = "https://dict-subsidiary.youdao.com"; 10 | 11 | h = function (t) { 12 | return crypto.createHash("md5").update(t.toString()).digest("hex") 13 | } 14 | 15 | var text = 'what' 16 | time = "".concat(text).concat(v).length % 10, 17 | r = "".concat(text).concat(v), 18 | o = h(r), 19 | n = "".concat(_).concat(text).concat(time).concat(w).concat(o), 20 | f = h(n), 21 | l = { 22 | q: text, 23 | le: 'en', 24 | t: time, 25 | client: _, 26 | sign: f, 27 | keyfrom: v 28 | } 29 | console.log(f) 30 | -------------------------------------------------------------------------------- /bobplugin/dependOnService/src/utils.js: -------------------------------------------------------------------------------- 1 | var config = require('./config.js'); 2 | 3 | const langMap = new Map(config.supportedLanguages); 4 | const langMapReverse = new Map(config.supportedLanguages.map(([standardLang, lang]) => [lang, standardLang])); 5 | 6 | // 将英文标点替换为中文标点 7 | function replacePunctuation(str) { 8 | return str.replace(/(\w)'/g, '$1’').replace(/'/g, '’') 9 | .replace(/(\w)"/g, '$1”').replace(/"/g, '”') 10 | .replace(/(\w)'/g, '$1‘').replace(/'/g, '‘') 11 | .replace(/(\w)'/g, '$1’').replace(/'/g, '’') 12 | .replace(/(\w)!/g, '$1!').replace(/!/g, '!') 13 | .replace(/(\w)\?/g, '$1?').replace(/\?/g, '?') 14 | .replace(/(\w),/g, '$1,').replace(/,/g, ',') 15 | .replace(/(\w)\./g, '$1。').replace(/\./g, '。') 16 | .replace(/(\w);/g, '$1;').replace(/;/g, ';') 17 | .replace(/(\w):/g, '$1:').replace(/:/g, ':') 18 | .replace(/(\w)\(/g, '$1(').replace(/\(/g, '(') 19 | .replace(/(\w)\)/g, '$1)').replace(/\)/g, ')') 20 | ; 21 | } 22 | 23 | exports.langMap = langMap; 24 | exports.langMapReverse = langMapReverse; 25 | exports.replacePunctuation = replacePunctuation; 26 | -------------------------------------------------------------------------------- /youdaoTranslateServer/javaScript/youdao.js: -------------------------------------------------------------------------------- 1 | const crypto = require('crypto'); 2 | const r = "fanyideskweb", i = "webfanyi"; 3 | function m(e) { 4 | return crypto.createHash("md5").update(e).digest() 5 | } 6 | function p(e) { 7 | return crypto.createHash("md5").update(e.toString()).digest("hex") 8 | } 9 | function b(e, t) { 10 | return p(`client=${r}&mysticTime=${e}&product=${i}&key=${t}`) 11 | } 12 | function f() { 13 | const e = 'fsdsogkndfokasodnaso', s = 'client,mysticTime,product', u = 'fanyi.web', l = '1.0.0', d = 'web'; 14 | const t = (new Date).getTime(); 15 | return { 16 | sign: b(t, e), 17 | client: r, 18 | product: i, 19 | appVersion: l, 20 | vendor: d, 21 | pointParam: s, 22 | mysticTime: t, 23 | keyfrom: u 24 | } 25 | } 26 | A = (t,o,n)=>{ 27 | o = "ydsecret://query/key/B*RGygVywfNBwpmBaZg*WT7SIOUP2T0C9WHMZN39j^DAdaZhAnxvGcCY6VYFwnHl" 28 | n = "ydsecret://query/iv/C@lZe2YzHtZ2CYgaXKSVfsb7Y4QWHjITPPZ0nQp87fBeJ!Iv6v^6fvi2WN@bYpJ4" 29 | if (!t) 30 | return null; 31 | const a = Buffer.alloc(16, m(o)) 32 | , r = Buffer.alloc(16, m(n)) 33 | , i = crypto.createDecipheriv("aes-128-cbc", a, r); 34 | let s = i.update(t, "base64", "utf-8"); 35 | return s += i.final("utf-8"), 36 | s 37 | } 38 | -------------------------------------------------------------------------------- /bobplugin/dependOnService/scripts/update_appcast.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | import json 3 | import sys 4 | from pathlib import Path 5 | 6 | 7 | def update_appcast(message): 8 | with open ("bobplugin/dependOnService/src/info.json", "r") as f: 9 | info = json.load(f) 10 | version = info["version"] 11 | release_file = Path(f"release/bob-plugin-akl-youdao-free-translate.bobplugin") 12 | assert release_file.is_file(), "Release file not exist" 13 | with open(release_file, "rb") as f: 14 | c = f.read() 15 | file_hash = hashlib.sha256(c).hexdigest() 16 | version_info = { 17 | "version": version, 18 | "desc": message, 19 | "sha256": file_hash, 20 | "url": f"https://github.com/akl7777777/bob-plugin-akl-youdao-free-translate/releases/download/v{version}/bob-plugin-akl-youdao-free-translate_v{version}.bobplugin", 21 | "minBobVersion": "0.5.0" 22 | } 23 | appcast_file = Path("bobplugin/dependOnService/appcast.json") 24 | if appcast_file.is_file(): 25 | with open(appcast_file, "r") as f: 26 | appcast = json.load(f) 27 | else: 28 | appcast = dict(identifier="com.akl.bob-plugin-akl-youdao-free-translate", versions=[]) 29 | appcast["versions"].insert(0, version_info) 30 | with open(appcast_file, "w") as f: 31 | json.dump(appcast, f, ensure_ascii=False, indent=2) 32 | print(f"v{version}") 33 | 34 | 35 | if __name__ == "__main__": 36 | message = sys.argv[1] 37 | update_appcast(message) 38 | -------------------------------------------------------------------------------- /bobplugin/dependOnService/src/info.json: -------------------------------------------------------------------------------- 1 | { 2 | "identifier": "com.akl.bob-plugin-akl-youdao-free-translate", 3 | "version": "0.2.2", 4 | "category": "translate", 5 | "name": "Free 有道翻译", 6 | "summary": "有道翻译免费免秘钥插件", 7 | "icon": "icon.png", 8 | "author": "akl7777777 ", 9 | "homepage": "https://github.com/akl7777777/bob-plugin-akl-youdao-free-translate", 10 | "appcast": "https://raw.githubusercontent.com/akl7777777/bob-plugin-akl-youdao-free-translate/main/bobplugin/dependOnService/appcast.json", 11 | "minBobVersion": "0.5.0", 12 | "options": [ 13 | { 14 | "identifier": "desc", 15 | "type": "menu", 16 | "title": "描述", 17 | "defaultValue": "akl7777777", 18 | "menuValues": [ 19 | { 20 | "title": "有道翻译网页版api无需token无需登录", 21 | "value": "akl7777777" 22 | } 23 | ], 24 | "desc": "小芍同学有道翻译网页版api无需token无需登录" 25 | }, 26 | { 27 | "identifier": "type", 28 | "type": "menu", 29 | "title": "类型", 30 | "defaultValue": "local", 31 | "menuValues": [ 32 | { 33 | "title": "本地不依赖服务", 34 | "value": "local" 35 | }, 36 | { 37 | "title": "依赖服务(需要自行启动服务)", 38 | "value": "service" 39 | } 40 | ], 41 | "desc": "翻译类型:本地版(无需额外配置直接使用);依赖服务(需要自行启动服务)" 42 | }, 43 | { 44 | "identifier": "serverUrl", 45 | "type": "text", 46 | "title": "接口域名", 47 | "defaultValue": "http://127.0.0.1:9527/youdaoTranslate", 48 | "desc": "本地启动服务地址默认为http://127.0.0.1:9527/youdaoTranslate;可以不用修改;远程服务器启动请自行填写服务器的域名;" 49 | } 50 | ] 51 | } 52 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release Plugin 2 | on: 3 | push: 4 | paths: 5 | - "bobplugin/dependOnService/src/info.json" 6 | jobs: 7 | release: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v3 11 | with: 12 | fetch-depth: 0 13 | 14 | - name: Get version 15 | id: get_version 16 | uses: battila7/get-version-action@v2 17 | 18 | - name: Package plugin 19 | run: mkdir release && zip -j -r release/bob-plugin-akl-youdao-free-translate.bobplugin ./bobplugin/dependOnService/src/* 20 | 21 | - name: Get last commit title 22 | id: last-commit 23 | run: echo "::set-output name=last_commit_title::$(git log -1 --pretty=format:%s)" 24 | 25 | - run: git checkout -- bobplugin/dependOnService/src/ 26 | 27 | - name: Update appcast.json 28 | id: run_script 29 | run: | 30 | output=$(python3 bobplugin/dependOnService/scripts/update_appcast.py "${{ steps.last-commit.outputs.last_commit_title }}") 31 | echo "version=$output" >> $GITHUB_OUTPUT 32 | 33 | - name: Package plugin final 34 | run: mv release/bob-plugin-akl-youdao-free-translate.bobplugin release/bob-plugin-akl-youdao-free-translate_${{ steps.run_script.outputs.version }}.bobplugin 35 | 36 | - name: Commit files 37 | run: | 38 | git config --global user.name 'akl7777777' 39 | git config --global user.email 'akl7777777@163.com' 40 | git add bobplugin/dependOnService/appcast.json 41 | git commit -am 'update appcast.json' 42 | 43 | - name: Push changes 44 | uses: ad-m/github-push-action@master 45 | with: 46 | github_token: ${{ secrets.GITHUB_TOKEN }} 47 | 48 | - name: Upload binaries to release 49 | uses: svenstaro/upload-release-action@v2 50 | with: 51 | repo_token: ${{ secrets.GITHUB_TOKEN }} 52 | file: release/bob-plugin-akl-youdao-free-translate_${{ steps.run_script.outputs.version }}.bobplugin 53 | asset_name: bob-plugin-akl-youdao-free-translate_${{ steps.run_script.outputs.version }}.bobplugin 54 | tag: ${{ steps.run_script.outputs.version }} 55 | overwrite: true 56 | body: "${{ steps.last-commit.outputs.last_commit_title }}" 57 | -------------------------------------------------------------------------------- /youdaoTranslateServer/pythonServer/youdao.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import subprocess 3 | from functools import partial 4 | from flask import Flask, request 5 | 6 | subprocess.Popen = partial(subprocess.Popen, encoding="utf-8") 7 | import execjs 8 | 9 | app = Flask(__name__) 10 | 11 | 12 | @app.route('/youdaoTranslate', methods=['POST']) 13 | def youdaoTranslate(): 14 | data = request.json 15 | # translate_words = input('请输入要翻译的内容:') 16 | translate_words = '' 17 | source_lang = 'AUTO' 18 | target_lang = 'AUTO' 19 | if 'text' in data: 20 | translate_words = data['text'] 21 | # if 'source_lang' in data: 22 | # source_lang = data['source_lang'] 23 | # if 'target_lang' in data: 24 | # target_lang = data['target_lang'] 25 | # ctx = execjs.compile(open('youdao.js', encoding='utf-8').read()) 26 | ctx = execjs.compile(''' 27 | const crypto = require('crypto'); 28 | const r = "fanyideskweb", i = "webfanyi"; 29 | function m(e) { 30 | return crypto.createHash("md5").update(e).digest() 31 | } 32 | function p(e) { 33 | return crypto.createHash("md5").update(e.toString()).digest("hex") 34 | } 35 | function b(e, t) { 36 | return p(`client=${r}&mysticTime=${e}&product=${i}&key=${t}`) 37 | } 38 | function f() { 39 | const e = 'fsdsogkndfokasodnaso', s = 'client,mysticTime,product', u = 'fanyi.web', l = '1.0.0', d = 'web'; 40 | const t = (new Date).getTime(); 41 | return { 42 | sign: b(t, e), 43 | client: r, 44 | product: i, 45 | appVersion: l, 46 | vendor: d, 47 | pointParam: s, 48 | mysticTime: t, 49 | keyfrom: u 50 | } 51 | } 52 | A = (t,o,n)=>{ 53 | o = "ydsecret://query/key/B*RGygVywfNBwpmBaZg*WT7SIOUP2T0C9WHMZN39j^DAdaZhAnxvGcCY6VYFwnHl" 54 | n = "ydsecret://query/iv/C@lZe2YzHtZ2CYgaXKSVfsb7Y4QWHjITPPZ0nQp87fBeJ!Iv6v^6fvi2WN@bYpJ4" 55 | if (!t) 56 | return null; 57 | const a = Buffer.alloc(16, m(o)) 58 | , r = Buffer.alloc(16, m(n)) 59 | , i = crypto.createDecipheriv("aes-128-cbc", a, r); 60 | let s = i.update(t, "base64", "utf-8"); 61 | return s += i.final("utf-8"), 62 | s 63 | } 64 | ''') 65 | params = ctx.call('f') 66 | data = { 67 | 'i': translate_words, 68 | 'from': source_lang, 69 | 'to': target_lang, 70 | 'domain': '0', 71 | 'dictResult': 'true', 72 | 'keyid': 'webfanyi' 73 | } 74 | headers = { 75 | 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36 Edg/108.0.1462.54', 76 | 'Cookie': 'OUTFOX_SEARCH_USER_ID_NCOO=976405377.6815147; OUTFOX_SEARCH_USER_ID=-198948307@211.83.126.235; _ga=GA1.2.1162596953.1667349221; search-popup-show=12-2', 77 | 'Referer': 'https://fanyi.youdao.com/' 78 | } 79 | data.update(params) 80 | results = requests.post('https://dict.youdao.com/webtranslate', data=data, headers=headers) 81 | fanyi = eval(ctx.call('A', results.text)) 82 | # print(fanyi['translateResult'][0][0]['tgt']) 83 | # 判断key是否存在,修正异常返回的处理逻辑 84 | if 'translateResult' in fanyi: 85 | for paragraph in fanyi['translateResult']: 86 | s = '' 87 | for item in paragraph: 88 | s += item['tgt'] 89 | print(s) 90 | return fanyi 91 | 92 | 93 | if __name__ == '__main__': 94 | app.run(host='0.0.0.0', port=9527) 95 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # bob-plugin-akl-youdao-free-translate 2 | 免费无限次使用有道翻译,根据网页版JavaScript加密算法开发的bobplugin;所以只要官网的算法不改,理论上就可以无限使用; 3 | 4 | ~~**本来是想做成纯插件的,但是bob内核JavaScript没有node那么全的加密库,所以只能借用Python启动一个服务运行虚拟的node环境**~~ 5 | 6 | #### **重大更新!有道插件新增免启动服务本地版,可以不用启动服务了;不过服务启动的方式依然保留.两种可以切换使用** 7 | 8 | ## **重大更新,支持单个单词详细查询,并且不用再启动服务了** 9 | 10 | ## **增加万字长文翻译功能;支持万字长文一次翻译,不用截断!** 11 | 12 | 13 | 14 | **下载地址:[有道Bob插件_v0.2.0](https://github.com/akl7777777/bob-plugin-akl-youdao-free-translate/releases/download/v0.2.0/bob-plugin-akl-youdao-free-translate_v0.2.0.bobplugin)** 15 | 16 | 使用方法:双击安装,直接使用,支持单词模式和超长万字文本模式自动切换 17 | 18 | 效果图如下: 19 | 20 | ![iShot_2023-03-05_19 02 08](https://user-images.githubusercontent.com/84266551/222956626-b69aa14e-de7f-437c-8677-d96e283d6d44.gif) 21 | 22 | 23 | image 24 | 25 | ### 友情链接==>ChatGPT免费桌面版客户端(支持Windows,macOS,Android) 26 | 桌面版ChatGPT下载地址:[OpenAI-ChatGPT免费桌面版客户端](https://github.com/akl7777777/free-chatgpt-client-pub) 27 | 28 | 手机版ChatGPT下载地址:[OpenAI-ChatGPT免费手机版客户端](https://github.com/akl7777777/free-chatgpt-client-mobile-pub) 29 | 30 | ### bob翻译插件大合集: 31 | 32 | >[OpenAI ChatGPT(免秘钥)插件](https://github.com/akl7777777/bob-plugin-akl-chatgpt-free-translate) 33 | 34 | >[DeepL翻译插件(免秘钥)](https://github.com/akl7777777/bob-plugin-akl-deepl-free-translate) 35 | 36 | >[有道翻译插件(免秘钥)](https://github.com/akl7777777/bob-plugin-akl-youdao-free-translate) 37 | 38 | >[CNKI学术翻译插件(免秘钥)](https://github.com/akl7777777/bob-plugin-akl-cnki-free-translate) 39 | 40 | >[火山翻译插件(免秘钥)](https://github.com/akl7777777/bob-plugin-akl-volcengine-free-translate) 41 | 42 | >[百度翻译插件(免秘钥)](https://github.com/akl7777777/bob-plugin-akl-baidu-free-translate) 43 | 44 | >[腾讯翻译君插件(免秘钥)](https://github.com/akl7777777/bob-plugin-akl-tencent-free-translate) 45 | 46 | >[腾讯交互翻译插件(免秘钥)](https://github.com/akl7777777/bob-plugin-akl-transmart-free-translate) 47 | 48 | >[彩云小译插件(免秘钥)](https://github.com/akl7777777/bob-plugin-akl-caiyunxiaoyi-free-translate) 49 | 50 | >[只为日语 - MOJi辞書(じしょ)](https://github.com/akl7777777/bob-plugin-akl-mojidict-translate) 51 | 52 | >[Papago Naver 韩语翻译(免秘钥)](https://github.com/akl7777777/bob-plugin-akl-papago-free-translate) 53 | 54 | >[Bob翻译剪切板图片的AlfredWorkflow](https://github.com/akl7777777/BobTranslateClipboard) 55 | 56 | >[Bob的Postman接口调试插件](https://github.com/akl7777777/bob-plugin-akl-postman) 57 | 58 | 59 | 60 | https://user-images.githubusercontent.com/84266551/221367028-17e66c24-12a8-458b-b96a-856b545cb271.mp4 61 | 62 | 63 | 64 | 65 | 项目结构简介: 66 | youdaoTranslateServer文件夹下用来启动Python服务,Python调用node环境的加密解密功能模拟请求 67 | bobplugin文件夹下有两个目录 68 | dependOnService里面是依赖于Python服务的插件,用此插件需要本地启动Python服务,本人亲测可用; 69 | independently里面是不依赖于其他服务的独立插件,但是此插件依赖的node环境尚不完善,还需要和bob开发者共通谈论完善方案,故不可用; 70 | 71 | bob插件主要为bob用户开发,bob是一款macOS上的翻译软件,bob官网地址:https://bobtranslate.com/ 72 | 73 | 74 | 使用方法如下(总共分三步): 75 | 76 | 第0步: 77 | 下载右侧的release 78 | https://github.com/akl7777777/bob-plugin-akl-youdao-free-translate/releases/download/v_0.0.7/youdaoTranslateServer_macos_x86_64 79 | 80 | 81 | 第一步: 82 | 运行youdaoTranslateServer可执行文件,由于我今天手头只有一台Intel芯片的MacBook,而Python又不能跨平台打包,所以暂时只有一个打包好的文件,不确定apple芯片是否可以用;有兴趣的同学可以自行搭建Python环境打包或者直接启动服务.我本地用的是Python3.79版本Intel芯片macOS系统;打包命令 pyinstaller --onefile youdao.py 当然在那之前你需要先 pip install pyinstaller 83 | 84 | 注意: macos由于对软件限制较⼤,对未知来源的程序是禁⽌使⽤的,所以双击多半打不开,此时需要按 85 | 下⾯的步骤进⾏操作。 86 | 87 | 打开终端,执行如下命令: 88 | chmod a+x <你的youdaoTranslateServer可执行文件存放的路径> 89 | 90 | 第⼀次跑时需要输⼊chmod。以后每次运⾏时,其它步骤不变,只是不必再输该chmod命令。 91 | 92 | 当然,如果你不想占用自己电脑的资源,有条件也可以用公司办公室的其他Windows电脑启动服务https://github.com/akl7777777/bob-plugin-akl-youdao-free-translate/releases/download/v_0.0.6/youdaoTranslateServer_windows_x86_64.exe 93 | 94 | 也可以使用公司或个人的Linux服务器启动服务https://github.com/akl7777777/bob-plugin-akl-youdao-free-translate/releases/download/v_0.0.6/youdaoTranslateServer_linux_x86_64 95 | 96 | 在后续的版本,插件会提供请求地址的输入框,填入对应的服务器地址即可; 97 | 98 | 第二步: 99 | 100 | 配置服务器域名,本地启动默认域名是http://127.0.0.1:9527/youdaoTranslate 可以不用修改;如果服务部署在其他机器,可以根据自己机器的域名来配置如下图 101 | 102 | image 103 | 104 | 第三步: 105 | 106 | 双击.bobplugin后缀的文件即可安装;安装后尽情享受吧! 107 | 108 | 效果图如下: 109 | 110 | image 111 | 112 | 113 | 此外,对于其他非bob用户,需要调用有道翻译的也可以直接调用服务如下图: 114 | 入参样例: 115 | {"text":"你好"} 116 | 返回值样例: 117 | { 118 | "code": 0, 119 | "translateResult": [ 120 | [ 121 | { 122 | "src": "你好", 123 | "srcPronounce": "nĭhăo", 124 | "tgt": "hello" 125 | } 126 | ] 127 | ], 128 | "type": "zh-CHS2en" 129 | } 130 | image 131 | 132 | 133 | 134 | ### 开发不易,如果喜欢的话,可以给请作者吃一份特色腰花面,或者喝一杯可乐 135 | 136 | 137 | 138 | image 139 | -------------------------------------------------------------------------------- /bobplugin/dependOnService/appcast.json: -------------------------------------------------------------------------------- 1 | { 2 | "identifier": "com.akl.bob-plugin-akl-youdao-free-translate", 3 | "versions": [ 4 | { 5 | "version": "0.2.2", 6 | "desc": "fix: 中文译文中的英文标点符号问题 #3中文译文中的括号总是英文的,复制过来后需要频繁改动,非常不方便。 希望能调整成中文。", 7 | "sha256": "354d80fc5ac54926669fdd7e93ce24eeb3ef02ad4405bf1dae97a2b65bd72c02", 8 | "url": "https://github.com/akl7777777/bob-plugin-akl-youdao-free-translate/releases/download/v0.2.2/bob-plugin-akl-youdao-free-translate_v0.2.2.bobplugin", 9 | "minBobVersion": "0.5.0" 10 | }, 11 | { 12 | "version": "0.2.1", 13 | "desc": "fix: 中文译文中的英文标点符号问题 #3中文译文中的括号总是英文的,复制过来后需要频繁改动,非常不方便。 希望能调整成中文。", 14 | "sha256": "b87bc541ba390dee8c7a429f37d5e78031d1c042d7a37dd2455c5f0419e037cc", 15 | "url": "https://github.com/akl7777777/bob-plugin-akl-youdao-free-translate/releases/download/v0.2.1/bob-plugin-akl-youdao-free-translate_v0.2.1.bobplugin", 16 | "minBobVersion": "0.5.0" 17 | }, 18 | { 19 | "version": "0.2.0", 20 | "desc": "fix: 修复官网修改参数导致无法翻译的问题", 21 | "sha256": "fba208ce2ef394a8292529a678b64db4fb75e88f0fff2393fe51fb389c38024c", 22 | "url": "https://github.com/akl7777777/bob-plugin-akl-youdao-free-translate/releases/download/v0.2.0/bob-plugin-akl-youdao-free-translate_v0.2.0.bobplugin", 23 | "minBobVersion": "0.5.0" 24 | }, 25 | { 26 | "version": "0.1.8", 27 | "desc": "优化", 28 | "sha256": "d2cefe3da0207330414e17ec68bac7d4681a39361fe8c4c964f09b99c573916c", 29 | "url": "https://github.com/akl7777777/bob-plugin-akl-youdao-free-translate/releases/download/v0.1.8/bob-plugin-akl-youdao-free-translate_v0.1.8.bobplugin", 30 | "minBobVersion": "0.5.0" 31 | }, 32 | { 33 | "version": "0.1.7", 34 | "desc": "修正词典发音无法区分英式美式的问题", 35 | "sha256": "fd6419be558aacb09a1d63890c85c188ffadbb6e9fc8e37ec58f6dc05c99efe7", 36 | "url": "https://github.com/akl7777777/bob-plugin-akl-youdao-free-translate/releases/download/v0.1.7/bob-plugin-akl-youdao-free-translate_v0.1.7.bobplugin", 37 | "minBobVersion": "0.5.0" 38 | }, 39 | { 40 | "version": "0.1.6", 41 | "desc": "优化词典和超长文本处理方式", 42 | "sha256": "cbe7dc7ca83f8c94dc8984cb93b30f62669435e6629f4ec24765a6c87ac9a6ca", 43 | "url": "https://github.com/akl7777777/bob-plugin-akl-youdao-free-translate/releases/download/v0.1.6/bob-plugin-akl-youdao-free-translate_v0.1.6.bobplugin", 44 | "minBobVersion": "0.5.0" 45 | }, 46 | { 47 | "version": "0.1.5", 48 | "desc": "增加万字长文翻译功能;支持万字长文一次翻译,不用截断!", 49 | "sha256": "127f2348341ef66d9068d507beb063b075e776632e6fd2ff65604c187c27cad9", 50 | "url": "https://github.com/akl7777777/bob-plugin-akl-youdao-free-translate/releases/download/v0.1.5/bob-plugin-akl-youdao-free-translate_v0.1.5.bobplugin", 51 | "minBobVersion": "0.5.0" 52 | }, 53 | { 54 | "version": "0.1.3", 55 | "desc": "重大更新!有道插件新增免启动服务本地版;1.新增查询单词功能,单词更详细;2.修复:增加字典单词发音,增加字典单词变形;3.修复:优化未查询到单词时的模糊匹配", 56 | "sha256": "1ec6b6a5b9a889c7238ff8ea04a3bb5dfeddb3cab0a636599fe6bf891e151a0b", 57 | "url": "https://github.com/akl7777777/bob-plugin-akl-youdao-free-translate/releases/download/v_0.1.3/youdao-dependOnService_v0.1.3.bobplugin", 58 | "minBobVersion": "0.5.0" 59 | }, 60 | { 61 | "version": "0.1.2", 62 | "desc": "重大更新!有道插件新增免启动服务本地版;1.新增查询单词功能,单词更详细;2.修复:增加字典单词发音,增加字典单词变形", 63 | "sha256": "4b74fcd905ab3c7cc82b955cae1a2c55b8c82ac8415ea58f71c7e28bf2b2a478", 64 | "url": "https://github.com/akl7777777/bob-plugin-akl-youdao-free-translate/releases/download/v_0.1.2/youdao-dependOnService_v0.1.2.bobplugin", 65 | "minBobVersion": "0.5.0" 66 | }, 67 | { 68 | "version": "0.1.1", 69 | "desc": "重大更新!有道插件新增免启动服务本地版,可以不用启动服务了;新增查询单词功能,单词更详细;", 70 | "sha256": "dd3211f590497535f648db5f5d2c2676c062fecf83480a285b703dfdd83d6020", 71 | "url": "https://github.com/akl7777777/bob-plugin-akl-youdao-free-translate/releases/download/v_0.1.1/youdao-dependOnService_v0.1.1.bobplugin", 72 | "minBobVersion": "0.5.0" 73 | }, 74 | { 75 | "version": "0.1.0", 76 | "desc": "重大更新!有道插件新增免启动服务本地版,可以不用启动服务了;新增查询单词功能,单词更详细;", 77 | "sha256": "d60f395653776cdcfedb725bdfd71599ffaa30523276130226cf44e1ec55e5bc", 78 | "url": "https://github.com/akl7777777/bob-plugin-akl-youdao-free-translate/releases/download/v_0.1.0/youdao-dependOnService_v0.1.0.bobplugin", 79 | "minBobVersion": "0.5.0" 80 | }, 81 | { 82 | "version": "0.0.9", 83 | "desc": "重大更新!有道插件新增免启动服务本地版,可以不用启动服务了", 84 | "sha256": "63a993c0f1a453e351ff82f6c3b9a61ba3bba38e93053c36ed7ea5ec49902e3d", 85 | "url": "https://github.com/akl7777777/bob-plugin-akl-youdao-free-translate/releases/download/v_0.0.9/youdao-dependOnService_v0.0.9.bobplugin", 86 | "minBobVersion": "0.5.0" 87 | }, 88 | { 89 | "version": "0.0.8", 90 | "desc": "更换图标以及插件名称,这样显得更专业", 91 | "sha256": "2c50fb5aee5cdc0ff555c0adc9fcd86d60bffac267476b7154c01d2a6edc011e", 92 | "url": "https://github.com/akl7777777/bob-plugin-akl-youdao-free-translate/releases/download/v_0.0.8/youdao-dependOnService_v0.0.8.bobplugin", 93 | "minBobVersion": "0.5.0" 94 | }, 95 | { 96 | "version": "0.0.7", 97 | "desc": "增加远程服务器配置功能,服务可以不用部署在自己本机了 ^-^ ", 98 | "sha256": "2d57d3b1020da4f0cfa0721f93c248f3c6fa874027966cbd51fc9151506666a7", 99 | "url": "https://github.com/akl7777777/bob-plugin-akl-youdao-free-translate/releases/download/v_0.0.7/youdao-dependOnService_v0.0.7.bobplugin", 100 | "minBobVersion": "0.5.0" 101 | }, 102 | { 103 | "version": "0.0.6", 104 | "desc": "修复多个自然段被截断只剩一个的问题", 105 | "sha256": "4658f1bffc72477a27530f1e04b139e6db727a6b293f711975019ff900ff656d", 106 | "url": "https://github.com/akl7777777/bob-plugin-akl-youdao-free-translate/releases/download/v_0.0.6/youdao-dependOnService_v0.0.6.bobplugin", 107 | "minBobVersion": "0.5.0" 108 | }, 109 | { 110 | "version": "0.0.5", 111 | "desc": "增加插件检测更新功能", 112 | "sha256": "52d092d509ae768c70cc87efc70ee718f0a7ddefb01b108e5e3c44bd90cd7592", 113 | "url": "https://github.com/akl7777777/bob-plugin-akl-youdao-free-translate/releases/download/bob-plugin-akl-youdao-free-translate_v0.0.5/youdao-dependOnService_v0.0.5.bobplugin", 114 | "minBobVersion": "0.5.0" 115 | }, 116 | { 117 | "version": "0.0.3", 118 | "desc": "初始版本", 119 | "sha256": "5fddc4f6cf57853467135cd8ec0914bc21191c20c7f77f3be1496b37da11ecf8", 120 | "url": "https://github.com/akl7777777/bob-plugin-akl-youdao-free-translate/releases/download/bob-plugin-akl-youdao-free-translate/dependOnService.bobplugin", 121 | "minBobVersion": "0.5.0" 122 | } 123 | ] 124 | } -------------------------------------------------------------------------------- /bobplugin/independently/src/main.js: -------------------------------------------------------------------------------- 1 | var config = require('./config.js'); 2 | var utils = require('./utils.js'); 3 | var CryptoJS = require("crypto-js"); 4 | 5 | /** 6 | * 由于各大服务商的语言代码都不大一样, 7 | * 所以我定义了一份 Bob 专用的语言代码,以便 Bob 主程序和插件之间互传语种。 8 | * Bob 语言代码列表 https://ripperhe.gitee.io/bob/#/plugin/addtion/language 9 | * 10 | * 转换的代码建议以下面的方式实现, 11 | * `xxx` 代表服务商特有的语言代码,请替换为真实的, 12 | * 具体支持的语种数量请根据实际情况而定。 13 | * 14 | * Bob 语言代码转服务商语言代码(以为 'zh-Hans' 为例): var lang = langMap.get('zh-Hans'); 15 | * 服务商语言代码转 Bob 语言代码: var standardLang = langMapReverse.get('xxx'); 16 | */ 17 | 18 | // var items = [ 19 | // ['auto', 'xxx'], 20 | // ['zh-Hans', 'xxx'], 21 | // ['zh-Hant', 'xxx'], 22 | // ['en', 'xxx'], 23 | // ]; 24 | 25 | // const crypto = require('crypto'); 26 | const r = "fanyideskweb", i = "webfanyi"; 27 | function m(e) { 28 | return CryptoJS.MD5(e).toString(CryptoJS.enc.Hex); 29 | } 30 | function p(e) { 31 | return CryptoJS.MD5(e.toString()).toString(CryptoJS.enc.Hex); 32 | } 33 | 34 | function b(e, t) { 35 | const r = "fanyideskweb", i = "webfanyi"; 36 | 37 | return p('client=' + r + '&mysticTime=' + e + '&product=' + i + '&key=' + t) 38 | } 39 | function f() { 40 | const r = "fanyideskweb", i = "webfanyi"; 41 | 42 | const e = 'fsdsogkndfokasodnaso', s = 'client,mysticTime,product', u = 'fanyi.web', l = '1.0.0', d = 'web'; 43 | 44 | const t = (new Date).getTime(); 45 | 46 | return { 47 | sign: b(t, e), 48 | client: r, 49 | product: i, 50 | appVersion: l, 51 | vendor: d, 52 | pointParam: s, 53 | mysticTime: t, 54 | keyfrom: u 55 | } 56 | } 57 | 58 | /* 59 | function A(t) { 60 | const key = "ydsecret://query/key/B*RGygVywfNBwpmBaZg*WT7SIOUP2T0C9WHMZN39j^DAdaZhAnxvGcCY6VYFwnHl"; 61 | const iv = "ydsecret://query/iv/C@lZe2YzHtZ2CYgaXKSVfsb7Y4QWHjITPPZ0nQp87fBeJ!Iv6v^6fvi2WN@bYpJ4"; 62 | 63 | if (!t) { 64 | return null; 65 | } 66 | 67 | $log.error('***********CryptoJS.enc.Utf8.parse(key)==>' + CryptoJS.enc.Utf8.parse(key)) 68 | $log.error('***********CryptoJS.enc.Utf8.parse(iv)==>' + CryptoJS.enc.Utf8.parse(iv)) 69 | $log.error('***********CryptoJS.enc.Base64.parse(t)==>' + CryptoJS.enc.Base64.parse(t)) 70 | const a = CryptoJS.enc.Utf8.parse(key); 71 | const r = CryptoJS.enc.Utf8.parse(iv); 72 | 73 | 74 | const ciphertextParams = CryptoJS.lib.CipherParams.create({ 75 | ciphertext: CryptoJS.enc.Base64.parse(t), 76 | }); 77 | $log.error('***********84==>' + 84) 78 | $log.error('***********typeof ciphertextParams==>' + typeof ciphertextParams) 79 | $log.error('***********ciphertextParams==>' + JSON.stringify(ciphertextParams)) 80 | 81 | 82 | const decrypted = CryptoJS.AES.decrypt(ciphertextParams, a, { 83 | iv: r, 84 | mode: CryptoJS.mode.CBC, 85 | padding: CryptoJS.pad.Pkcs7, 86 | }); 87 | $log.error('***********decrypted==>' + decrypted) 88 | return decrypted.toString(CryptoJS.enc.Utf8); 89 | } 90 | */ 91 | 92 | /* 93 | function A (t, o, n) { 94 | o = "ydsecret://query/key/B*RGygVywfNBwpmBaZg*WT7SIOUP2T0C9WHMZN39j^DAdaZhAnxvGcCY6VYFwnHl" 95 | n = "ydsecret://query/iv/C@lZe2YzHtZ2CYgaXKSVfsb7Y4QWHjITPPZ0nQp87fBeJ!Iv6v^6fvi2WN@bYpJ4" 96 | if (!t) 97 | return null; 98 | const a = CryptoJS.enc.Hex.parse(m(o)) 99 | , r = CryptoJS.enc.Hex.parse(m(n)) 100 | , i = CryptoJS.AES.decrypt(CryptoJS.enc.Base64.parse(t), a, { 101 | iv: r, 102 | mode: CryptoJS.mode.CBC, 103 | padding: CryptoJS.pad.Pkcs7 104 | }); 105 | let s = i.toString(CryptoJS.enc.Utf8); 106 | return s 107 | } 108 | */ 109 | 110 | function A (t, o, n) { 111 | o = "ydsecret://query/key/BRGygVywfNBwpmBaZgWT7SIOUP2T0C9WHMZN39j^DAdaZhAnxvGcCY6VYFwnHl" 112 | n = "ydsecret://query/iv/C@lZe2YzHtZ2CYgaXKSVfsb7Y4QWHjITPPZ0nQp87fBeJ!Iv6v^6fvi2WN@bYpJ4" 113 | if (!t) 114 | return null; 115 | const a = CryptoJS.enc.Hex.parse(m(o)), 116 | r = CryptoJS.enc.Hex.parse(m(n)), 117 | i = CryptoJS.AES.decrypt(t, a, { 118 | iv: r 119 | }); 120 | return i.toString(CryptoJS.enc.Utf8); 121 | } 122 | /*function A (t, o, n) { 123 | $log.error('***********66t==>' + t) 124 | 125 | o = "ydsecret://query/key/B*RGygVywfNBwpmBaZg*WT7SIOUP2T0C9WHMZN39j^DAdaZhAnxvGcCY6VYFwnHl" 126 | n = "ydsecret://query/iv/C@lZe2YzHtZ2CYgaXKSVfsb7Y4QWHjITPPZ0nQp87fBeJ!Iv6v^6fvi2WN@bYpJ4" 127 | if (!t) 128 | return null; 129 | // const a = Buffer.alloc(16, m(o)); 130 | // const r = Buffer.alloc(16, m(n)); 131 | // const a = padKey(o) 132 | // const r = padIV(n) 133 | 134 | // const i = CryptoJS.createDecipheriv("aes-128-cbc", a, r); 135 | const i = aes128cbcDecrypt(o,n,t) 136 | let s = i.update(t, "base64", "utf-8"); 137 | return s += i.final("utf-8"), 138 | s 139 | // $log.error('***********CryptoJS.enc.Base64.parse(t)==>' + CryptoJS.enc.Base64.parse(t)) 140 | 141 | // const ciphertextParams = CryptoJS.lib.CipherParams.create({ 142 | // ciphertext: CryptoJS.enc.Base64.parse(t), 143 | // }); 144 | // $log.error('***********ciphertextParams==>' + JSON.stringify(ciphertextParams)) 145 | // // 解密 146 | // const decrypted = CryptoJS.AES.decrypt( 147 | // ciphertextParams, 148 | // CryptoJS.enc.Utf8.parse(o), 149 | // { 150 | // iv: CryptoJS.enc.Utf8.parse(n), 151 | // mode: CryptoJS.mode.CBC, 152 | // padding: CryptoJS.pad.Pkcs7, 153 | // } 154 | // ); 155 | // // 将解密后的字节数组转换为UTF-8字符串 156 | // const plaintext = decrypted.toString(CryptoJS.enc.Utf8); 157 | // $log.error('***********plaintext==>' + plaintext) 158 | // return plaintext; 159 | } 160 | */ 161 | 162 | function supportLanguages() { 163 | $log.error('***********' + JSON.stringify(config.supportedLanguages)) 164 | return config.supportedLanguages.map(([standardLang]) => standardLang); 165 | } 166 | 167 | function translate(query, completion) { 168 | // const apiClient = new api.Api($option.apikey, $option.service); 169 | 170 | (async () => { 171 | const targetLanguage = utils.langMap.get(query.detectTo); 172 | const sourceLanguage = utils.langMap.get(query.detectFrom); 173 | if (!targetLanguage) { 174 | const err = new Error(); 175 | Object.assign(err, { 176 | _type: 'unsupportLanguage', 177 | _message: '不支持该语种', 178 | }); 179 | throw err; 180 | } 181 | $log.info('***********==>' + 82) 182 | 183 | const source_lang = sourceLanguage || 'ZH'; 184 | const target_lang = targetLanguage || 'EN'; 185 | const translate_text = query.text || ''; 186 | let response; 187 | $log.info('***********==>' + 88) 188 | 189 | if (translate_text !== '') { 190 | const url = 'https://dict.youdao.com/webtranslate'; 191 | $log.info('***********==>' + 92) 192 | try { 193 | 194 | $log.info('***********url==>' + url) 195 | const originData = { 196 | 'i': translate_text, 197 | 'from': 'AUTO', 198 | 'to': 'AUTO', 199 | 'domain': '0', 200 | 'dictResult': 'true', 201 | 'keyid': 'webfanyi' 202 | } 203 | let fData = {} 204 | try { 205 | $log.info('***********CryptoJS.HmacSHA1==>' + CryptoJS.HmacSHA1("Message", "Key")); 206 | $log.info('***********CryptoJS.MD5(e).toString(CryptoJS.enc.Hex)==>' + CryptoJS.MD5("Message").toString(CryptoJS.enc.Hex)); 207 | $log.info('***********typeof f()==>' + typeof f); 208 | 209 | fData = f() 210 | } catch (error) { 211 | 212 | $log.info('***********error==>' + JSON.stringify(error)) 213 | } 214 | const body = Object.assign(originData, fData); 215 | $log.info('***********body==>' + JSON.stringify(body)) 216 | 217 | $http.request({ 218 | method: "POST", 219 | url: url, 220 | header: { 221 | 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36 Edg/108.0.1462.54', 222 | 'Cookie': 'OUTFOX_SEARCH_USER_ID_NCOO=976405377.6815147; OUTFOX_SEARCH_USER_ID=-198948307@211.83.126.235; _ga=GA1.2.1162596953.1667349221; search-popup-show=12-2', 223 | 'Referer': 'https://fanyi.youdao.com/' 224 | }, 225 | body: body, 226 | handler: function (resp) { 227 | $log.error('***********response==>' + JSON.stringify(resp)) 228 | $log.error('***********resp.data==>' + JSON.stringify(resp.data)) 229 | let rs = A(resp.data) 230 | $log.error('***********rs==>' + rs) 231 | completion({ 232 | result: { 233 | from: query.detectFrom, 234 | to: query.detectTo, 235 | toParagraphs: rs.split('\n'), 236 | }, 237 | }); 238 | } 239 | }); 240 | } 241 | catch (e) { 242 | $log.error('接口请求错误 ==> ' + JSON.stringify(e)) 243 | Object.assign(e, { 244 | _type: 'network', 245 | _message: '接口请求错误 - ' + JSON.stringify(e), 246 | }); 247 | throw e; 248 | } 249 | } 250 | })().catch((err) => { 251 | $log.error('***********解析返回值异常==>' + JSON.stringify(err)) 252 | completion({ 253 | error: { 254 | type: err._type || 'unknown', 255 | message: err._message || '未知错误', 256 | addtion: err._addtion, 257 | }, 258 | }); 259 | }); 260 | } 261 | 262 | exports.supportLanguages = supportLanguages; 263 | exports.translate = translate; 264 | -------------------------------------------------------------------------------- /bobplugin/dependOnService/src/main.js: -------------------------------------------------------------------------------- 1 | var config = require('./config.js'); 2 | var utils = require('./utils.js'); 3 | var CryptoJS = require("crypto-js"); 4 | 5 | function supportLanguages() { 6 | return config.supportedLanguages.map(([standardLang]) => standardLang); 7 | } 8 | 9 | function translate(query, completion) { 10 | (async () => { 11 | const targetLanguage = utils.langMap.get(query.detectTo); 12 | const sourceLanguage = utils.langMap.get(query.detectFrom); 13 | if (!targetLanguage) { 14 | const err = new Error(); 15 | Object.assign(err, { 16 | _type: 'unsupportLanguage', 17 | _message: '不支持该语种', 18 | }); 19 | throw err; 20 | } 21 | const source_lang = sourceLanguage || 'zh-CHS'; 22 | const target_lang = targetLanguage || 'en'; 23 | const translate_text = query.text || ''; 24 | 25 | const dictionaryUrl = 'https://dict.youdao.com/jsonapi_s?doctype=json&jsonversion=4' 26 | 27 | if (translate_text !== '') { 28 | // 优化英文单词判定正则表达式 29 | if (/^[a-zA-Z,\.\?!'\s]+$/.test(translate_text) 30 | && translate_text.split(/\s+/).filter(word => /^[a-zA-Z\s]+$/.test(word)).length === 1) { 31 | // 只做英文单词,正则排除 32 | let y = ["option_avatar", "nickname"] 33 | , w = "Mk6hqtUp33DGGtoS63tTJbMUYjRrG1Lu" 34 | , v = "webdict" 35 | , _ = "web" 36 | const h = function (t) { 37 | return CryptoJS.MD5(t.toString()).toString(CryptoJS.enc.Hex); 38 | } 39 | let time = "".concat(translate_text).concat(v).length % 10, 40 | r = "".concat(translate_text).concat(v), 41 | o = h(r), 42 | n = "".concat(_).concat(translate_text).concat(time + '').concat(w).concat(o), 43 | f = h(n) 44 | 45 | // 查字典,不查句子 46 | const body = Object.assign({}, { 47 | "q": translate_text, 48 | "keyfrom": "webdict", 49 | "sign": f, 50 | "client": "web", 51 | "t": time 52 | }); 53 | $http.request({ 54 | method: "POST", 55 | url: dictionaryUrl, 56 | header: { 57 | "content-type": "application/x-www-form-urlencoded", 58 | "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36" 59 | }, 60 | body: body, 61 | handler: function (resp) { 62 | if (resp.error) { 63 | completion({ 64 | error: { 65 | type: resp.error.code || 'unknown', 66 | message: resp.error.localizedDescription || '未知错误', 67 | addtion: resp.error.localizedDescription, 68 | }, 69 | }); 70 | } 71 | const toDict = { 72 | word: translate_text, 73 | phonetics: [], 74 | parts: [], 75 | exchanges: [], 76 | additions: [] 77 | } 78 | if (resp.data.ec && resp.data.ec.word && resp.data.ec.word.trs && resp.data.ec.word.trs.length) { 79 | const word = resp.data.ec.word 80 | if (word.usphone) { 81 | toDict.phonetics.push({ 82 | "type": "us", 83 | "value": word.usphone, 84 | "tts": { 85 | "type": "url", 86 | "value": "https://dict.youdao.com/dictvoice?audio=" + word.usspeech + "&type=2" 87 | } 88 | }) 89 | } 90 | if (word.ukphone) { 91 | toDict.phonetics.push({ 92 | "type": "uk", 93 | "value": word.ukphone, 94 | "tts": { 95 | "type": "url", 96 | "value": "https://dict.youdao.com/dictvoice?audio=" + word.ukspeech + "&type=1" 97 | } 98 | }) 99 | } 100 | word.trs.forEach(function (e) { 101 | toDict.parts.push({part: e.pos, means: [e.tran]}) 102 | }) 103 | if (word.wfs && word.wfs.length) { 104 | word.wfs.forEach(function (e) { 105 | toDict.exchanges.push({name: e.wf.name, words: [e.wf.value]}) 106 | }) 107 | } 108 | if (word.prototype) { 109 | toDict.exchanges.push({name: '原形', words: [word.prototype]}) 110 | } 111 | toDict.additions.push({ 112 | name: '标签', 113 | value: (resp.data.ec.exam_type ? resp.data.ec.exam_type.join('/') : '') 114 | }) 115 | completion({ 116 | result: { 117 | from: query.detectFrom, 118 | to: query.detectTo, 119 | fromParagraphs: translate_text.split('\n'), 120 | toParagraphs: resp.data.ec.web_trans, 121 | toDict: toDict, 122 | }, 123 | }); 124 | } else if (resp.data.typos && resp.data.typos.typo && resp.data.typos.typo.length) { 125 | // 优化未查询到单词时的模糊匹配 126 | resp.data.typos.typo.forEach(function (e) { 127 | toDict.exchanges.push({name: '您要找的是不是:' + e.trans, words: [e.word]}) 128 | }) 129 | // toDict.phonetics.push({ 130 | // "type": "us", 131 | // "value": "未找到" 132 | // }) 133 | toDict.parts.push({part: '抱歉,', means: ['未找到“' + translate_text + '”相关的词']}) 134 | completion({ 135 | result: { 136 | from: query.detectFrom, 137 | to: query.detectTo, 138 | fromParagraphs: translate_text.split('\n'), 139 | toParagraphs: ('抱歉没有找到“' + translate_text + '”相关的词').split('\n'), 140 | toDict: toDict, 141 | }, 142 | }); 143 | } else { 144 | $log.error('*********** 词典查询err ==> ' + JSON.stringify(resp.data)) 145 | completion({ 146 | error: { 147 | type: 'unknown', 148 | message: JSON.stringify(resp.data) || '未知错误', 149 | addtion: '未知错误', 150 | }, 151 | }); 152 | } 153 | } 154 | }); 155 | } else { 156 | const serverUrl = $option.serverUrl; 157 | const type = $option.type; 158 | let url = serverUrl || 'http://127.0.0.1:9527/youdaoTranslate'; 159 | if (type === 'local') { 160 | const localHeader = { 161 | "content-type": "application/x-www-form-urlencoded; charset=UTF-8", 162 | "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36" 163 | } 164 | url = 'https://aidemo.youdao.com/trans' 165 | // 对超长的文本进行分段翻译,判断文本是否超过1000字符,如果超过,则按照句号进行拆分,取一千以内的最后一个句号的位置,进行分段翻译 166 | // 判断超过10000字符直接返回,避免循环调用接口过多导致封禁 167 | const originText = query.text 168 | if (originText.length > 10000) { 169 | completion({ 170 | error: { 171 | type: 'api', 172 | message: '接口限制单次调用1000字符以内,为了避免循环次数过多封禁ip,总字符数不能超过10000个字符!', 173 | }, 174 | }); 175 | } 176 | // 不能超过1000个字符,所以需要循环拆分每隔1000调用一次 177 | let paramText = query.text 178 | // 定义返回结果拼接变量 179 | let concatRs = [] 180 | // 定义指向切分位置 181 | let index = 0 182 | while (true) { 183 | if (originText.substring(index).length > 1000) { 184 | let tmpText = originText.substring(index, index + 1000) 185 | let idx = tmpText.lastIndexOf("。") 186 | if (tmpText.lastIndexOf(".") > idx) { 187 | idx = tmpText.lastIndexOf(".") 188 | } 189 | if (tmpText.lastIndexOf("\n") > idx) { 190 | idx = tmpText.lastIndexOf("\n") 191 | } 192 | if (tmpText.lastIndexOf(".") > idx) { 193 | idx = tmpText.lastIndexOf(".") 194 | } 195 | if (idx < 0) { 196 | completion({ 197 | error: { 198 | type: 'api', 199 | message: '单句翻译不能超过1000个字符!', 200 | }, 201 | }); 202 | break 203 | } 204 | index = index + idx + 1 205 | paramText = tmpText.substring(0, idx + 1) 206 | const response = await $http.request({ 207 | method: 'POST', 208 | url: url, 209 | header: localHeader, 210 | body: Object.assign({}, {"q": paramText, "from": source_lang, "to": target_lang}) 211 | }); 212 | if (response.error) { 213 | const {statusCode} = response.response; 214 | let reason; 215 | if (statusCode >= 400 && statusCode < 500) { 216 | reason = 'param'; 217 | } else { 218 | reason = 'api'; 219 | } 220 | completion({ 221 | error: { 222 | type: reason, 223 | message: `接口响应错误 - ${response.data.msg}`, 224 | addtion: JSON.stringify(response), 225 | }, 226 | }); 227 | break 228 | } else { 229 | const translations = response.data.translation; 230 | if (!translations || !translations.length) { 231 | continue 232 | } 233 | translations.forEach(function (e) { 234 | // 如果目标是中文,则需要将英文标点替换为中文标点 235 | if (target_lang === 'zh-CHS') { 236 | e = utils.replacePunctuation(e) 237 | } 238 | concatRs.push(e) 239 | }) 240 | // concatRs.push('\n') 241 | } 242 | } else { 243 | const response = await $http.request({ 244 | method: 'POST', 245 | url: url, 246 | header: localHeader, 247 | body: Object.assign({}, { 248 | "q": originText.substring(index), 249 | "from": source_lang, 250 | "to": target_lang 251 | }) 252 | }); 253 | if (response.error) { 254 | const {statusCode} = response.response; 255 | let reason; 256 | if (statusCode >= 400 && statusCode < 500) { 257 | reason = 'param'; 258 | } else { 259 | reason = 'api'; 260 | } 261 | completion({ 262 | error: { 263 | type: reason, 264 | message: `接口响应错误 - ${response.data.msg}`, 265 | addtion: JSON.stringify(response), 266 | }, 267 | }); 268 | } else { 269 | const translations = response.data.translation; 270 | if (!translations || !translations.length) { 271 | completion({ 272 | error: { 273 | type: 'api', 274 | message: '接口未返回翻译结果', 275 | }, 276 | }); 277 | return; 278 | } 279 | translations.forEach(function (e) { 280 | // 如果目标是中文,则需要将英文标点替换为中文标点 281 | if (target_lang === 'zh-CHS') { 282 | e = utils.replacePunctuation(e) 283 | } 284 | concatRs.push(e) 285 | }) 286 | } 287 | break 288 | } 289 | } 290 | completion({ 291 | result: { 292 | from: query.detectFrom, 293 | to: query.detectTo, 294 | toParagraphs: concatRs, 295 | }, 296 | }); 297 | } else { 298 | try { 299 | const body = Object.assign({}, { 300 | "text": translate_text, 301 | "source_lang": source_lang, 302 | "target_lang": target_lang 303 | }); 304 | $http.request({ 305 | method: "POST", 306 | url: url, 307 | header: { 308 | "Content-Type": "application/json" 309 | }, 310 | body: body, 311 | handler: function (resp) { 312 | if (resp.error) { 313 | completion({ 314 | error: { 315 | type: resp.error.code || 'unknown', 316 | message: resp.error.localizedDescription || '未知错误', 317 | addtion: resp.error.localizedDescription, 318 | }, 319 | }); 320 | } 321 | const rs = [] 322 | if (resp.data.translateResult && resp.data.translateResult.length) { 323 | for (let i = 0; i < resp.data.translateResult.length; i++) { 324 | if (resp.data.translateResult[i].length) { 325 | let rsParagraph = '' 326 | for (let j = 0; j < resp.data.translateResult[i].length; j++) { 327 | rsParagraph += resp.data.translateResult[i][j].tgt 328 | } 329 | // 如果目标是中文,则需要将英文标点替换为中文标点 330 | if (target_lang === 'zh-CHS') { 331 | rsParagraph = utils.replacePunctuation(rsParagraph) 332 | } 333 | rs.push(rsParagraph) 334 | } 335 | } 336 | } 337 | completion({ 338 | result: { 339 | from: query.detectFrom, 340 | to: query.detectTo, 341 | toParagraphs: rs, 342 | }, 343 | }); 344 | } 345 | }); 346 | } catch (e) { 347 | $log.error('接口请求错误 ==> ' + JSON.stringify(e)) 348 | Object.assign(e, { 349 | _type: 'network', 350 | _message: '接口请求错误 - ' + JSON.stringify(e), 351 | }); 352 | throw e; 353 | } 354 | } 355 | } 356 | 357 | } 358 | })().catch((err) => { 359 | $log.error('***********解析返回值异常==>' + JSON.stringify(err)) 360 | completion({ 361 | error: { 362 | type: err._type || 'unknown', 363 | message: err._message || '未知错误', 364 | addtion: err._addtion, 365 | }, 366 | }); 367 | }); 368 | } 369 | 370 | exports.supportLanguages = supportLanguages; 371 | exports.translate = translate; 372 | --------------------------------------------------------------------------------