/* eslint-disable no-multi-spaces */
// ==UserScript==
// @name Baidu Translate
// @name:zh-CN 百度翻译
// @name:en Baidu Translate
// @namespace PY-DNG APIS
// @version 0.1
// @description Baidu translate api support for userscripts. No need for baidu's token.
// @description:zh-CN 用户脚本版的百度翻译API支持,无需申请token,@require即用
// @description:en Baidu translate api support for userscripts. No need for baidu's token.
// @author PY-DNG
// @license MIT
// @icon data:image/vnd.microsoft.icon;base64,AAABAAEAICAAAAEAIACoEAAAFgAAACgAAAAgAAAAQAAAAAEAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPzbt074plC+9Y0e9PWIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1jR70+KZQvvzbuEwAAAAAAAAAAAAAAAD6wYWE9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//rBhYQAAAAA/Nu4TPWIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//zbuEz4plC+9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/+KZQvvaOIPL1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1jh/y9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/2kyr//vDh//vRpP/3okf/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//aRJv////////////7w4f/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/3oET//u/g//7v4P/2lCz/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9o8i/////////////vDh//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//ekS/////////////aWMP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/2jyL////////////96tb/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/96RL////////////9pYw//WIFP/1iBT/96NJ//ecO//2mDP/9pIn//WNHv////////////3q1v/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/3pEv////////////2ljD/9YgU//m5df////////////////////////////////////////v3//3n0P/948j//N/A//zdvf/817H//Nex//ixZP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//ekS/////////////aWMP/1iBT/9o8i//aYM//3n0L/96BD//elTf/4qFT////////////+8ub/+bt4//rAhP/7zJr//Ny5//3r2P/94sb/9Y0d//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/96RL////////////9pYw//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP////////////3s2f/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/3pEv////////////2ljD/9YgU//WIFP/2kCX/+saO//m9ff/5tWz/+Kxc/////////////vLl//aOIP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//ekS/////////////aWMP/1iBT/9YgU//rGjv/+7+D//vfu//748P////////////////////////////////////////////rHkP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/96RL////////////9pYw//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFf////////////727f/3okf/+KtZ//mzav/5tGv/9YgV//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/3pEv////////////2ljD/9po3//rGjv/4qlb/9Y4f//WIFP/1iBT/9YgU/////////////vjx//WIFP/1iBT/9pg0//m3cP/6x5D/9Y0d//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//ekS/////////////aWMP/1iBT/+KdS//3kyv///Pn//N6+//m1bf/2jyP//eXM//rEiv/2mjf/+bl0//3o0v/////////////////82rb/9Y0d//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/817H//Nex//zXsf/817H//OHD////////////9pYw//WIFP/1iBT/9YgU//irWv/969j///////758//6xYz/+bJn//7x5P/////////////////////////////////+7dz/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU///////////////////////////////////////2ljD/9YgU//WIFP/1iBT/9YgU//WKGP/5vHv///r1////////////////////////+fT//N28//rCh//4rFz/9po3//WLGv/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9pgz//aYM//2mDP/9pgz//aYM//2mDP/9pgz//WKGP/1iBT/9YgU//WIFP/1iBT/9YgU//WJF//83r7//////////////////eLF//WMHP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/+8uY///////+7t7//efP////////////+9Km//WJF//1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//irWv//////+9Wr//aOIP/2jiD//eXM////////////+86e//WIFf/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//rDiP/+9+///vfv//m2bv/1iBT/9YgU//WIFP/1jh///vLm//rJlf/1iBX/9YgU//WIFP/2kyn//vPn////////////+sWN//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/+seQ////////////+bl1//WIFP/2kCX//vLm//758////fv//efQ//zhxP/82bP/+9Gk//rJlP/82bT///////////////7//eLF//aXMv/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/6x5D////////////5uXX/9YgU//aYM//4sGL/+LBj//m4cv/5vHr/+sOJ//vLmP/70qb//Nm0//zfwP/+9Or////////////6xo//9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WMHP/2kCT/9pAk//WLGv/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WNHv/837//+r+B//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/2jiDy9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9o4g8vimUL71iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/4plC+/Ny5TPWIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//zbuEwAAAAA+sGFhPWIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/6wYWEAAAAAAAAAAAAAAAA/Nu4TPimUL71jh/y9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WIFP/1iBT/9YgU//WOH/L4plC+/Ny5TAAAAAAAAAAA4AAAB4AAAAGAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAABgAAAAeAAAAc=
// @grant GM_xmlhttpRequest
// @connect fanyi.baidu.com
// ==/UserScript==
/* Usage:
** baidu_translate(details): details = {
text: <string> Text you want to translate.
Required argument, no default value.
callback: <function> Callback function when translation succeed,
e.g. function(result) {console.log(result);}.
Argument `result` will be <string> translate result.
Required argument, no default value.
[src]: <string> Source language; e.g. 'zh' for Chinese, 'en' for English, 'ja' for Japanese, ....
Default: Auto detect.
[dst]: <string> Destination language,
Default: 'zh'.
[split]: <number> Text spliting max length, just leave blank if you don't know what this means.
What it is: Baidu has limited that up to 5000 letters can be translated each single API
request, so the translate function will split text by about every ${split} letters, and
translates each of them with a single API request then concatenate the translate result
together again. Don't worry if any sentence to be splited, actually the translate
function splits text with '\n' first, then concatenate the '\n'-splited strings as long
as possible while keeping its length <= ${split}.
Default: 5000
[onerror]: <function> Callback function when translation failed,
e.g. function(reason) {console.log(reason);}.
Argument: `reason` may be anything that causes its failure, just for debugging and do
not use it in production.
Default: function() {}.
[retry]: <number> Times to retry before onerror function being called or an error being thrown,
Default: 3.
}
** Or:
** baidu_translate(text, src, dst, callback, onerror, split, retry)
**
** Overloads:
** baidu_translate(text, src, dst, callback, onerror, split)
** baidu_translate(text, dst, callback, onerror, split)
** baidu_translate(text, callback, onerror, split)
** baidu_translate(text, callback, onerror)
** baidu_translate(text, callback)
*/
let baidu_translate, bdTransReady;
[baidu_translate, bdTransReady] = (function() {
const BDT = new BaiduTranslateAPI();
return [baidu_translate, bdTransReady];
function baidu_translate() {
// Check BDT ready
if (!BDT.gtk || !BDT.token) {
//throw new Error('BaiduTranslateAPI not ready');
return false;
}
// Get argument
let text='', src, dst, callback=function() {}, split=5000, onerror=function() {}, retry=3;
switch (arguments.length) {
case 1: {
const details = arguments[0];
details.hasOwnProperty('text') && (text = details.text);
details.hasOwnProperty('src') && (src = details.src);
details.hasOwnProperty('dst') && (dst = details.dst);
details.hasOwnProperty('callback') && (callback = details.callback);
details.hasOwnProperty('split') && (split = details.split);
break;
}
case 2:
[text, callback] = arguments;
break;
case 3:
[text, callback, onerror] = arguments;
break;
case 4:
[text, callback, onerror, split] = arguments;
break;
case 5:
[text, dst, callback, onerror, split] = arguments;
break;
case 6:
[text, src, dst, callback, onerror, split] = arguments;
break;
case 7:
[text, src, dst, callback, onerror, split, retry] = arguments;
break;
}
// Split lines
split = split ? split : Infinity;
const textarr = text.replaceAll(/[\n\r]+/g, '\n').split('\n');
if (textarr.some((t) => (t.length > split))) {
throw new Error('Some paragraph is longer than given split length (' + split + ')');
}
// Prepare
const AM = new AsyncManager();
const result = [];
AM.onfinish = function() {
callback(result.join(''));
}
// Send requests
let index = 0;
while (textarr.length > 0) {
let t = '';
while (textarr.length > 0 && t.length + textarr[0].length < split) {
t += '\n' + textarr.shift();
}
t = t.substr(1);
AM.add();
BDT.translate({
text: t,
args: [++index],
src: src,
dst: dst,
onerror: onerror,
retry: retry,
callback: function(json, i) {
const temp_result = json.trans_result.data.reduce(function(pre, cur) {
return pre + '\n'.repeat(cur.prefixWrap+1) + cur.dst;
}, '');
result[i] = temp_result;
AM.finish();
}
});
}
AM.finishEvent = true;
return true;
}
function bdTransReady(callback) {
BDT.oninit = callback;
}
function BaiduTranslateAPI() {
const BT = this;
BT.gtk = BT.token = null;
BT.inited = false;
let oninit=function() {};
Object.defineProperty(BT, 'oninit', {
enumerable: true,
set: (f) => (typeof f === 'function' && (oninit = f) && BT.inited && oninit()),
get: () => (oninit)
});
init();
BT.calcSign = calcSign;
BT.translate = translate;
async function translate(details) {
const callback = details.callback;
const text = details.text;
const src = details.src || await langDetect(text);
const dst = details.dst || 'zh';
const onerror = details.onerror || function() {};
const retry = details.retry || 0;
const args = details.args || [];
GM_xmlhttpRequest({
method: 'POST',
url: 'https://fanyi.baidu.com/v2transapi',
headers: {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36',
'Content-Type': 'application/x-www-form-urlencoded'
},
data: toQueryString({
'from': src,
'to': dst,
'query': text,
'simple_means_flag': 3,
'sign': calcSign(text),
'token': BT.token
}),
onload: function(response) {
response.status !== 200 && Err(response);
const json = JSON.parse(response.responseText);
json.error && Err(json);
callback.apply(null, [json].concat(args));
function Err(e) {
!_onerror(e) && console.log('Retrying...\nleft: ' + (retry-1).toString());
console.log(e);
throw new Error('Server returned with an error (logged above)');
}
},
onerror: _onerror,
ontimeout: _onerror,
onabort: _onerror,
});
// Returns true for error, false for retry
function _onerror(e) {
console.log('sign = ' + calcSign(text));
if (retry > 0) {
setTimeout(retryRequest.bind(null, e), 500);
return false;
}
onerror(e);
return true;
}
function retryRequest(e) {
translate({
callback: callback,
text: text,
src: src,
dst: dst,
onerror: onerror,
retry: retry - 1,
args: args
});
}
}
function langDetect(text) {
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: 'POST',
url: 'https://fanyi.baidu.com/langdetect',
headers: {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36',
'Content-Type': 'application/x-www-form-urlencoded'
},
data: toQueryString({'query': text}),
onload: function(response) {
const json = JSON.parse(response.responseText);
resolve(json.lan);
},
onerror: function(e) {reject(e)},
ontimeout: function(e) {reject(e)},
onabort: function(e) {reject(e)},
});
});
}
// Calc sign based on query-text and gtk
function calcSign(query) {
function a(r, o) {
for (var t = 0; t < o.length - 2; t += 3) {
var a = o.charAt(t + 2);
a = a >= "a" ? a.charCodeAt(0) - 87 : Number(a),
a = "+" === o.charAt(t + 1) ? r >>> a: r << a,
r = "+" === o.charAt(t) ? r + a & 4294967295 : r ^ a
}
return r
}
var C = null;
var token = function(r, _gtk) {
var o = r.length;
o > 30 && (r = "" + r.substr(0, 10) + r.substr(Math.floor(o / 2) - 5, 10) + r.substring(r.length, r.length - 10));
var t = void 0,
t = null !== C ? C: (C = _gtk || "") || "";
for (var e = t.split("."), h = Number(e[0]) || 0, i = Number(e[1]) || 0, d = [], f = 0, g = 0; g < r.length; g++) {
var m = r.charCodeAt(g);
128 > m ? d[f++] = m: (2048 > m ? d[f++] = m >> 6 | 192 : (55296 === (64512 & m) && g + 1 < r.length && 56320 === (64512 & r.charCodeAt(g + 1)) ? (m = 65536 + ((1023 & m) << 10) + (1023 & r.charCodeAt(++g)), d[f++] = m >> 18 | 240, d[f++] = m >> 12 & 63 | 128) : d[f++] = m >> 12 | 224, d[f++] = m >> 6 & 63 | 128), d[f++] = 63 & m | 128)
}
for (var S = h,
u = "+-a^+6",
l = "+-3^+b+-f",
s = 0; s < d.length; s++) S += d[s],
S = a(S, u);
return S = a(S, l),
S ^= i,
0 > S && (S = (2147483647 & S) + 2147483648),
S %= 1e6,
S.toString() + "." + (S ^ h)
}
return token(query, BT.gtk);
}
function toQueryString(obj) {
const USP = new URLSearchParams();
for (const [key, value] of Object.entries(obj)) {
USP.append(key, value);
}
return USP.toString();
}
// Request token and gtk
function init() {
// Request twice, make sure gtk is latest,
// or may get 998 error while requesting translate API
requestIndex(requestIndex.bind(null, function(html) {
BT.token = html.match(/token: ["'](.*?)["'],/)[1];
BT.gtk = html.match(/window.gtk = ["'](.*?)["'];/)[1];
BT.inited = true;
oninit();
}));
function requestIndex(callback) {
const url = 'https://fanyi.baidu.com';
GM_xmlhttpRequest({
method: 'GET',
headers: {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36'
},
url: url,
onload: function(response) {
callback(response.responseText);
}
});
}
}
}
function AsyncManager() {
const AM = this;
// Ongoing xhr count
this.taskCount = 0;
// Whether generate finish events
let finishEvent = false;
Object.defineProperty(this, 'finishEvent', {
configurable: true,
enumerable: true,
get: () => (finishEvent),
set: (b) => {
finishEvent = b;
b && AM.taskCount === 0 && AM.onfinish && AM.onfinish();
}
});
// Add one task
this.add = () => (++AM.taskCount);
// Finish one task
this.finish = () => ((--AM.taskCount === 0 && AM.finishEvent && AM.onfinish && AM.onfinish(), AM.taskCount));
}
}) ();