某道翻译请求关键参数和返回数据解密过程分析-20230405
本帖最后由 hans7 于 2023-4-7 01:27 编辑## 引言
今天本英语渣用了下谋道翻译,惊讶地发现谋道返回的接口数据是加密的。想着我已经很久没碰逆向了,那就来研究一下吧,~~顺便水篇入门文~~。PS:整个过程没有用到动态调试。
**作者:(https://blog.csdn.net/hans774882968)以及(https://juejin.cn/user/1464964842528888)以及(https://www.52pojie.cn/home.php?mod=space&uid=1906177)**
本文52pojie:https://www.52pojie.cn/thread-1769988-1-1.html
本文juejin:https://juejin.cn/post/7218487123212664890/
本文CSDN:https://blog.csdn.net/hans774882968/article/details/129976697
## webtranslate接口返回加密数据的解密过程
1. 谋道翻译webtranslate接口:https://dict.moudao.com/webtranslate
2. 谋道翻译webtranslate获取`secretKey`接口:https://dict.moudao.com/webtranslate/key
抓包,找到附近代码:
```js
nn["a"].getTextTranslateResult({
i: e.data.keyword,
from: e.data.from,
to: e.data.to,
...n,
dictResult: !0,
keyid: "webfanyi"
}, o).then(o=>{
nn["a"].cancelLastGpt();
const n = nn["a"].decodeData(o, an["a"].state.text.decodeKey, an["a"].state.text.decodeIv)
, a = n ? JSON.parse(n) : {};
console.log("解密后的接口数据:", a), // 谋道故意放水,直接给答案?
0 === a.code ? e.success && t(e.success)(a) : e.fail && t(e.fail)(a)
}
```
`o`就是接口加密数据,主要需要确定`decodeKey, decodeIv`。因为某道翻译前端是`webpack`打包的应用,所以可以这么找`nn, an`的位置:
首先看到
```js
var nn = o("8139")
, an = o("4360")
, sn = o("bc3a");
```
打开Chrome Devtools的Search Tab,搜索`8139`,很快找到(https://fanyi.moudao.com/js/app.e4e9fbd0.js)
```js
8139: function(e, t, o) {
"use strict";
// ...
},
8393: //...
```
于是可知`decodeData`内容:
```js
T = (t,o,n)=>{
if (!t)
return null;
const a = e.alloc(16, f(o))
, i = e.alloc(16, f(n))
, r = c.a.createDecipheriv("aes-128-cbc", a, i);
let s = r.update(t, "base64", "utf-8");
return s += r.final("utf-8"),
s
}
```
不过`decodeKey`和`decodeIv`并不是很好定位……还是通过Chrome Devtools的Search Tab直接搜到的。
```js
const i = {
secretKey: "",
dictResult: {},
decodeKey: "ydsecret://query/key/B*RGygVywfNBwpmBaZg*WT7SIOUP2T0C9WHMZN39j^DAdaZhAnxvGcCY6VYFwnHl",
decodeIv: "ydsecret://query/iv/C@lZe2YzHtZ2CYgaXKSVfsb7Y4QWHjITPPZ0nQp87fBeJ!Iv6v^6fvi2WN@bYpJ4",
allowStroke: !1,
showPjm: !1,
showRomanPronunciation: !1,
showWordsNumber: !0
}
```
知道这两个变量的值以后,也可以马后炮地倒推一下`4360`这个模块做的事:
```js
4360: function(e, t, o) {
"use strict";
o("13d5");
var n = o("5502");
const a = []
, c = o("c653")
, i = c.keys().reduce((e,t)=>{ // c.keys()取出的是["./domain.js"]数组
const o = t.replace(/^\.\/(.*)\.\w+$/, "$1");
a.push(o);
const n = c(t);
return e = n.default,
e
}
, {});
t["a"] = Object(n["a"])({
modules: i
})
},
```
`i`变量可以猜测是导出的模块,`c = o("c653")`似乎在做模块整合。
```js
c653: function(e, t, o) {
var n = {
"./domain.js": "d2a7",
"./language.js": "c083",
"./login.js": "b5ce",
"./text.js": "1a68"
};
function a(e) {
var t = c(e);
return o(t)
}
function c(e) {
if (!o.o(n, e)) {
var t = new Error("Cannot find module '" + e + "'");
throw t.code = "MODULE_NOT_FOUND",
t
}
return n
}
a.keys = function() {
return Object.keys(n)
}
,
a.resolve = c,
e.exports = a,
a.id = "c653"
},
```
看到`./text.js`,结合`an["a"].state.text.decodeKey`可以猜测`1a68`就是关键模块。
```js
"1a68": function(e, t, o) {
"use strict";
o.r(t);
var n = o("8139")
, a = o("8544")
, c = o("c34f");
const i = {
secretKey: "",
dictResult: {},
decodeKey: "ydsecret://query/key/B*RGygVywfNBwpmBaZg*WT7SIOUP2T0C9WHMZN39j^DAdaZhAnxvGcCY6VYFwnHl",
decodeIv: "ydsecret://query/iv/C@lZe2YzHtZ2CYgaXKSVfsb7Y4QWHjITPPZ0nQp87fBeJ!Iv6v^6fvi2WN@bYpJ4",
allowStroke: !1,
showPjm: !1,
showRomanPronunciation: !1,
showWordsNumber: !0
}
, r = {
secretKey: e=>e.secretKey,
dictResult: e=>e.dictResult
}
, s = {
fetchTextTranslateSecretKey: ({commit: e},t)=>{
const o = "webfanyi-key-getter"
, a = "asdjnjfenknafdfsdfsd";
n["a"].getTextTranslateSecretKey({
keyid: o
}, a).then(t=>{
0 === t.code && t.data.secretKey && e("UPDATE_SECRET_KEY", t.data.secretKey)
}
).catch(e=>{}
)
}
,
setDictResult: ({commit: e},t)=>{
e("SET_DICTRESULT", t)
}
,
initTextTranslateSettingStore: ({commit: e},t)=>{
const o = a["a"].get("allowStroke")
, n = a["a"].get("showPjm")
, c = a["a"].get("showRomanPronunciation")
, i = a["a"].get("showWordsNumber");
e("SET_ALLOW_STROKE", null !== o && o),
e("SET_SHOW_PJM", null !== n && n),
e("SET_SHOW_ROMAN_PRONUNCICATION", null !== c && c),
e("SET_SHOW_WORDS_NUMBER", null === i || i)
}
}
, l = {
UPDATE_SECRET_KEY(e, t) {
e.secretKey = t
},
SET_DICTRESULT(e, t) {
e.dictResult = t
},
SET_ALLOW_STROKE(e, t) {
e.allowStroke = t,
a["a"].set("allowStroke", t),
Object(c["b"])(t)
},
SET_SHOW_PJM(e, t) {
e.showPjm = t,
a["a"].set("showPjm", t)
},
SET_SHOW_ROMAN_PRONUNCICATION(e, t) {
e.showRomanPronunciation = t,
a["a"].set("showRomanPronunciation", t)
},
SET_SHOW_WORDS_NUMBER(e, t) {
e.showWordsNumber = t,
a["a"].set("showWordsNumber", t)
}
};
t["default"] = {
state: i,
getters: r,
mutations: l,
actions: s
}
},
```
显然`1a68`是一个`vuex`模块~~🐔⌨️🍚~~,这个模块在后文《webtranslate接口的`sign`参数生成过程分析》还会用到。
至此,`decodeData`3个参数都知道了,分析下其内容:
```js
T = (t,o,n)=>{
if (!t)
return null;
const a = e.alloc(16, f(o))
, i = e.alloc(16, f(n))
, r = c.a.createDecipheriv("aes-128-cbc", a, i);
let s = r.update(t, "base64", "utf-8");
return s += r.final("utf-8"),
s
}
```
1. `e`是什么?它出现在`chunk-vendors`里,根据`webpack`常识,`chunk-vendors`一般就是标准库。注释里有一句`The buffer module from node.js, for the browser.`,所以`e`就是`node.js Buffer`的polyfill。
2. `c.a`是什么?`createDecipheriv`也出现在`chunk-vendors`,所以也属于标准库,网上搜一下`createDecipheriv`可知`c.a`是`node.js`自带的`crypto`模块。
3. `f`就是同一个模块定义的函数:
```js
function f(e) {
return c.a.createHash("md5").update(e).digest()
}
```
至此,我们已经可以写出解密代码:
```js
const crypto = require('crypto');
function getMd5(e) {
return crypto.createHash('md5').update(e).digest();
}
function decryptData(encryptedText) {
const algo = 'aes-128-cbc';
const decodeKey = 'ydsecret://query/key/B*RGygVywfNBwpmBaZg*WT7SIOUP2T0C9WHMZN39j^DAdaZhAnxvGcCY6VYFwnHl';
const decodeIv = 'ydsecret://query/iv/C@lZe2YzHtZ2CYgaXKSVfsb7Y4QWHjITPPZ0nQp87fBeJ!Iv6v^6fvi2WN@bYpJ4';
const md5Key = Buffer.alloc(16, getMd5(decodeKey));
const md5Iv = Buffer.alloc(16, getMd5(decodeIv));
const decipher = crypto.createDecipheriv(algo, md5Key, md5Iv);
let res = decipher.update(encryptedText, 'base64', 'utf-8');
res += decipher.final('utf-8');
return res;
}
function getTranslateResult(text) {
const o = JSON.parse(text);
return o.translateResult.reduce((res, item) => {
return res + item.tgt;
}, '');
}
const encryptedText = '<接口的返回值>';
const text = decryptData(encryptedText);
const translateResult = getTranslateResult(text);
console.log(translateResult);
```
题外话:为什么某道翻译会有一句`console.log("解密后的接口数据", a)`,并且`a`比我们得到的解密结果多了几个参数?因为这个对象在接口数据解密后被追加了一些属性。
## webtranslate接口的sign参数生成过程分析
首先还是考虑搜关键词:`sign`不仅要在前端生成,还要在后端校验,所以一般来说,`sign`要求能通过这个接口的其他参数生成,因此我们挑选了`mysticTime`。我们搜到一个名为`8139`的模块:
```js
const l = "fanyideskweb"
, d = "webfanyi"
, u = "client,mysticTime,product"
, m = "1.0.0"
, p = "web"
, b = "fanyi.web";
function f(e) {
return c.a.createHash("md5").update(e).digest()
}
function g(e) {
return c.a.createHash("md5").update(e.toString()).digest("hex")
}
function v(e, t) {
return g(`client=${l}&mysticTime=${e}&product=${d}&key=${t}`)
}
function h(e) {
const t = (new Date).getTime();
return {
sign: v(t, e),
client: l,
product: d,
appVersion: m,
vendor: p,
pointParam: u,
mysticTime: t,
keyfrom: b
}
}
```
根据上文分析结果,`c.a`就是`node.js`自带的`crypto`模块。于是只剩一个疑点了:`h`函数的`e`参数。往下可以翻到`h`的调用方式:
```js
const A = (e,t)=>Object(n["a"])("https://dict.moudao.com/webtranslate/key", {
...e,
...h(t)
})
, O = (e,t)=>Object(n["d"])("https://dict.moudao.com/webtranslate", {
...e,
...h(t)
}, {
headers: {
"Content-Type": "application/x-www-form-urlencoded"
}
})
```
我们只需要确定箭头函数第二个参数`t`。对应的模块导出代码:
```js
t["a"] = {
getTextTranslateSecretKey: A,
getTextTranslateResult: O,
getTextTranslateKeyword: y,
decodeData: T,
feedback: x,
getAigcEntrance: w,
getAigcStyle: k,
getAigcTran: C,
fanyiFeedback: E,
cancelLastGpt: j
}
```
以`O`为例,我们搜索`getTextTranslateResult`,找到:
```js
const o = an["a"].state.text.secretKey;
// ...
nn["a"].getTextTranslateResult({
i: e.data.keyword,
from: e.data.from,
to: e.data.to,
...n,
dictResult: !0,
keyid: "webfanyi"
}, o)
```
看到熟悉的`an["a"].state.text`,所以答案就在上文分析提到的`vuex`模块。
```js
const i = {
secretKey: ""
}
```
难道`secretKey`就是空串?我们写代码,在node中运行,发现是错的。随后我在上述`vuex`模块中发现了修改`secretKey`的函数`UPDATE_SECRET_KEY`。我们搜一下:
```js
fetchTextTranslateSecretKey: ({commit: e},t)=>{
const o = "webfanyi-key-getter"
, a = "asdjnjfenknafdfsdfsd";
n["a"].getTextTranslateSecretKey({
keyid: o
}, a).then(t=>{
0 === t.code && t.data.secretKey && e("UPDATE_SECRET_KEY", t.data.secretKey)
}
).catch(e=>{}
)
}
```
发现`8139`模块出现过`getTextTranslateSecretKey`这个关键词,所以我们需要从`https://dict.moudao.com/webtranslate/key`这个接口中拿到`secretKey`。
综上,我们可以写出代码:
```js
const crypto = require('crypto');
// mysticTime = (new Date).getTime();
function getSign(mysticTime, secretKey) {
const l = "fanyideskweb"
, d = "webfanyi"
, u = "client,mysticTime,product"
, m = "1.0.0"
, p = "web"
, b = "fanyi.web";
function g(e) {
return crypto.createHash("md5").update(e.toString()).digest("hex")
}
return g(`client=${l}&mysticTime=${mysticTime}&product=${d}&key=${secretKey}`);
}
console.log(getSign(1680688241299, 'fsdsogkndfokasodnaso') === '7c5dbf08b8e0ecdf6895f623f335a320');
```
结束了吗?还没!接下来看`getTextTranslateSecretKey`接口的请求参数,发现也有一个`sign`参数,我们还需要继续分析。我们很容易搜到以下代码:
```js
fetchTextTranslateSecretKey: ({commit: e},t)=>{
const o = "webfanyi-key-getter"
, a = "asdjnjfenknafdfsdfsd";
n["a"].getTextTranslateSecretKey({
keyid: o
}, a).then(t=>{
0 === t.code && t.data.secretKey && e("UPDATE_SECRET_KEY", t.data.secretKey)
}
).catch(e=>{}
)
}
```
其密钥就是`'asdjnjfenknafdfsdfsd'`。至此,分析也就结束了。
```js
const crypto = require('crypto');
// mysticTime = (new Date).getTime();
function getSign(mysticTime, secretKey) {
const l = "fanyideskweb"
, d = "webfanyi"
, u = "client,mysticTime,product"
, m = "1.0.0"
, p = "web"
, b = "fanyi.web";
function g(e) {
return crypto.createHash("md5").update(e.toString()).digest("hex")
}
return g(`client=${l}&mysticTime=${mysticTime}&product=${d}&key=${secretKey}`);
}
// getTextTranslateResult
console.log(getSign(1680688241299, 'fsdsogkndfokasodnaso') === '7c5dbf08b8e0ecdf6895f623f335a320');
// getTextTranslateSecretKey
console.log(getSign(1680688236068, 'asdjnjfenknafdfsdfsd') === 'f01914c8a1e374094258ed80b94d9abb')
```
## 梳理一下+cookie反爬补充+python代码~
相比于去年,难度提升太多了!
由`'asdjnjfenknafdfsdfsd'`获取`getTextTranslateSecretKey`所需的`sign`参数,请求获取`secretKey`→由`secretKey`获取`getTextTranslateResult`所需的`sign`参数,请求获取翻译结果→`aes-128-cbc`获取解密后的翻译结果数据。
下面根据评论区 (https://www.52pojie.cn/home.php?mod=space&uid=2005421) 佬的代码补充python代码部分。因为我没有跟完全流程,所以还是错过了一些风景。某道有做**cookie反爬**,url:https://rlogs.youdao.com/rlog.php ,可以看到响应头有set-cookie:
```
Set-Cookie: OUTFOX_SEARCH_USER_ID=-1072836051@183.240.8.75; Domain=youdao.com; Expires=Sat, 29-Mar-2053 14:05:37 GMT; Path=/
```
不过,他的代码是上个月的,请求上述url没有带get参数也能过。现在请求上述url必须要带时间`_ntms`参数,才能得到`set-cookie`响应头。具体可参考我提供的python代码。
我的python代码是从他的代码修改得来:
1. 增强可测试性。
2. 没有写死cookie。
```python
import requests
import hashlib
import base64
import time
import json
from Crypto.Cipher import AES
from Crypto.Hash import MD5
class YouDao:
def __init__(self):
self.l = "fanyideskweb"
self.d = "webfanyi"
self.AES_KEY = b"ydsecret://query/key/B*RGygVywfNBwpmBaZg*WT7SIOUP2T0C9WHMZN39j^DAdaZhAnxvGcCY6VYFwnHl"
self.AES_IV = b"ydsecret://query/iv/C@lZe2YzHtZ2CYgaXKSVfsb7Y4QWHjITPPZ0nQp87fBeJ!Iv6v^6fvi2WN@bYpJ4"
self.const_secret_key = "asdjnjfenknafdfsdfsd"
self.sessionObj = requests.session()
def encrypt_md5(self, str):
md = hashlib.md5(str.encode('utf-8')).hexdigest()
return md
def set_cookie_request(self):
t = int(time.time() * 1000)
# var g = new Date(b.lastModified); E = g.getTime() / 1e3; // b =
# document
last_modified = (t - 14 * 86400000) // 1000
url = f"https://rlogs.youdao.com/rlog.php?_npid=fanyiweb&_ncat=pageview&_ncoo=775934043.5983925&_nssn=NULL&_nver=1.2.0&_ntms={t}&_nref=http%3A%2F%2Ffanyi.youdao.com%2F&_nurl=https%3A%2F%2Ffanyi.youdao.com%2Findex.html%23%2F&_nres=1440x900&_nlmf={last_modified}&_njve=0&_nchr=utf-8&_nfrg=%2F&/=NULL&screen=1440*900"
resp = self.sessionObj.get(url)
print('https://rlogs.youdao.com/rlog.php headers', resp.headers)# dbg
print('self.sessionObj.cookies',
self.sessionObj.cookies.items())# dbg
def prepare_secret_key_params(self):
t = str(int(time.time() * 1000))
sign = self.get_sign(t, self.const_secret_key)
params = {
"sign": sign,
"client": "fanyideskweb",
"product": "webfanyi",
"appVersion": "1.0.0",
"vendor": "web",
"pointParam": "client,mysticTime,product",
"mysticTime": t,
"keyfrom": "fanyi.web",
"keyid": "webfanyi-key-getter",
}
return params
def get_secret_key(self):
params = self.prepare_secret_key_params()
secret_key_url = "https://dict.youdao.com/webtranslate/key"
res = self.sessionObj.get(secret_key_url, params=params).json()
secret_key = res["data"]["secretKey"]
print(secret_key)# dbg
return secret_key
def get_sign(self, t, key):
sign = self.encrypt_md5(
f"client={self.l}&mysticTime={t}&product={self.d}&key={key}")
return sign
def youdao_decrypt(self, src: str) -> dict:
key = self.AES_KEY
iv = self.AES_IV
cryptor = AES.new(
MD5.new(key).digest()[:16],
AES.MODE_CBC,
MD5.new(iv).digest()[:16]
)
res = cryptor.decrypt(base64.urlsafe_b64decode(src))
txt = res.decode("utf-8")
return json.loads(txt[:txt.rindex("}") + 1])
def get_actual_translate_result(self, resultJSON):
result = ''
for arr in resultJSON["translateResult"]:
for item in arr:
result += item["tgt"]
return result
def prepare_translate_data(self, msg, secret_key):
headers = {
"Accept": "application/json, text/plain, */*",
"Accept-Language": "zh-CN,zh;q=0.9",
"Connection": "keep-alive",
"Content-Type": "application/x-www-form-urlencoded",
"Origin": "https://fanyi.youdao.com",
"Referer": "https://fanyi.youdao.com/",
"Sec-Fetch-Dest": "empty",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Site": "same-site",
"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",
"sec-ch-ua": "\"Chromium\";v=\"110\", \"Not A(Brand\";v=\"24\", \"Google Chrome\";v=\"110\"",
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": "\"macOS\""}
t = str(int(time.time() * 1000))
sign = self.get_sign(t, secret_key)
data = {
"i": f"{msg}",
"from": "auto",
"to": "",
"dictResult": "true",
"keyid": "webfanyi",
"sign": f"{sign}",
"client": "fanyideskweb",
"product": "webfanyi",
"appVersion": "1.0.0",
"vendor": "web",
"pointParam": "client,mysticTime,product",
"mysticTime": f"{t}",
"keyfrom": "fanyi.web"
}
return headers, data
def translate(self, msg):
self.set_cookie_request()
secret_key = self.get_secret_key()
headers, data = self.prepare_translate_data(msg, secret_key)
translate_url = "https://dict.youdao.com/webtranslate"
response = self.sessionObj.post(
translate_url, headers=headers, data=data).text
print(response)# dbg
resultJSON = self.youdao_decrypt(response)
print(resultJSON)# dbg
result = self.get_actual_translate_result(resultJSON)
return resultJSON, result
def get_input_text():
fname = 'youdao_in.txt'
with open(fname, 'r', encoding='utf-8') as f:
return f.read()
if __name__ == '__main__':
youDao = YouDao()
msg = get_input_text()
resultJSON, result = youDao.translate(msg)
print(result)
```
## 谋道翻译用到的vuex
我们回过头来看`4360`模块:
```js
4360: function(e, t, o) {
"use strict";
o("13d5");
var n = o("5502");
const a = []
, c = o("c653")
, i = c.keys().reduce((e,t)=>{ // c.keys()取出的是["./domain.js"]数组
const o = t.replace(/^\.\/(.*)\.\w+$/, "$1");
a.push(o);
const n = c(t);
return e = n.default,
e
}
, {});
t["a"] = Object(n["a"])({
modules: i
})
},
```
这相当于
```js
export default new Vuex.Store({
modules: {
domain: {},
language: {},
login: {},
text: {},
}
})
```
而每个模块都是`{ actions, getters, mutations, state }`的结构,以`./text.js`为例:
```js
{
actions: ...,
getters: ...,
mutations: ...,
state: {
"secretKey": "",
"dictResult": {},
"decodeKey": "ydsecret://query/key/B*RGygVywfNBwpmBaZg*WT7SIOUP2T0C9WHMZN39j^DAdaZhAnxvGcCY6VYFwnHl",
"decodeIv": "ydsecret://query/iv/C@lZe2YzHtZ2CYgaXKSVfsb7Y4QWHjITPPZ0nQp87fBeJ!Iv6v^6fvi2WN@bYpJ4",
"allowStroke": false,
"showPjm": false,
"showRomanPronunciation": false,
"showWordsNumber": true
}
}
```
使用时,`an["a"].state.text.decodeKey`相当于
```js
import store from '@/store.js';
store.state.text.decodeKey;
``` 可以提供一个python版本的供你们学习,这里额外提一句,调用接口翻译的时候需要加入cookie,有道有做cookie反爬,但实际不校验cookie有效,所以可以写死
# -*- coding:UTF-8 -*-
# author:sommuni
# contact: test@test.com
# datetime:2023/3/9 20:01
# software: PyCharm
"""
文件说明:
"""
import requests,hashlib,base64,time,json
from Crypto.Cipher import AES
from Crypto.Hash import MD5
class YouDao:
def __init__(self):
self.l = "fanyideskweb"
self.d = "webfanyi"
self.AES_KEY = b"ydsecret://query/key/B*RGygVywfNBwpmBaZg*WT7SIOUP2T0C9WHMZN39j^DAdaZhAnxvGcCY6VYFwnHl"
self.AES_IV = b"ydsecret://query/iv/C@lZe2YzHtZ2CYgaXKSVfsb7Y4QWHjITPPZ0nQp87fBeJ!Iv6v^6fvi2WN@bYpJ4"
self.key = "asdjnjfenknafdfsdfsd"
def encrypt_md5(self,str):
md = hashlib.md5(str.encode('utf-8')).hexdigest()
return md# 加密
def get_secret_key(self):
url = "https://rlogs.youdao.com/rlog.php"
session = requests.session()
session.get(url)
t = str(int(time.time() * 1000))
sign = self.get_sign(t,self.key)
params = {
"sign": sign,
"client": "fanyideskweb",
"product": "webfanyi",
"appVersion": "1.0.0",
"vendor": "web",
"pointParam": "client,mysticTime,product",
"mysticTime": t,
"keyfrom": "fanyi.web",
"keyid": "webfanyi-key-getter",
}
res = session.get("https://dict.youdao.com/webtranslate/key", params=params).json()
secret_key = res["data"]["secretKey"]
# print(secret_key)
return secret_key
def get_sign(self,t,key):
sign = self.encrypt_md5(f"client={self.l}&mysticTime={t}&product={self.d}&key={key}")
return sign
def YouDao_decrypt(self, src: str) -> dict:
key = self.AES_KEY
iv = self.AES_IV
cryptor = AES.new(MD5.new(key).digest()[:16], AES.MODE_CBC, MD5.new(iv).digest()[:16])
res = cryptor.decrypt(base64.urlsafe_b64decode(src))
txt = res.decode("utf-8")
return json.loads(txt[: txt.rindex("}") + 1])
def translate(self,msg):
secret_key = self.get_secret_key()
url = "https://dict.youdao.com/webtranslate"
headers = {
"Accept": "application/json, text/plain, */*",
"Accept-Language": "zh-CN,zh;q=0.9",
"Connection": "keep-alive",
"Content-Type": "application/x-www-form-urlencoded",
'Cookie': 'OUTFOX_SEARCH_USER_ID=-1103603819@10.112.57.88; OUTFOX_SEARCH_USER_ID_NCOO=1058534174.1041499',
"Origin": "https://fanyi.youdao.com",
"Referer": "https://fanyi.youdao.com/",
"Sec-Fetch-Dest": "empty",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Site": "same-site",
"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",
"sec-ch-ua": "\"Chromium\";v=\"110\", \"Not A(Brand\";v=\"24\", \"Google Chrome\";v=\"110\"",
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": "\"macOS\""
}
t = str(int(time.time()*1000))
sign = self.get_sign(t,secret_key)
data = {
"i": f"{msg}",
"from": "auto",
"to": "",
"dictResult": "true",
"keyid": "webfanyi",
"sign": f"{sign}",
"client": "fanyideskweb",
"product": "webfanyi",
"appVersion": "1.0.0",
"vendor": "web",
"pointParam": "client,mysticTime,product",
"mysticTime": f"{t}",
"keyfrom": "fanyi.web"
}
response = requests.post(url, headers=headers,data=data).text
# print(response)
result = self.YouDao_decrypt(response)
return result
if __name__ == '__main__':
YouDao = YouDao()
msg = input(f"请输入需要翻译的单词:")
result = YouDao.translate(msg)
print(result)
可高并发的谷歌翻译接口
```
async function translation(array) {
var splicing = []
if (!(array instanceof Array)) {
array =
}
for (let i = 0; i < array.length; i++) {
splicing.push(
{
"originalText": array,
"translatedText": null,
"detectedLanguage": null,
"status": "translating",
"waitTranlate": {}
}
)
}
return await makeRequest("auto", "zh-CN", splicing)//无并发限制端口
async function makeRequest(sourceLanguage, targetLanguage, requests) {
return await new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open(
"POST",
"https://translate.googleapis.com/translate_a/t?anno=3&client=te&v=1.0&format=html" + GetExtraParameters(sourceLanguage, targetLanguage, requests)
);
xhr.setRequestHeader(
"Content-Type",
"application/x-www-form-urlencoded"
);
xhr.responseType = "json";
xhr.onload = (event) => {
resolve(xhr.response);
};
xhr.onerror = xhr.onabort = xhr.ontimeout = (event) => { console.error(event); reject(); };
xhr.send(getRequestBody(sourceLanguage, targetLanguage, requests));
});
function getRequestBody(sourceLanguage, targetLanguage, requests) {
return requests
.map((info) => `&q=${encodeURIComponent(info.originalText)}`)
.join("");
}
function GetExtraParameters(sourceLanguage, targetLanguage, requests) {
return `&sl=${sourceLanguage}&tl=${targetLanguage}&tk=${calcHash(requests.map((info) => info.originalText).join(""))}`
function calcHash(query) {
const windowTkk = "448487.932609646";
const tkkSplited = windowTkk.split(".");
const tkkIndex = Number(tkkSplited) || 0;
const tkkKey = Number(tkkSplited) || 0;
const bytesArray = transformQuery(query);
let encondingRound = tkkIndex;
for (const item of bytesArray) {
encondingRound += item;
encondingRound = shiftLeftOrRightThenSumOrXor(
encondingRound,
"+-a^+6"
);
}
encondingRound = shiftLeftOrRightThenSumOrXor(
encondingRound,
"+-3^+b+-f"
);
encondingRound ^= tkkKey;
if (encondingRound <= 0) {
encondingRound = (encondingRound & 2147483647) + 2147483648;
}
const normalizedResult = encondingRound % 1000000;
return normalizedResult.toString() + "." + (normalizedResult ^ tkkIndex);
function transformQuery(query) {
/** @type {Array<number>} */
const bytesArray = [];
let idx = 0;
for (let i = 0; i < query.length; i++) {
let charCode = query.charCodeAt(i);
if (128 > charCode) {
bytesArray = charCode;
} else {
if (2048 > charCode) {
bytesArray = (charCode >> 6) | 192;
} else {
if (
55296 == (charCode & 64512) &&
i + 1 < query.length &&
56320 == (query.charCodeAt(i + 1) & 64512)
) {
charCode =
65536 +
((charCode & 1023) << 10) +
(query.charCodeAt(++i) & 1023);
bytesArray = (charCode >> 18) | 240;
bytesArray = ((charCode >> 12) & 63) | 128;
} else {
bytesArray = (charCode >> 12) | 224;
}
bytesArray = ((charCode >> 6) & 63) | 128;
}
bytesArray = (charCode & 63) | 128;
}
}
return bytesArray;
}
function shiftLeftOrRightThenSumOrXor(num, optString) {
for (let i = 0; i < optString.length - 2; i += 3) {
/** @type {string|number} */
let acc = optString.charAt(i + 2);
if ("a" <= acc) {
acc = acc.charCodeAt(0) - 87;
} else {
acc = Number(acc);
}
if (optString.charAt(i + 1) == "+") {
acc = num >>> acc;
} else {
acc = num << acc;
}
if (optString.charAt(i) == "+") {
num += acc & 4294967295;
} else {
num ^= acc;
}
}
return num;
}
}
}
}
}
console.log(await translation(['hi', 'omg', 'yes']));
``` {:301_997:}
感谢分享 我先插个眼,等以后再{:1_908:}来好好学习! 学习了,谢谢分享 本来想打开moudao的页面对照研究一下,可是为什么打不开呢? cn2jp 发表于 2023-4-5 20:46
本来想打开moudao的页面对照研究一下,可是为什么打不开呢?
当然不能直接点开{:1_925:} 学习了,感谢分享! 学习了,感谢分享 感谢分享