推特翻译机

该脚本用于翻译推特为中文,不会经过中间服务器。

As of 2020-12-29. See the latest version.

  1. // ==UserScript==
  2. // @name 推特翻译机
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.28
  5. // @description 该脚本用于翻译推特为中文,不会经过中间服务器。
  6. // @author HolynnChen
  7. // @match *://*.twitter.com/*
  8. // @match *://*.youtube.com/*
  9. // @match *://*.facebook.com/*
  10. // @match *://*.reddit.com/*
  11. // @match *://*.5ch.net/*
  12. // @match *://*.discord.com/*
  13. // @grant GM_xmlhttpRequest
  14. // @grant GM_setValue
  15. // @grant GM_getValue
  16. // @require https://cdn.bootcdn.net/ajax/libs/crypto-js/4.0.0/crypto-js.min.js
  17. // @require https://cdn.jsdelivr.net/npm/js-base64@2.5.2/base64.min.js
  18. // @run-at document-body
  19. // ==/UserScript==
  20.  
  21. //保护可能被覆盖的方法
  22. const sessionStorage = window.sessionStorage; // 也就discord能干出这种事
  23. //
  24.  
  25. const transdict={'谷歌翻译':translate_gg,'腾讯翻译':translate_tencent,'有道翻译':translate_youdao,'有道翻译mobile':translate_youdao_mobile,'百度翻译':translate_baidu,'彩云小译':translate_caiyun,'必应翻译':translate_biying,'Papago翻译':translate_papago,'爱词霸翻译':translate_icib,'关闭翻译':()=>{}};
  26. const startup={'有道翻译':translate_youdao_startup,'腾讯翻译':translate_tencent_startup,'百度翻译':translate_baidu_startup,'彩云小译':translate_caiyun_startup};
  27. const baseoptions = {
  28. 'enable_pass_lang': {
  29. declare: '不翻译中文',
  30. default_value: false,
  31. change_func: self => {
  32. if (self.checked) sessionStorage.clear()
  33. }
  34. },
  35. 'remove_url': {
  36. declare: '自动过滤url',
  37. default_value: true,
  38. },
  39. 'show_info': {
  40. declare: '显示翻译源',
  41. default_value: true,
  42. },
  43. 'fullscrenn_hidden':{
  44. declare: '全屏时不显示',
  45. default_value: true,
  46. }
  47. };
  48.  
  49. const [enable_pass_lang,remove_url,show_info,fullscrenn_hidden]=Object.keys(baseoptions).map(key=>GM_getValue(key,baseoptions[key].default_value));
  50.  
  51. const globalProcessingSave=[];
  52.  
  53. function initPanel(){
  54. let choice=GM_getValue('translate_choice','谷歌翻译');
  55. let select=document.createElement("select");
  56. select.className='js_translate';
  57. select.style='height:35px;width:100px;background-color:#fff;border-radius:17.5px;text-align-last:center;color:#000000;margin:5px 0';
  58. select.onchange=()=>{
  59. GM_setValue('translate_choice',select.value);
  60. title.innerText="控制面板(请刷新以应用)"
  61. };
  62. for(let i in transdict)select.innerHTML+='<option value="'+i+'">'+i+'</option>';
  63. //
  64. let enable_details = document.createElement('details');
  65. enable_details.innerHTML+="<summary>启用规则</summary>"
  66. for(let i in rules){
  67. let temp=document.createElement('input');
  68. temp.type='checkbox';
  69. temp.name=i;
  70. if(GM_getValue("enable_rule:"+temp.name,true))temp.setAttribute('checked',true)
  71. enable_details.appendChild(temp);
  72. enable_details.innerHTML+="<span>"+rules[i].name+"</span><br>";
  73. }
  74. let mask=document.createElement('div'),dialog=document.createElement("div"),js_dialog=document.createElement("div"),title=document.createElement('p');
  75. //
  76. window.top.document.body.appendChild(mask);
  77. dialog.appendChild(js_dialog);
  78. mask.appendChild(dialog);
  79. js_dialog.appendChild(title)
  80. js_dialog.appendChild(document.createElement('p').appendChild(select));
  81. js_dialog.appendChild(document.createElement('p').appendChild(enable_details));
  82. //
  83. mask.style="display: none;position: fixed;height: 100vh;width: 100vw;z-index: 99999;top: 0;left: 0;overflow: hidden;background-color: rgba(0,0,0,0.4);justify-content: center;align-items: center;"
  84. mask.addEventListener('click',event=>{if(event.target===mask)mask.style.display='none'});
  85. dialog.style='padding:0;border-radius:10px;background-color: #fff;box-shadow: 0 0 5px 4px rgba(0,0,0,0.3);';
  86. js_dialog.style="min-height:10vh;min-width:10vw;display:flex;flex-direction:column;align-items:center;padding:10px;border-radius:4px;color:#000";
  87. title.style='margin:5px 0;font-size:20px;';
  88. title.innerText="控制面板";
  89. for(let i in baseoptions){
  90. let temp=document.createElement('input'),temp_p=document.createElement('p');
  91. js_dialog.appendChild(temp_p);
  92. temp_p.appendChild(temp);
  93. temp.type='checkbox';
  94. temp.name=i;
  95. temp_p.style="display:flex;align-items: center;margin:5px 0"
  96. temp_p.innerHTML+=baseoptions[i].declare;
  97. }
  98. for(let i of js_dialog.querySelectorAll('input')){
  99. if(i.name&&baseoptions[i.name]){
  100. i.onclick=_=>{title.innerText="控制面板(请刷新以应用)";GM_setValue(i.name,i.checked);if(baseoptions[i.name].change_func)baseoptions[i.name].change_func(i)}
  101. i.checked=GM_getValue(i.name,baseoptions[i.name].default_value)
  102. }
  103. };
  104. for(let i of enable_details.querySelectorAll('input'))i.onclick=_=>{title.innerText="控制面板(请刷新以应用)";GM_setValue('enable_rule:'+i.name,i.checked)}
  105. let open=document.createElement('div');
  106. open.style=`z-index:9999;height:35px;width:35px;background-color:#fff;position:fixed;border-radius:17.5px;right:${GM_getValue('position_right','9px')};top:${GM_getValue('position_top','9px')};text-align-last:center;color:#000000;display:flex;align-items:center;justify-content:center;cursor: pointer;font-size:15px;user-select:none`;
  107. open.innerHTML="译";
  108. open.onclick=()=>{mask.style.display='flex'};
  109. open.draggable=true;
  110. open.addEventListener("dragstart",function(ev){this.tempNode=document.createElement('div');this.tempNode.style="width:1px;height:1px;opacity:0";document.body.appendChild(this.tempNode);ev.dataTransfer.setDragImage(this.tempNode,0,0);this.oldX=ev.offsetX-Number(this.style.width.replace('px',''));this.oldY=ev.offsetY});
  111. open.addEventListener("drag",function(ev){if(!ev.x&&!ev.y)return;this.style.right=(window.innerWidth-ev.x+this.oldX)+"px";this.style.top=(ev.y-this.oldY)+"px"});
  112. open.addEventListener("dragend",function(ev){GM_setValue("position_right",this.style.right);GM_setValue("position_top",this.style.top);document.body.removeChild(this.tempNode)});
  113. open.addEventListener("touchstart", ev=>{ev.preventDefault();ev=ev.touches[0];open._tempTouch={};const base=open.getClientRects()[0];open._tempTouch.oldX=base.x+base.width-ev.clientX;open._tempTouch.oldY=base.y-ev.clientY});
  114. open.addEventListener("touchmove",ev=>{ev=ev.touches[0];open.style.right=(window.innerWidth-open._tempTouch.oldX-ev.clientX)+'px';open.style.top=(ev.clientY+open._tempTouch.oldY)+'px';open._tempIsMove=true});
  115. open.addEventListener("touchend",()=>{GM_setValue("position_right",open.style.right);GM_setValue("position_top",open.style.top);if(!open._tempIsMove){mask.style.display='flex'};open._tempIsMove=false})
  116. window.top.document.body.appendChild(open);
  117. window.top.document.querySelector('.js_translate option[value='+choice+']').selected=true;
  118. if(fullscrenn_hidden)window.top.document.addEventListener('fullscreenchange',()=>{open.style.display=window.top.document.fullscreenElement?"none":"flex"});
  119. }
  120.  
  121. const rules={
  122. 'tweetdeck':{
  123. name:'tweetdeck',
  124. matcher:/https:\/\/tweetdeck.twitter.com/,
  125. selector:baseSelector('.js-quoted-tweet-text:not([data-translate]),.js-tweet-text:not([data-translate])'),
  126. textGetter:baseTextGetter,
  127. textSetter:baseTextSetter
  128. },
  129. 'twitter':{
  130. name:'推特通用',
  131. matcher:/https:\/\/[a-zA-Z.]*?twitter\.com/,
  132. selector:()=>{
  133. const key=Object.keys(document.querySelector('#react-root>div')||{}).find(item=>item.match('^__reactEventHandlers'));
  134. if(!key)return [];
  135. return baseSelector('article div[dir="auto"]:not([data-translate])')().map(item=>item.parentNode)
  136. .map(item=>{
  137. const obj=item[key].children;
  138. if(Array.isArray(obj)){
  139. let index=obj.filter(inner=>inner).findIndex(inner=>inner&&inner.props&&inner.props.lang);
  140. if (index>-1)return item.children[index];
  141. }
  142. if(Object.prototype.toString.call(obj)==='[object Object]' && obj.props && obj.props.lang){
  143. return item.firstElementChild
  144. }
  145. return null;
  146. }).filter(item=>item);
  147. },
  148. textGetter:element=>{
  149. let content=element.localName=='p'?element.innerText:[...element.querySelectorAll('span')].filter(node=>node.parentElement===element).map(e=>e.innerText).join('');
  150. if(remove_url)content=url_filter(content);
  151. return content;
  152. },
  153. textSetter:baseTextSetter
  154. },
  155. 'youtube':{
  156. name:'youtube评论区',
  157. matcher:/https:\/\/.*?.youtube.com\/watch\?v=*/,
  158. selector:()=>{
  159. const result=[...document.querySelectorAll('#content>#content-text')].filter(item=>item.childNodes.length==item.__data.text.runs.length);
  160. return result;
  161. },
  162. textGetter:element=>remove_url?url_filter(element.innerText):element.innerText,
  163. textSetter:(element,name,text)=>{
  164. element.updateText_([...element.__data.text.runs,{text:`\n\n${show_info?"-----------"+name+"-----------":""}\n\n`+text}]);
  165. element.parentNode.parentNode.removeAttribute('collapsed');
  166. }
  167. },
  168. 'facebook':{
  169. name:'facebook通用',
  170. matcher:/https:\/\/www.facebook.com\/.+/,
  171. selector:()=>{
  172. [...document.querySelectorAll('.text_exposed_root:not(.text_exposed)')].forEach(item=>{item.className+=" text_exposed"})
  173. const articles = baseSelector('div[data-testid=post_message]:not([data-translate])')();
  174. const comments = baseSelector('.commentable_item *[role=article] *[data-ft]:not(a):not([data-translate])')().filter(item=>!item.querySelector('*[data-ft]')).map(item=>{
  175. if(item.tagName!="DIV")return item;
  176. return item.querySelector('span[dir]')
  177. });
  178. return [...articles,...comments];
  179. },
  180. textGetter:baseTextGetter,
  181. textSetter:baseTextSetter
  182. },
  183. 'reddit':{
  184. name:'reddit评论',
  185. matcher:/https:\/\/www.reddit.com\/.+/,
  186. selector:baseSelector('div[data-test-id=comment]:not([data-translate])'),
  187. textGetter:baseTextGetter,
  188. textSetter:baseTextSetter
  189. },
  190. '5ch':{
  191. name:'5ch评论',
  192. matcher:/http(|s):\/\/.*?.5ch.net\/.+/,
  193. selector:baseSelector('.post>.message:not([data-translate])'),
  194. textGetter:baseTextGetter,
  195. textSetter:baseTextSetter
  196. },
  197. 'discord':{
  198. name:'discord聊天',
  199. matcher:/https:\/\/discord.com\/.+/,
  200. selector:baseSelector('div[class*=messageContent]:not([data-translate])'),
  201. textGetter:baseTextGetter,
  202. textSetter:baseTextSetter
  203. }
  204. };
  205.  
  206.  
  207. (function() {
  208. 'use strict';
  209. const GetActiveRule = ()=>rules[Object.keys(rules).filter(item=>GM_getValue("enable_rule:"+item,true)).find(item=>rules[item].matcher.test(document.location.href))];
  210. let url=document.location.href;
  211. let rule=GetActiveRule();
  212. setInterval(()=>{
  213. if(document.location.href!=url){
  214. url=document.location.href;
  215. const ruleNew=GetActiveRule();
  216. if(ruleNew!=rule){
  217. if(ruleNew!=null){
  218. console.log(`【翻译机】检测到URl变更,改为使用【${ruleNew.name}】规则`)
  219. }else{
  220. console.log("【翻译机】当前无匹配规则")
  221. }
  222. rule=ruleNew;
  223. }
  224. }
  225. },200)
  226. console.log(rule?`【翻译机】使用【${rule.name}】规则`:"【翻译机】当前无匹配规则");
  227. let main=_=>{
  228. if(!rule)return;
  229. const choice=GM_getValue('translate_choice','谷歌翻译');
  230. const temp=[...new Set(rule.selector())];
  231. for(let i=0;i<temp.length;i++){
  232. const now=temp[i];
  233. if(globalProcessingSave.includes(now))continue;
  234. globalProcessingSave.push(now);
  235. const text=rule.textGetter(now);
  236. if(text.length==0)continue;
  237. if(sessionStorage.getItem(choice+'-'+text)){
  238. rule.textSetter(now,choice,sessionStorage.getItem(choice+'-'+text));
  239. removeItem(globalProcessingSave,now)
  240. }else{
  241. pass_lang(text).then(lang=>transdict[choice](text,lang)).then(s=>{
  242. rule.textSetter(now,choice,s);
  243. removeItem(globalProcessingSave,now);
  244. })
  245. }
  246. }
  247. };
  248. PromiseRetryWrap(startup[GM_getValue('translate_choice','谷歌翻译')]).then(()=>{document.js_translater=setInterval(main,20)});
  249. initPanel();
  250. })();
  251.  
  252. //--综合工具区--start
  253.  
  254. function removeItem(arr,item){
  255. const index=arr.indexOf(item);
  256. if(index>-1)arr.splice(index,1);
  257. }
  258.  
  259. function baseSelector(selector){
  260. return ()=>Array.from(document.querySelectorAll(selector),item=>{item.dataset.translate="processed";return item;})
  261. }
  262.  
  263. function baseTextGetter(e){
  264. return remove_url?url_filter(e.innerText):e.innerText;
  265. }
  266.  
  267. function baseTextSetter(e,name,text){//change element text
  268. if(text.length==0)text='翻译异常';
  269. e.innerHTML+=`<span style="white-space:pre-wrap">\n\n${show_info?"-----------"+name+"-----------":""}\n\n`+text+'</span>';
  270. }
  271.  
  272. function url_filter(text){
  273. return text.replace(/(https?|ftp|file):\/\/[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]/g,'');
  274. }
  275.  
  276. async function pass_lang(raw){//确认是否为中文,是则中断promise
  277. if(!enable_pass_lang)return;
  278. try{
  279. const result = await check_lang(raw)
  280. if(result=='zh')return new Promise(()=>{});
  281. return result
  282. }catch(err){
  283. console.log(err);
  284. return
  285. }
  286. return
  287. }
  288.  
  289. async function check_lang(raw){
  290. const options = {
  291. method:"POST",
  292. url:'https://fanyi.baidu.com/langdetect',
  293. data:'query='+encodeURIComponent(raw.replace(/[\uD800-\uDBFF]$/, "").slice(0,50)),
  294. headers: {
  295. "Content-Type": "application/x-www-form-urlencoded",
  296. }
  297. }
  298. const res = await Request(options);
  299. try{
  300. return JSON.parse(res.responseText).lan
  301. }catch(err){
  302. console.log(err);
  303. return
  304. }
  305. }
  306.  
  307. //--综合工具区--end
  308.  
  309. //--谷歌翻译--start
  310. async function translate_gg(raw){
  311. const options = {
  312. method:"POST",
  313. url:"https://translate.google.com/_/TranslateWebserverUi/data/batchexecute",
  314. data: "f.req="+encodeURIComponent(JSON.stringify([[["MkEWBc",JSON.stringify([[raw,"auto","zh-CN",true]]),null,"generic"]]])),
  315. headers: {
  316. "Content-Type": "application/x-www-form-urlencoded",
  317. },
  318. anonymous:true,
  319. nocache:true,
  320. }
  321. return await BaseTranslate('谷歌翻译',raw,options,res=>JSON.parse(JSON.parse(res.slice(4))[0][2])[1][0][0][5].map(item=>item[0]).join(''))
  322. }
  323.  
  324. //--谷歌翻译--end
  325.  
  326. //--百度翻译--start
  327. function tk(a,b){
  328. var d = b.split(".");
  329. b = Number(d[0]) || 0;
  330. for (var e = [], f = 0, g = 0; g < a.length; g++) {
  331. var k = a.charCodeAt(g);
  332. 128 > k ? e[f++] = k : (2048 > k ? e[f++] = k >> 6 | 192 : (55296 == (k & 64512) && g + 1 < a.length && 56320 == (a.charCodeAt(g + 1) & 64512) ? (k = 65536 + ((k & 1023) << 10) + (a.charCodeAt(++g) & 1023),
  333. e[f++] = k >> 18 | 240,
  334. e[f++] = k >> 12 & 63 | 128) : e[f++] = k >> 12 | 224,
  335. e[f++] = k >> 6 & 63 | 128),
  336. e[f++] = k & 63 | 128)
  337. }
  338. a = b;
  339. for (f = 0; f < e.length; f++)a = Fo(a+e[f], "+-a^+6");
  340. a = Fo(a, "+-3^+b+-f");
  341. a ^= Number(d[1]) || 0;
  342. 0 > a && (a = (a & 2147483647) + 2147483648);
  343. a %= 1E6;
  344. return a.toString() + "." + (a ^ b)
  345. }
  346. function Fo(a, b) {
  347. for (var c = 0; c < b.length - 2; c += 3) {
  348. var d = b.charAt(c + 2);
  349. d = "a" <= d ? d.charCodeAt(0) - 87 : Number(d);
  350. d = "+" == b.charAt(c + 1) ? a >>> d : a << d;
  351. a = "+" == b.charAt(c) ? a + d & 4294967295 : a ^ d
  352. }
  353. return a
  354. }
  355.  
  356. async function translate_baidu_startup(){
  357. if(sessionStorage.getItem('baidu_gtk')&&sessionStorage.getItem('baidu_token'))return;
  358. const options = {
  359. method:'GET',
  360. url:'https://fanyi.baidu.com',
  361. }
  362. const res = await Request(options);
  363. sessionStorage.setItem('baidu_gtk',/window\.gtk = '(.*?)'/.exec(res.responseText)[1]);
  364. sessionStorage.setItem('baidu_token',/token: '(.*?)'/.exec(res.responseText)[1])
  365. }
  366.  
  367. async function translate_baidu(raw,lang){
  368. if(!lang){
  369. lang = await check_lang(raw)
  370. }
  371. const processed_raw = raw.length>30?(raw.substr(0,10)+raw.substr(~~(raw.length/2)-5,10)+raw.substr(-10)):raw;//process
  372. const tk_key = sessionStorage.getItem('baidu_gtk');
  373. const token = sessionStorage.getItem('baidu_token');//get token
  374. const options = {
  375. method:"POST",
  376. url:'https://fanyi.baidu.com/v2transapi',
  377. data:'from='+lang+'&to=zh&query='+encodeURIComponent(raw)+'&transtype=translang&simple_means_flag=3&sign='+tk(processed_raw,tk_key)+"&token="+token+"&domain=common",
  378. headers: {
  379. "referer": 'https://fanyi.baidu.com',
  380. "Content-Type": 'application/x-www-form-urlencoded; charset=UTF-8',
  381. },
  382. }
  383. console.log(options.data,tk_key,token);
  384. return await BaseTranslate('百度翻译',raw,options,res=>JSON.parse(res).trans_result.data.map(item=>item.dst).join('\n'))
  385. }
  386.  
  387. //--百度翻译--end
  388.  
  389. //--爱词霸翻译--start
  390.  
  391. async function translate_icib(raw){
  392. const options = {
  393. method:"POST",
  394. url:'http://fy.iciba.com/ajax.php?a=fy',
  395. data:'f=auto&t=auto&w='+encodeURIComponent(raw),
  396. headers: {
  397. "Content-Type": "application/x-www-form-urlencoded",
  398. },
  399. }
  400. return await BaseTranslate('爱词霸翻译',raw,options,res=>JSON.parse(res).content.out)
  401. }
  402.  
  403. //--爱词霸翻译--end
  404.  
  405.  
  406. //--必应翻译--start
  407.  
  408. async function translate_biying(raw){
  409. const options = {
  410. method:"POST",
  411. url:'https://cn.bing.com/ttranslatev3',
  412. data:'fromLang=auto-detect&to=zh-Hans&text='+encodeURIComponent(raw),
  413. headers: {
  414. "Content-Type": "application/x-www-form-urlencoded",
  415. },
  416. }
  417. return await BaseTranslate('必应翻译',raw,options,res=>JSON.parse(res)[0].translations[0].text)
  418. }
  419.  
  420. //--必应翻译--end
  421.  
  422. //--有道翻译--start
  423.  
  424. async function translate_youdao_startup(){
  425. if(sessionStorage.getItem('youdao_key'))return;
  426. const options = {
  427. method:'GET',
  428. url:'http://fanyi.youdao.com',
  429. }
  430. const res = await Request(options);
  431. options.url = res.responseText.match(/http.*?fanyi.min.js/g)[0];
  432. const js_res = await Request(options);
  433. sessionStorage.setItem('youdao_key',/"fanyideskweb"[+a-z]{5}"(.*?)"/.exec(js_res.responseText)[1]);
  434. }
  435.  
  436. async function translate_youdao(raw){
  437. const ts=""+(new Date).getTime(),salt=ts+parseInt(10 * Math.random(), 10);
  438. const result=[
  439. 'i='+encodeURIComponent(raw),
  440. 'salt='+salt,
  441. 'sign='+CryptoJS.MD5("fanyideskweb"+raw+salt+sessionStorage.getItem('youdao_key')),
  442. 'ts='+ts,
  443. 'doctype=json&version=2.1&keyfrom=fanyi.web&action=FY_BY_REALTlME&typoResult=false&from=AUTO&to=AUTO&smartresult=dict&client=fanyideskweb'
  444. ].join('&')
  445. const options = {
  446. method:"POST",
  447. url:'http://fanyi.youdao.com/translate_o?smartresult=dict&smartresult=rule',
  448. data:result,
  449. headers: {
  450. "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
  451. "Referer": "http://fanyi.youdao.com/",
  452. "User-Agent": "test",
  453. },
  454. }
  455. return await BaseTranslate('有道翻译',raw,options,res=>JSON.parse(res).translateResult.map(e=>e.map(t=>t.tgt).join('')).join('\n'))
  456. }
  457.  
  458. //--有道翻译--end
  459.  
  460. //--有道翻译m--start
  461. async function translate_youdao_mobile(raw){
  462. const options = {
  463. method:"POST",
  464. url:'http://m.youdao.com/translate',
  465. data:"inputtext="+encodeURIComponent(raw)+"&type=AUTO",
  466. anonymous:true,
  467. headers: {
  468. "Content-Type": "application/x-www-form-urlencoded"
  469. }
  470. }
  471. return await BaseTranslate('有道翻译mobile',raw,options,res=>/id="translateResult">\s*?<li>([\s\S]*?)<\/li>\s*?<\/ul/.exec(res)[1])
  472. }
  473. //--有道翻译m--end
  474.  
  475. //--腾讯翻译--start
  476.  
  477. async function translate_tencent_startup(){
  478. setTimeout(translate_tencent_startup,30000)//token刷新
  479. const base_options = {
  480. method: 'GET',
  481. url: 'http://fanyi.qq.com'
  482. }
  483. const base_res = await Request(base_options)
  484. const uri = /reauthuri = "(.*?)"/.exec(base_res.responseText)[1]
  485. const options = {
  486. method:'POST',
  487. url:'https://fanyi.qq.com/api/'+uri
  488. }
  489. const res = await Request(options);
  490. const data = JSON.parse(res.responseText);
  491. sessionStorage.setItem('tencent_qtv',data.qtv)
  492. sessionStorage.setItem('tencent_qtk',data.qtk)
  493. }
  494.  
  495.  
  496. async function translate_tencent(raw){
  497. const qtk=sessionStorage.getItem('tencent_qtk'),qtv=sessionStorage.getItem('tencent_qtv');
  498. const options = {
  499. method:'POST',
  500. url:'https://fanyi.qq.com/api/translate',
  501. data:`source=auto&target=zh&sourceText=${encodeURIComponent(raw)}&qtv=${encodeURIComponent(qtv)}&qtk=${encodeURIComponent(qtk)}`,
  502. headers: {
  503. "Origin":"https://fanyi.qq.com",
  504. "Content-Type": "application/x-www-form-urlencoded",
  505. "Referer": "https://fanyi.qq.com/"
  506. }
  507. }
  508. return await BaseTranslate('腾讯翻译',raw,options,res=>JSON.parse(res).translate.records.map(e=>e.targetText).join(''))
  509. }
  510.  
  511. //--腾讯翻译--end
  512.  
  513. //--彩云翻译--start
  514.  
  515. async function translate_caiyun_startup(){
  516. if(sessionStorage.getItem('caiyun_id') && sessionStorage.getItem('caiyun_jwt'))return;
  517. const browser_id=CryptoJS.MD5(Math.random().toString()).toString();
  518. sessionStorage.setItem('caiyun_id',browser_id);
  519. const options= {
  520. method:"POST",
  521. url:'https://api.interpreter.caiyunai.com/v1/user/jwt/generate',
  522. headers:{
  523. "Content-Type": "application/json",
  524. "X-Authorization": "token:qgemv4jr1y38jyq6vhvi",
  525. },
  526. data:JSON.stringify({browser_id}),
  527. }
  528. const res = await Request(options);
  529. sessionStorage.setItem('caiyun_jwt',JSON.parse(res.responseText).jwt);
  530. }
  531.  
  532. async function translate_caiyun(raw){
  533. const source="NOPQRSTUVWXYZABCDEFGHIJKLMnopqrstuvwxyzabcdefghijklm";
  534. const dic=[..."ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"].reduce((dic,current,index)=>{dic[current]=source[index];return dic},{});
  535. const decoder = line => Base64.decode([...line].map(i=>dic[i]||i).join(""))
  536. const options = {
  537. method:"POST",
  538. url:'https://api.interpreter.caiyunai.com/v1/translator',
  539. data:JSON.stringify({
  540. "source":raw.split('\n'),
  541. "trans_type": "auto2zh",
  542. "detect": true,
  543. "browser_id": sessionStorage.getItem('caiyun_id')
  544. }),
  545. headers: {
  546. "X-Authorization": "token:qgemv4jr1y38jyq6vhvi",
  547. "T-Authorization": sessionStorage.getItem('caiyun_jwt')
  548. }
  549. }
  550. return await BaseTranslate('彩云小译',raw,options,res=>JSON.parse(res).target.map(decoder).join('\n'))
  551. }
  552.  
  553. //--彩云翻译--end
  554.  
  555. //--papago翻译--start
  556.  
  557. async function translate_papago(raw){
  558. const time= Date.now();
  559. const options = {
  560. method:'POST',
  561. url:'https://papago.naver.com/apis/n2mt/translate',
  562. data:`deviceId=${time}&source=auto&target=zh-CN&text=${encodeURIComponent(raw)}`,
  563. headers:{
  564. "authorization":'PPG '+time+':'+CryptoJS.HmacMD5(time+'\nhttps://papago.naver.com/apis/n2mt/translate\n'+time, "v1.5.2_0d13cb6cf4").toString(CryptoJS.enc.Base64),
  565. "x-apigw-partnerid":"papago",
  566. "device-type":'pc',
  567. "timestamp":time,
  568. "Content-Type": "application/x-www-form-urlencoded",
  569. }
  570. }
  571. return await BaseTranslate('Papago',raw,options,res=>JSON.parse(res).translatedText)
  572. }
  573.  
  574. //--papago翻译--end
  575.  
  576. //--异步请求包装工具--start
  577.  
  578. async function PromiseRetryWrap(task,options,...values){
  579. const {RetryTimes,ErrProcesser} = options||{};
  580. let retryTimes = RetryTimes||5;
  581. const usedErrProcesser = ErrProcesser || (err =>{throw err});
  582. if(!task)return;
  583. while(true){
  584. try{
  585. return await task(...values);
  586. }catch(err){
  587. if(!--retryTimes){
  588. console.log(err);
  589. return usedErrProcesser(err);
  590. }
  591. }
  592. }
  593. }
  594.  
  595. async function BaseTranslate(name,raw,options,processer){
  596. const toDo = async ()=>{
  597. var tmp;
  598. try{
  599. const data = await Request(options);
  600. tmp = data.responseText;
  601. const result = await processer(tmp);
  602. sessionStorage.setItem(name+'-'+raw,result)
  603. return result
  604. }catch(err){
  605. throw {
  606. responseText: tmp,
  607. err: err
  608. }
  609. }
  610. }
  611. return await PromiseRetryWrap(toDo,{RetryTimes:3,ErrProcesser:()=>"翻译出错"})
  612. }
  613.  
  614. function Request(options){
  615. return new Promise((reslove,reject)=>GM_xmlhttpRequest({...options,onload:reslove,onerror:reject}))
  616. }
  617.  
  618. //--异步请求包装工具--end