划词翻译:多词典查询

划词翻译调用“有道词典(有道翻译)、金山词霸、Bing 词典(必应词典)、剑桥高阶、沪江小D、谷歌翻译”

目前为 2019-06-03 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name 划词翻译:多词典查询
  3. // @namespace http://tampermonkey.net/
  4. // @version 5.4
  5. // @description 划词翻译调用“有道词典(有道翻译)、金山词霸、Bing 词典(必应词典)、剑桥高阶、沪江小D、谷歌翻译”
  6. // @author https://github.com/barrer
  7. // @match http://*/*
  8. // @include https://*/*
  9. // @include file:///*
  10. // @run-at document-start
  11. // @connect youdao.com
  12. // @connect iciba.com
  13. // @connect translate.google.cn
  14. // @connect hjenglish.com
  15. // @connect bing.com
  16. // @connect chinacloudapi.cn
  17. // @connect cambridge.org
  18. // @grant GM_xmlhttpRequest
  19. // ==/UserScript==
  20.  
  21. (function () {
  22. 'use strict';
  23.  
  24. // Your code here...
  25. /**样式*/
  26. var style = document.createElement('style');
  27. style.textContent = `
  28. /*组件样式*/
  29. :host{all:unset!important}
  30. :host{all:initial!important}
  31. *{word-wrap:break-word!important;word-break:break-word!important}
  32. a{color:#00c;text-decoration:none;cursor:pointer}
  33. a:hover{text-decoration:none}
  34. a:active{text-decoration:underline}
  35. img{cursor:pointer;display:inline-block;width:16px;height:16px;border:1px solid #dfe1e5;border-radius:4px;background-color:rgba(255,255,255,1);padding:2px;margin:0;margin-right:5px;box-sizing:content-box;vertical-align:middle}
  36. img:last-of-type{margin-right:auto}
  37. img:hover{border:1px solid #f90}
  38. img[activate]{border:1px solid #f90}
  39. img[activate]:hover{border:1px solid #f90}
  40. table{font-size:inherit;color:inherit}
  41. tr-icon{display:none;position:absolute;padding:0;margin:0;cursor:move;box-sizing:content-box;font-size:13px;text-align:left;border:0;border-radius:4px;color:black;z-index:2147483647;background:transparent}
  42. tr-icon[activate]{background:#fff;-webkit-box-shadow:0 3px 8px 0 rgba(0,0,0,0.2),0 0 0 0 rgba(0,0,0,0.08);box-shadow:0 3px 8px 0 rgba(0,0,0,0.2),0 0 0 0 rgba(0,0,0,0.08)}
  43. tr-audio{display:block;margin-bottom:5px}
  44. tr-audio a{margin-right:1em;font-size:80%}
  45. tr-audio a:last-of-type{margin-right:auto}
  46. tr-content{display:block;max-width:300px;max-height:200px;width:300px;height:200px;overflow-x:auto;overflow-y:scroll;background:white;padding:2px 8px;margin-top:5px;box-sizing:content-box;font-family:"Helvetica Neue","Helvetica","Arial","sans-serif";font-size:13px;font-weight:normal;line-height:normal;-webkit-font-smoothing:auto;font-smoothing:auto;text-rendering:auto}
  47. tr-engine~tr-engine{margin-top:1em}
  48. tr-engine .title{color:#00c;display:inline-block;font-weight:bold}
  49. tr-engine .title:hover{text-decoration:none}
  50. /*各引擎样式*/
  51. .google .sentences,.google .trans,.google .orig,.google .dict,.google .pos,.none{display:block}
  52. .google .backend,.google .entry,.google .base_form,.google .pos_enum,.google .src,.google .confidence,.google .ld_result,.none{display:none}
  53. .google .orig{color:#777}
  54. .google .pos{margin-top:1em}
  55. .google .pos:before{content:"<"}
  56. .google .pos:after{content:">"}
  57. .google .terms:before{content:"〔"}
  58. .google .terms:after{content:"〕"}
  59. .google .terms{margin-right:.2em}
  60. .youdao .pron{margin-right:1em}
  61. .youdao .phone{color:#777;margin-right:1em}
  62. .youdao .phone:before{content:"["}
  63. .youdao .phone:after{content:"]"}
  64. .youdao .pos:before{content:"<"}
  65. .youdao .pos:after{content:">"}
  66. .youdao .phrs{display:none}
  67. .youdao .trs>.tr>.exam{display:none}
  68. .youdao .trs>.tr>.l{display:block;margin-left:1em}
  69. .youdao [class="#text"]{font-style:italic}
  70. .youdao .return-phrase,.youdao [class="@action"],.none{display:none}
  71. .hjenglish dl,.hjenglish dt,.hjenglish dd,.hjenglish p,.hjenglish ul,.hjenglish li,.hjenglish h3{margin:0;padding:0;margin-block-start:0;margin-block-end:0;margin-inline-start:0;margin-inline-end:0}
  72. .hjenglish h3{font-size:1em;font-weight:normal}
  73. .hjenglish .detail-pron,.hjenglish .pronounces{color:#777}
  74. .hjenglish ul{margin-left:2em}
  75. .hjenglish .def-sentence-from,.hjenglish .def-sentence-to{display:none}
  76. .hjenglish .detail-groups dd h3:before{counter-increment:eq;content:counter(eq) ".";display:block;width:22px;float:left}
  77. .hjenglish .detail-groups dl{counter-reset:eq;margin-bottom:.5em;clear:both}
  78. .hjenglish ol,.hjenglish ul{list-style:none}
  79. .hjenglish dd{margin-left:1em}
  80. .hjenglish dd>p{margin-left:2.5em}
  81. .bing h1,.bing strong{font-size:1em;font-weight:normal;margin:0;padding:0}
  82. .bing .concise ul{list-style:none;margin:0;padding:0}
  83. .bing .hd_tf{margin-right:1em}
  84. .bing .concise .pos{margin-right:.2em}
  85. .bing .concise .web{margin-right:auto}
  86. .bing .concise .web:after{content:":"}
  87. .bing .oald{margin-top:.4em}
  88. .bing .hd_tf_lh div{display:inline;color:#777}
  89. .bing #authid td:first-child{width:22px;margin:0;padding:0}
  90. .bing .def_row{vertical-align:top}
  91. .bing .bil_dis,.bing .val_dis{padding-right:.25em}
  92. .bing .li_exs{display:none}
  93. .bing .li_id{border:0;padding:.2em}
  94. .bing .infor,.bing .sen_com,.bing .com_sep,.bing .bil,.bing .gra{padding-right:.25em}
  95. .bing .infor,.bing .label{padding-left:.25em}
  96. .bing .each_seg+.each_seg{margin-top:.5em}
  97. .bing .de_co div{display:inline}
  98. .bing .idm_seg,.bing .li_ids_co{margin-left:1em}
  99. .bing .sim{display:inline}
  100. .cambridge .entry~.entry{margin-top:1em}
  101. .cambridge p,.cambridge h2,.cambridge h3{padding:0;margin:0}
  102. .cambridge h2,.cambridge h3{font-size:1em;font-weight:normal}
  103. .cambridge .headword .hw{display:block}
  104. .cambridge .pron{color:#777;margin-right:1em}
  105. .cambridge b.def{font-weight:normal}
  106. .cambridge .epp-xref{border:1px solid #777;border-radius:.5em;padding:0 2px;font-size:80%}
  107. .cambridge .examp,.cambridge .extraexamps,.cambridge .cols,.cambridge .xref,.cambridge .fcdo{display:none}
  108. .cambridge .entry-body__el+.entry-body__el{margin-top:1em}
  109. .cambridge .pos-body{margin-left:1em}
  110. .iciba strong{font-size:1em;font-weight:normal}
  111. .iciba p{padding:0;margin:0}
  112. .iciba .icIBahyI-footer,.iciba .icIBahyI-suggest{display:none}
  113. .iciba .icIBahyI-prons{color:#777}
  114. .iciba .icIBahyI-eg{margin-right:1em}
  115. `;
  116. // iframe 工具库
  117. var iframe = document.createElement('iframe');
  118. var iframeWin = null;
  119. var iframeDoc = null;
  120. iframe.style.display = 'none';
  121. var icon = document.createElement('tr-icon'), //翻译图标
  122. content = document.createElement('tr-content'), // 内容面板
  123. contentList = document.createElement('div'), //翻译内容结果集(HTML内容)列表
  124. selected, // 当前选中文本
  125. engineId, // 当前翻译引擎
  126. engineTriggerTime, // 引擎触发时间(milliseconds)
  127. idsType; // 当前翻译面板内容列表数组
  128. // 初始化内容面板
  129. content.appendChild(contentList);
  130. // 发音缓存
  131. var audioCache = {}; // {'mp3 download url': data}
  132. // 翻译引擎结果集
  133. var engineResult = {}; // id: DOM
  134. // 唯一 ID
  135. var ids = {
  136. ICIBA: 'iciba',
  137. ICIBA_LOWER_CASE: 'icibaLowerCase',
  138. YOUDAO: 'youdao',
  139. YOUDAO_LOWER_CASE: 'youdaoLowerCase',
  140. BING: 'bing',
  141. HJENGLISH: 'hjenglish',
  142. GOOGLE: 'google',
  143. CAMBRIDGE: 'cambridge'
  144. };
  145. // 唯一 ID 扩展
  146. var idsExtension = {
  147. // ID 组
  148. LIST_DICT: [ids.ICIBA, ids.YOUDAO, ids.BING, ids.HJENGLISH, ids.CAMBRIDGE],
  149. LIST_DICT_LOWER_CASE: [ids.ICIBA, ids.ICIBA_LOWER_CASE, ids.YOUDAO, ids.YOUDAO_LOWER_CASE, ids.BING, ids.HJENGLISH, ids.CAMBRIDGE],
  150. LIST_GOOGLE: [ids.GOOGLE],
  151. // 去重比对(大小写翻译可能一样)
  152. lowerCaseMap: (function () {
  153. var obj = {};
  154. obj[ids.ICIBA_LOWER_CASE] = ids.ICIBA;
  155. obj[ids.YOUDAO_LOWER_CASE] = ids.YOUDAO;
  156. return obj;
  157. })(),
  158. // 标题
  159. names: (function () {
  160. var obj = {};
  161. obj[ids.ICIBA] = '金山词霸';
  162. obj[ids.ICIBA_LOWER_CASE] = '';
  163. obj[ids.YOUDAO] = '有道词典';
  164. obj[ids.YOUDAO_LOWER_CASE] = '';
  165. obj[ids.BING] = 'Bing 词典';
  166. obj[ids.HJENGLISH] = '沪江小D';
  167. obj[ids.GOOGLE] = '谷歌翻译';
  168. obj[ids.CAMBRIDGE] = '剑桥高阶';
  169. return obj;
  170. })(),
  171. // 跳转到网站(“%q%”占位符或者 function text -> return URL)
  172. links: (function () {
  173. var obj = {};
  174. obj[ids.ICIBA] = 'http://www.iciba.com/%q%';
  175. obj[ids.ICIBA_LOWER_CASE] = '';
  176. obj[ids.YOUDAO] = 'https://dict.youdao.com/w/eng/%q%';
  177. obj[ids.YOUDAO_LOWER_CASE] = '';
  178. obj[ids.BING] = 'https://cn.bing.com/dict/search?q=%q%';
  179. obj[ids.HJENGLISH] = 'https://dict.hjenglish.com/w/%q%';
  180. obj[ids.GOOGLE] = function (text) {
  181. var rst = '';
  182. if (hasChineseByRange(text)) {
  183. rst = 'https://translate.google.cn/#view=home&op=translate&sl=auto&tl=en&text=' + encodeURIComponent(text);
  184. } else {
  185. rst = 'https://translate.google.cn/#view=home&op=translate&sl=auto&tl=zh-CN&text=' + encodeURIComponent(text);
  186. }
  187. return rst;
  188. };
  189. obj[ids.CAMBRIDGE] = 'https://dictionary.cambridge.org/dictionary/english-chinese-simplified/%q%';
  190. return obj;
  191. })(),
  192. // 翻译引擎
  193. engines: (function () {
  194. var obj = {};
  195. obj[ids.ICIBA] = function (text, time) {
  196. ajax('http://open.iciba.com/huaci_v3/dict.php?word=' + encodeURIComponent(text), function (rst) {
  197. putEngineResult(ids.ICIBA, parseIciba(rst), time);
  198. showContent();
  199. }, function (rst) {
  200. putEngineResult(ids.ICIBA, htmlToDom('error: 无法连接翻译服务'), time);
  201. showContent();
  202. });
  203. };
  204. obj[ids.ICIBA_LOWER_CASE] = function (text, time) {
  205. ajax('http://open.iciba.com/huaci_v3/dict.php?word=' + encodeURIComponent(text.toLowerCase()), function (rst) {
  206. putEngineResult(ids.ICIBA_LOWER_CASE, parseIciba(rst), time);
  207. showContent();
  208. }, function (rst) {
  209. putEngineResult(ids.ICIBA_LOWER_CASE, htmlToDom('error: 无法连接翻译服务'), time);
  210. showContent();
  211. });
  212. };
  213. obj[ids.YOUDAO] = function (text, time) {
  214. ajax('https://dict.youdao.com/jsonapi?xmlVersion=5.1&jsonversion=2&q=' + encodeURIComponent(text), function (rst) {
  215. putEngineResult(ids.YOUDAO, parseYoudao(rst), time)
  216. showContent();
  217. }, function (rst) {
  218. putEngineResult(ids.YOUDAO, htmlToDom('error: 无法连接翻译服务'), time);
  219. showContent();
  220. });
  221. };
  222. obj[ids.YOUDAO_LOWER_CASE] = function (text, time) {
  223. ajax('https://dict.youdao.com/jsonapi?xmlVersion=5.1&jsonversion=2&q=' + encodeURIComponent(text.toLowerCase()), function (rst) {
  224. putEngineResult(ids.YOUDAO_LOWER_CASE, parseYoudao(rst), time);
  225. showContent();
  226. }, function (rst) {
  227. putEngineResult(ids.YOUDAO_LOWER_CASE, htmlToDom('error: 无法连接翻译服务'), time)
  228. showContent();
  229. });
  230. };
  231. obj[ids.BING] = function (text, time) {
  232. ajax('https://cn.bing.com/dict/search?q=' + encodeURIComponent(text), function (rst) {
  233. putEngineResult(ids.BING, parseBing(rst), time);
  234. showContent();
  235. }, function (rst) {
  236. putEngineResult(ids.BING, htmlToDom('error: 无法连接翻译服务'), time);
  237. showContent();
  238. }, {
  239. headers: {
  240. 'Cookie': 'ENSEARCH=BENVER=0;' // 中文结果
  241. }
  242. });
  243. };
  244. obj[ids.HJENGLISH] = function (text, time) {
  245. ajax('https://dict.hjenglish.com/w/' + encodeURIComponent(text), function (rst) {
  246. putEngineResult(ids.HJENGLISH, parseHjenglish(rst), time);
  247. showContent();
  248. }, function (rst) {
  249. putEngineResult(ids.HJENGLISH, htmlToDom('error: 无法连接翻译服务'), time);
  250. showContent();
  251. }, {
  252. headers: {
  253. 'Cookie': 'HJ_SID=' + uuid() + '; HJ_SSID_3=' + uuid() + '; HJ_CST=1; HJ_CSST_3=1; HJ_UID=' + uuid()
  254. }
  255. });
  256. };
  257. obj[ids.GOOGLE] = function (text, time) {
  258. var url = 'https://translate.google.cn/translate_a/single?client=gtx&dt=t&dt=bd&dj=1&source=input&hl=zh-CN&sl=auto';
  259. url += '&tk=' + token(text);
  260. if (hasChineseByRange(text)) {
  261. url += '&tl=en&q=' + encodeURIComponent(text);
  262. } else {
  263. url += '&tl=zh-CN&q=' + encodeURIComponent(text);
  264. }
  265. ajax(url, function (rst) {
  266. putEngineResult(ids.GOOGLE, parseGoogle(rst), time);
  267. showContent();
  268. }, function (rst) {
  269. putEngineResult(ids.GOOGLE, htmlToDom('error: 无法连接翻译服务'), time);
  270. showContent();
  271. });
  272. };
  273. obj[ids.CAMBRIDGE] = function (text, time) {
  274. var url = 'https://dictionary.cambridge.org/dictionary/english-chinese-simplified/' + encodeURIComponent(text);
  275. ajax(url, function (rst) {
  276. putEngineResult(ids.CAMBRIDGE, parseCambridge(rst), time);
  277. showContent();
  278. }, function (rst) {
  279. putEngineResult(ids.CAMBRIDGE, htmlToDom('error: 无法连接翻译服务'), time);
  280. showContent();
  281. });
  282. };
  283. return obj;
  284. })()
  285. }
  286. // 绑定图标拖动事件
  287. var iconDrag = new Drag(icon);
  288. // 图标数组
  289. var iconArray = [{
  290. name: '多词典查询',
  291. id: 'icon-dict',
  292. image: 'data:image/png;base64,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',
  293. trigger: function (text, time) {
  294. idsType = idsExtension.LIST_DICT;
  295. if (text != text.toLowerCase()) {
  296. idsType = idsExtension.LIST_DICT_LOWER_CASE; // 改为大小写 ID 组(大小写各请求一次)
  297. }
  298. idsType.forEach(function (id) {
  299. idsExtension.engines[id](text, time);
  300. });
  301. initContent(); // 初始化翻译面板
  302. displayContent(); // 立马显示翻译面板
  303. }
  304. }, {
  305. name: '谷歌翻译',
  306. id: 'icon-google',
  307. image: 'data:image/png;base64,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',
  308. trigger: function (text, time) {
  309. idsType = idsExtension.LIST_GOOGLE;
  310. idsType.forEach(function (id) {
  311. idsExtension.engines[id](text, time);
  312. });
  313. initContent(); // 初始化翻译面板
  314. displayContent(); // 立马显示翻译面板
  315. }
  316. }];
  317. // 添加翻译引擎图标
  318. iconArray.forEach(function (obj) {
  319. var img = document.createElement('img');
  320. img.setAttribute('src', obj.image);
  321. img.setAttribute('alt', obj.name);
  322. img.setAttribute('title', obj.name);
  323. img.setAttribute('icon-id', obj.id);
  324. img.addEventListener('mouseup', function () {
  325. if (engineId == obj.id) {
  326. // 已经是当前翻译引擎,不做任何处理
  327. } else {
  328. icon.setAttribute('activate', 'activate'); // 标注面板展开
  329. engineId = obj.id; // 翻译引擎 ID
  330. engineTriggerTime = new Date().getTime(); // 引擎触发时间
  331. engineActivateShow(); // 显示翻译引擎指示器
  332. audioCache = {}; // 清空发音缓存
  333. engineResult = {}; // 清空翻译引擎结果集
  334. obj.trigger(selected, engineTriggerTime); // 启动翻译引擎
  335. }
  336. });
  337. icon.appendChild(img);
  338. });
  339. // 添加内容面板(放图标后面)
  340. icon.appendChild(content);
  341. // 添加样式、翻译图标到 DOM
  342. var root = document.createElement('div');
  343. document.documentElement.appendChild(root);
  344. var shadow = root.attachShadow({
  345. mode: 'closed'
  346. });
  347. // iframe 工具库加入 Shadow
  348. shadow.appendChild(iframe);
  349. iframeWin = iframe.contentWindow;
  350. iframeDoc = iframe.contentDocument;
  351. // 外部样式表
  352. var link = document.createElement('link');
  353. link.rel = 'stylesheet';
  354. link.type = 'text/css';
  355. link.href = createObjectURLWithTry(new Blob(['\ufeff', style.textContent], {
  356. type: 'text/css;charset=UTF-8'
  357. }));
  358. // 多种方式最大化兼容:Content Security Policy
  359. // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy
  360. shadow.appendChild(style); // 内部样式表
  361. shadow.appendChild(link); // 外部样式表
  362. // 翻译图标加入 Shadow
  363. shadow.appendChild(icon);
  364. // 鼠标事件:防止选中的文本消失
  365. document.addEventListener('mousedown', function (e) {
  366. log('mousedown event:', e);
  367. if (e.target == icon || (e.target.parentNode && e.target.parentNode == icon)) { // 点击了翻译图标
  368. e.preventDefault();
  369. }
  370. });
  371. // 鼠标事件:防止选中的文本消失;显示、隐藏翻译图标
  372. document.addEventListener('mouseup', function (e) {
  373. log('mouseup event:', e);
  374. if (e.target == icon || (e.target.parentNode && e.target.parentNode == icon)) { // 点击了翻译图标
  375. e.preventDefault();
  376. return;
  377. }
  378. selected = window.getSelection().toString().trim(); // 当前选中文本
  379. log('click text:' + selected);
  380. if (selected && icon.style.display == 'none') { // 显示翻译图标
  381. log('show icon');
  382. log(selected + ' | ' + e.pageX + ' | ' + e.pageY);
  383. icon.style.top = e.pageY + 8 + 'px';
  384. icon.style.left = e.pageX + 4 + 'px';
  385. icon.style.display = 'block';
  386. // 兼容部分 Content Security Policy
  387. icon.style.position = 'absolute';
  388. icon.style.zIndex = '2147483647';
  389. } else if (!selected) { // 隐藏翻译图标
  390. log('hide icon:mouseup');
  391. hideIcon();
  392. }
  393. });
  394. // 选中变化事件:当点击已经选中的文本的时候,隐藏翻译图标(此时浏览器动作是:选中的文本已经取消选中了)
  395. document.addEventListener('selectionchange', function (e) {
  396. log('selectionchange event:', e);
  397. log('selectionchange:' + window.getSelection().toString());
  398. if (!window.getSelection().toString().trim()) {
  399. log('hide icon:selectionchange');
  400. hideIcon();
  401. }
  402. });
  403. // 内容面板滚动事件
  404. content.addEventListener('scroll', function (e) {
  405. if (content.scrollHeight - content.scrollTop === content.clientHeight) {
  406. log('scroll bottom', e);
  407. e.preventDefault();
  408. e.stopPropagation();
  409. } else if (content.scrollTop === 0) {
  410. log('scroll top', e);
  411. e.preventDefault();
  412. e.stopPropagation();
  413. }
  414. });
  415. /**日志输出*/
  416. function log() {
  417. var debug = false;
  418. if (!debug) {
  419. return;
  420. }
  421. if (arguments) {
  422. for (var i = 0; i < arguments.length; i++) {
  423. console.log(arguments[i]);
  424. }
  425. }
  426. }
  427. /**鼠标拖动*/
  428. function Drag(element) {
  429. this.dragging = false;
  430. this.startDragTime = 0;
  431. this.stopDragTime = 0;
  432. this.mouseDownPositionX = 0;
  433. this.mouseDownPositionY = 0;
  434. this.elementOriginalLeft = parseInt(element.style.left);
  435. this.elementOriginalTop = parseInt(element.style.top);
  436. var ref = this;
  437. this.startDrag = function (e) {
  438. e.preventDefault();
  439. ref.dragging = true;
  440. ref.startDragTime = new Date().getTime();
  441. ref.mouseDownPositionX = e.clientX;
  442. ref.mouseDownPositionY = e.clientY;
  443. ref.elementOriginalLeft = parseInt(element.style.left);
  444. ref.elementOriginalTop = parseInt(element.style.top);
  445. // set mousemove event
  446. window.addEventListener('mousemove', ref.dragElement);
  447. log('startDrag');
  448. };
  449. this.unsetMouseMove = function () {
  450. // unset mousemove event
  451. window.removeEventListener('mousemove', ref.dragElement);
  452. };
  453. this.stopDrag = function (e) {
  454. e.preventDefault();
  455. ref.dragging = false;
  456. ref.stopDragTime = new Date().getTime();
  457. ref.unsetMouseMove();
  458. log('stopDrag');
  459. };
  460. this.dragElement = function (e) {
  461. log('dragging');
  462. if (!ref.dragging) {
  463. return;
  464. }
  465. e.preventDefault();
  466. // move element
  467. element.style.left = ref.elementOriginalLeft + (e.clientX - ref.mouseDownPositionX) + 'px';
  468. element.style.top = ref.elementOriginalTop + (e.clientY - ref.mouseDownPositionY) + 'px';
  469. log('dragElement');
  470. };
  471. element.onmousedown = this.startDrag;
  472. element.onmouseup = this.stopDrag;
  473. }
  474. /**是否拖动图标*/
  475. function isDrag() {
  476. return iconDrag.elementOriginalLeft != parseInt(icon.style.left) ||
  477. iconDrag.elementOriginalTop != parseInt(icon.style.top);
  478. }
  479. /**强制结束拖动*/
  480. function forceStopDrag() {
  481. if (iconDrag) {
  482. // 强制设置鼠标拖动事件结束,防止由于网页本身的其它鼠标事件冲突而导致没有侦测到:mouseup
  483. iconDrag.dragging = false;
  484. iconDrag.unsetMouseMove();
  485. }
  486. }
  487. /**是否包含汉字*/
  488. function hasChineseByRange(str) {
  489. return /[\u4e00-\u9fa5]/ig.test(str);
  490. }
  491. /**uuid*/
  492. function uuid() {
  493. return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
  494. var r = Math.random() * 16 | 0,
  495. v = c == 'x' ? r : (r & 0x3 | 0x8);
  496. return v.toString(16);
  497. });
  498. }
  499. /**对象转 xml*/
  500. function objToXml(obj) {
  501. var xml = '';
  502. for (var prop in obj) {
  503. if (obj[prop] instanceof iframeWin.Function) {
  504. continue;
  505. }
  506. xml += obj[prop] instanceof iframeWin.Array ? '' : '<' + prop + '>';
  507. if (obj[prop] instanceof iframeWin.Array) {
  508. for (var array in obj[prop]) {
  509. if (obj[prop][array] instanceof iframeWin.Function) {
  510. continue;
  511. }
  512. xml += '<' + prop + '>';
  513. xml += objToXml(new iframeWin.Object(obj[prop][array]));
  514. xml += '</' + prop + '>';
  515. }
  516. } else if (obj[prop] instanceof iframeWin.Object) {
  517. xml += objToXml(new iframeWin.Object(obj[prop]));
  518. } else {
  519. xml += obj[prop];
  520. }
  521. xml += obj[prop] instanceof iframeWin.Array ? '' : '</' + prop + '>';
  522. }
  523. var xml = xml.replace(/<\/?[0-9]{1,}>/g, '');
  524. return xml
  525. }
  526. /**xml 转 html*/
  527. function xmlToHtml(xml, tag) {
  528. return xml.replace(/<([^/]+?)>/g, '<' + tag + ' class="$1">')
  529. .replace(/<\/(.+?)>/g, '</' + tag + '>');
  530. }
  531. // html 字符串转 DOM
  532. function htmlToDom(html) {
  533. var div = document.createElement('div');
  534. div.innerHTML = html;
  535. return div;
  536. }
  537. /**清理 html*/
  538. function cleanHtml(html) {
  539. html = html.replace(/<script[\s\S]*?<\/script>/ig, '')
  540. .replace(/<link[\s\S]*?>/ig, '')
  541. .replace(/<style[\s\S]*?<\/style>/ig, '')
  542. .replace(/<img[\s\S]*?>/ig, '');
  543. html = cleanAttr(html, 'on[a-z]*');
  544. return html;
  545. }
  546. /**
  547. * 清理指定属性(忽略大小写)
  548. * @param attr 支持正则表示(如“on[a-z]*”,表示清理“on”开头的属性:onclick、onmove等)
  549. */
  550. function cleanAttr(html, attr) {
  551. var regex = ' ' + attr + '="([^"<>]*)"';
  552. return html.replace(new RegExp(regex, 'ig'), '');
  553. }
  554. /**带异常处理的 createObjectURL*/
  555. function createObjectURLWithTry(blob) {
  556. try {
  557. return iframeWin.URL.createObjectURL(blob);
  558. } catch (error) {
  559. log(error);
  560. }
  561. return '';
  562. }
  563. /**ajax 跨域访问公共方法*/
  564. function ajax(url, success, error, obj) {
  565. if (!!!obj) {
  566. obj = {};
  567. }
  568. if (!!!obj.method) {
  569. obj.method = 'GET';
  570. }
  571. // >>>因为Tampermonkey跨域访问(a.com)时会自动携带对应域名(a.com)的对应cookie
  572. // 不会携带当前域名的cookie
  573. // 所以,GM_xmlhttpRequest【不存在】cookie跨域访问安全性问题
  574. // 以下设置的cookie会添加到已有cookie的后面<<<
  575. if (!!!obj.headers) {
  576. obj.headers = {
  577. 'cookie': ''
  578. };
  579. }
  580. GM_xmlhttpRequest({
  581. method: obj.method,
  582. url: url,
  583. headers: obj.headers,
  584. responseType: obj.responseType,
  585. data: obj.data,
  586. onload: function (res) {
  587. success(res.responseText, res, obj);
  588. },
  589. onerror: function (res) {
  590. error(res.responseText, res, obj);
  591. },
  592. onabort: function (res) {
  593. error('the request was aborted', res, obj);
  594. },
  595. ontimeout: function (res) {
  596. error('the request failed due to a timeout', res, obj);
  597. },
  598. onreadystatechange: function () {
  599. log('ajax:', arguments);
  600. }
  601. });
  602. }
  603. /**放入翻译引擎结果集*/
  604. function putEngineResult(id, value, time) {
  605. if (time == engineTriggerTime) { // 是本次触发的异步ajax请求
  606. engineResult[id] = value;
  607. }
  608. }
  609. /**显示内容面板*/
  610. function displayContent() {
  611. var panelWidth = 316 + 8; // icon 展开后总宽度
  612. var panelHeight = 231 + 8; // icon 展开后总高度
  613. // 计算位置
  614. log('content position:',
  615. 'window.scrollY', window.scrollY,
  616. 'document.documentElement.scrollTop', document.documentElement.scrollTop,
  617. 'document.body.scrollTop', document.body.scrollTop,
  618. 'window.innerHeight', window.innerHeight,
  619. 'document.documentElement.clientHeight', document.documentElement.clientHeight,
  620. 'document.body.clientHeight', document.body.clientHeight,
  621. 'icon.style.top', icon.style.top,
  622. 'window.scrollX', window.scrollX,
  623. 'document.documentElement.scrollLeft', document.documentElement.scrollLeft,
  624. 'document.body.scrollLeft', document.body.scrollLeft,
  625. 'window.innerWidth', window.innerWidth,
  626. 'document.documentElement.clientWidth', document.documentElement.clientWidth,
  627. 'document.body.clientWidth', document.body.clientWidth,
  628. 'icon.style.left', icon.style.left
  629. );
  630. var scrollTop = Math.max(parseInt(document.documentElement.scrollTop), parseInt(document.body.scrollTop));
  631. var scrollLeft = Math.max(parseInt(document.documentElement.scrollLeft), parseInt(document.body.scrollLeft));
  632. var clientHeight = [parseInt(document.documentElement.clientHeight), parseInt(document.body.clientHeight)].filter(function (x) {
  633. return x <= parseInt(window.innerHeight);
  634. }).sort(function (a, b) {
  635. return a > b ? -1 : (a == b ? 0 : 1);
  636. })[0]; // 找出最大值且小于等于 window 的高度
  637. if (!clientHeight) { // 网页缩放导致可能数组为空([0] 为 undefined)
  638. clientHeight = parseInt(window.innerHeight);
  639. }
  640. var clientWidth = [parseInt(document.documentElement.clientWidth), parseInt(document.body.clientWidth)].filter(function (x) {
  641. return x <= parseInt(window.innerWidth);
  642. }).sort(function (a, b) {
  643. return a > b ? -1 : (a == b ? 0 : 1);
  644. })[0]; // 找出最大值且小于等于 window 的宽度
  645. if (!clientWidth) { // 网页缩放导致可能数组为空([0] 为 undefined)
  646. clientWidth = parseInt(window.innerWidth);
  647. }
  648. var iconNewTop = -1;
  649. if (parseInt(icon.style.top) < scrollTop) {
  650. log('Y adjust top');
  651. iconNewTop = scrollTop;
  652. } else if (parseInt(icon.style.top) + panelHeight > scrollTop + clientHeight) {
  653. log('Y adjust bottom');
  654. iconNewTop = parseInt(scrollTop + clientHeight - panelHeight);
  655. }
  656. if (iconNewTop != -1 && Math.abs(iconNewTop - parseInt(icon.style.top)) <= panelHeight) {
  657. log('Y set iconNewTop', iconNewTop);
  658. icon.style.top = iconNewTop + 'px';
  659. }
  660. var iconNewLeft = -1;
  661. if (parseInt(icon.style.left) < scrollLeft) {
  662. log('X adjust left');
  663. iconNewLeft = scrollLeft;
  664. } else if (parseInt(icon.style.left) + panelWidth > scrollLeft + clientWidth) {
  665. log('X adjust right');
  666. iconNewLeft = parseInt(scrollLeft + clientWidth - panelWidth);
  667. }
  668. if (iconNewLeft != -1 && Math.abs(iconNewLeft - parseInt(icon.style.left)) <= panelWidth) {
  669. log('X set iconNewLeft', iconNewLeft);
  670. icon.style.left = iconNewLeft + 'px';
  671. }
  672. content.scrollTop = 0; // 翻译面板滚动到顶端
  673. content.scrollLeft = 0; // 翻译面板滚动到左端
  674. content.style.display = 'block';
  675. }
  676. /**初始化面板*/
  677. function initContent() {
  678. contentList.innerHTML = ''; // 清空翻译内容列表
  679. // 发音
  680. var audio = document.createElement('tr-audio');
  681. audio.appendChild(getPlayButton({
  682. name: 'US',
  683. url: 'https://dict.youdao.com/dictvoice?audio=' + selected + '&type=2'
  684. }));
  685. audio.appendChild(getPlayButton({
  686. name: 'UK',
  687. url: 'https://dict.youdao.com/dictvoice?audio=' + selected + '&type=1'
  688. }));
  689. if (engineId != 'icon-google') { // 谷歌翻译不显示发音图标
  690. contentList.appendChild(audio);
  691. }
  692. // 初始化翻译引擎结构(此时内容暂未填充)
  693. idsType.forEach(function (id) {
  694. if (id in idsExtension.names) {
  695. var engine = document.createElement('tr-engine');
  696. engine.setAttribute('data-id', id);
  697. engine.style.display = 'none'; // 暂无内容默认隐藏
  698. // 标题
  699. if (idsExtension.names[id]) {
  700. var title = document.createElement('a');
  701. title.innerHTML = idsExtension.names[id];
  702. title.setAttribute('class', 'title');
  703. var href = 'javascript:void(0)';
  704. if (idsExtension.links[id]) {
  705. var link = idsExtension.links[id];
  706. if (typeof link == 'string') {
  707. if (link.length > 0) {
  708. href = link.replace(/%q%/ig, encodeURIComponent(selected));
  709. }
  710. } else if (typeof link == 'function') {
  711. var fnHref = link(selected);
  712. if (fnHref.length > 0) {
  713. href = fnHref;
  714. }
  715. }
  716. }
  717. title.setAttribute('rel', 'noreferrer noopener');
  718. title.setAttribute('target', '_blank');
  719. title.setAttribute('href', href);
  720. title.setAttribute('title', '打开源网站');
  721. engine.appendChild(title);
  722. }
  723. contentList.appendChild(engine);
  724. }
  725. });
  726. }
  727. /**内容面板填充数据*/
  728. function showContent() {
  729. // 填充已有结果集引擎内容
  730. idsType.forEach(function (id) {
  731. if (engineResult[id] && !(id in idsExtension.lowerCaseMap)) { // 跳过小写的内容填充
  732. var engine = contentList.querySelector('tr-engine[data-id="' + id + '"]');
  733. if (engine) {
  734. engine.appendChild(engineResult[id]);
  735. engine.removeAttribute('data-id');
  736. engine.style.display = 'block';
  737. }
  738. }
  739. });
  740. // 比较大小写内容
  741. for (var id in idsExtension.lowerCaseMap) {
  742. if (engineResult[id] &&
  743. engineResult[idsExtension.lowerCaseMap[id]] &&
  744. engineResult[id].innerHTML != engineResult[idsExtension.lowerCaseMap[id]].innerHTML &&
  745. engineResult[id].innerHTML.toLowerCase() != engineResult[idsExtension.lowerCaseMap[id]].innerHTML.toLowerCase()) {
  746. var engine = contentList.querySelector('tr-engine[data-id="' + id + '"]');
  747. if (engine) {
  748. engine.appendChild(engineResult[id]);
  749. engine.removeAttribute('data-id');
  750. engine.style.display = 'block';
  751. }
  752. }
  753. }
  754. }
  755. /**隐藏翻译引擎指示器*/
  756. function engineActivateHide() {
  757. icon.querySelectorAll('img[activate]').forEach(function (ele) {
  758. ele.removeAttribute('activate');
  759. });
  760. }
  761. /**显示翻译引擎指示器*/
  762. function engineActivateShow() {
  763. engineActivateHide();
  764. icon.querySelector('img[icon-id="' + engineId + '"]').setAttribute('activate', 'activate');
  765. }
  766. /**隐藏 icon*/
  767. function hideIcon() {
  768. icon.style.display = 'none';
  769. icon.removeAttribute('activate'); // 标注面板关闭
  770. content.style.display = 'none';
  771. engineId = '';
  772. engineTriggerTime = 0;
  773. engineActivateHide();
  774. audioCache = {};
  775. engineResult = {};
  776. forceStopDrag();
  777. }
  778. /**发音*/
  779. function play(obj) {
  780. if (obj.url in audioCache) { // 查找缓存
  781. log('audio in cache', obj, audioCache);
  782. playArrayBuffer(audioCache[obj.url]); // 播放
  783. } else {
  784. ajax(obj.url, function (rst, res) {
  785. audioCache[obj.url] = res.response; // 放入缓存
  786. playArrayBuffer(audioCache[obj.url]); // 播放
  787. }, function (rst) {
  788. log(rst);
  789. }, {
  790. responseType: 'arraybuffer'
  791. });
  792. }
  793. }
  794. /**播放 ArrayBuffer 音频*/
  795. function playArrayBuffer(arrayBuffer) {
  796. var context = new iframeWin.AudioContext() || new iframeWin.webkitAudioContext();
  797. context.decodeAudioData(arrayBuffer.slice(0), function (audioBuffer) { // `slice(0)`克隆一份(`decodeAudioData`后原数组清空)
  798. var bufferSource = context.createBufferSource();
  799. bufferSource.buffer = audioBuffer;
  800. bufferSource.connect(context.destination);
  801. bufferSource.start();
  802. });
  803. }
  804. /**得到发音按钮*/
  805. function getPlayButton(obj) {
  806. var type = document.createElement('a');
  807. type.innerHTML = obj.name;
  808. type.setAttribute('href', 'javascript:void(0)');
  809. type.setAttribute('class', 'audio-button');
  810. type.setAttribute('title', '点击发音');
  811. type.addEventListener('mouseup', function () {
  812. play(obj);
  813. });
  814. return type;
  815. }
  816. /**有道词典排版*/
  817. function parseYoudao(rst) {
  818. var html = '';
  819. try {
  820. var rstJson = iframeWin.JSON.parse(rst),
  821. phoneStyle = 'color:#777;';
  822. if (rstJson.ec) {
  823. var word = rstJson.ec.word[0],
  824. tr = '';
  825. var trs = word.trs,
  826. ukphone = word.ukphone,
  827. usphone = word.usphone,
  828. phone = word.phone,
  829. returnPhrase = word['return-phrase'];
  830. if (returnPhrase && returnPhrase.l && returnPhrase.l.i) {
  831. html += '<div>' + returnPhrase.l.i + '</div>';
  832. }
  833. html += '<div>';
  834. if (ukphone && ukphone.length != 0) {
  835. html += '<span class="pron" style="' + phoneStyle + '">英 [' + ukphone + '] </span>';
  836. }
  837. if (usphone && usphone.length != 0) {
  838. html += '<span class="pron" style="' + phoneStyle + '">美 [' + usphone + '] </span>';
  839. }
  840. html += '</div>';
  841. if (phone && phone.length != 0) {
  842. html += '<div class="pron" style="' + phoneStyle + '">[' + phone + '] </div>';
  843. }
  844. trs.forEach(element => {
  845. tr += '<div>' + element.tr[0].l.i[0] + '</div>';
  846. });
  847. html += tr;
  848. }
  849. // 网络释义
  850. if (rstJson.web_trans &&
  851. rstJson.web_trans['web-translation'] &&
  852. rstJson.web_trans['web-translation'].length > 0 &&
  853. rstJson.web_trans['web-translation'][0]['@same'] &&
  854. rstJson.web_trans['web-translation'][0]['@same'] == 'true' &&
  855. rstJson.web_trans['web-translation'][0].trans &&
  856. rstJson.web_trans['web-translation'][0].trans.length > 0) {
  857. var webTrans = '网络:';
  858. rstJson.web_trans['web-translation'][0].trans.forEach(function (obj, i) {
  859. if (obj.value) {
  860. if (obj.cls && obj.cls.cl && obj.cls.cl.length > 0) {
  861. obj.cls.cl.forEach(function (cl) {
  862. webTrans += '[' + cl + ']';
  863. });
  864. }
  865. webTrans += obj.value;
  866. if (rstJson.web_trans['web-translation'][0].trans.length - 1 != i) {
  867. webTrans += ';';
  868. }
  869. }
  870. });
  871. html += '<div>' + webTrans + '</div>';
  872. }
  873. // 中英翻译
  874. if (rstJson.ce_new && rstJson.ce_new.word) {
  875. html += '<div>' +
  876. '《新汉英大辞典》<br>' + xmlToHtml(objToXml(rstJson.ce_new.word), 'div') +
  877. '</div>';
  878. }
  879. // 中文翻译
  880. if (rstJson.hh && rstJson.hh.word) {
  881. html += '<div>' +
  882. '《现代汉语大词典》<br>' + xmlToHtml(objToXml(rstJson.hh.word), 'span') +
  883. '</div>';
  884. }
  885. // 长句翻译
  886. if (rstJson.fanyi && rstJson.fanyi.tran) {
  887. html += rstJson.fanyi.tran;
  888. }
  889. } catch (error) {
  890. log(error);
  891. html += error;
  892. }
  893. var dom = document.createElement('div');
  894. dom.setAttribute('class', ids.YOUDAO);
  895. dom.innerHTML = html;
  896. return dom;
  897. }
  898. /**金山词霸排版*/
  899. function parseIciba(rst) {
  900. var dom = document.createElement('div');
  901. dom.setAttribute('class', ids.ICIBA);
  902. try {
  903. rst = rst.replace(/\n/g, ' ');
  904. rst = /dict.innerHTML='(.*)'; \tdict.style.display = "block";/g.exec(rst)[1];
  905. rst = rst
  906. .replace(/\\"/g, '"')
  907. .replace(/\\'/g, '\'') // inner-city 这个词会多一个斜杠,金山词霸的数据有些许瑕疵
  908. .replace(/onclick=/g, 'data-onclick=');
  909. rst = cleanAttr(rst, 'style');
  910. // 标识符处理
  911. var symbolRegex = /(<span class="icIBahyI-fl">.*?(?: xml:lang=).*?<\/span>)/ig;
  912. var symbolMatch;
  913. var symbolResult = [];
  914. while ((symbolMatch = symbolRegex.exec(rst)) != null) {
  915. symbolResult.push(symbolMatch[1]);
  916. }
  917. symbolResult.forEach(function (str) {
  918. rst = rst.replace(str,
  919. str.replace(/\[英\]/g, '英')
  920. .replace(/\[美\]/g, '美')
  921. );
  922. });
  923. rst = cleanHtml(rst)
  924. .replace(/(?:a>)/ig, 'span>')
  925. .replace(/(?:<a)/ig, '<span');
  926. var doc = htmlToDom(rst);
  927. // 发音
  928. doc.querySelectorAll('[title="真人发音"],[title="机器发音"]').forEach(function (ele) {
  929. var str = ele.getAttribute('data-onclick');
  930. var regex = /'(http:\/\/.*?)'/ig;
  931. var match = regex.exec(str);
  932. if (match && match.length >= 1) {
  933. ele.appendChild(getPlayButton({
  934. name: '♫',
  935. url: match[1]
  936. }));
  937. }
  938. });
  939. // 内容
  940. dom.appendChild(doc);
  941. } catch (error) {
  942. log(error);
  943. dom.appendChild(htmlToDom(error));
  944. }
  945. return dom;
  946. }
  947. /**沪江小D排版*/
  948. function parseHjenglish(rst) {
  949. var dom = document.createElement('div');
  950. dom.setAttribute('class', ids.HJENGLISH);
  951. try {
  952. var doc = htmlToDom(cleanHtml(rst));
  953. var label = doc.querySelector('.word-details-item-content header');
  954. var entry = doc.querySelector('.word-text h2');
  955. var collins = doc.querySelector('div[data-id="detail"] .word-details-item-content .detail-groups');
  956. if (entry) {
  957. var entryDom = document.createElement('div');
  958. entryDom.setAttribute('class', 'entry');
  959. entryDom.innerHTML = entry.innerHTML;
  960. dom.appendChild(entryDom);
  961. if (collins) {
  962. if (label) {
  963. var regex = /(《.*?》)/ig;
  964. var match = regex.exec(label.innerHTML);
  965. if (match && match[1]) {
  966. dom.appendChild(htmlToDom('<div>' + match[1] + '</div>'));
  967. }
  968. }
  969. dom.appendChild(collins);
  970. }
  971. }
  972. } catch (error) {
  973. log(error);
  974. dom.appendChild(htmlToDom(error));
  975. }
  976. return dom;
  977. }
  978. /**必应词典排版*/
  979. function parseBing(rst) {
  980. var html = '';
  981. try {
  982. rst = rst.replace(/onmouseover/ig, 'data-sound'); // 发音链接预处理
  983. rst = cleanHtml(rst)
  984. .replace(/(?:a>)/ig, 'span>')
  985. .replace(/(?:<a)/ig, '<span');
  986. var doc = htmlToDom(rst);
  987. doc.querySelectorAll('.hw_ti').forEach(function (ele) { // 牛津词头(不准)
  988. ele.remove();
  989. });
  990. var entry = doc.querySelector('.qdef .hd_area');
  991. var concise = doc.querySelector('.qdef ul');
  992. var tense = doc.querySelector('.qdef .hd_div1');
  993. var oald = doc.querySelector('#authid');
  994. if (entry) {
  995. html += '<div class="entry">' + entry.innerHTML + '</div>';
  996. if (concise) {
  997. html += '<div class="concise">' + concise.outerHTML + '</div>';
  998. }
  999. if (tense) {
  1000. html += '<div class="tense">' + tense.outerHTML + '</div>';
  1001. }
  1002. if (oald) {
  1003. html += '<div class="oald">《牛津高阶英汉双解词典第八版》<br>' + oald.outerHTML + '</div>';
  1004. }
  1005. }
  1006. // 计算机翻译
  1007. var machineTrans = doc.querySelector('.smt_hw');
  1008. if (machineTrans && machineTrans.innerHTML.indexOf('计算机翻译') != -1) {
  1009. var parent = machineTrans.parentNode;
  1010. var zhText = parent.querySelector('.p1-11');
  1011. if (zhText) {
  1012. html += '<div class="machine-trans">' + zhText.outerHTML + '</div>';;
  1013. }
  1014. }
  1015. } catch (error) {
  1016. log(error);
  1017. html += error;
  1018. }
  1019. var dom = document.createElement('div');
  1020. dom.setAttribute('class', ids.BING);
  1021. dom.innerHTML = html;
  1022. // 发音
  1023. dom.querySelectorAll('[data-sound]').forEach(function (ele) {
  1024. var str = ele.getAttribute('data-sound');
  1025. var regex = /'(https:\/\/.*?)'/ig;
  1026. var match = regex.exec(str);
  1027. if (match && match.length >= 1) {
  1028. ele.appendChild(getPlayButton({
  1029. name: '♫',
  1030. url: match[1]
  1031. }));
  1032. }
  1033. });
  1034. return dom;
  1035. }
  1036. /**谷歌翻译排版*/
  1037. function parseGoogle(rst) {
  1038. var dom = document.createElement('div');
  1039. dom.setAttribute('class', ids.GOOGLE);
  1040. try {
  1041. dom.appendChild(htmlToDom(xmlToHtml(objToXml(iframeWin.JSON.parse(rst)), 'span')));
  1042. } catch (error) {
  1043. log(error);
  1044. dom.appendChild(htmlToDom(error));
  1045. }
  1046. return dom;
  1047. }
  1048. /**剑桥高阶排版*/
  1049. function parseCambridge(rst) {
  1050. var dom = document.createElement('div');
  1051. dom.setAttribute('class', ids.CAMBRIDGE);
  1052. try {
  1053. rst = cleanHtml(rst).replace(/(?:a>)/ig, 'span>')
  1054. .replace(/(?:<a)/ig, '<span');
  1055. var doc = htmlToDom(rst);
  1056. // 发音
  1057. doc.querySelectorAll('.audio_play_button').forEach(function (ele) {
  1058. ele.appendChild(getPlayButton({
  1059. name: '♫',
  1060. url: 'https://dictionary.cambridge.org/' + ele.getAttribute('data-src-mp3')
  1061. }));
  1062. });
  1063. // 内容
  1064. doc.querySelectorAll('.entry').forEach(function (ele) {
  1065. dom.appendChild(ele);
  1066. });
  1067. } catch (error) {
  1068. log(error);
  1069. dom.appendChild(htmlToDom(error));
  1070. }
  1071. return dom;
  1072. }
  1073. /**
  1074. * 谷歌翻译 token 计算
  1075. * https://github.com/hujingshuang/MTrans
  1076. * */
  1077. function token(a) {
  1078. var k = "";
  1079. var b = 406644;
  1080. var b1 = 3293161072;
  1081.  
  1082. var jd = ".";
  1083. var sb = "+-a^+6";
  1084. var Zb = "+-3^+b+-f";
  1085.  
  1086. for (var e = [], f = 0, g = 0; g < a.length; g++) {
  1087. var m = a.charCodeAt(g);
  1088. 128 > m ? e[f++] = m : (2048 > m ? e[f++] = m >> 6 | 192 : (55296 == (m & 64512) && g + 1 < a.length && 56320 == (a.charCodeAt(g + 1) & 64512) ? (m = 65536 + ((m & 1023) << 10) + (a.charCodeAt(++g) & 1023), e[f++] = m >> 18 | 240, e[f++] = m >> 12 & 63 | 128) : e[f++] = m >> 12 | 224, e[f++] = m >> 6 & 63 | 128), e[f++] = m & 63 | 128)
  1089. }
  1090. a = b;
  1091. for (f = 0; f < e.length; f++) a += e[f],
  1092. a = RL(a, sb);
  1093. a = RL(a, Zb);
  1094. a ^= b1 || 0;
  1095. 0 > a && (a = (a & 2147483647) + 2147483648);
  1096. a %= 1E6;
  1097. return a.toString() + jd + (a ^ b)
  1098. };
  1099.  
  1100. function RL(a, b) {
  1101. var t = "a";
  1102. var Yb = "+";
  1103. for (var c = 0; c < b.length - 2; c += 3) {
  1104. var d = b.charAt(c + 2),
  1105. d = d >= t ? d.charCodeAt(0) - 87 : Number(d),
  1106. d = b.charAt(c + 1) == Yb ? a >>> d : a << d;
  1107. a = b.charAt(c) == Yb ? a + d & 4294967295 : a ^ d
  1108. }
  1109. return a
  1110. }
  1111. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址