某道翻译请求关键参数和返回数据解密过程分析-20230405

查看 152|回复 10
作者:hans7   
引言
今天本英语渣用了下谋道翻译,惊讶地发现谋道返回的接口数据是加密的。想着我已经很久没碰逆向了,那就来研究一下吧,顺便水篇入门文。PS:整个过程没有用到动态调试。
作者:hans774882968以及hans774882968以及hans774882968
本文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接口返回加密数据的解密过程
[ol]
  • 谋道翻译webtranslate接口:https://dict.moudao.com/webtranslate
  • 谋道翻译webtranslate获取secretKey接口:https://dict.moudao.com/webtranslate/key
    [/ol]
    抓包,找到附近代码:
    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的位置:
    首先看到
    var nn = o("8139")
      , an = o("4360")
      , sn = o("bc3a");
    打开Chrome Devtools的Search Tab,搜索8139,很快找到(https://fanyi.moudao.com/js/app.e4e9fbd0.js)
    8139: function(e, t, o) {
      "use strict";
      // ...
    },
    8393: //...
    于是可知decodeData内容:
    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直接搜到的。
    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这个模块做的事:
    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[o] = n.default,
            e
        }
        , {});
        t["a"] = Object(n["a"])({
            modules: i
        })
    },
    i变量可以猜测是导出的模块,c = o("c653")似乎在做模块整合。
        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[e]
            }
            a.keys = function() {
                return Object.keys(n)
            }
            ,
            a.resolve = c,
            e.exports = a,
            a.id = "c653"
        },
    看到./text.js,结合an["a"].state.text.decodeKey可以猜测1a68就是关键模块。
        "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参数生成过程分析》还会用到。
    至此,decodeData3个参数都知道了,分析下其内容:
    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
    }
    [ol]
  • e是什么?它出现在chunk-vendors里,根据webpack常识,chunk-vendors一般就是标准库。注释里有一句The buffer module from node.js, for the browser.,所以e就是node.js Buffer的polyfill。
  • c.a是什么?createDecipheriv也出现在chunk-vendors,所以也属于标准库,网上搜一下createDecipheriv可知c.a是node.js自带的crypto模块。
  • f就是同一个模块定义的函数:
    [/ol]
    function f(e) {
        return c.a.createHash("md5").update(e).digest()
    }
    至此,我们已经可以写出解密代码:
    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[0].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的模块:
    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的调用方式:
    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。对应的模块导出代码:
    t["a"] = {
        getTextTranslateSecretKey: A,
        getTextTranslateResult: O,
        getTextTranslateKeyword: y,
        decodeData: T,
        feedback: x,
        getAigcEntrance: w,
        getAigcStyle: k,
        getAigcTran: C,
        fanyiFeedback: E,
        cancelLastGpt: j
    }
    以O为例,我们搜索getTextTranslateResult,找到:
    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模块。
    const i = {
        secretKey: ""
    }
    难道secretKey就是空串?我们写代码,在node中运行,发现是错的。随后我在上述vuex模块中发现了修改secretKey的函数UPDATE_SECRET_KEY。我们搜一下:
    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。
    综上,我们可以写出代码:
    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参数,我们还需要继续分析。我们很容易搜到以下代码:
    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'。至此,分析也就结束了。
    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获取解密后的翻译结果数据。
    下面根据评论区 Sommuni 佬的代码补充python代码部分。因为我没有跟完全流程,所以还是错过了一些风景。某道有做cookie反爬,url:https://rlogs.youdao.com/rlog.php ,可以看到响应头有set-cookie:
    Set-Cookie: [email protected]; Domain=youdao.com; Expires=Sat, 29-Mar-2053 14:05:37 GMT; Path=/
    不过,他的代码是上个月的,请求上述url没有带get参数也能过。现在请求上述url必须要带时间_ntms参数,才能得到set-cookie响应头。具体可参考我提供的python代码。
    我的python代码是从他的代码修改得来:
    [ol]
  • 增强可测试性。
  • 没有写死cookie。
    [/ol]
    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模块:
    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[o] = n.default,
            e
        }
        , {});
        t["a"] = Object(n["a"])({
            modules: i
        })
    },
    这相当于
    export default new Vuex.Store({
        modules: {
            domain: {},
            language: {},
            login: {},
            text: {},
        }
    })
    而每个模块都是{ actions, getters, mutations, state }的结构,以./text.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相当于
    import store from '@/store.js';
    store.state.text.decodeKey;

    模块, 接口

  • Sommuni   

    可以提供一个python版本的供你们学习,这里额外提一句,调用接口翻译的时候需要加入cookie,有道有做cookie反爬,但实际不校验cookie有效,所以可以写死
    [Python] 纯文本查看 复制代码# -*- coding:UTF-8 -*-
    # author:sommuni
    # contact: [email protected]
    # 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': '[email protected]; 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 = [array]
        }
        for (let i = 0; i  {
                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]) || 0;
                    const tkkKey = Number(tkkSplited[1]) || 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 } */
                        const bytesArray = [];
                        let idx = 0;
                        for (let i = 0; i  charCode) {
                                bytesArray[idx++] = charCode;
                            } else {
                                if (2048 > charCode) {
                                    bytesArray[idx++] = (charCode >> 6) | 192;
                                } else {
                                    if (
                                        55296 == (charCode & 64512) &&
                                        i + 1 > 18) | 240;
                                        bytesArray[idx++] = ((charCode >> 12) & 63) | 128;
                                    } else {
                                        bytesArray[idx++] = (charCode >> 12) | 224;
                                    }
                                    bytesArray[idx++] = ((charCode >> 6) & 63) | 128;
                                }
                                bytesArray[idx++] = (charCode & 63) | 128;
                            }
                        }
                        return bytesArray;
                    }
                    function shiftLeftOrRightThenSumOrXor(num, optString) {
                        for (let i = 0; i >> acc;
                            } else {
                                acc = num
    xouou   

    感谢分享
    sorryzzital   

    我先插个眼,等以后再来好好学习!
    ameiz   

    学习了,谢谢分享
    cn2jp   

    本来想打开moudao的页面对照研究一下,可是为什么打不开呢?
    hans7
    OP
      


    cn2jp 发表于 2023-4-5 20:46
    本来想打开moudao的页面对照研究一下,可是为什么打不开呢?

    当然不能直接点开
    Avicii111   

    学习了,感谢分享!
    hgfu566   

    学习了,感谢分享
    您需要登录后才可以回帖 登录 | 立即注册

    返回顶部