diff --git a/README.md b/README.md index b15a983..db0f4ec 100644 --- a/README.md +++ b/README.md @@ -77,78 +77,82 @@ - - 属于移动端网页 API,精准度不是非常高。 - 为页面模式,翻译结果较多时显示效果更好。 -2. 谷歌 +2. 有道翻译 + - + - 属于网页版 API,单次翻译字数限制为 5000 字。 +3. 谷歌 - - 翻译内容较少,仅有基本翻译。 - 若不能直接访问谷歌,可修改 hosts。 -3. 谷歌 +4. 谷歌 - - 翻译内容较少,仅有基本翻译。 - 若不能直接访问谷歌,可修改 hosts,或设置代理。 -4. 必应 +5. 必应 - - 必应词典,仅支持单词,不支持句子。 -5. 爱词霸 +6. 爱词霸 - - 翻译内容较少,仅有基本翻译。 -6. 腾讯交互翻译 +7. 腾讯交互翻译 - - 翻译内容较少,仅有基本翻译。 - 支持 21 种语言互译。 - 法语、西班牙语、英语和中文能互译,其他语言都只能与英语和中文互译。 -7. 微软 +8. 微软 - - 翻译内容较少,仅有基本翻译。 -8. 福昕 +9. 福昕 - - 翻译内容较少,仅有基本翻译。 -9. CNKI - - - - 学术翻译 -10. DeepL +10. CNKI + - + - 学术翻译 +11. DeepL - - 免费 API,响应相对较慢。 -11. DeepL X +12. DeepL X - - 将 DeepL API 逆向之后的三方包,不再需要 Auth Key,基于 DeepL Free 版。 - 需要配置 API 的地址。 -12. DeepL API +13. DeepL API - - + - - 官方 API,分别对应 Free 和 Pro 版,需要到[DeepL API](https://www.deepl.com/pro-api)订阅方案并获取 Auth Key。 -13. 有道翻译 +14. 有道翻译 - - 支持 112 种语言互译,其中自动可以识别中文、英文、日文、韩文、法文、西班牙文、葡萄牙文、俄文、越南文、德文、阿拉伯文、印尼文、意大利文 - 支持术语表 - 需要到[有道智云](https://ai.youdao.com/doc.s)申请获取应用 ID 和应用密钥。 -14. 百度翻译 +15. 百度翻译 - - 翻译内容较少,仅有基本翻译。 - 需要到[百度翻译开放平台](https://fanyi-api.baidu.com)申请获取 APP ID 和密钥。 -15. 阿里翻译 +16. 阿里翻译 - - 翻译内容较少,仅有基本翻译。 - 支持 214 种语言互译。 - 除繁体中文、蒙语、粤语外,其他 212 种语言,可支持任意两种语言之间互译。 - 繁体中文、蒙语、粤语仅支持与中文之间的互译。 - 需要到[阿里云](https://www.aliyun.com/product/ai/base_alimt)申请获取 Access Key ID 和 Secret Access Key。 -16. 腾讯翻译君 +17. 腾讯翻译君 - - 翻译内容较少,仅有基本翻译。 - 需要到[腾讯云](https://cloud.tencent.com/product/tmt)申请获取 Secret ID 和 Secret Key。 -17. 火山翻译 +18. 火山翻译 - - 翻译内容较少,仅有基本翻译。 - 需要到[火山引擎](https://www.volcengine.com/docs/4640/130262)开通服务并获取 Access Key ID 和 Secret Access Key。 -18. 华为翻译 +19. 华为翻译 - - 翻译内容较少,仅有基本翻译。 - 需要到[华为云](https://www.huaweicloud.com/product/nlpf.html)开通服务并获取 AK、SK 和 Project ID。 -19. 彩云小译 +20. 彩云小译 - - 翻译内容较少,仅有基本翻译。 - 需要到[彩云科技](https://fanyi.caiyunapp.com/#/api)申请获取应用 Token。 -20. 小牛翻译 +21. 小牛翻译 - - 翻译内容较少,仅有基本翻译。 - 需要到[小牛翻译](https://niutrans.com/cloud/account_info/info)获取 API-KEY。 diff --git a/assets/images/youDaoWeb.png b/assets/images/youDaoWeb.png new file mode 100644 index 0000000..c0327f4 Binary files /dev/null and b/assets/images/youDaoWeb.png differ diff --git a/assets/js/api/youDaoWeb.js b/assets/js/api/youDaoWeb.js new file mode 100644 index 0000000..fe718e8 --- /dev/null +++ b/assets/js/api/youDaoWeb.js @@ -0,0 +1,126 @@ +const errorCodeMsgYouDaoWeb = { + 103: "翻译文本过长", + 104: "获取密钥失败", + 4411: "API请求太过频繁,请稍后再试。可进入设置页面切换其他API", + 9999: "其他错误,可进入设置页面切换其他API", +}; + +async function lookupYouDaoWeb(word) { + let data = []; + if (word.length > 5000) { + data.push({ + title: errTitle, + description: errorCodeMsgYouDaoWeb[103], + }); + return data; + } + + let keys = await getKeys(); + if (!keys || !keys.secretKey || !keys.aesKey || !keys.aesIv) { + data.push({ + title: errTitle, + description: errorCodeMsgYouDaoWeb[104], + }); + return data; + } + + let payload = { + i: word, + from: "auto", + keyid: "webfanyi", + appVersion: "1.0.0", + vendor: "web", + pointParam: "client,mysticTime,product", + keyfrom: "fanyi.web", + }; + let queryParams = getQueryParams( + options.youDaoWeb.client, + options.youDaoWeb.product, + keys.secretKey + ); + let param = Object.assign(payload, queryParams); + let headers = { + Accept: "application/json, text/plain, */*", + Cookie: "OUTFOX_SEARCH_USER_ID=0@127.0.0.1", + Referer: "https://fanyi.youdao.com/", + "User-Agent": + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36", + }; + + const api = options.youDaoWeb.api; + const path = options.youDaoWeb.path + "?" + stringify(param); + + let response; + try { + response = await window.doPost(api, path, null, headers, encodeURIComponent(stringify(param))); + let decryptedText = decryptText(response, keys.aesKey, keys.aesIv); + let resData = JSON.parse(decryptedText); + console.log(resData); + let sentences = resData?.translateResult[0]; + let langs = resData.type.replace("zh-CHS", "zh-CN").split("2"); + let langSource = langs[0]; + let langTarget = langs[1]; + let trans; + if (sentences && sentences.length != 0) { + trans = sentences.map(item => item.tgt); + } + if (trans && trans.length != 0) { + const tran = trans.join(""); + let phoneticHtml = getPhoneticHtml(word, tran, langSource, langTarget); + let dataTitle = `${tran}${phoneticHtml}`; + data.push({ title: dataTitle, description: "基本释义" }); + } + + let trs = resData?.dictResult?.ec?.word?.trs; + if (trs && trs.length != 0) { + for (let i = 0; i < trs.length; i++) { + data.push({ + title: trs[i].tran, + description: `变形 ${trs[i].pos}`, + }); + } + } + } catch (error) { + data.push({ + title: errTitle, + description: errorCodeMsgYouDaoWeb[9999], + }); + } + return data; +} + +async function getKeys() { + const keyURL = "https://dict.youdao.com/webtranslate/key"; + const baseParams = { keyid: "webfanyi-key-getter", pointParam: "client,mysticTime,product" }; + let queryParams = getQueryParams( + options.youDaoWeb.client, + options.youDaoWeb.product, + options.youDaoWeb.key + ); + + let params = Object.assign(baseParams, queryParams); + let keyRes; + try { + keyRes = await get(keyURL + "?" + stringify(params)); + return keyRes?.data; + } catch (error) { + console.error(error); + return null; + } +} + +function getQueryParams(client, product, key) { + const time = getTimestamp(); + let signStr = window.MD5(`client=${client}&mysticTime=${time}&product=${product}&key=${key}`); + return { + client: client, + product: product, + mysticTime: time, + sign: signStr, + }; +} + +function decryptText(str, aesKey, aesIv) { + const txt = str.replace(/-/g, "+").replace(/_/g, "/"); + return window.AES_CBC_Decrypt(txt, aesKey, aesIv); +} diff --git a/assets/js/index.js b/assets/js/index.js index e5bf1c3..ab1f961 100644 --- a/assets/js/index.js +++ b/assets/js/index.js @@ -149,7 +149,8 @@ async function switchApi(word) { option == Object.keys(options)[17] || option == Object.keys(options)[18] || option == Object.keys(options)[19] || - option == Object.keys(options)[20] + option == Object.keys(options)[20] || + option == Object.keys(options)[21] ) { $("#page").addClass("hide"); $("#setting").addClass("hide"); @@ -158,63 +159,66 @@ async function switchApi(word) { let data = []; switch (option) { case Object.keys(options)[1]: - data = await lookupGoogleAPI(word); + data = await lookupYouDaoWeb(word); break; case Object.keys(options)[2]: - data = await lookupGoogle(word); + data = await lookupGoogleAPI(word); break; case Object.keys(options)[3]: - data = await lookupBing(word); + data = await lookupGoogle(word); break; case Object.keys(options)[4]: - data = await lookupICiBa(word); + data = await lookupBing(word); break; case Object.keys(options)[5]: - data = await lookupTranSmart(word); + data = await lookupICiBa(word); break; case Object.keys(options)[6]: - data = await lookupMicrosoft(word); + data = await lookupTranSmart(word); break; case Object.keys(options)[7]: - data = await lookupFoxIT(word); + data = await lookupMicrosoft(word); break; case Object.keys(options)[8]: - data = await lookupCNKI(word); + data = await lookupFoxIT(word); break; case Object.keys(options)[9]: - data = await lookupDeepL(word); + data = await lookupCNKI(word); break; case Object.keys(options)[10]: - data = await lookupDeepLX(word); + data = await lookupDeepL(word); break; case Object.keys(options)[11]: - data = await lookupDeepLAPI(word, "free"); + data = await lookupDeepLX(word); break; case Object.keys(options)[12]: - data = await lookupDeepLAPI(word, "pro"); + data = await lookupDeepLAPI(word, "free"); break; case Object.keys(options)[13]: - data = await lookupYouDao(word); + data = await lookupDeepLAPI(word, "pro"); break; case Object.keys(options)[14]: - data = await lookupBaiDu(word); + data = await lookupYouDao(word); break; case Object.keys(options)[15]: - data = await lookupAliYun(word); + data = await lookupBaiDu(word); break; case Object.keys(options)[16]: - data = await lookupTencent(word); + data = await lookupAliYun(word); break; case Object.keys(options)[17]: - data = await lookupHuoShan(word); + data = await lookupTencent(word); break; case Object.keys(options)[18]: - data = await lookupHuaWei(word); + data = await lookupHuoShan(word); break; case Object.keys(options)[19]: - data = await lookupCaiYun(word); + data = await lookupHuaWei(word); break; case Object.keys(options)[20]: + data = await lookupCaiYun(word); + break; + case Object.keys(options)[21]: data = await lookupXiaoNiu(word); break; default: diff --git a/assets/js/setting.js b/assets/js/setting.js index 6144658..2afc6d5 100644 --- a/assets/js/setting.js +++ b/assets/js/setting.js @@ -4,6 +4,15 @@ const options = { api: "https://m.youdao.com/dict?le=eng&q=", logo: "youDaoWap.png", }, + youDaoWeb: { + name: "有道网页版", + api: "dict.youdao.com", + path: "/webtranslate", + logo: "youDaoWeb.png", + client: "fanyideskweb", + product: "webfanyi", + key: "asdjnjfenknafdfsdfsd", + }, googleAPI: { name: "谷歌", api: "https://translate.googleapis.com/translate_a/single?client=gtx&ie=UTF-8&oe=UTF-8&dt=bd&dt=t&sl=auto", @@ -635,7 +644,7 @@ const options = { }; // Set the default API. -const defaultAPI = Object.keys(options)[4]; +const defaultAPI = Object.keys(options)[1]; const DEFAULT_SPEAK = { speakSwitch: true, diff --git a/index.html b/index.html index bdf32c4..c9cf387 100644 --- a/index.html +++ b/index.html @@ -148,6 +148,12 @@ 有道 + +
+
+ +
+ +
@@ -1370,6 +1392,7 @@ + diff --git a/preload.js b/preload.js index 209ff8b..419f558 100644 --- a/preload.js +++ b/preload.js @@ -137,4 +137,18 @@ window.AES_ECB_Decrypt = function (str, t) { return CryptoJS.AES.decrypt(str, CryptoJS.enc.Utf8.parse(t), e).toString(CryptoJS.enc.Utf8); }; +window.AES_CBC_Decrypt = function (str, secretKey, iv) { + return CryptoJS.AES.decrypt( + { + ciphertext: CryptoJS.enc.Base64.parse(str), + }, + CryptoJS.MD5(secretKey), + { + iv: CryptoJS.MD5(iv), + mode: CryptoJS.mode.CBC, + padding: CryptoJS.pad.Pkcs7, + } + ).toString(CryptoJS.enc.Utf8); +}; + window.huaWeiSigner = signer;